-- @author QueuedResonance 2022, SolarEdge 2024

local ZombieHandler = {}

local ZomboWin = require("ZomboWin/ZomboWin")

require("ZomboWin/Temptation")

local ZomboWinActType = ZomboWinActType
local ZomboWinDefeatConfig = ZomboWinDefeatConfig

local ISTimedActionQueue = ISTimedActionQueue
local SurvivorFactory = SurvivorFactory
local IsoPlayer = IsoPlayer

local instanceof = instanceof
local ZombRand = ZombRand
local pairs = pairs

local isoPlayersInAct = {}

local tickUpdateRate = 5
local lastTickUpdated = 0

local defeatAvailable = true;

-- hLevel RNG (out of 10) ... if ('local var' < rng) then { return; } ... if (true) then { skip; } ... low var = more chance to skip
local hLrng1 = 11;	--10
local hLrng2 = 4;	--03
local hLrng3 = 3;	--02
local hLrng4 = 1;	--00

local dfZombie = nil;
--local runOnce = false;

-- zombieUselessList; zombieToUselessList;
zUL = {};
zTUL = {};

function Playerdisabler(player, bool)
	--player:setMoving(not bool);
	player:setIgnoreContextKey(bool);
    player:setIgnoreInputsForDirection(bool);
	--player:setAllowSprint(bool);
	--player:setAllowRun(bool);
	--player:setForceSprint(bool);
	--player:setForceRun(bool);
    player:setAuthorizeMeleeAction(not bool);
	player:setAuthorizeShoveStomp(not bool);
    player:setIgnoreAimingInput(bool);
	player:setBannedAttacking(bool);
    player:setCanShout(not bool);
    player:setBlockMovement(bool);
end

function defeatReset()
	defeatAvailable = true;
	--print(defeatAvailable);
end

ZombieHandler.CheckPartsInTheseOrder = {
"Underwear","UnderwearBottom","UnderwearTop","BodyCostume","TankTop","Legs5","Skirt","Pants","Legs1","Dress","Torso1Legs1","ShortSleeveShirt","Tshirt","Shirt","Sweater","SweaterHat","JacketHat","Jacket","BathRobe","FullTop","TorsoExtra","FullSuit","Mask","Hat","MaskEyes","MaskFull","FullHat","FullSuitHead",
--"Underwear","UnderwearBottom","UnderwearTop","UnderwearExtra2","UnderwearExtra1","BodyCostume","TankTop","Socks","ThighHighSocks","Legs5","Skirt","Pants","Legs1","Dress","Torso1Legs1","ShortSleeveShirt","Tshirt","Shirt","Sweater","SweaterHat","JacketHat","Jacket","BathRobe","FullTop","TorsoExtra","FullSuit","Hands","Eyes","Mask","Hat","MaskEyes","MaskFull","FullHat","FullSuitHead"
}
ZombieHandler.CheckPartsInTheseOrderv2 = {
"Underwear","UnderwearBottom","UnderwearTop","BodyCostume","TankTop","Legs5","Pants","Legs1","Torso1Legs1","ShortSleeveShirt","Tshirt","Shirt","Sweater","SweaterHat","JacketHat","Jacket","BathRobe","FullTop","TorsoExtra","FullSuit","Mask","Hat","MaskEyes","MaskFull","FullHat","FullSuitHead",
--"Underwear","UnderwearBottom","UnderwearTop","UnderwearExtra2","UnderwearExtra1","BodyCostume","TankTop","Socks","ThighHighSocks","Legs5","Skirt","Pants","Legs1","Dress","Torso1Legs1","ShortSleeveShirt","Tshirt","Shirt","Sweater","SweaterHat","JacketHat","Jacket","BathRobe","FullTop","TorsoExtra","FullSuit","Hands","Eyes","Mask","Hat","MaskEyes","MaskFull","FullHat","FullSuitHead"
}
ZombieHandler.CheckPartsSubmissiveTrue = {
"Underwear","UnderwearBottom","BodyCostume","Legs5","Skirt","Pants","Legs1","Dress","Torso1Legs1","BathRobe","FullTop","TorsoExtra","FullSuit","FullSuitHead",
}
ZombieHandler.CheckPartsSubmissiveFalse = {
"Underwear","UnderwearBottom","BodyCostume","Legs5","Pants","Legs1","Torso1Legs1","BathRobe","FullTop","TorsoExtra","FullSuit","FullSuitHead",
}

local holeType = {
	{"ForeArm_L", "ForeArm_R", "Torso_Upper", "Torso_Lower", "UpperArm_L", "UpperArm_R"},
	{"Groin", "LowerLeg_L", "LowerLeg_R", "UpperLeg_L", "UpperLeg_R", "Torso_Lower"},
	{"Torso_Upper", "Torso_Lower", "Groin"},
}

local clothingToHoleMap = {
    Jacket = 1,
    FullTop = 1,
    Sweater = 1,
    Shirt = 1,
    ShortSleeveShirt = 1,
    Tshirt = 1,
    TorsoExtra = 1,
    TankTop = 1,
    BathRobe = 1,
    UnderwearTop = 1,
    
    Pants = 2,
    Legs1 = 2,
	Legs5 = 2,
    Skirt = 2,
    UnderwearBottom = 2,
    
    FullSuit = 3,
    Torso1Legs1 = 3,
    Dress = 3,
}

--- Check if this target can be defeated due to armor and clothing
-- @param zombie (not sure what to do with this yet)
-- @param target

local function clearAnim(target)
	isoPlayersInAct[target].Ended = true;
	isoPlayersInAct[key] = nil;
	target:getModData().ZomboWinSexScene = false;
end
	
local function exertPenalty(target)
	clearAnim(target); 
	HaloTextHelper.addText(target, getText("Not enough stamina for sex"), HaloTextHelper.getColorRed());
end

local function maleEnduranceCheck(target)
    local isMainHeroFemale = target:isFemale()

    if not isMainHeroFemale then
        local endurance = target:getStats():getEndurance()

        if target:HasTrait("youngBuck") and endurance <= 0.1 then 
            exertPenalty(target)
            return false;
        elseif target:HasTrait("AARP") and endurance <= 0.75 then 
            exertPenalty(target)
            return false;
        elseif target:HasTrait("inPrime") and endurance <= 0.25 then 
            exertPenalty(target)
            return false;
        elseif target:HasTrait("spunkDefault") and endurance <= 0.75 then 
            exertPenalty(target)
            return false;
        elseif target:HasTrait("lowEffort") and endurance <= 0.5 then 
            exertPenalty(target)
            return false;
        else
            return true;
        end
    end

    return true;
end

