-- @author QueuedResonance 2022, SolarEdge 2024

--[[
All NPC mods are no longer being supported. Bandits mod is the new unofficial, official 'NPC' mod.

]]

local ZombieHandler = {}

local ZomboWin = require("ZomboWin/ZomboWin")

require("ZomboWin/Temptation");
require("ZomboWin/ZLMain");
require("ZomboWin/zombieUpdate");
require("ZomboWinDefeatStrip/zombieUpdate");
require("ZomboWinDefeatStrip/BodyCount");

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 = {};

zTieAvailable = {};

--local dAttempt = 99999---1;		--- The number of NPC defeat attempts before they're ignored.
local sq1 = nil;
local sq2 = nil;

if getActivatedMods():contains("Bandits") == true then
	require("Bandits/BanditUpdate");
	require("Bandits/Bandit");
	require("Bandits/BanditBrain");
	require("Bandits/ZombiePrograms");
	require("Bandits/BanditPrograms");
	require("Bandits/BanditUtils");
	require("Bandits/BanditPlayer");
end

function playerDisabler(player, bool)
	if bool == true and player:isNPC() then
		--player:setMoving(not bool);
		--player:setKnockedDown(bool);
		--player:setSlowFactor(1.0);
		--player:setSlowTimer(10.0);
		--player:StopAllActionQueue();
		player:StopAllActionQueueAiming();
		player:StopAllActionQueueWalking();
		player:StopAllActionQueueRunning();
		--player:resetModel();	---experimental
		--player:setbFalling(bool);
	end
	--player:setMoving(not bool);
	player:setIgnoreContextKey(bool);
    player:setIgnoreInputsForDirection(bool);
	--player:setAllowSprint(not bool);
	--player:setAllowRun(not bool);
	--player:setForceSprint(not bool);
	--player:setForceRun(not bool);
    player:setAuthorizeMeleeAction(not bool);
	player:setAuthorizeShoveStomp(not bool);
    player:setIgnoreAimingInput(bool);
	player:setIgnoreMovement(bool);
	player:setRunning(not bool);
	--player:setBumpDone(bool);
	player:setBannedAttacking(bool);
    player:setCanShout(not bool);
    player:setBlockMovement(bool);
end

function npcDisabler(player, bool)
	--player:setMoving(not bool);
	--player:setKnockedDown(bool);
	--player:setSlowFactor(1.0);
	--player:setSlowTimer(10.0);
	--player:StopAllActionQueue();
	player:setBlockMovement(bool);
	player:StopAllActionQueueAiming();
	player:StopAllActionQueueWalking();
	player:StopAllActionQueueRunning();
	player:NPCSetAiming(not bool);
	player:NPCSetAttack(not bool);
	player:NPCSetMelee(not bool);
	player:NPCSetRunning(not bool);
	--player:NPCSetJustMoved(not bool);
	player:resetModel();
	player:resetModelNextFrame();
	--player:setAsleep(bool);
end

local function disarm(target)
	if target:getPrimaryHandItem() ~= nil or target:getSecondaryHandItem() ~= nil then target:dropHandItems(); end
end

function defeatReset()
	--print("defeat reset called.");
	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

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

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 = 2;
										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
								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.5);
				target:setSlowTimer(0.5);
			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 traitValues = {
	["cute"] = -25,
	["Fugly"] = 15,
	["Lucky"] = -15,
	["Unlucky"] = 15,
	["Everydrop"] = -10,
	["Virgin"] = -10,
	["Inexperienced"] = -3,
	["Experienced"] = -5,
	["Nymphomaniac"] = -8,
	["Innocent"] = -5,
	["Lewd"] = -2,
	["youngBuck"] = -8,
	["AARP"] = 8,
	["inPrime"] = -5,
	["spunkDefault"] = 0,
	["lowEffort"] = 5,
	["unkempt"] = 20,
	["dislikesShowers"] = 12,
	["neatTidy"] = -6,
	["lovesClean"] = -10,
	["Athletic"] = -10,
	["Fit"] = -8,
	["Smoker"] = 3,
	["smokesAfter"] = 3,
	["Stout"] = -2,
	["Strong"] = -5,
	["Feeble"] = 3,
	["Weak"] = 6,
	["Overweight"] = 4,
	["Obese"] = 10,
	["Unfit"] = 3,
	["Underweight"] = 2,
	["Very Underweight"] = 5,
	["Emaciated"] = 10,
}

