-- @author QueuedResonance 2022

local ZombieHandler = {}

local ZomboWin = require("ZomboWin/ZomboWin")

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;

local actingZombie = nil;
local actingDummy = nil;

function Playerdisabler(player, bool)
    player:setIgnoreInputsForDirection(bool)
    player:setAuthorizeMeleeAction(not bool)
	player:setAuthorizeShoveStomp(not bool)
    player:setIgnoreAimingInput(bool)
    player:setCanShout(not bool)
    player:setBlockMovement(bool)
end

function defeatReset()
	print("defeatAvailable = true");
	defeatAvailable = true;
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",
}

--- 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 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

	if not canGrape then
		isoPlayersInAct[target] = {Ended = true, Tick = 0}
	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
							local bodyPart = clothingList[v]

							if location == bodyPart then
								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 ZomboWinDefeatConfig.ModOptions.options.box2 then
									local act = ISInventoryTransferAction:new(target, item, item:getContainer(), ISInventoryPage.floorContainer[1])
									act:transferItem(item)
								else
									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 then
			HaloTextHelper.addTextWithArrow(player, getText("Dominate"), true, HaloTextHelper.getColorGreen());
			undressOverDefeat = true;
			--defeatReset();
			return undressOverDefeat;
		end
	end
	
	--- Unkempt trait
	-- Trait Desc: At least they still find you edible.<br>- Zombies will rarely undress or defeat you
	if target:HasTrait("unkempt") 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("Unkempt"), 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 ZomboWinDefeatConfig.ModOptions.options.box8 then
				if numAmount ~= 0 then
					zombie:setStaggerBack(true);
				end
			end
			
			-- slow factor
			if ZomboWinDefeatConfig.ModOptions.options.box10 then
				target:setSlowFactor(0.85);
				target:setSlowTimer(0.6);
			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
	--print("2");
		isoPlayersInAct[target] = {Ended = false, Tick = 0, TimeOut = 0}
		
		if funcUndressOverDefeat(zombie, target) then
			--print("complete");
		end
	end