local function CheckClothingList()
	local player = getPlayer();
	local ClothingCheckList = nil;
	
	if not ZomboWinDefeatConfig.ModOptions.options.box5 then
		if player:HasTrait("submissive") then
			ClothingCheckList = ZombieHandler.CheckPartsSubmissiveTrue;
		else
			ClothingCheckList = ZombieHandler.CheckPartsInTheseOrder;
		end
	else
		if player:HasTrait("submissive") then
			ClothingCheckList = ZombieHandler.CheckPartsSubmissiveFalse;
		else
			ClothingCheckList = ZombieHandler.CheckPartsInTheseOrderv2;
		end
	end
	
	return ClothingCheckList
end

local function CheckForClothingOnly(zombie, target)
	--- Copied from ISInventoryPaneContextMenu.lua
	local wornItems = target:getWornItems();
	local canGrape = true;

	--- For some reason PZ can return nil for wornItems
	if wornItems then
		--- Loop through the character's currently equipped items
		for i = wornItems:size(), 1, -1 do
			local wornItem = wornItems:get(i - 1) --- Get the current worn item

			if wornItem then
				local item = wornItem:getItem() --- Receive item data
				local location = wornItem:getLocation() --- Receive location of where this item is located on the body
				if item:IsClothing() then --- Make sure it is a clothing piece
					--- Iterate through the major clothing list the zombie will want to strip
					local removedItem = false;
					local clothingList = CheckClothingList();
					
					for v = 1, #clothingList do
						if canGrape then
							local bodyPart = clothingList[v];

							if location == bodyPart then
								canGrape = false;
								removedItem = true;
							end
						end
					end
					
					if removedItem then
						break;
					end
				end
			end
		end
	end

	return canGrape
end