function cFactCalculate(player, traitList)
	player:getModData().cFact = 0;
	
	for trait, value in pairs(traitList) do
        if player:HasTrait(trait) then
            player:getModData().cFact = player:getModData().cFact + value;
        end
    end
end

function playAnim(zombie, dummy, target, chosenAnimation, includesBandit)
			
			local function cruelBRoll()
				--- cruel logic ---
				local cruelLogic = {
					[1] = {friendly = 70, nothing = 30, cruel = 0},  -- Trusting
					[2] = {friendly = 50, nothing = 50, cruel = 0},  -- Friendly
					[3] = {friendly = 30, nothing = 70, cruel = 0}, -- Hesitant
					[4] = {friendly = 0, nothing = 100, cruel = 0},  -- Neutral
					[5] = {friendly = 5, nothing = 70, cruel = 25}, -- Irritable
					[6] = {friendly = 0, nothing = 30, cruel = 70},  -- Ornery
					[7] = {friendly = 0, nothing = 20, cruel = 80},  -- Cruel
					[8] = {friendly = 33, nothing = 33, cruel = 33}, -- Random
				}

				local cruelChance = ZomboWinDefeatConfig.ModOptions.options.dropdown15
				local dOption = ZomboWinDefeatConfig.ModOptions.options.dropdown13
				local isBandit = zombie:getVariableBoolean("Bandit")
				local isSubdued = zombie:getModData().isSubdued
				local isCruel = zombie:getModData().isCruel

				local function setFriendly(bandit, bool)
					if Bandit.IsHostile(bandit) then
						Bandit.SetHostile(bandit, not bool)
					end
				end

				if isBandit and dOption ~= 2 then
					if Bandit.IsHostile(zombie) and not isSubdued then
						--- Retrieve probabilities for the current cruelChance ---
						local prob = cruelLogic[cruelChance]
						if prob then
							local roll = ZombRand(100) + 1
							
							--- Determine outcome based on roll ---
							if roll <= prob.friendly then
								--print("roll: friendy");
								zombie:getModData().isOwner = true
								target:getModData().isOwned = true	----------------------------------------------------------- Owned code here -----------------------------------------------------------
								setFriendly(zombie, true)
								slayerSpeak(zombie, "banCruelF");
							elseif roll <= prob.friendly + prob.nothing then
								--- Do nothing
								--print("roll: nothing");
							else
								--print("roll: cruel");
								zombie:getModData().isCruel = true
								zombie:getModData().cruelTick = 0;
								slayerSpeak(zombie, "banCruelH");
								--- moments before disaster ---
								setFriendly(zombie, true)
							end
						end
					end
				end
			end
			
			local function offerSmex(player)
				if not player:getModData().isOffering or player:getModData().isOffering == nil then return; else player:getModData().isOffering = false; end
				
				local rewardOption = (ZomboWinDefeatConfig.ModOptions.options.dropdown17);
				local maxReward = -1;
				
				if rewardOption == 1 then
					maxReward = -1;
				elseif rewardOption == 2 then
					maxReward = 3;
				elseif rewardOption == 3 then
					maxReward = 5;
				end
				--[[
				> the count is reset but only for one bandit per [reset time] code needs to be adjusted
				> zombie:ModData() is reset every login, making abusing this reward system very easy or even unintentional
				]]
				zombie:getModData().sFoods = zombie:getModData().sFoods or 0;	---counts up to max
				zombie:getModData().sFoodsNew = zombie:getModData().sFoodsNew or 0;		---counter for reset
				
				if maxReward ~= -1 and zombie:getModData().sFoods <= maxReward then 
					zombie:getModData().sFoods = (zombie:getModData().sFoods + 1) 
					print("offerings: " .. zombie:getModData().sFoods .. " /" .. maxReward);
				end

				if maxReward ~= -1 and zombie:getModData().sFoods >= maxReward then 
					zombie:getModData().wasSubdued = true;
					slayerSpeak(zombie, "banRewardF");
					if zombie:getModData().sFoods > maxReward then 
						print("maxed offerings: " .. zombie:getModData().sFoods .. " /" .. maxReward);
						return; 
					end
				end
				
				local function reducer(item, amount)
					local hunger = item:getHungChange();
					local thirst = item:getThirstChange();
					local unhappy = item:getUnhappyChange();
					local boredom = item:getBoredomChange();
					local calories = item:getCalories();
					local carbs = item:getCarbohydrates();
					local fats = item:getLipids();
					local protein = item:getProteins();
					local stress = item:getStressChange();
					
					if hunger ~= nil then item:setHungChange(hunger/amount); end
					if thirst ~= nil then item:setThirstChange(thirst/amount); end
					if unhappy ~= nil then item:setUnhappyChange(unhappy/amount); end
					if boredom ~= nil then item:setBoredomChange(boredom/amount); end
					if calories ~= nil then item:setCalories(calories/amount); end
					if carbs ~= nil then item:setCarbohydrates(carbs/amount); end
					if fats ~= nil then item:setLipids(fats/amount); end
					if protein ~= nil then item:setProteins(protein/amount); end
					if stress ~= nil then item:setStressChange(stress/amount); end
				end
				
				local itemsT1 = {};
				--- T1 = tier 1, in case of future expansions
				table.insert(itemsT1, "Base.DogfoodOpen");
				table.insert(itemsT1, "Base.TunaTinOpen");
				table.insert(itemsT1, "Base.OpenBeans");
				table.insert(itemsT1, "Base.CannedCornedBeefOpen");
				table.insert(itemsT1, "Base.CannedChiliOpen");
				table.insert(itemsT1, "Base.CannedBologneseOpen");
				table.insert(itemsT1, "Base.CannedCarrotsOpen");
				table.insert(itemsT1, "Base.CannedCornOpen");
				table.insert(itemsT1, "Base.CannedMushroomSoupOpen");
				table.insert(itemsT1, "Base.CannedPeasOpen");
				table.insert(itemsT1, "Base.CannedPotatoOpen");
				table.insert(itemsT1, "Base.CannedSardinesOpen");
				table.insert(itemsT1, "Base.CannedTomatoOpen");
				table.insert(itemsT1, "Base.TinnedSoupOpen");
				table.insert(itemsT1, "Base.CannedFruitCocktailOpen");
				table.insert(itemsT1, "Base.CannedPeachesOpen");
				table.insert(itemsT1, "Base.CannedPineappleOpen");
				table.insert(itemsT1, "Base.JuiceBox");
				table.insert(itemsT1, "Base.Pop2");
				table.insert(itemsT1, "Base.Crisps");
				table.insert(itemsT1, "Base.Crisps2");
				table.insert(itemsT1, "Base.BeerCan");
				table.insert(itemsT1, "Base.BeerBottle");
				table.insert(itemsT1, "Base.WhiskeyFull");
				table.insert(itemsT1, "Base.PopBottle");
				table.insert(itemsT1, "Base.Cigarettes");

				local lengthT1 = 0;
				local inventory = player:getInventory();

				for k, v in pairs(itemsT1) do lengthT1 = lengthT1 + 1; end
				local x = (ZombRand(lengthT1) + 1);
				if x == 0 then x = 1 end;
				local item = (itemsT1[x]);
				if item ~= nil then 
					local cFact = player:getModData().cFact or 0;
					local chance = ZombRand(60 + cFact)		--- positive cFact increases chance of not rolling 0.
					--print("cFact chance rolled: " .. chance .. " of " .. 60 + cFact);

					if item ~= "Base.Cigarettes" then
						local item = inventory:AddItem(item);
						
						if chance < 0 then chance = 0; end
						if chance ~= 0 then
							local bonus = 0;
							if cFact < 0 then bonus = ((cFact * 3) / 100); if bonus < -1 then bonus = -1; end end 
							local depletion = (ZombRand(2,5) + bonus)	--- cFact affects this value too!
							if depletion <= 1.09 then depletion = 1.1; end	--- because you can't divide by 0.
							--print("depletion: " .. depletion);
							reducer(item, depletion);
							HaloTextHelper.addTextWithArrow(player, getText("Leftovers: ") .. item:getName(), true, HaloTextHelper.getColorGreen());
							--HaloTextHelper.addText(player, getText("Leftovers: ") .. item:getName(), HaloTextHelper.getColorGreen());
						else
							HaloTextHelper.addTextWithArrow(player, getText("Received: ") .. item:getName(), true, HaloTextHelper.getColorGreen());
						end
					else
						local loop = ZombRand(1,4);
						local item = " Cigarettes";
						
						for i = 1, loop do
							inventory:AddItem("Base.Cigarettes", 1);
						end
						HaloTextHelper.addTextWithArrow(player, getText("Received: ") .. tostring(loop) .. getText(item), true, HaloTextHelper.getColorGreen());
					end
				end	
				---- additional codes here ---- codes below give "1" count of the item no matter the number within () but working if using to only give one! yay!
				--inventory:AddItem("Base.Nails", 1);				
			end
			
			local function increaseCount(target)	----slimit no longer used, use #byCount.
				local slimitMax = target:getModData().slimitMax
				
				if slimitMax == nil then 
					--print("increaseCount: slimitMax is nil");
					target:getModData().slimitMax = 1; 
					slimitMax = target:getModData().slimitMax; 
				end
				
				target:getModData().byCount = target:getModData().byCount or {};
				local byCount = target:getModData().byCount
				
				--if slimitMax ~= -1 then --- promiscuous needs to have a counter too
				if #byCount <= target:getModData().slimitMax then
					NewID(target, zombie);
					--print(getText("sex count: ") .. #byCount .. " /" .. target:getModData().slimitMax)
					if slimitMax ~= -1 then Check(target); end
				end
				
				if #byCount > target:getModData().slimitMax then 
					NewID(target, zombie);
						
					if slimitMax ~= -1 then
						local bodyDamage = target:getBodyDamage()
						local unhappiness = bodyDamage:getUnhappynessLevel()
						
						HaloTextHelper.addText(target, getText("Unhappiness increase: having too much sex"), HaloTextHelper.getColorRed());
						bodyDamage:setUnhappynessLevel(unhappiness + 50)
					end
				end
			end
			
			local function cleanupV2(dummy, zombie)
			--- Temporarily set 'em far away
				dummy:setX(dummy:getX() + 999)

				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:getModData().isSubdued = zombie:getModData().isSubdued or false;
				local isBandit = zombie:getVariableBoolean("Bandit");
				zombie:getModData().ZomboWinSexScene = false;
				--- from IDing acting bandit ---
				if zombie:getModData().isPartner then zombie:getModData().isPartner = false; end
				zombie:setInvincible(false)
				zombie:setInvisible(false)
				zombie:setNoDamage(false)
				if not isBandit then
					if zombie:getModData().isSubdued == true then
						zombie:setCanWalk(false);
					else
						zombie:getModData().isWatching = false;
						zombie:setUseless(false)
						zombie:setCanWalk(true);
					end
				end
				
				--print("resetv2 called");
				defeatReset();
				local target = getPlayer();
				target:getModData().ZomboWinSexScene = false;
				playerDisabler(target, false) --Enable player controls
				cruelBRoll();
			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() + 999)

				local function _deleteDummy(tick)
					if tick >= 100 then
						--print("resetv1 tick maxed");
						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:getModData().isSubdued = zombie:getModData().isSubdued or false;
				local isBandit = zombie:getVariableBoolean("Bandit");
				zombie:getModData().ZomboWinSexScene = false;
				--- from IDing acting bandit ---
				if zombie:getModData().isPartner then zombie:getModData().isPartner = false; end
				zombie:setInvincible(false)
				zombie:setInvisible(false)
				zombie:setNoDamage(false)
				if not isBandit then
					if zombie:getModData().isSubdued == true then
						zombie:setCanWalk(false);
					else
						zombie:getModData().isWatching = false;
						zombie:setUseless(false)
						zombie:setCanWalk(true);
					end
				end
				
				--print("resetv1 called");
				defeatReset();
				local target = getPlayer();
				target:getModData().ZomboWinSexScene = false;
				playerDisabler(target, false) --Enable player controls
				cruelBRoll();
				end

			ZomboWin.AnimationHandler.Play(nil, {dummy, target}, chosenAnimation, true, true, includesBandit, {
				Update = function(action)
					--print(tostring(chosenAnimation.id));
					--if includesBandit then print("isbandit true!"); else print("isbandit false!"); end		--- double check, sickness still occurs for bandit defeats
					--isoPlayersInAct[target].TimeOut = 0		---> doing this, not recommended!!!
					--[[if target:isNPC() then
						npcDisabler(target, true);
						target:setX(dummy:getX());
						target:setY(dummy:getY());
						target:setZ(dummy:getZ());
					end]]	---npc support is being slowly phased out, in favor of bandits mod.
					------------------- Health stabilizer: Update -------------------
					local currentHP = target:getBodyDamage():getHealth();
					target:getModData().zHandler = target:getModData().zHandler or {};
					target:getModData().zHandler.animHP = target:getModData().zHandler.animHP;
					
					--if player:getModData().zHandler.animHP == nil then player:getModData().zHandler.animHP = currentHP; end
					if target:getModData().zHandler.animHP > currentHP then
						local difHP = target:getModData().zHandler.animHP - currentHP;
						-- Insert modifier with 'difHP' for exhaustion penalty feature
						target:getBodyDamage():AddGeneralHealth(difHP);
					end
					
					local function updateHandlerFunc(dummy, target)
						
						------------------- 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 includesBandit and 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 -------------------
						if not includesBandit then
							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
						end
						
						------------------- Sickness debuff -------------------
						local maxSick = (ZomboWinDefeatConfig.ModOptions.options.dropdown9);
						
						if maxSick ~= 1 then
							if not includesBandit then
								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
								
								if maxSick ~= 1 and not target:HasTrait("unblessing") then
									if maxSick == 2 then
										if bodyDamage:getFoodSicknessLevel() <= 49 then
											bodyDamage:setFoodSicknessLevel(sickness + defeatSickness)
										end
									elseif maxSick == 3 then
										if bodyDamage:getFoodSicknessLevel() <= 74 then
											bodyDamage:setFoodSicknessLevel(sickness + defeatSickness)
										end
									elseif maxSick == 4 then
										if bodyDamage:getFoodSicknessLevel() <= 89 then
											bodyDamage:setFoodSicknessLevel(sickness + defeatSickness)
										end
									elseif maxSick == 5 then
										if bodyDamage:getFoodSicknessLevel() <= 99 then
											bodyDamage:setFoodSicknessLevel(sickness + defeatSickness)
										end
									end
								end
							end
						end
						------------------- Zombification debuff -------------------
						
						if not target:isNPC() then
							if target:HasTrait("unblessing") then
								local zwinfect = target:getBodyDamage();
								local infect = zwinfect:getInfectionLevel();
								local iamt = .0008 --zwinfect:getInfectionTime();
								
								if target:HasTrait("ProneToIllness") then
									iamt = .0012;
								elseif target:HasTrait("Resilient") then
									iamt = .0004
								end
								
								if infect > 0 then
									zwinfect:setInfectionLevel(infect - iamt);
								end
								
								--[[
								if player:getModData().zHandler.infection > 0 then
									player:getModData().zHandler.infection = (player:getModData().zHandler.infection - iamt)
								end
								]]
							end
						end
						
					end		--- belongs to local function
					
					if not target:isNPC() then updateHandlerFunc(dummy, target); end
					if not ZomboWinDefeatConfig.ModOptions.options.box17 and target:isNPC() then updateHandlerFunc(dummy, target); end
					--if target:isNPC() then playerDisabler(target, true); end
					
					------------------- Aoe set Watching -------------------
					local enemies = target:getSpottedList()
					local maxDistance = 4

					for i = 0, enemies:size() - 1 do
						local enemy = enemies:get(i)
						local isBandit = enemy:getVariableBoolean("Bandit");
						if enemy:isAlive() and enemy:isZombie() and enemy:DistTo(target) <= maxDistance and not isBandit then
							if enemy:DistTo(target) <= (maxDistance/2) then
								enemy:setStaggerBack(true);
							end
							if not zTUL[enemy] and not enemy:isUseless() then
								enemy:getModData().isWatching = true;
								--enemy:setUseless(true); --- applied elsewhere
								table.insert(zUL, enemy);
								zTUL[enemy] = true;
							end
							--enemy:setKnockedDown(true);
						end
					end
					--print("Update finished.");
					
					---------- dummy clothes removal ----------
					if ZomboWinDefeatConfig.ModOptions.options.box15 then
						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 -------------------
					
					------------------- end of: update -------------------
				end,
				WaitToStart = function(action)
					
					--local player = target;
					if target == nil then target = getPlayer(); end
					zombie:setInvisible(true)
					local isBandit = zombie:getVariableBoolean("Bandit");
					if not isBandit then zombie:getModData().isWatching = true; end
					zombie:getModData().ZomboWinSexScene = true;
					--target:getModData().ZomboWinSexScene = true;
					dummy:setGhostMode(true)
					dummy:setInvisible(false)
					
					if target ~= nil then
						--local player = target
						--if target:isNPC() then disarm(target); end
						playerDisabler(target, true) --Disable player controls
					end
					
					------------------- Health stabilizer: Wait To Start -------------------
					local currentHP = target:getBodyDamage():getHealth();
					target:getModData().zHandler = target:getModData().zHandler or {};
					target:getModData().zHandler.animHP = currentHP;
					
					if target:HasTrait("unblessing") then
						target:getBodyDamage():setInfected(false);
					end
					------------------- cFact logic: Wait To Start -------------------
					if target:getModData().isOffering then	
						cFactCalculate(target, traitValues);		--- resets cFact .. anything affecting cFact MUST go below this line of code!
					
					------------------- cFact dirty and bloody: Wait To Start -------------------
						local filth = target:getHumanVisual();
						local dirtL = 0;
						local bloodL = 0;
						local totalFilth = 0;
						
						local function round(number)
							return math.floor(number + 0.5);
						end
						
						for i = 0, BloodBodyPartType.MAX:index() - 1 do
							local bloodBodyPartType = BloodBodyPartType.FromIndex(i)
							dirtL = dirtL + filth:getDirt(bloodBodyPartType);
						end
						
						for i = 0, BloodBodyPartType.MAX:index() - 1 do
							local bloodBodyPartType = BloodBodyPartType.FromIndex(i)
							bloodL = bloodL + filth:getBlood(bloodBodyPartType);
						end
						
						totalFilth = round(dirtL + bloodL);
						target:getModData().cFact = target:getModData().cFact + totalFilth;
						--[[
						print("dirt level: " .. dirtL);
						print("blood level: " .. bloodL);
						print("total filth: " .. totalFilth);
						print("accum cFact: " .. player:getModData().cFact);]]
					end
					------------------- Push other zombies away -------------------
					if #zUL > 0 then
						for i = #zUL, 1, -1 do
							local enemy = zUL[i]
							if enemy ~= zombie then
								enemy:setUseless(false);
								enemy:getModData().isWatching = true;
							end
						end
					end
					zUL = {};
					zTUL = {};
					
					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 and not isBandit 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(target, false) --Enable player controls
						defeatReset();
						action:forceStop()
					end
					--print("Wait finished.");
					------------------- end of: wait to start-------------------
				end,
				Start = function(action)
				
					--local player = target;
					zombie:setInvisible(true)
					dummy:setInvisible(false)
					target:getModData().ZomboWinSexScene = true;
					
					if target ~= nil then
						--local player = target
						--if target:isNPC() then disarm(target); end
						playerDisabler(target, true) --Disable player controls
					end
					
					isoPlayersInAct[dummy] = nil
					--print("Start finished");
					------------------- end -------------------
				end,
				Stop = function(action)
					--local player = target;
					target:getModData().ZomboWinSexScene = false;
					-- this will execute regardless of traits.
					if target ~= nil then 
						if not target:isNPC() then target:getModData().timedHandler.hoursSinceLastDefeat = target:getModData().timedHandler.hoursSinceLastDefeat or 0 end
						
						local function stopHandlerFunc(dummy, target)
							-- Hard-Mode: It Follows
							if not target:HasTrait("itFollows") then
							--if not ZomboWinDefeatConfig.ModOptions.options.box13 then
								--[[if target:HasTrait("unblessing") then
									player:getBodyDamage():setInfectionTime(player:getModData().zHandler.infectTime)
									player:getBodyDamage():setInfectionLevel(player:getModData().zHandler.infection)
								end]]
								-- Protoype only, not final!
								if target:HasTrait("Virgin") then
									local bodyDamage = target:getBodyDamage()
									local unhappiness = bodyDamage:getUnhappynessLevel()
									
									if not includesBandit then bodyDamage:setUnhappynessLevel(unhappiness + 100); end
									HaloTextHelper.addTextWithArrow(target, getText("Virginity"), false, HaloTextHelper.getColorRed());
									target:getTraits():remove("Virgin")
									target: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 includesBandit and not target: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") and not target:HasTrait("dpress") then
											if not target:HasTrait("Traumatized") then
												HaloTextHelper.addTextWithArrow(target, 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 includesBandit and not target:getModData().isWilling then
									--target:getBodyDamage():setInfectionLevel(0)
									target:setHealth(0)
								end
							end
							
							------------------- npc kill code -------------------

						end
						
						if not target:isNPC() then stopHandlerFunc(dummy, target); end
						if not ZomboWinDefeatConfig.ModOptions.options.box17 and target:isNPC() then stopHandlerFunc(dummy, target); end

					end
					------------------- UnUseless bystanders -------------------
					for i = 1, #zUL do
						local enemy = zUL[i]
						enemy:getModData().isWatching = false;
						enemy:setUseless(false)
						enemy:setCanWalk(true)
						--table.remove(zUL, i)
					end
					------------------- end -------------------
					--if player:getModData().isOffering then offerSmex(target); end
					cleanup();  ----------------maybe check with ZomboWinSexScene.... bypass this cleanup? perhaps for perform? make animations play until cancelled?-------------------------
					--print("Stop finished.");
					------------------- end -------------------
				end,				
				Perform = function(action)
					--target:getModData().isSoloPlay = target:getModData().isSoloPlay or true;
					--if target:getModData().isSoloPlay == false then target:getModData().isSoloPlay = true; print("solo is now reset(true)."); else print("solo has been true."); end
					--local player = target;
					target:getModData().ZomboWinSexScene = false;
					if target ~= nil then 
						-- this will execute regardless of traits.
						if not target:isNPC() then target:getModData().timedHandler.hoursSinceLastDefeat = target:getModData().timedHandler.hoursSinceLastDefeat or 0; end
											
						--------------------------------- s limit increase ---------------------------------
						if ZomboWinDefeatConfig.ModOptions.options.box19 or includesBandit then
							increaseCount(target);
						end
						--------------------------------- s limit end ---------------------------------
						--if target:isNPC() then target:getModData().safeA = 0; end
						
						local function performHandlerFunc(dummy, target)
							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(target, 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()

										if not includesBandit then bodyDamage:setUnhappynessLevel(unhappiness + 100); end
										target:getTraits():remove("Virgin")
										target: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 includesBandit and not target: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") and not target:HasTrait("dpress") then
											if not target:HasTrait("Traumatized") then
												HaloTextHelper.addTextWithArrow(target, 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
							
								if target:HasTrait("pacify") then
									HaloTextHelper.addTextWithArrow(target, getText("Pacified"), true, HaloTextHelper.getColorGreen());
									getSoundManager():PlayWorldSound('pacify01', true, target:getCurrentSquare(), 0, 4, 1, false)
									zombie:setUseless(true);
									zombie:getModData().isSubdued = 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(target, getText("Succubus"), true, HaloTextHelper.getColorGreen());
									zombie:Kill(target);
								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
							
								tCounterReset(target);
							else
								-- player kill code
								if not includesBandit and not target:getModData().isWilling then
									--target:getBodyDamage():setInfectionLevel(0)
									target:setHealth(0)
								end
								-- npc kill code

							end
						end
						
						if not target:isNPC() then performHandlerFunc(dummy, target); end
						if not ZomboWinDefeatConfig.ModOptions.options.box17 and target:isNPC() then performHandlerFunc(dummy, target); end
					end
					
					-- this zombie is the acting zombie. sometimes push doesn't apply to it, this ensures it does.
					if not includesBandit then
						zombie:setStaggerBack(true);
						zombie:setKnockedDown(true);
					end
					
					------------------- Bystander Converter -------------------
					for i = 1, #zUL do
						local enemy = zUL[i]
						enemy:getModData().isWatching = false;
						enemy:setUseless(false)
						enemy:setCanWalk(true)
						--table.remove(zUL, i)
					end
					------------------- end -------------------
					--print("perform: cleanup called");
					if target:getModData().isOffering == true then offerSmex(target); --[[else print("isOffering false");]] end
					cleanup();
				end
			})
			
			isoPlayersInAct[dummy] = {Dummy = dummy, TimeOut = 0, Callback = cleanup}
end

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; end of initializations");
			if not isWilling then	--< willing bypass, first.
			--Anti Staggering Mechanic, availiable for both sexes
				if not target:isNPC() then
					if target:isRunning() or target:isSprinting() then
						zombie:setStaggerBack(true);
						HaloTextHelper.addText(target, getText("Grab Dodged"), HaloTextHelper.getColorGreen());
						clearAnim(target); --resets scenes
						defeatReset();
						--[[if sq1 ~= nil then sq2 = sq1; end
						
						-- Update the current location
						sq1 = target:getSquare()

						-- Compare the two squares' coordinates (x, y, z) to check if the player has moved
						if sq2 and sq1 then
							if sq1:getX() == sq2:getX() and sq1:getY() == sq2:getY() and sq1:getZ() == sq2:getZ() then
								print("Player hasn't moved")
							else
								print("Player has moved")
								sq1 = nil; sq2 = nil;
								zombie:setStaggerBack(true);
								HaloTextHelper.addText(target, getText("Grab Dodged"), HaloTextHelper.getColorGreen());
								clearAnim(target); --resets scenes
								defeatReset();
								return;
							end
						end]]
					end
				end
			--Undress Code
				if funcUndressOverDefeat(zombie, target) then
					clearAnim(target); --resets scenes
					defeatReset();
					return;
				end
			end
			--print("Checkpoint B; end of undressing logic");
			--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 target:HasTrait("itFollows") then	--As of PR1.15, only it follows will bump players.
					-- 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() and not isMainHeroFemale 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; end of male/itFollows conditions check");
			--- Only straight zombie sex for now
			if isMainHeroFemale == zombieIsFemale then clearAnim(target); --[[print("MarkMarkMark");]] return; end
			--print("Checkpoint C.1");
			--- Time to grape em
			--if target:isNPC() then disarm(target); end
			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");
			local animationList = {};
			--- detects zombie to target direction
			if zombie:isBehind(target) or not zombie:isBehind(target) and isWilling then
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat", "f2b"}, {}, true);
				--print("Checkpoint D.1, zombie is behind.");
			else
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat", "f2f"}, {}, true);
				--print("Checkpoint D.2, zombie is in front.");
			end
			--- end of animation selector conditions ---
			--print(animationList);
			if animationList == nil then return; end
			local index = ZombRand(1, #animationList + 1)
			local chosenAnimation = animationList[index]
			if chosenAnimation == nil then return; end
			
			local includesBandit = false;
			if target:isZombie() then
				includesBandit = target:getVariableBoolean("Bandit");
			elseif zombie:isZombie() then
				includesBandit = zombie:getVariableBoolean("Bandit");
			end
			
			--print("Checkpoint E, playing animation...");
			playAnim(zombie, dummy, target, chosenAnimation, includesBandit);

			--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 target:isZombie() then return; end
	if not target:isAlive() 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.
	
	if zombie:isZombieAttacking(target) then	
		local dOption = (ZomboWinDefeatConfig.ModOptions.options.dropdown13);
		local isBandit = zombie:getVariableBoolean("Bandit");
		
		if dOption == 2 then
			if isBandit then 
				--print("exclusion detected: bandit. return.");
				return; 
			end
		elseif dOption == 3 then
			if not isBandit then 
				--print("exclusion detected: zombie. return.");
				return; 
			end
		end
		
		--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;
				--if target:isNPC() then target:getModData().safeA = (target:getModData().safeA + 1) or 0; end
				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 = 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

local function resetDefeat()
	local player = getPlayer();
	--if player:isNPC() then player:getModData().safeA = 0; end
	--player:getModData().slimit = 0;	--- disable this after debugging
	player:getModData().ZomboWinSexScene = false;
	player:getModData().dontDefeat = false;
	defeatAvailable = true;
	playerDisabler(player, false);
end

local function knockoutFeature(source, target)
	local idPlayer = source ~= nil and target ~= nil and source:isZombie() ~= target:isZombie();
	if idPlayer then 
		--[[local weapon = player:getPrimaryHandItem();
		local weapon2 = player:getSecondaryHandItem();
		if weapon == nil and weapon2 == nil then return; end]]
		local zombie = source;
		if target:isZombie() and not target:isProne() and not target:getModData().isSubdued and not target:isCrawling() then
			print("a zombie being attacked.");
			zombie = target;
			---- insert chances here ----
			local chances = ((ZomboWinDefeatConfig.ModOptions.options.dropdown8) - 1);	--- 0,1,2,3
			if chances ~= 3 then
				local numRand = ZombRand(3);	--- 0,1,2
				if chances <= numRand then
					print("a zombie is knockedout.");
					--source:Kill(zombie);
					zombie:setBumpDone(true);
					--zombie:setAlwaysKnockedDown(true);
					--zombie:setBumpType("pushedFront");
					zombie:getModData().isSubdued = true;
					if source:isBehind(zombie) then
						--f2b--
						HaloTextHelper.addTextWithArrow(source, getText("KnockdownBack"), true, HaloTextHelper.getColorGreen());
						zombie:setBumpFall(true);
						zombie:setBumpType("trippingFromSprint");
					else
						--f2f--
						HaloTextHelper.addTextWithArrow(source, getText("KnockdownFront"), true, HaloTextHelper.getColorGreen());
						zombie:setBumpFall(true);
						zombie:setBumpFallType("pushedFront");
					end
					
				end
			end
		else
			--print("a zombie is attacking.");
		end
	end
end

--- Hook up event listeners
Events.OnZombieUpdate.Add(OnZombieUpdate);
--Events.OnWeaponHitCharacter.Add(knockoutFeature) --WIP
Events.OnTick.Add(OnGrabImmunity);
--Events.OnKeyStartPressed.Add(executeStruggle);
Events.OnGameStart.Add(resetDefeat);

return ZombieHandler;