end

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

	if target:getModData().ZomboWinSexScene then return end
	if target:getModData().dontDefeat then return end
	if not ZomboWinDefeatConfig.ModOptions.options.box0 then return end

	if not isoPlayersInAct[target] then
		-- Hello curious person(s), If you did the math, so did I. This works better.
		local grabDistance = ((ZomboWinDefeatConfig.ModOptions.options.dropdown3 * 0.20) + 0.10)

		if zombie:DistTo(target) < grabDistance then
			isoPlayersInAct[target] = {Ended = false, Tick = 0, TimeOut = 0}
			
			if funcUndressOverDefeat(zombie, target) then
				return;
			end
			
			--- Only straight zombie sex for now
			if isMainHeroFemale == zombieIsFemale then return end

			--- Time to grape em
			local dummy = ZomboWin.ZombieHandler:convertZombieToSurvivor(zombie)
			dummy:getModData().dontDefeat = true
			
			-- assigns acting vars here
			actingZombie = zombie;
			actingDummy = dummy;

			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
		
			--- 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)
				
				animHP = nil;
				defeatReset();
				Playerdisabler(target, false) --Enable player controls
				
				--null actors
				actingDummy = nil;
				actingZombie = nil;
			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
				isoPlayersInAct[target].Ended = true

				--- 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)
				
				animHP = nil;
				defeatReset();
				Playerdisabler(target, false) --Enable player controls
				
				--null actors
				actingDummy = nil;
				actingZombie = nil;
				
				--player:setZombiesDontAttack​(false) <-- Doesn't work, don't use
				--target:setInvincible(false)
				--target:setNoDamage(false)
			end

			ZomboWin.AnimationHandler.Play(nil, {dummy, target}, chosenAnimation, true, true, {
				Update = function(action)
					
					isoPlayersInAct[target].TimeOut = 0
					local player = target;
					
					-- Health stabilizer
					local currentHP = player:getBodyDamage():getHealth();
					if animHP == nil then animHP = currentHP; end
					if animHP > currentHP then
						local difHP = animHP - currentHP;
						-- Insert modifier with 'difHP' for exhaustion penalty feature
						player:getBodyDamage():AddGeneralHealth(difHP);
					end
					
					--npc stationary code
					if 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
					
					-- Sexual Endurance
					local endurance = target:getStats():getEndurance();
					local enduranceLoss = 0.00010;
					
					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("lowEffort") then
						enduranceLoss = 0.00002;
					end
					
					target:getStats():setEndurance(endurance - enduranceLoss);
					if target:getStats():getEndurance() < 0.0 then
						target:getStats():setEndurance(0.0);
					end
					
					-- Stress and Unhappiness during animation
					if not target:HasTrait("Lewd") and not target:HasTrait("Nymphomaniac") then
						if target:HasTrait("Wimp") then
							local bodyDamage = target:getBodyDamage()
							local unhappiness = bodyDamage:getUnhappynessLevel()
							local stats = target:getStats()
							
							stats:setStress(stats:getStress() + 0.006)
							bodyDamage:setUnhappynessLevel(unhappiness + 0.08)
						else
							local bodyDamage = target:getBodyDamage()
							local unhappiness = bodyDamage:getUnhappynessLevel()
							local stats = target:getStats()
							
							stats:setStress(stats:getStress() + 0.003)
							bodyDamage:setUnhappynessLevel(unhappiness + 0.04)
						end
					end

					if target:HasTrait("Masochist") or target:HasTrait("Nymphomaniac") then
						local bodyDamage = target:getBodyDamage()
						local unhappiness = bodyDamage:getUnhappynessLevel()

						bodyDamage:setUnhappynessLevel(unhappiness - 0.02)
					end
					
					-- Pain, 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
					
					Groin:setAdditionalPain(Groin:getAdditionalPain() + pain)
					if Groin:getAdditionalPain() >= 100 then
						Groin:setAdditionalPain(100)
					end
					
					-- Sickness
					local bodyDamage = target:getBodyDamage()
					local sickness = bodyDamage:getFoodSicknessLevel()
					local defeatSickness = ((ZomboWinDefeatConfig.ModOptions.options.dropdown4 * 0.01) - 0.01)
					print(defeatSickness);
					bodyDamage:setFoodSicknessLevel(sickness + defeatSickness)
					if bodyDamage:getFoodSicknessLevel() > 99 then
						bodyDamage:setFoodSicknessLevel(99);
					end
					
					--Push other zombies away
					local enemies = target:getSpottedList()
					local maxDistance = 1.5

					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
				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
					
					--set locxxx
					--[[
					if px == nil then local px = target:getX(); end
					if py == nil then local py = target:getY(); end
					if pz == nil then local pz = target:getZ(); end]]
					local px = dummy:getX();
					local py = dummy:getY();
					local pz = dummy:getZ();
					
					--local animHP = player:getBodyDamage():getHealth(); <-- Method doesn't work, returns null.

					--target:setInvincible(true)
					--target:setNoDamage(true)
					--target:setKnockedDown(true)
					
					--Push other zombies away
					local enemies = target:getSpottedList(	)
					local maxDistance = 1.5

					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
				end,
				Start = function(action)
				
					local player = target;
					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
				end,
				Stop = function(action)
					
					local player = target;
					-- this will execute regardless of traits.
					hoursSinceLastDefeat = 0;
					
					-- Hard-Mode: It Follows
					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 
							-- currently only 'Lewd' and 'Nymphomaniac' prevents 'Traumatized' trait.
							if not target:HasTrait("Lewd") and not target:HasTrait("Nymphomaniac") then
								if not target:HasTrait("Traumatized") then
									HaloTextHelper.addTextWithArrow(player, getText("Traumatized"), false, HaloTextHelper.getColorRed());
									target:getTraits():add("Traumatized");
								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
						target:getBodyDamage():setInfectionLevel(0)
						target:setHealth(0)
					end
					
					cleanup()
				end,
				
				Perform = function(action)
					
					local player = target;
					-- this will execute regardless of traits.
					hoursSinceLastDefeat = 0;
					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.
						
						-- currently only 'Lewd' and 'Nymphomaniac' prevents 'Traumatized' trait.
						if not target:HasTrait("Lewd") and not target:HasTrait("Nymphomaniac") then
							if not target:HasTrait("Traumatized") and sexTrauma ~= 1 then
								HaloTextHelper.addTextWithArrow(player, getText("Traumatized"), false, HaloTextHelper.getColorRed());
								target:getTraits():add("Traumatized");
							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.060)
						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)
					end
					
					cleanup()
					
					-- Hard-Mode: It Follows
					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
						target:getBodyDamage():setInfectionLevel(0)
						target:setHealth(0)
					end
					
				end
			})

			isoPlayersInAct[dummy] = {Dummy = dummy, TimeOut = 0, Callback = cleanup}
		end
	end
end

local function OnZombieUpdate(zombie, tick)
	if zombie:isUseless() then return end

	local target = zombie:getTarget()
	if not target then return end
	
	if zombie:isZombieAttacking(target) then
		if defeatAvailable then
			local isMainHeroFemale = target:isFemale()
			local zombieIsFemale = zombie:isFemale()
			if isMainHeroFemale ~= zombieIsFemale or not isMainHeroFemale == zombieIsFemale then 
				--print("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 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 = 10
		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();
				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();
					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();
					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.OnTick.Add(OnGrabImmunity);
--Events.OnKeyStartPressed.Add(executeStruggle);

return ZombieHandler;