local function CheckAndRemoveClothing(zombie, target)
	--- Copied from ISInventoryPaneContextMenu.lua
	local wornItems = target:getWornItems()
	local canGrape = true

	--- For some reason PZ can return nil for wornItems
	if wornItems then
		--- Loop through the character's currently equipped items
		for i = wornItems:size(), 1, -1 do
			local wornItem = wornItems:get(i - 1) --- Get the current worn item

			if wornItem then
				local item = wornItem:getItem() --- Receive item data
				local location = wornItem:getLocation() --- Receive location of where this item is located on the body
				if item:IsClothing() then --- Make sure it is a clothing piece
					--- Iterate through the major clothing list the zombie will want to strip
					local removedItem = false;
					local clothingList = CheckClothingList();
					
					for v = 1, #clothingList do
						if canGrape then
							-- check this local var to determine if it returns item type..eg jacket, shirt, pants, etc
							local bodyPart = clothingList[v]

							if location == bodyPart then
											-- Editor Note --
								-- location or bodyPart returns clothing type from here on out.
								--print("Oi! Body part is: " .. bodyPart);
								canGrape = false;

								--- Remove favorite so it can be dropped
								if item:isFavorite() then
									item:setFavorite(false);
								end
								
								--- Drop or unequip depending on settings ---
								HaloTextHelper.addTextWithArrow(target, getText("Clothing"), false, HaloTextHelper.getColorRed());
								
								if target:HasTrait("viciousZombies") and not target:isNPC() then
												-- Editor note --
									-- here should be the code to check local var "item:itemlocation/ type"
									-- then compare item to clothing list index to determine which subindex it belongs to.
									-- then when item subindex is retrieved, thats when i'll know which holetype to use.
									-- then a local var will be set to remember what clothing type and which holes to receive damage.
									local function getIndexNum(bodyPart)
										return clothingToHoleMap[bodyPart] or 0
									end

									local indexNum = getIndexNum(bodyPart)
									if IndexNum ~= 0 and not IndexNum == nil then 
										--print("IndexNum returned as: " .. indexNum)
										
										local conditionDamage = 1;
										local dmgMultiplierChance = 0;
										
										--[[
										-- Effectively making double damage a 10% chance -- shhh! nobody else has to know! -- awh dangit -- prematurely wears out clothes -- better to loop hole and damage twice thru a loop func :) but not today.
										if ZombRand(10) == dmgMultiplierChance then
											conditionDamage = 2;
										end
										]]
													-- Editor note --
										-- apply condition damage
										item:setCondition(item:getCondition() - conditionDamage)
										
													-- Editor note --
										-- apply hole to clothing
										local holeIndex = holeType[indexNum]
										local chosenHole = holeIndex[ZombRand(1, #holeIndex)]
										local bloodPart = BloodBodyPartType.FromString(chosenHole)

										target:addHole(bloodPart)
									end
									----------------------------------------------------------------------- You Are Here. ----------------------------------------------------------------------------------
								end
								
								if target:HasTrait("betterAlive") and not target:isNPC() then
								--if ZomboWinDefeatConfig.ModOptions.options.box2 then
									local act = ISInventoryTransferAction:new(target, item, item:getContainer(), ISInventoryPage.floorContainer[1])
									act:transferItem(item)
								elseif not target:isNPC() then
									local act = ISInventoryTransferAction:new(target, item, item:getContainer())
									local addToWorld = act:removeItemOnCharacter()
								end
								
								if ZomboWinDefeatConfig.ModOptions.options.box2 and target:isNPC() then
								--if ZomboWinDefeatConfig.ModOptions.options.box2 then
									local act = ISInventoryTransferAction:new(target, item, item:getContainer(), ISInventoryPage.floorContainer[1])
									act:transferItem(item)
								elseif target:isNPC() then
									local act = ISInventoryTransferAction:new(target, item, item:getContainer())
									local addToWorld = act:removeItemOnCharacter()
								end
								
								-- Innocent, add panic
								if target:HasTrait("Innocent") then
									local player = target
									local currentPanic = player:getStats():getPanic()
									player:getStats():setPanic(currentPanic + 10)
									if player:getStats():getPanic() > 99 then
										player:getStats():setPanic(99)
									end
								end
								
								ISInventoryPage.renderDirty = true;
								removedItem = true;
							end
						end
					end
					
					if removedItem then
						break
					end
				end
			end
		end
	end

	if not canGrape then
		isoPlayersInAct[target] = {Ended = true, Tick = 0}
	end

	return canGrape
end

local function funcUndressOverDefeat(zombie, target)
	local undressOverDefeat = false;
	local player = target;
	--print("5");
	
	--- Dominate trait
	if target:HasTrait("dominate") then
		local currentHp = target:getBodyDamage():getHealth();
		if currentHp > 34 or not maleEnduranceCheck(target) then
			HaloTextHelper.addTextWithArrow(player, getText("Dominate"), true, HaloTextHelper.getColorGreen());
			undressOverDefeat = true;
			--defeatReset();
			return undressOverDefeat;
		end
	end
	
	--- Unkempt trait ---> Fugly
	-- Trait Desc: At least they still find you edible.<br>- Zombies will rarely undress or defeat you
	if target:HasTrait("Fugly") then
		local tooFugly = false;
		local chance = ZombRand(5)
		-- Finally, 80% chance the zombie will do regular attack.
		if not tooFugly and chance ~= 0 then
			tooFugly = true;
		end
		
		if tooFugly then
			HaloTextHelper.addTextWithArrow(player, getText("Fugly"), true, HaloTextHelper.getColorGreen());
			undressOverDefeat = true;
			--defeatReset();
			return undressOverDefeat;
		end
	end
	
	--- Strip check
	if not ZomboWinDefeatConfig.ModOptions.options.box3 then
		if not CheckForClothingOnly(zombie, target) then
			local numAmount = ((ZomboWinDefeatConfig.ModOptions.options.dropdown1) - 1)
			if numAmount == 6 then numAmount = 20 end
			for i = 1, numAmount do
				CheckAndRemoveClothing(zombie, target)
			end
			
			-- No stagger if no clothes lost
			if target:HasTrait("buttonedUp") then
			--if ZomboWinDefeatConfig.ModOptions.options.box8 then
				if numAmount ~= 0 then
					--zombie:setStaggerBack(true);
					zombie:setBumpType("trippingFromSprint");
					zombie:setSlowFactor(0.85);
					zombie:setSlowTimer(0.8);
				end
			elseif ZomboWinDefeatConfig.ModOptions.options.box8 and target:isNPC() then
				if numAmount ~= 0 then
					zombie:setStaggerBack(true);
				end
			else
				if numAmount ~= 0 then
					zombie:setStaggerBack(true);
				end
			end
			
			-- slow factor
			if target:HasTrait("baggyStyle") then
			--if ZomboWinDefeatConfig.ModOptions.options.box10 then
				target:setSlowFactor(0.85);
				target:setSlowTimer(0.3);
			elseif ZomboWinDefeatConfig.ModOptions.options.box10 and target:isNPC() then
				target:setSlowFactor(0.85);
				target:setSlowTimer(0.3);
			end
			
			-- play sound indicator, option01 is a check for none undressed on grab.
			local option01 = ((ZomboWinDefeatConfig.ModOptions.options.dropdown1) - 1)
			if option01 ~= 0 then
				local rando = ZombRand(3);
				if rando == 0 then
					getSoundManager():PlayWorldSound('crip01', true, target:getCurrentSquare(), 0, 4, 1, false);
					elseif rando == 1 then
					getSoundManager():PlayWorldSound('crip02', true, target:getCurrentSquare(), 0, 4, 1, false);
					elseif rando == 2 then
					getSoundManager():PlayWorldSound('crip03', true, target:getCurrentSquare(), 0, 4, 1, false);
				end
			end
			
			
			undressOverDefeat = true;
			--defeatReset();
			return undressOverDefeat;
		end
	end
	
	--defeatReset();
	return undressOverDefeat;
end

--- Make the zombie do lewd things to the target
-- @param IsoZombie
-- @param IsoPlayer
local function undressNotDefeatTarget(zombie, target)
	local isMainHeroFemale = target:isFemale()
	local zombieIsFemale = zombie:isFemale()
	--print("0");
	if target:getModData().ZomboWinSexScene then return end
	if target:getModData().dontDefeat then return end
	if not ZomboWinDefeatConfig.ModOptions.options.box0 then return end
	--print("1");

	local grabDistance = ZomboWinDefeatConfig.ModOptions.options.dropdown3 * 0.2

	if zombie:DistTo(target) < grabDistance then
		if funcUndressOverDefeat(zombie, target) then
			--print("complete");
		end
	end
end

local function playAnim(zombie, dummy, target, chosenAnimation)
			
			local function cleanupV2(dummy, zombie)
			--- Temporarily set 'em far away
				dummy:setX(dummy:getX() + 9)

				local function _deleteDummy(tick)
					if tick >= 100 then
						ISTimedActionQueue.clear(target)
						dummy:setInvincible(false)
						dummy:setInvisible(false)
						dummy:setGhostMode(false)
						dummy:removeFromWorld()
						dummy:removeFromSquare()
						Events.OnTick.Remove(_deleteDummy)
					end
				end

				Events.OnTick.Add(_deleteDummy)

				zombie:setUseless(false)
				zombie:setInvincible(false)
				zombie:setInvisible(false)
				zombie:setNoDamage(false)
				
				defeatReset();
				Playerdisabler(target, false) --Enable player controls
			end
			
			local function cleanup()
				if target == nil then cleanupV2(dummy, zombie); return; end
				if isoPlayersInAct[target] == nil then cleanupV2(dummy, zombie); return; end
				if isoPlayersInAct[target].Ended == nil then cleanupV2(dummy, zombie); return; end
				clearAnim(target);

				--- Temporarily set 'em far away
				dummy:setX(dummy:getX() + 9)

				local function _deleteDummy(tick)
					if tick >= 100 then
						ISTimedActionQueue.clear(target)
						dummy:setInvincible(false)
						dummy:setInvisible(false)
						dummy:setGhostMode(false)
						dummy:removeFromWorld()
						dummy:removeFromSquare()
						Events.OnTick.Remove(_deleteDummy)
					end
				end

				Events.OnTick.Add(_deleteDummy)

				zombie:setUseless(false)
				zombie:setInvincible(false)
				zombie:setInvisible(false)
				zombie:setNoDamage(false)
				
				defeatReset();
				Playerdisabler(target, false) --Enable player controls
				
				--player:setZombiesDontAttack​(false) <-- Doesn't work, don't use
				--target:setInvincible(false)
				--target:setNoDamage(false)
			end
			--print("Checkpoint Final");
			
			--zombie:setHitTime(0);
			--zombie:shouldGetUpFromCrawl();
			--zombie:resetForReuse(); --removes zomb clothes!?
			--zombie:setCrawler(true); --if not crawler, they lay on ground.
			--zombie:knockDown(true);
			--zombie:setReanimate(true);
			
			ZomboWin.AnimationHandler.Play(nil, {dummy, target}, chosenAnimation, true, true, {
				Update = function(action)
					
					local player = target;
					isoPlayersInAct[target].TimeOut = 0
					
					------------------- Health stabilizer: Update -------------------
					local currentHP = player:getBodyDamage():getHealth();
					player:getModData().zHandler = player:getModData().zHandler or {};
					player:getModData().zHandler.animHP = player:getModData().zHandler.animHP;
					
					--if player:getModData().zHandler.animHP == nil then player:getModData().zHandler.animHP = currentHP; end
					if player:getModData().zHandler.animHP > currentHP then
						local difHP = player:getModData().zHandler.animHP - currentHP;
						-- Insert modifier with 'difHP' for exhaustion penalty feature
						player:getBodyDamage():AddGeneralHealth(difHP);
					end
					
					------------------- npc stationary code -------------------
					--[[
					if target:isNPC() and px ~= nil and py ~= nil and pz ~= nil then
						target:setX(px);
						target:setY(py);
						target:setZ(pz);
						target:setLX(px);
						target:setLY(py);
						target:setLZ(pz);
					end]]
					
					--npc use Only
					if artTicker == nil then artTicker = 0; end
					artTicker = artTicker + 1;
					
					------------------- Sexual Endurance debuff -------------------
					local endurance = target:getStats():getEndurance();
					local enduranceLoss = 0.00010;
					--should rework this for both sexes!
					if target:HasTrait("youngBuck") then
						enduranceLoss = 0.00004;
					elseif target:HasTrait("AARP") then
						enduranceLoss = 0.00014;
					elseif target:HasTrait("inPrime") then
						enduranceLoss = 0.00008;
					elseif target:HasTrait("spunkDefault") then
						enduranceLoss = 0.00010;
					elseif target:HasTrait("lowEffort") then
						enduranceLoss = 0.00002;
					end
					
					-- Sexual Endurance: female modifier
					if isMainHeroFemale then enduranceLoss = (enduranceLoss / 4); end
					
					target:getStats():setEndurance(endurance - enduranceLoss);
					if target:getStats():getEndurance() < 0.0 then
						target:getStats():setEndurance(0.0);
					end
					
					------------------- Stress and Unhappiness debuff -------------------
					if not target:HasTrait("Lewd") and not target:HasTrait("Nymphomaniac") then
						local bodyDamage = target:getBodyDamage()
						local unhappiness = bodyDamage:getUnhappynessLevel()
						local stats = target:getStats()
						local stressAdd = 0;
						local unhappinessAdd = 0;
						
						if target:HasTrait("Wimp") then
							stressAdd = 0.006;
							unhappinessAdd =  0.08;
						else
							stressAdd = 0.003;
							unhappinessAdd =  0.04;
						end
						
					-- Stress and Unhappiness: male modifier
						if not isMainHeroFemale then stressAdd = (0.0); unhappinessAdd = (0.0); end
						
					-- Stress and Unhappiness: trait modifier
						if target:HasTrait("Masochist") or target:HasTrait("Nymphomaniac") then stressAdd = 0.0015; unhappinessAdd = 0.02; end
						
						stats:setStress(stats:getStress() + stressAdd);
						bodyDamage:setUnhappynessLevel(unhappiness + unhappinessAdd);
					end
					
					------------------- Pain debuff, Groin -------------------
					local Groin = target:getBodyDamage():getBodyPart(BodyPartType.FromString("Groin"))
					local pain = 0.03;
					-- Default pain is: 0.03
					-- Why no switch in lua!?
					if target:HasTrait("Virgin") then
					pain = 0.08;
					elseif target:HasTrait("Inexperienced") then
					pain = 0.04;
					elseif target:HasTrait("Experienced") then
					pain = 0.02;
					end
					
					if target:HasTrait("Lewd") or target:HasTrait("Nymphomaniac") then
						pain = 0.01;
					end
					
					-- Pain: male modifier
					if not isMainHeroFemale then pain = 0; end
					
					Groin:setAdditionalPain(Groin:getAdditionalPain() + pain)
					if Groin:getAdditionalPain() >= 100 then
						Groin:setAdditionalPain(100)
					end
					
					------------------- Sickness debuff -------------------
					local bodyDamage = target:getBodyDamage()
					local sickness = bodyDamage:getFoodSicknessLevel()
					local defeatSickness = 0;
					if not target:isNPC() then
						if not target:HasTrait("unkempt") and not target:HasTrait("dislikesShowers") and not target:HasTrait("neatTidy") and not target:HasTrait("lovesClean") then
							-- player did not select a trait.
							defeatSickness = 0.03;
						elseif target:HasTrait("unkempt") then
							defeatSickness = 0.07;
						elseif target:HasTrait("dislikesShowers") then
							defeatSickness = 0.04;
						elseif target:HasTrait("neatTidy") then
							defeatSickness = 0.01;
						elseif target:HasTrait("lovesClean") then
							defeatSickness = 0.005;
						end
					else
						defeatSickness = ((ZomboWinDefeatConfig.ModOptions.options.dropdown4 * 0.01) - 0.01);
					end
					
					bodyDamage:setFoodSicknessLevel(sickness + defeatSickness)
					if bodyDamage:getFoodSicknessLevel() > 99 then
						bodyDamage:setFoodSicknessLevel(99);
					end
					
					------------------- Aoe Force Push -------------------
					local enemies = target:getSpottedList()
					local maxDistance = 4

					for i = 0, enemies:size() - 1 do
						local enemy = enemies:get(i)
						if enemy:isZombie() and enemy:DistTo(target) <= maxDistance then
							if enemy:DistTo(target) <= (maxDistance/2) then
								enemy:setStaggerBack(true);
							end
							if not zTUL[enemy] and not enemy:isUseless() then
								enemy:setUseless(true);
								table.insert(zUL, enemy);
								zTUL[enemy] = true;
							end
							--enemy:setKnockedDown(true);
						end
					end
					--print("Update finished.");
					
					---------- dummy clothes removal ----------
					local ClothingCheckList = ZombieHandler.CheckPartsSubmissiveTrue;
					local wornItems = dummy:getWornItems();
					if wornItems then
						for i = wornItems:size(), 1, -1 do
							local wornItem = wornItems:get(i - 1)
							if wornItem then
								local item = wornItem:getItem();
								local location = wornItem:getLocation();
								if item:IsClothing() then
									local clothingList = CheckClothingList();
									for v = 1, #clothingList do
										local bodyPart = clothingList[v];
										if location == bodyPart then
											local act = ISInventoryTransferAction:new(dummy, item, item:getContainer())
											local addToWorld = act:removeItemOnCharacter()
										end
									end
								end
							end
						end
					end
					------------------- end -------------------
					
					------------------- end of: update -------------------
				end,
				WaitToStart = function(action)
					
					local player = target;
					zombie:setInvisible(true)
					dummy:setGhostMode(true)
					dummy:setInvisible(false)
					
					if target ~= nil then
						local player = target
						Playerdisabler(player, true) --Disable player controls
					end
					
					------------------- Health stabilizer: Wait To Start -------------------
					local currentHP = player:getBodyDamage():getHealth();
					player:getModData().zHandler = player:getModData().zHandler or {};
					player:getModData().zHandler.animHP = currentHP;
					
					--[[
					local px = dummy:getX();
					local py = dummy:getY();
					local pz = dummy:getZ();]]

					--target:setInvincible(true)
					--target:setNoDamage(true)
					--target:setKnockedDown(true)
					
					------------------- Push other zombies away -------------------
					zTUL = {};
					zUL = {};
					local enemies = target:getSpottedList()
					local maxDistance = 2

					for i = 0, enemies:size() - 1 do
						local enemy = enemies:get(i)
						if enemy:isZombie() and enemy:DistTo(target) <= maxDistance then
							enemy:setStaggerBack(true)
							--enemy:setKnockedDown(true)
						end
					end
					
					--- Fix bugged character if the dummy zombie despawns
					if not action.TimeOut then
						action.TimeOut = 0
					end

					action.TimeOut = action.TimeOut + 1

					if action.TimeOut > 250 then
						Playerdisabler(player, false) --Enable player controls
						defeatReset();
						action:forceStop()
					end
					--print("Wait finished.");
					------------------- end of: wait to start-------------------
				end,
				Start = function(action)
				
					local player = target;
					--Fixes some errors on using NPC -- author AlexTheMan21
					player:getModData().Temptation = player:getModData().Temptation or {};
					player:getModData().timedHandler = player:getModData().timedHandler or {};
					--
					zombie:setInvisible(true)
					dummy:setInvisible(false)
					
					if target ~= nil then
						local player = target
						Playerdisabler(player, true) --Disable player controls
					end
					--target:setInvincible(true)
					--target:setNoDamage(true)
					
					isoPlayersInAct[dummy] = nil
					--print("Start finished");
					------------------- end -------------------
				end,
				Stop = function(action)
					
					local player = target;
					-- this will execute regardless of traits.
					player:getModData().timedHandler.hoursSinceLastDefeat = 0;
					
					-- Hard-Mode: It Follows
					if not target:HasTrait("itFollows") then
					--if not ZomboWinDefeatConfig.ModOptions.options.box13 then
						-- Protoype only, not final!
						if target:HasTrait("Virgin") then
							local bodyDamage = target:getBodyDamage()
							local unhappiness = bodyDamage:getUnhappynessLevel()
							
							bodyDamage:setUnhappynessLevel(unhappiness + 100)
							HaloTextHelper.addTextWithArrow(player, getText("Virginity"), false, HaloTextHelper.getColorRed());
							player:getTraits():remove("Virgin")
							player:getTraits():add("Inexperienced")
						end
						
						-- this really belongs in a local func but I don't know how to send an argument in lua yet -.-"
						-- hopefully this works...
						--addTraumatizeTrait();
						--Nope.
						
						local sexTrauma = (ZomboWinDefeatConfig.ModOptions.options.dropdown5);
						if sexTrauma ~= 1 then 
							if not player:getModData().isWilling then
								-- currently only 'Lewd' and 'Nymphomaniac' prevents 'Traumatized' trait.
								if not target:HasTrait("Lewd") and not target:HasTrait("Nymphomaniac") and not target:HasTrait("Necrophiliac") then
									if not target:HasTrait("Traumatized") then
										HaloTextHelper.addTextWithArrow(player, getText("Traumatized"), true, HaloTextHelper.getColorRed());
										target:getTraits():add("Traumatized");
									end
								end
							end
						end
						
						-- Personality: Smokes
						if target:HasTrait("smokesAfter") then 
							local stress = target:getStats():getStressFromCigarettes();
							local addedStress = 0.25;
							
							if stress < addedStress then
								target:getStats():setStressFromCigarettes(addedStress);
							else
								target:getStats():setStressFromCigarettes(stress + addedStress);
							end
						end
					else
					------------------- player kill code -------------------
						if not target:getModData().isWilling then
							target:getBodyDamage():setInfectionLevel(0)
							target:setHealth(0)
						end
					end
					
					------------------- npc kill code -------------------
					if ZomboWinDefeatConfig.ModOptions.options.box13 and target:isNPC() then
						if artTicker ~= nil then
							if artTicker >= 240 then
								target:getBodyDamage():setInfectionLevel(0)
								target:setHealth(0)
							else
								print("artTicker < 240");
							end
						else
							print("artTicker = nil");
						end
					end
					
					------------------- UnUseless bystanders -------------------
					for i = 1, #zUL do
						local enemy = zUL[i]
						enemy:setUseless(false)
					end
					------------------- end -------------------
					
					--print("Update finished.");
					
					cleanup()
					--print("Stop finished.");
					------------------- end -------------------
				end,				
				Perform = function(action)
					
					local player = target;
					-- this will execute regardless of traits.
					player:getModData().timedHandler.hoursSinceLastDefeat = 0;
					if not target:HasTrait("itFollows") then
					--if not ZomboWinDefeatConfig.ModOptions.options.box13 then
						if target:HasTrait("Necrophiliac") then
							local bodyDamage = target:getBodyDamage()
							local unhappiness = bodyDamage:getUnhappynessLevel()
							
							HaloTextHelper.addTextWithArrow(player, getText("Necrophiliac"), true, HaloTextHelper.getColorGreen());
							bodyDamage:setUnhappynessLevel(unhappiness - 30)
						end
						
						if target:HasTrait("Virgin") then
							--[[ -- Cannot return animation name... [WIP]
							if chosenAnimation[2] == "idDefeat_KneeBJ" then
								local bodyDamage = target:getBodyDamage()
								local unhappiness = bodyDamage:getUnhappynessLevel()

								bodyDamage:setUnhappynessLevel(unhappiness + 60)
								
								if not player:HasTrait("Traumatized") then
									player:getTraits():add("Traumatized")
								end
							else]] -- Cannot return animation name... [WIP]
								local bodyDamage = target:getBodyDamage()
								local unhappiness = bodyDamage:getUnhappynessLevel()

								bodyDamage:setUnhappynessLevel(unhappiness + 100)
								player:getTraits():remove("Virgin")
								player:getTraits():add("Inexperienced")
								
							--end -- Cannot return animation name... [WIP]
						end
						
						-- this really belongs in a local func but I don't know how to send an argument in lua yet? -.-"
						-- hopefully this works...
						--addTraumatizeTrait();
						--still nope.
						
						local sexTrauma = (ZomboWinDefeatConfig.ModOptions.options.dropdown5);
						if sexTrauma ~= 1 then 
							if not player:getModData().isWilling then
								-- currently only 'Lewd' and 'Nymphomaniac' prevents 'Traumatized' trait.
								if not target:HasTrait("Lewd") and not target:HasTrait("Nymphomaniac") and not target:HasTrait("Necrophiliac") then
									if not target:HasTrait("Traumatized") then
										HaloTextHelper.addTextWithArrow(player, getText("Traumatized"), true, HaloTextHelper.getColorRed());
										target:getTraits():add("Traumatized");
									end
								end
							end
						end
						
						-- for ease of condition checking, this stays down here!
						if target:HasTrait("Everydrop") and not target:HasTrait("Traumatized") then
							getSoundManager():PlayWorldSound('FeGulp01', true, target:getCurrentSquare(), 0, 4, 1, false)
							local currentHunger = target:getStats():getHunger()
							target:getStats():setHunger(currentHunger - 0.040)
						end
						
						-- Personality: Smokes
						if target:HasTrait("smokesAfter") then 
							local stress = target:getStats():getStressFromCigarettes();
							local addedStress = 0.25;
							
							if stress < addedStress then
								target:getStats():setStressFromCigarettes(addedStress);
							else
								target:getStats():setStressFromCigarettes(stress + addedStress);
							end
						end
						
						--[[
						--Push other zombies away for Perform() Only!
						local enemies = target:getSpottedList()
						local maxDistance = 3.0

						for i = 0, enemies:size() - 1 do
							local enemy = enemies:get(i)
							if enemy:isZombie() and enemy:DistTo(target) <= maxDistance then
								enemy:setStaggerBack(true)
								enemy:setKnockedDown(true)
								-- This make a aoe.
								--[[
								if target:HasTrait("pacify") then
									enemy:setUseless(true);
								end
								if target:HasTrait("succubus") then
									enemy:Kill(player);
								end
								]]				--[[
							end
						end]]
						
						-- this zombie is the acting zombie. sometimes push doesn't apply to it, this ensures it does.
						zombie:setStaggerBack(true)
						zombie:setKnockedDown(true)
						
						
						------------------- Bystander Converter -------------------
						for i = 1, #zUL do
							local enemy = zUL[i]
							enemy:setUseless(false)
						end
						------------------- end -------------------
						
					end
					
					tCounterReset(target);
					cleanup()
					
					-- Hard-Mode: It Follows
					if not target:HasTrait("itFollows") then
					--if not ZomboWinDefeatConfig.ModOptions.options.box13 then
						if target:HasTrait("pacify") then
							HaloTextHelper.addTextWithArrow(player, getText("Pacified"), true, HaloTextHelper.getColorGreen());
							getSoundManager():PlayWorldSound('pacify01', true, target:getCurrentSquare(), 0, 4, 1, false)
							zombie:setUseless(true);
						end
						
						if target:HasTrait("succubus") then
							if target:isFemale() then
								getSoundManager():PlayWorldSound('succubus01', true, target:getCurrentSquare(), 0, 4, 1, false)
							else
								getSoundManager():PlayWorldSound('succubus02', true, target:getCurrentSquare(), 0, 4, 1, false)
							end
							HaloTextHelper.addTextWithArrow(player, getText("Succubus"), true, HaloTextHelper.getColorGreen());
							zombie:Kill(player);
						end
						
						if target:HasTrait("gonorrhea") then
							zombie:SetOnFire();
							zombie:setOnFire(true);
						end
						
						-- This stays down here.
						local sexTrauma = (ZomboWinDefeatConfig.ModOptions.options.dropdown5);
						if sexTrauma == 1 then 
							if target:HasTrait("Traumatized") then
								target:getTraits():remove("Traumatized");
							end
						end
					else
						-- player kill code
						if not target:getModData().isWilling then
							target:getBodyDamage():setInfectionLevel(0)
							target:setHealth(0)
						end
					end
					
					-- npc kill code
					if ZomboWinDefeatConfig.ModOptions.options.box13 and target:isNPC() then
						if artTicker ~= nil then
							if artTicker >= 240 then
								target:getBodyDamage():setInfectionLevel(0)
								target:setHealth(0)
							else
								print("artTicker < 240");
							end
						else
							print("artTicker = nil");
						end
					end
					
				end
			})

end

local function attemptToDefeatTarget(zombie, target)
	local isMainHeroFemale = target:isFemale()
	local zombieIsFemale = zombie:isFemale()

	if not ZomboWinDefeatConfig.ModOptions.options.box0 then return end
	if target:getModData().ZomboWinSexScene then return end
	if target:getModData().dontDefeat then return end
	
	-- Catch: if called from regular defeat
	if target:getModData().isWilling == nil then target:getModData().isWilling = false; --[[print("attempt catch triggered");]] end		-- Set to false.
	-- Check if called from zombie Defeat
	if target:getModData().isWilling == true then
		--print("attempt: isWilling is set!");
	end
	
	local isWilling = target:getModData().isWilling;	-- Assign var if true, else do nothing.

	if not isoPlayersInAct[target] then
		local grabDistance = ((ZomboWinDefeatConfig.ModOptions.options.dropdown3 * 0.20) + 0.20)
		if zombie:DistTo(target) < grabDistance then
			isoPlayersInAct[target] = {Ended = false, Tick = 0, TimeOut = 0}
			--print("Checkpoint A");
			if not isWilling then	--< willing bypass, first.
			--Anti Staggering Mechanic, availiable for both sexes
				if target:isRunning() or target:isSprinting() then
					--zombie:setStaggerBack(true);
					HaloTextHelper.addText(target, getText("Grab Dodged"), HaloTextHelper.getColorGreen());
					clearAnim(target); --resets scenes
					defeatReset();
					return;
				end
			--Undress Code
				if funcUndressOverDefeat(zombie, target) then
					clearAnim(target); --resets scenes
					defeatReset();
					return;
				end
			end
		--print("Checkpoint B");
			--Defeat Condition Skip for new mechanic + exertPenalty
			if not ZomboWinDefeatConfig.ModOptions.options.box3 then

				-------------------Male exertion penalty-------------------
				if not maleEnduranceCheck(target) then print("Endurance: is exhausted. STOP!!"); return; end
				
				-------------------New Defeat Mechanic-------------------
				
				if not isWilling then --< willing bypass, second
					if isMainHeroFemale or target:HasTrait("itFollows") then	--checks for female only because if not female then male. Unless player has a specific trait.
				-- Stagger Mechanic, Exclusive to females
						if not target:isBumped() then
							--here be pushing codes
							target:setBumpType("stagger");
							target:setBumpDone(false);
							target:setBumpFall(false); --true = fall, false = no fall
							zombie:setStaggerBack(true); --fair play.
							
							if zombie:isBehind(target) then
								target:setBumpFallType("pushedBehind");
								--target:setBumpFallType("pushedFront"); --uno reverse
							else
								target:setBumpFallType("pushedFront");
								--target:setBumpFallType("pushedBehind"); --uno reverse
							end
							
							clearAnim(target); --resets scenes
							defeatReset();
							return;
						end
					elseif not isPanickedCheck() then		-- player is neither panicked or initiating the defeat: zombie is attempting defeat by attacking player
				-- Horny Mechanic, Exclusive to males... for now.
						if hLevel == 0 then -- No moodle, no chance
							--HaloTextHelper.addText(target, getText("No Defeat"), HaloTextHelper.getColorWhite());
							clearAnim(target); 
							return;
						elseif hLevel == 4 then -- level 1 moodle, low chance.
							local rng = ZombRand(hLrng1);
							--print(rng);
							
							if rng == 0 then 
								zombie:setStaggerBack(true);
								HaloTextHelper.addText(target, getText("Temptation: Resisted"), HaloTextHelper.getColorWhite());
								clearAnim(target); 
								return; 
							end
						elseif hLevel == 3 then -- level 2 moodle, high.
							local rng = ZombRand(hLrng2);
							--print(rng);
							
							if rng == 0 then 
								zombie:setStaggerBack(true);
								HaloTextHelper.addText(target, getText("Temptation: Resisted"), HaloTextHelper.getColorWhite());
								clearAnim(target); 
								return; 
							end
						elseif hLevel == 2 then -- level 3 moodle, very high.
							local rng = ZombRand(hLrng3);
							--print(rng);
							
							if rng == 0 then 
								zombie:setStaggerBack(true);
								HaloTextHelper.addText(target, getText("Temptation: Resisted"), HaloTextHelper.getColorWhite());
								clearAnim(target); 
								return; 
							end
						elseif hLevel == 1 then -- level 4 moodle, absolute.
							local rng = ZombRand(hLrng4);
							--print(rng);
							
							if rng == 0 then 
								--zombie:setStaggerBack(true);
								--HaloTextHelper.addText(target, getText("No Defeat"), HaloTextHelper.getColorWhite());
								--clearAnim(target);
								--return; 
							end
						end
					end
				end
			end
			--print("Checkpoint C");
			--- Only straight zombie sex for now
			if isMainHeroFemale == zombieIsFemale then clearAnim(target); --[[print("MarkMarkMark");]] return; end
			--print("Checkpoint C.1");
			--- Time to grape em
			local dummy = ZomboWin.ZombieHandler:convertZombieToSurvivor(zombie)
			dummy:getModData().dontDefeat = true

			if isMainHeroFemale and zombieIsFemale then
				--- Lesbian
				maleCount = 0
				femaleCount = 2
			elseif isMainHeroFemale == false and zombieIsFemale == false then
				--- Gay
				maleCount = 2
				femaleCount = 0
			else
				--- Straight
				maleCount = 1
				femaleCount = 1
			end
			--print("Checkpoint D");
			--- Choose random animation as a test
			local animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat"})
			if animationList == nil then return; end
			local index = ZombRand(1, #animationList + 1)
			local chosenAnimation = animationList[index]
			if chosenAnimation == nil then return; end
			
			local function cleanupV2(dummy, zombie)
			--- Temporarily set 'em far away
				dummy:setX(dummy:getX() + 9)

				local function _deleteDummy(tick)
					if tick >= 100 then
						ISTimedActionQueue.clear(target)
						dummy:setInvincible(false)
						dummy:setInvisible(false)
						dummy:setGhostMode(false)
						dummy:removeFromWorld()
						dummy:removeFromSquare()
						Events.OnTick.Remove(_deleteDummy)
					end
				end

				Events.OnTick.Add(_deleteDummy)

				zombie:setUseless(false)
				zombie:setInvincible(false)
				zombie:setInvisible(false)
				zombie:setNoDamage(false)
				
				--clearAnim(target);
				defeatReset();
				Playerdisabler(target, false) --Enable player controls
			end
			
			local function cleanup()
				if target == nil then cleanupV2(dummy, zombie); return; end
				if isoPlayersInAct[target] == nil then cleanupV2(dummy, zombie); return; end
				if isoPlayersInAct[target].Ended == nil then cleanupV2(dummy, zombie); return; end
				clearAnim(target);

				--- Temporarily set 'em far away
				dummy:setX(dummy:getX() + 9)

				local function _deleteDummy(tick)
					if tick >= 100 then
						ISTimedActionQueue.clear(target)
						dummy:setInvincible(false)
						dummy:setInvisible(false)
						dummy:setGhostMode(false)
						dummy:removeFromWorld()
						dummy:removeFromSquare()
						Events.OnTick.Remove(_deleteDummy)
					end
				end

				Events.OnTick.Add(_deleteDummy)

				zombie:setUseless(false)
				zombie:setInvincible(false)
				zombie:setInvisible(false)
				zombie:setNoDamage(false)
				
				--clearAnim(target);
				defeatReset();
				Playerdisabler(target, false) --Enable player controls
				
				--player:setZombiesDontAttack​(false) <-- Doesn't work, don't use
				--target:setInvincible(false)
				--target:setNoDamage(false)
			end
			--print("Checkpoint E");
			playAnim(zombie, dummy, target, chosenAnimation);

			isoPlayersInAct[dummy] = {Dummy = dummy, TimeOut = 0, Callback = cleanup}
		end
	--end
	else
		--print("isoPlayersInAct[target] - Issue!");
		clearAnim(target);
	end
end

local function OnZombieUpdate(zombie, tick)		-- func: when zombie attacks player
	if not ZomboWinDefeatConfig.ModOptions.options.box0 then return; end
	local target = zombie:getTarget()
	
	if target == nil then return; end
	if not ZomboWinDefeatConfig.ModOptions.options.box14 and target:isNPC() then return; end
	if zombie:isUseless() then return end
	if zombie:isProne() then return end
	if zombie:isCrawling() then return end
	if zombie:isStaggerBack() then return end
	
	if target:getModData().dontDefeat then return end

	if not target then return end
	
	if target:getModData().ZomboWinSexScene then return; end --hopefully this reduces RAM usage.
	
	zombie:getModData().zHandler = zombie:getModData().zHandler or {};
	zombie:getModData().zHandler.eligible = zombie:getModData().zHandler.eligible or false;
	if not zombie:isAttacking() then zombie:getModData().zHandler.eligible = true; end
	
	if zombie:isZombieAttacking(target) and zombie:getModData().zHandler.eligible == true then
		zombie:getModData().zHandler.eligible = false;
		--print("A defeat is being called.");
		
		--this is more of a catch-all errors code... shouldn't be needed!
		if not defeatAvailable then
			if not target:getModData().ZomboWinSexScene then
				defeatReset();
			end
		end
	
		if defeatAvailable then
			local isMainHeroFemale = target:isFemale()
			local zombieIsFemale = zombie:isFemale()
			
			defeatAvailable = false;
			
			if isMainHeroFemale ~= zombieIsFemale or not isMainHeroFemale == zombieIsFemale then 
				--print("Straight defeat is called.");
				--defeatAvailable = false;
				attemptToDefeatTarget(zombie, target);
			else
				--print("Undress-only is called.");
				--defeatAvailable = false;
				if ZomboWinDefeatConfig.ModOptions.options.box7 then
					if not CheckForClothingOnly(zombie, target) then
						--print("Check clothing is called.");
						undressNotDefeatTarget(zombie, target);
					else
						--print("Failed: check clothing.");
					end
				end
			end
		end
	end
end

local function OnZombieAttacked(zombie, character)		-- func: when player attacks zombie
	----- Check initial conditions -----
	local target = character;
	local isMainHeroFemale = target:isFemale()
	local zombieIsFemale = zombie:isFemale()
	--print("A zombie is attacked.");
	if not ZomboWinDefeatConfig.ModOptions.options.box0 then --[[print("zDD: Defeat NOT ON. STOP");]] return; end
	if target:getModData().ZomboWinSexScene then --[[print("zDD: scene is active!");]] target:getModData().ZomboWinSexScene = false; return; end
	
	 ----------------------- Deactivates defeat for female players -----------------------
	if isMainHeroFemale then --[[print("zDD: Female player. STOP");]] return; end
	if not character:isDoShove() then --[[print("zDD: fail for no push");]] return; end	--detects shoving. Not able to try regular attack.
	
	----- Check zombie condition -----
	--if dfZombie == dfZombie then print("zDD: same zomb detected. STOP"); return; end
	if zombie:isHitLegsWhileOnFloor() then return; end
	--if zombie:isKnifeDeath() then return; end
	--if zombie:isFakeDead() then return; end
	if zombie:isCrawling() then return; end
	
	----- Check for Rope -----
	local weapon = character:getPrimaryHandItem() --player:getPrimaryHandItem()
	if weapon == nil then --[[print("zDD: Nil weapon! STOP");]] return; end
	if not weapon:hasTag("Rope") then --[[print("zDD: No rope. STOP");]] return; end
	
	-- no defeat for innocent --
	if character:HasTrait("Innocent") then HaloTextHelper.addText(character, getText("No Subdue: Innocent trait"), HaloTextHelper.getColorRed()); return; end
	if character:HasTrait("submissive") then HaloTextHelper.addText(character, getText("No Subdue: Submissive trait"), HaloTextHelper.getColorRed()); return; end
	if character:HasTrait("itFollows") then HaloTextHelper.addText(character, getText("No Subdue: it Follows trait"), HaloTextHelper.getColorRed()); return; end
	--print(target);

	if isPanickedCheck() then HaloTextHelper.addText(character, getText("Subdue: Failed, Panic"), HaloTextHelper.getColorRed()); return; end
	--print("Panic Check passed!");
	-- What is "isWilling"? -- It initiates a reverse zombie defeat, compared to the original zomb defeat conditions. A bypass if you will.
	target:getModData().isWilling = false
	local isWilling = target:getModData().isWilling
	
	if isMainHeroFemale ~= zombieIsFemale then 
		----- Here lies RNG -----
		local grapeChance = 0;
		local accumChance = 0;
		local necroChance = 15;
		local rapisChance = 30;
		local strLevel = target:getPerkLevel(Perks.Strength);
		local strChance = (strLevel * 5); --avg level 5 strength = 25
		
		if not zombie:isUseless() then 
			grapeChance = ZombRand(101);
			if character:HasTrait("Necrophiliac") then accumChance = accumChance + necroChance; end
			if character:HasTrait("Rapist") then accumChance = accumChance + rapisChance; end
			accumChance = accumChance + strChance;
		else
			-- 100% chance to subdue pacified zamby.
			accumChance = 102;
		end
		
		if accumChance > grapeChance then
			--print("Zombie target is set.");
			target:getModData().isWilling = true;
			if dfZombie == nil or dfZombie ~= dfZombie then dfZombie = zombie; end
		else
			HaloTextHelper.addText(character, getText("Subdue: Failed"), HaloTextHelper.getColorRed());
		end
	end
end

local function clearDFZombie(target)
	if dfZombie == nil then return; end
	dfZombie:setImmortalTutorialZombie(false);
	dfZombie = nil; 
	
	target:getModData().isWilling = false;
	--print("DFZ Cleared!");
end

local function zombieDownedDefeat(player)
	if not ZomboWinDefeatConfig.ModOptions.options.box0 then return; end
	
	if dfZombie == nil then --[[print("zombie is currently nil.");]] return; end
	--print("Target zombie acquired.");
	local target = player;
	if target:getModData().ZomboWinSexScene then clearDFZombie(target); return; end
	if not target:getModData().isWilling then --[[print("zDD: not willing!");]] clearDFZombie(target); return; end
	if dfZombie:isStaggerBack() then --[[print("zDD: staggering. STOP.");]] return; end --This here is very important! Staggering zombies dont work well with defeats. Do not clear DF Zombie!
	--if dfZombie:isProne() then clearDFZombie(); return; end
	
	---------------------- defeat conditions ----------------------
	
	local maxDistance = 6;
	
	if dfZombie:DistTo(target) >= maxDistance then  
		--print("Target zombie cleared: dist too great.");
		clearDFZombie(target);
		return; 
	end
	--[[
	if not dfZombie:isCanWalk() then 
		print("Target zombie cleared: is not downed.");
		--clearDFZombie();
		return; 
	end
	]]
	---------------------- end conditions ----------------------
	
	local isWilling = target:getModData().isWilling
	--print("Target zombie defeat initiating!");
	dfZombie:setImmortalTutorialZombie(true);
	attemptToDefeatTarget(dfZombie, target);
	
	--print("Target zombie defeat: end of script.");
	clearDFZombie(target);
end

local function OnGrabImmunity(tick)
	lastTickUpdated = lastTickUpdated + 1

	--- Have a minimum tick update time to increase performance, we don't need to spam the thing every frame
	if lastTickUpdated >= tickUpdateRate then
		lastTickUpdated = 0
		
		local cooldown = (ZomboWinDefeatConfig.ModOptions.options.dropdown2 * 200) - 200
		if cooldown == 0 then
			cooldown = 1
		end


		for key, data in pairs(isoPlayersInAct) do
			if data.Dummy then
				--- Delete glitched animation dummies
				data.TimeOut = data.TimeOut + tickUpdateRate

				if data.TimeOut > 100 then
					data.Callback(data.Dummy)
					isoPlayersInAct[key] = nil
					local player = getPlayer();
					Playerdisabler(player, false) --Enable player controls
					--defeatReset();
					--print("TimeoOut 100");
				end
			else
				if data.Ended then
					data.Tick = data.Tick + tickUpdateRate

					if data.Tick >= cooldown then
						isoPlayersInAct[key] = nil
						local player = getPlayer();
						Playerdisabler(player, false) --Enable player controls
						--defeatReset();
						--print("Cooldown");
					end
				else
					data.TimeOut = data.TimeOut + tickUpdateRate

					if data.TimeOut > 1000 then
						isoPlayersInAct[key] = nil
						local player = getPlayer();
						Playerdisabler(player, false) --Enable player controls
						--defeatReset();
						--print("TimeoOut 1000");
					end
				end
			end
		end
	end
end

local function executeStruggle()
	print("struggle input");
	local target = getPlayer();
	if target == nil then return; end
	print("player detected");
	if not target:getModData().ZomboWinSexScene then return end
	print("struggle scene detected");

	if actingZombie ~= nil then
		actingZombie:setStaggerBack(true);
	end
	
	if actingDummy ~= nil then
		actingDummy:setStaggerBack(true);
	end
	
	--[[cleanup();
	for key, data in pairs(isoPlayersInAct) do
		isoPlayersInAct[key] = nil
	end]]
	--local player = getPlayer();
	--Playerdisabler(player, false) --Enable player controls
end


--- Hook up event listeners
Events.OnZombieUpdate.Add(OnZombieUpdate);
Events.OnHitZombie.Add(OnZombieAttacked);
Events.OnPlayerUpdate.Add(zombieDownedDefeat);
Events.OnTick.Add(OnGrabImmunity);
--Events.OnKeyStartPressed.Add(executeStruggle);

return ZombieHandler;