-- Author SolarEdge 2024

--- 684, will not return until B42 Stable is released

local zombieUpdate = {};

local ZomboWin = require("ZomboWin/ZomboWin")
require("ZomboWin/ZomboWinAnimationUtils")
require("ZomboWin/Temptation");
require("ZomboWin/ZLMain");
require("ZomboWin/BodyCount");
require("ZomboWin/AnimationHandler");
require("ISBaseTimedAction");


local ZomboWinActType = ZomboWinActType

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

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

local isoPlayersInAct = {}

local tickUpdateRate = 20;			--: Ticks upto
local lastTickUpdated = 0;			--: For bandits
local anotherTick = 0;				--: For zombies
local aThirdTick = 0;
local banditsDcd = 60;
local zombiesDcd = 60;
local cruelTimer = 15;

local grabDistance = 1.4
local maxDistance = 1.4

local banditsEnabled = false;

if getActivatedMods():contains("\\Bandits2") == true then
	banditsEnabled = true;
	require("Bandits/BanditUpdate");
	require("Bandits/Bandit");
	require("Bandits/BanditBrain");
	require("Bandits/ZombiePrograms");
	require("Bandits/BanditPrograms");
	require("Bandits/BanditUtils");
	require("Bandits/BanditPlayer");
	--- local isBandit = zombie:getVariableBoolean("Bandit");

Bandit.zwSoundTab = {
    strPerverse = 	          			{prefix = "ZW1Perverse_", chance = 30, randMax = 6},
	lesPerverse = 	          			{prefix = "ZW2Perverse_", chance = 30, randMax = 6},
	gayPerverse = 	          			{prefix = "ZW3Perverse_", chance = 30, randMax = 6},
	strStrip = 	          				{prefix = "ZW1Strip_", chance = 40, randMax = 6},
	lesStrip = 	          				{prefix = "ZW2Strip_", chance = 40, randMax = 6},
	gayStrip = 	          				{prefix = "ZW3Strip_", chance = 40, randMax = 6},
	reBandit = 	         				{prefix = "ZWreBandit_", chance = 40, randMax = 6},
	banRapist = 	          			{prefix = "ZWbanRapist_", chance = 40, randMax = 6},
	banCruelF = 	          			{prefix = "ZWbanMoraleF_", chance = 100, randMax = 6},
	banCruelH = 	          			{prefix = "ZWbanMoraleH_", chance = 100, randMax = 6},
	banOwnerH =							{prefix = "ZWbanOwnerH_", chance = 100, randMax = 6},
	banRewardF =						{prefix = "ZWbanRewardF_", chance = 100, randMax = 3},
}

end

	function slayerShove(enemy, player, isfake)
		if not banditsEnabled then return; end
		if not enemy:getVariableBoolean("Bandit") then return; end
		local facing = player:isFacingObject(enemy, 0.5)
		if player:getModData().ZomboWinSexScene then return end
												
		enemy:faceThisObject(player);
		enemy:setBumpType("Shove");
		
		if not isfake then
			player:clearVariable("BumpFallType")
			player:setBumpType("stagger")

			if facing then player:setBumpFallType("pushedFront") else player:setBumpFallType("pushedBehind") end
		end
	end
	
	function slayerSpeakChance(enemy, phrase)
		if not banditsEnabled then return; end
		if not enemy:getVariableBoolean("Bandit") then return; end
		if ZombRand(3) == 1 then 
			return false;
		end
		
		local sex = "Male"
		if enemy:isFemale() then 
			sex = "Female" 
		end

		local config = Bandit.zwSoundTab[phrase]
		if config then
			local r = ZombRand(100)
			if r < config.chance then
				if SandboxVars.ZomboWin.banditCustomLines and enemy:getModData().isRapist then
					local sound = config.prefix .. sex .. "_" .. tostring(1 + ZombRand(config.randMax))
					local text = "IGUI_Bandits_Speech_" .. sound
					if Bandit.IsHostile(enemy) then
						enemy:addLineChatElement(getText(text), 0.8, 0.1, 0.1)
					else
						enemy:addLineChatElement(getText(text), 0.1, 0.8, 0.1)
					end
				end
				return true;
			end
		end
	end
	
	function slayerSpeak(enemy, phrase)
		if not banditsEnabled then return; end
		if not enemy:getVariableBoolean("Bandit") then return; end
		local sex = "Male"
		if enemy:isFemale() then 
			sex = "Female" 
		end

		local config = Bandit.zwSoundTab[phrase]
		if config then
			local r = ZombRand(100)
			if r < config.chance then
				if SandboxVars.ZomboWin.banditCustomLines and enemy:getModData().isRapist then
					local sound = config.prefix .. sex .. "_" .. tostring(1 + ZombRand(config.randMax))
					local text = "IGUI_Bandits_Speech_" .. sound
					if Bandit.IsHostile(enemy) then
						enemy:addLineChatElement(getText(text), 0.8, 0.1, 0.1)
					else
						enemy:addLineChatElement(getText(text), 0.1, 0.8, 0.1)
					end
				end
			end
		end
	end
	
	local function becomeRapist(bandit)
		if not banditsEnabled then return; end
		if not bandit:getVariableBoolean("Bandit") then return; end
		
		local config = {}
		config.mustSee = true
		config.hearDist = 7
		---- [bug]: bandits fighting bandits will switch often between bandit/ defeat causing a lot of chatter [fixed] ----
		local closestZombie = BanditUtils.GetClosestZombieLocation(bandit)
		local closestBandit = BanditUtils.GetClosestEnemyBanditLocation(bandit)
		local closestPlayer = BanditUtils.GetClosestPlayerLocation(bandit, config)
		
		local function beRapist(bandit)
			print("rapists is called");
			Bandit.SetHostile(bandit, false);
			Bandit.SetHostileP(bandit, false);
			isBanditPrideful(bandit);	--- yes there are two checks
			bandit:getModData().isRapist = true;
			if bandit:getModData().isCruel then bandit:getModData().cruelTick = 0; end
			if Bandit.GetProgram(bandit).name ~= "Defeat" then
				Bandit.SetProgram(bandit, "Defeat", {});
				Bandit.SetProgramStage(bandit, "Prepare");
				slayerSpeakChance(bandit, "banRapist");
			end
		end
		

		if closestPlayer then
			local closestZombieDist = closestZombie and closestZombie.dist or math.huge
			local closestBanditDist = closestBandit and closestBandit.dist or math.huge
			
			if closestPlayer.dist < closestBanditDist and closestPlayer.dist < closestZombieDist then
				beRapist(bandit)
			end
		end
	end
	
	local function normalize(bandit, player)
		if not banditsEnabled then return; end
		if not bandit:getVariableBoolean("Bandit") then return; end
		---- [bug]: attacking 'morale: friendly' bandits don't switch them to aggro [fixed] ----
		--if bandit:getModData().isPartner then bandit:getModData().isPartner = false; end
		if bandit:spotted(player, false) then return; end
		if player:CanSee(bandit) then return; end
		print("normalize is called");
		if not Bandit.IsHostile(bandit) then Bandit.SetHostile(bandit, true);Bandit.SetHostileP(bandit, true); end
		bandit:getModData().isRapist = false;
		local brain = BanditBrain.Get(bandit)
		
		if brain.program.name ~= "Bandit" then
			if Bandit.GetProgram(bandit).name ~= "Bandit" then
				--print("a bandit is set to bandit program");
				Bandit.SetProgram(bandit, "Bandit", {});
				Bandit.SetProgramStage(bandit, "Prepare");
				phrase = "reBandit";
				slayerSpeakChance(bandit, phrase);
			end
		end
	end
	
	local function cruelNormalize(bandit)
		if not banditsEnabled then return; end
		if not bandit:getVariableBoolean("Bandit") then return; end
		print("cruel normalize is called");
		Bandit.SetHostile(bandit, true);
		Bandit.SetHostileP(bandit, true);
		bandit:getModData().isRapist = false;
		if Bandit.GetProgram(bandit).name ~= "Bandit" then
			Bandit.SetProgram(bandit, "Bandit", {});
			Bandit.SetProgramStage(bandit, "Prepare");
		end
	end
	
	local function optionNormalize(bandit)
		if not banditsEnabled then return; end
		if not bandit:getVariableBoolean("Bandit") then return; end
		--- this is used for alt normalization from switching defeat options
		print("option normalize is called");
		--if bandit:getModData().isPartner then bandit:getModData().isPartner = false; end
		bandit:getModData().isRapist = false;
		if Bandit.GetProgram(bandit).name == "Defeat" then
			--print("a rapist bandit is set to bandit program");
			Bandit.SetProgram(bandit, "Bandit", {});
			Bandit.SetProgramStage(bandit, "Prepare");
		end
	end
	
	-- turns a zombie into a bandit -- copy-paste from Slayers code
	function zwBanditize(zombie, brain)
	print("zwBanditize is called.");
		-- load brain
		BanditBrain.Update(zombie, brain)

		-- just in case
		zombie:setNoTeeth(true)

		-- used to determine if zombie is a bandit, can be used by other mods
		zombie:setVariable("Bandit", true)

		-- bandit primary and secondary hand items
		zombie:setVariable("BanditPrimary", "")
		zombie:setVariable("BanditSecondary", "")

		-- bandit walking type defined in animations
		zombie:setWalkType("Walk")
		zombie:setVariable("BanditWalkType", "Walk")

		-- this shit here is important, removes black screen crashes
		-- with this var set, game engine skips testDefense function that
		-- wrongly refers to moodles, which zombie object does not have
		zombie:setVariable("ZombieHitReaction", "Chainsaw")

		-- stfu
		zombie:getEmitter():stopAll()

		zombie:setPrimaryHandItem(nil)
		zombie:setSecondaryHandItem(nil)
		zombie:resetEquippedHandsModels()
		zombie:clearAttachedItems()

		-- makes bandit unstuck after spawns
		zombie:setTurnAlertedValues(-5, 5)

	end
	
	-- turns bandit into a zombie -- copy-paste from Slayers code
	function zwZombify(bandit)
	print("zwZombify is called.");
		bandit:setNoTeeth(false)
		bandit:setUseless(false)
		bandit:setVariable("Bandit", false)
		bandit:setVariable("BanditPrimary", "")
		bandit:setVariable("BanditSecondary", "")
		bandit:setWalkType("2")
		bandit:setVariable("BanditWalkType", "")
		bandit:setPrimaryHandItem(nil)
		bandit:setSecondaryHandItem(nil)
		bandit:resetEquippedHandsModels()
		bandit:clearAttachedItems()
		BanditBrain.Remove(bandit)
	end
	
	local function isCooldown(player, enemy, cooldown)
		--- pbDcd = players' bandit defeat cool down
		if enemy:getVariableBoolean("bandit") then
			local pbDcd = player:getModData().bDcd or cooldown;
			if pbDcd > cooldown then
				--print("cooldown finished");
				return false
			end
		else
			local pbzDcd = player:getModData().zDcd or cooldown;
			if pbzDcd > cooldown then
				print("zcooldown finished");
				return false
			end
		end
		print("is on cooldown")
		return true;
	end

	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"}
	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"}
	CheckPartsSubmissiveTrue = {
	"Underwear","UnderwearBottom","BodyCostume","Legs5","Skirt","Pants","Legs1","Dress","Torso1Legs1","BathRobe","FullTop","TorsoExtra","FullSuit","FullSuitHead"}
	CheckPartsSubmissiveFalse = {
	"Underwear","UnderwearBottom","BodyCostume","Legs5","Pants","Legs1","Torso1Legs1","BathRobe","FullTop","TorsoExtra","FullSuit","FullSuitHead"}

	local function CheckClothingList()
		local player = getPlayer();
		local ClothingCheckList = {};
		local ignoreSkirts = (SandboxVars.ZomboWin.skirtLogic)
		
		if not ignoreSkirts then
			if player:HasTrait("submissive") then
				ClothingCheckList = CheckPartsSubmissiveTrue;
			else
				ClothingCheckList = CheckPartsInTheseOrder;
			end
		else
			if player:HasTrait("submissive") then
				ClothingCheckList = CheckPartsSubmissiveFalse;
			else
				ClothingCheckList = CheckPartsInTheseOrderv2;
			end
		end
		
		return ClothingCheckList
	end

	local function isWearingClothes(zombie, target)
	if SandboxVars.ZomboWin.skipLogic then return true; end
	--- 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());
								local conditionDamage = ((SandboxVars.ZomboWin.stripDamage) - 1);
								
								if target:HasTrait("viciousZombies") then
									conditionDamage = (conditionDamage + 1);
									local dmgMultiplierChance = 0;
									if ZombRand(10) == dmgMultiplierChance then
										conditionDamage = (conditionDamage + 1);
									end
								end
								
								if conditionDamage > 0 then
									item:setCondition(item:getCondition() - conditionDamage)
								end
								
								--- its here ---
								if target:HasTrait("betterAlive") then
									local act = ISInventoryTransferAction:new(target, item, item:getContainer(), ISInventoryPage.floorContainer[1])
									act:transferItem(item);
								elseif not target:isNPC() then
									target:removeWornItem(item);
								end
								
								-- Innocent, add panic
								if target:HasTrait("Innocent") then
									local currentPanic = target:getStats():getPanic()
									target:getStats():setPanic(currentPanic + 10)
									if target:getStats():getPanic() > 99 then
										target:getStats():setPanic(99)
									end
								end
								
								-- No stagger if no clothes lost
								if target:HasTrait("buttonedUp") then
									zombie:setBumpType("trippingFromSprint");
									zombie:setSlowFactor(0.85);
									zombie:setSlowTimer(0.8);
								else
									if not zombie:getVariableBoolean("bandit") then
										--zombie:setStaggerBack(true);
									end
								end
								
								if target:HasTrait("baggyStyle") then
									target:setSlowFactor(0.85);
									target:setSlowTimer(0.3);
								end
								
								-- play sound indicator, option01 is a check for none undressed on grab.
								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
								
								ISInventoryPage.renderDirty = true;
								removedItem = true;
							end
						end
					end
					
					if removedItem then
						break
					end
				end
			end
		end
	end

	return canGrape
end
	
	local function resetAnim(target)
		target:getModData().ZomboWinSexScene = false;
	end
	
	local function exertPenalty(target)
		resetAnim(target); 
		target:Say("I can't f**k right now!");
		--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 attemptBanditDefeat(zombie, target)	--this for bandit only! b > p
		--print("cp. 1");
		if not SandboxVars.ZomboWin.enableMod then return; end
		if target:getModData().ZomboWinSexScene then return; end
		--print("cp. 2");
		
		local isMainHeroFemale = target:isFemale()
		local isZombieFemale = zombie:isFemale()
---------------------------------------------------------------------- Bandits: Attempt Defeat Conditions -------------------------------------------------
		--- cancels non gay bandits from defeats
		if not isMainHeroFemale and not isBanditPrideful(zombie) then return; end
		
		--- no FxF animations available ---
		if isMainHeroFemale and isTargetFemale then return; end
		--print("cp. 3");
		
		--grabDistance = 1.4
		if zombie:DistTo(target) > grabDistance then return; end
		
		--Defeat Condition Skip for new mechanic + exertPenalty
		if not SandboxVars.ZomboWin.skipLogic then
			-------------------Male exertion penalty-------------------
			if not maleEnduranceCheck(target) and isZombieFemale then 
				--print("Endurance: is exhausted. STOP!!");
				return;
			end
			
			if not isPanickedCheck() and not isMainHeroFemale and isZombieFemale then		-- player is neither panicked or initiating the defeat: zombie is attempting defeat by attacking player
			-- Horny Mechanic, Exclusive to males... for now.
				--print("horny is being called");
				if hLevel == 0 then -- No moodle, no chance
					----HaloTextHelper.addText(target, getText("No Defeat"), --HaloTextHelper.getColorWhite());
					resetAnim(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());
						resetAnim(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());
						resetAnim(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());
						resetAnim(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());
						--resetAnim(target);
						--return; 
					end
				end
			end
		end
		--print("cp. 4");
-----------------------------------------------------------------------------------------------------------------------------------------------------------
		local actorGender = "";		---actorGender will ALWAYS reference the player
		--- insert defeat gayness here --- bandit gayness should have already been determined! --- error here somewhere
		if isMainHeroFemale then actorGender = "Female"; else actorGender = "Male"; end
		
		--print("cp. 5");
		local animationList = {};
--															---- Testing In Progress ----
		--- detects zombie to target direction --- This is bugged, allowing females to play male animations, there aren't any strap-ons in PZ!!
		if zombie:isBehind(target) or not zombie:isBehind(target) then
			if not isMainHeroFemale and not isZombieFemale and isBanditPrideful(zombie) then 
				print("animation options 1");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"f2b", "mxm"}, {}, true);
			else
				print("animation options 2");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat", "f2b"}, {}, true);
			end
		else
			if not isMainHeroFemale and not isZombieFemale and isBanditPrideful(zombie) then 
				print("animation options 3");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"f2b", "mxm"}, {}, true);
			else
				print("animation options 4");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat", "f2f"}, {}, true);
			end
		end
--]]
		
--[[
		if zombie:isBehind(target) or not zombie:isBehind(target) then
			if not isMainHeroFemale and not isZombieFemale and isBanditPrideful(zombie) then 
				print("animation options 1");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"gPreDefeat", "f2b", "mxm"}, {}, true);
			else
				print("animation options 2");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"PreDefeat", "f2b"}, {}, true);
			end
		else
			if not isMainHeroFemale and not isZombieFemale and isBanditPrideful(zombie) then 
				print("animation options 3");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"gPreDefeat", "f2b", "mxm"}, {}, true);
			else
				print("animation options 4");
				animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"PreDefeat", "f2f"}, {}, true);
			end
		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
		--print("cp. 6, chosenAnim: " .. tostring(chosenAnimation.id));
		
		local function getPerformByGender(animation, targetGender)
			for _, actor in ipairs(animation.actors) do
				if actor.gender == targetGender then
					return actor.stages[1].perform
				end
			end
			return nil -- Return nil if no matching gender is found
		end
		
		local performValue = getPerformByGender(chosenAnimation, actorGender)
		--if performValue == nil then print("unable to ID anim for bandit."); return; else print("performValue: " .. tostring(performValue)); end
		
		target:getModData().isOffering = false;	---this method needs SERIOUS refinement, will be referenced in 'ApplyBasicTraits'
		target:getModData().isPartner = zombie;	---this method needs SERIOUS refinement, will be referenced in 'ApplyBasicTraits'
		--target:getModData().ZomboWinSexScene = true ----fix me please
		
		--print("animHandler.Play is called.");
		ZomboWin.AnimationHandler.Play(nil, {target}, chosenAnimation, true, true);
				---need to return bandits to previous brain and program---if not bandits will stand and die...could be updated in zombie update...
		local brain = BanditBrain.Get(zombie)
		local rbrain = Bandit.GetProgram(zombie).name;
		if brain.program.name ~= "Defeat" then
			if Bandit.GetProgram(zombie).name ~= "Defeat" then
				Bandit.SetProgram(zombie, "Defeat", {});
				Bandit.SetProgramStage(zombie, "Subdued");
				sceneStart(zombie, target, performValue, rbrain);
			else
				Bandit.SetProgramStage(zombie, "Subdued");
				sceneStart(zombie, target, performValue, rbrain);
			end
		else
			Bandit.SetProgramStage(zombie, "Subdued");
			sceneStart(zombie, target, performValue, rbrain);
		end
	end
	
	local function attemptZombieDefeat(zombie, target)	--this for zombies only! z > p
		--print("cpz. 1");
		if not SandboxVars.ZomboWin.enableMod then return; end
		
		if target:getModData().ZomboWinSexScene then print("pactive scene");return; end
		if zombie:getModData().ZomboWinSexScene then print("zactive scene");return; end
		zombie:getModData().ZomboWinSexScene = true;
		--print("cpz. 2");
		
		local isMainHeroFemale = target:isFemale()
		local isZombieFemale = zombie:isFemale()
		
		--- straight sex only for ZxP
		if isMainHeroFemale and isZombieFemale then print("FxF"); return; end
		if not isMainHeroFemale and not isZombieFemale then print("MxM"); return; end
		
		if zombie:DistTo(target) > grabDistance then print("too far");return; end
--[[
			--Defeat Condition Skip for new mechanic + exertPenalty
		if not SandboxVars.ZomboWin.skipLogic then
			if target:isAlive() and zombie:isAlive() then
				if not target:isProne() and not zombie:isProne() then
					if not target:isKnockedDown() and not target:isBumped() and not target:isBumpStaggered() and not target:isZombie() then
						if not target:getModData().ZomboWinSexScene then
							local zcooldown = (player:getModData().zDcd or 0);
							if zcooldown < 0 then zcooldown = 0 end
							if not isCooldown(target, zombie, zcooldown) then
								target:getModData().zDcd = 0;
								if isWearingClothes(zombie, target) then
									return;
								end
							end
						end
					end
				end
			end
		
			-------------------Male exertion penalty-------------------
			if not maleEnduranceCheck(target) and isZombieFemale then 
				--print("Endurance: is exhausted. STOP!!");
				return;
			end
			-----------------------------------------------------------

			---------------------It Follows Defeat Mechanic---------------------
			if target:HasTrait("itFollows") then
				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");
					else
						target:setBumpFallType("pushedFront");
					end
					return;
				end
			end
			--------------------------------------------------------------
		end
]]
		--local zActor = ZomboWin.ZombieHandler:convertZombieToSurvivor(zombie)		---- this code breaks multiplayer .. including the called code ---- ... currently OFF til B42 Stable (684)
		zActor:getModData().ZomboWinSexScene = true
		zActor:getModData().isExpire = true	---future dummy removal code
		target:getModData().zActor = zActor;
		--print("cpz. 3");
		if isMainHeroFemale and isZombieFemale then
			--- Lesbian
			maleCount = 0
			femaleCount = 2
		elseif isMainHeroFemale == false and isZombieFemale == false then
			--- Gay
			maleCount = 2
			femaleCount = 0
		else
			--- Straight
			maleCount = 1
			femaleCount = 1
		end

		local animationList = {};
		--- detects zombie to target direction
		if zombie:isBehind(target) or not zombie:isBehind(target) then
			animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat", "f2b"}, {}, true);
		else
			animationList = ZomboWin.AnimationUtils:getAnimations(2, maleCount, femaleCount, {"zDefeat", "f2f"}, {}, true);
		end
		
		--print("cpz. 4");
		if animationList == nil then return; end
		local index = ZombRand(1, #animationList + 1)
		local chosenAnimation = animationList[index]
		if chosenAnimation == nil then return; end
		
		--print("cpz. 5");
		-------- transfer zombie.data to player.data --------
		target:getModData().isOffering = false;
		target:getModData().isPartner = zombie;
		
		ZomboWin.AnimationHandler.Play(nil, {target, zActor}, chosenAnimation, true, true);
	end
	
	local function onBanditUpdate(zombie)	--- Shares some code with Zombies
		if not SandboxVars.ZomboWin.enableMod then return; end
		if not zombie:isAlive() then return; end
		local dOption = (SandboxVars.ZomboWin.dWho)
		local isAlive = zombie:isAlive();
		local isBandit = zombie:getVariableBoolean("Bandit");
		local isWatching = zombie:getModData().isWatching;
		local isSubdued = zombie:getModData().isSubdued;
		local isZomboWinSexScene = zombie:getModData().ZomboWinSexScene;
		local isActiveScene = zombie:getModData().activeScene;
		local isRapist = zombie:getModData().isRapist;
		local isCruel = zombie:getModData().isCruel;
		if zombie:getModData().cruelTick == nil then zombie:getModData().cruelTick = 0; end
		
		local function cruelFunc(zombie)
			if zombie:getModData().cruelTick <= cruelTimer and zombie:getModData().cruelTick ~= -1 then 
				zombie:getModData().cruelTick = zombie:getModData().cruelTick + 1;
				--print(tostring(zombie:getModData().cruelTick) .. " /" .. tostring(cruelTimer));
			elseif zombie:getModData().cruelTick > cruelTimer and zombie:getModData().cruelTick ~= 1 then 
				--print("roll: force normalize");
				zombie:getModData().cruelTick = -1;
				cruelNormalize(zombie);
			end
		end

			-------------------------------------------- prevents rapist from being hostile --------------------------------------------
			---- maybe a future feature, having rapist become aggro if attacked by stubborn player(s).
			if isAlive and isRapist then
				if Bandit.IsHostile(zombie) then
					--print("hostile rapist converted");
					Bandit.SetHostileP(zombie, false);
					Bandit.SetHostile(zombie, false);
				end
			end
			-----------------------------------------------------------------------------------------------------------------------------

		--- tick counter ---
		anotherTick = anotherTick or tickUpdateRate;
		anotherTick = anotherTick - 1
		if anotherTick > 0 then return; elseif anotherTick <= 0 then anotherTick = tickUpdateRate; end
		--print("bu: boop");
		
		----------------------------------------------- force animation override --------------------------------------- paired with ZAForn animation override!
		if isZomboWinSexScene or isActiveScene then 
		--if zombie:getModData().activeScene == true then 
			if isBandit then if Bandit.HasMoveTask(zombie) then Bandit.ClearMoveTasks(zombie); end end
			if zombie:getModData().sendAnim ~= nil then
				if zombie:isBumped() then 
					local currentAnim = zombie:getBumpType();
					if currentAnim ~= zombie:getModData().sendAnim then
						--print("update bump override is called");
						if isBandit then Bandit.RemoveTask(zombie); end
					end
					zombie:setBumpType(tostring(zombie:getModData().sendAnim));
				end
			end
		end
		------------------------------------------------------- end -----------------------------------------------------

		----------------------------------- isCruel -----------------------------------
		if banditsEnabled and dOption ~= 1 and dOption ~= 4 then
			if isBandit then
				if not Bandit.IsHostile(zombie) and isCruel then
					--- cruel tick is to provide a countdown before they start attacking
					cruelFunc(zombie);
				end
			elseif isCruel then 
				--- catch: because zombies can't be cruel ---
				cruelFunc(zombie);
			end
		end
		---------------------------------------- end ----------------------------------------

		----------------------------------- become Rapist -----------------------------------
		if banditsEnabled and dOption ~= 1 and dOption ~= 4 then
			if isBandit then
				if Bandit.IsHostile(zombie) and not isRapist then
					if not isSubdued then
						local brain = BanditBrain.Get(zombie)
						if brain.program.name == "Bandit" or brain.program.name == "Thief" then
							--- conditions before a bandit goes into defeat mode
							--if zombie:getModData().ZomboWinSexScene then print("they grape!"); end
							--[[				
							local config = {}
							config.mustSee = true
							config.hearDist = 7
							]]
							local closestPlayer = getPlayer(); --BanditUtils.GetClosestPlayerLocation(zombie, config)
							local targetPlayer = {};
							local isMainHeroFemale = closestPlayer:isFemale();
							local isBanditFemale = zombie:isFemale();
							local isBanditPride = zombie:getModData().isPrideful
							local perchance = 0
							local maxDist = (((SandboxVars.ZomboWin.bBanDDis) * 4) + 2)
							local maxHP = (SandboxVars.ZomboWin.bBanDHP)
							if maxHP == 1 then maxHP = 999 elseif maxHP == 2 then maxHP = 90 elseif maxHP == 3 then maxHP = 80 elseif maxHP == 4 then maxHP = 70 elseif maxHP == 5 then maxHP = 60 elseif maxHP == 6 then maxHP = 50 elseif maxHP == 7 then maxHP = 40 elseif maxHP == 8 then maxHP = 30 end
							
							--if isMainHeroFemale and isBanditFemale or not isMainHeroFemale and not isBanditFemale then return; end	--- samesex breaks defeats, so this is disabled
							
							-------- if player is nil or too far away --------
							if closestPlayer ~= nil then
								targetPlayer = closestPlayer;
								------------------ this could be used to check if targetPlayer == closestPlayer to ensure the bandit is still after the same player ------------------
								if zombie:DistTo(targetPlayer) > maxDist then 
									if isRapist then
										normalize(zombie, targetPlayer);
										return;
									end
								end
							else
								if isRapist then
									normalize(zombie, targetPlayer);
									return;
								end
							end
							---------------------------------------------------
							
							local currentHp = targetPlayer:getBodyDamage():getHealth();
							if currentHp < maxHP and targetPlayer:isAlive() then 
								if not zombie:isProne() and not zombie:isKnockedDown() and not zombie:isStaggerBack() and zombie:DistTo(targetPlayer) < maxDist then
									--[[
									--- no FxF animations available ---
										if isMainHeroFemale and isTargetFemale then 
											print("defeat: no FxF animations");
											return; 
										end
					
									if not isMainHeroFemale and not isBanditFemale then
										if not isBanditPrideful(zombie) then 
											print("defeat: bandit not prideful");
											return;
										end
										print("defeat: bandit is prideful, MxM");
									end
									]]
									isBanditPrideful(zombie);	--- work around for now

									local isSexing = targetPlayer:getModData().ZomboWinSexScene
									if not isSexing then 
										local dChance = (SandboxVars.ZomboWin.bBanDHo)
										if dChance == 5 then perchance = -1
										elseif dChance == 4 then perchance = ZombRand(151); 
										elseif dChance == 3 then perchance = ZombRand(101); 
										elseif dChance == 2 then perchance = ZombRand(56); 
										elseif dChance == 1 then perchance = ZombRand(1); 
										end
									else
										--- makes non horny bandits horny because they'll break the defeat
										perchance = -1;
										becomeRapist(zombie);
									end
								
									if perchance ~= -1 and perchance == 0 and not isCruel then
										becomeRapist(zombie);
									else
										--print("conditions met, roll failed: " .. tostring(perchance));
									end
								end
							else
								-------- if the player's HP manages to heal beyond threshold or sandbox var is changed --------
								if isRapist then
									normalize(zombie, targetPlayer);
								end
							end
						end
					else
						--if zombie:getModData().ZomboWinSexScene then print("you grape!"); end
					end
				--[[else
					if not zombie:getModData().isSubdued then
						normalize(zombie);
					end]]
				end
			end
		elseif banditsEnabled and dOption == 1 or dOption == 4 and isRapist then
			if isBandit then optionNormalize(zombie); end
		end
		---------------------------------------- end ----------------------------------------

		-------------------------------- isWatching: inUpdate --------------------------------	
		if isAlive and isWatching then
			--- bandits already excluded from isWatching ---
			if not zombie:isUseless() then zombie:setUseless(true);	end
			if not zombie:isNoTeeth() then zombie:setNoTeeth(true); end
		end	
		---------------------------------------- end -----------------------------------------

		-------------------------------- isSubdued: inUpdate --------------------------------
		if isAlive and isSubdued then
			local function forceUseless(zombie) --brute forces uselessness
				if not isBandit then ---subdued bandits will handle their own subdued animation
					if not zombie:isCanWalk() then zombie:setCanWalk(false); end
					if not zombie:isUseless() then zombie:setUseless(true);	end
					if not zombie:isNoTeeth() then zombie:setNoTeeth(true); end
					--if not zombie:isSitAgainstWall() then zombie:setSitAgainstWall(true); end	--- sitting zombies cannot play ANY anims ---
				end
				if zombie:isCrawling() then zombie:setCrawler(false); end
			end
			if isBandit then
				--print("a bandit is set to knock down.");
				--BanditUpdate.OnZombieDead(zombie);	--- this works but bandit zombifies, could be useful for other things.
				if Bandit.IsHostile(zombie) then Bandit.SetHostile(zombie, false); Bandit.SetHostileP(zombie, false); end
				local brain = BanditBrain.Get(zombie)
				if brain.program.name ~= "Defeat" then
					if Bandit.GetProgram(zombie).name ~= "Defeat" then
						Bandit.SetProgram(zombie, "Defeat", {});
						Bandit.SetProgramStage(zombie, "Subdued");
					else
						Bandit.SetProgramStage(zombie, "Subdued");
					end
				else
					Bandit.SetProgramStage(zombie, "Subdued");
				end
			end
			forceUseless(zombie);
		end
		---------------------------------------- end ----------------------------------------
		
		--------------------------------- activeScene: inUpdate ---------------------------------
		if isAlive and isSubdued and isActiveScene and not isBandit then
			if not zombie:isBumped() then
				zombie:setBumpType(tostring(zombie:getModData().sendAnim));
			end
		end	
		-----------------------------------------------------------------------------------------
	end

	local function onZumbieUpdate(zombie)	--- Shares NO code with Bandits... currently off til B42 Stable (684)
--[[
		--print("zu: cp 1");
		if not SandboxVars.ZomboWin.enableMod then return; end
		if not zombie:isAlive() then return; end
		
			--- Condition: disallow misc
		local target = zombie:getTarget()
		if target == nil then return; end
		if target:isZombie() then return; end	--- no ZxZ, yet.
		if not target:isAlive() then return; end
		if target:getModData().ZomboWinSexScene then return; end
		if not zombie:isZombieAttacking(target) then return; end

		aThirdTick = aThirdTick or tickUpdateRate;
		aThirdTick = aThirdTick - 1
		if aThirdTick > 0 then return; elseif aThirdTick <= 0 then aThirdTick = tickUpdateRate; end

		--print("zu: cp 2");
		local dOption = (SandboxVars.ZomboWin.dWho)
		--local isAlive = zombie:isAlive();		---not used
		local isBandit = zombie:getVariableBoolean("Bandit");
		local isWatching = zombie:getModData().isWatching;
		local isSubdued = zombie:getModData().isSubdued;
		local isZomboWinSexScene = zombie:getModData().ZomboWinSexScene;
		local isActiveScene = zombie:getModData().activeScene;
		
			--- Master: end if bandit
		if isBandit then print("isBandit"); return; end
			--- Sandbox: dWho setting
		if dOption == 2 or dOption == 4 then print("dOption == 2 or 4"); return; end
			--- Condition: disallow unfavorable
		if zombie:isUseless() then print("isUseless"); return; end
		if zombie:isProne() then print("isProne"); return; end
		if zombie:isCrawling() then print("isCrawling"); return; end
		if zombie:isStaggerBack() then print("isStaggerBack"); return; end
			--- Condition: logic check
		if isSubdued then print("isSubdued"); return; end
		if isWatching then print("isWatching"); return; end

		--print("zu: cp 3");
			-------------------------- rest of code goes here --------------------------
			if zombie:isZombieAttacking(target) then	
				
				local isMainHeroFemale = target:isFemale()
				local isZombieFemale = zombie:isFemale()
				
				if isMainHeroFemale ~= isZombieFemale then 
					--print("zu: cp 4");
					attemptZombieDefeat(zombie, target);
				end
			end
			----------------------------------------------------------------------------
]]
	end

local function onZombieReset(zombie)
	if zombie:getModData().isSubdued then zombie:getModData().isSubdued = false; end
end

local function OnPlayerUpdate(player)
	if not SandboxVars.ZomboWin.enableMod then return; end
	local dOption = (SandboxVars.ZomboWin.dWho)
	--if player:getModData().ZomboWinSexScene then print("sexscene!") end
	local cooldown = 0;
	local zcooldown = 0;
	if banditsEnabled then
		cooldown = (SandboxVars.ZomboWin.bBanDCD * banditsDcd) - banditsDcd
		if cooldown < 0 then cooldown = 0 end
	end
	
	if dOption ~= 1 and dOption ~= 4 then
		zcooldown = (SandboxVars.ZomboWin.zDCD * zombiesDcd) - zombiesDcd
		if zcooldown < 0 then zcooldown = 0 end
	end
	
	--if not banditsEnabled then print("bandits not detected"); end
	--if not ZWex then print("defeat not detected"); end
	
	if banditsEnabled and dOption ~= 1 and dOption ~= 4 then	
		if player:isAlive() and not player:getModData().ZomboWinSexScene then 
			--- bDcd = bandit defeat cool down
			player:getModData().bDcd = player:getModData().bDcd or cooldown;
			player:getModData().bDcd = (player:getModData().bDcd + 1);
		end
	end	
	
	if dOption ~= 2 and dOption ~= 4 then	
		if player:isAlive() and not player:getModData().ZomboWinSexScene then 
			--- zDcd = zombie defeat cool down
			player:getModData().zDcd = player:getModData().zDcd or zcooldown;
			player:getModData().zDcd = (player:getModData().zDcd + 1);
		end
	end

	lastTickUpdated = lastTickUpdated or tickUpdateRate;
	lastTickUpdated = lastTickUpdated - 1
	if lastTickUpdated > 0 then return; elseif lastTickUpdated <= 0 then lastTickUpdated = tickUpdateRate; end
--[[
	---------------------------------------------------- stuff --------------------------------------------------------------------
	local function endAction(player)
		print("bandit stopped anim");
		player:setBumpType("stagger")
		player:getModData().isPartner = nil;
		player:getModData().logPartner = false;
		--player.action:forceStop();
		player:forceStop();
	end
	
	if player:getModData().ZomboWinSexScene and player:getModData().isPartner ~= nil then
		local partner = player:getModData().isPartner;
		if instanceof(partner, "IsoZombie") then
			local isBandit = partner:getVariableBoolean("Bandit");
			
			if isBandit then
				if not partner:getModData().activeScene then
					endAction(player);
				end
			end
		else
			endAction(player);
		end
		if not partner:isAlive() then
			endAction(player);
		end
	end
	-------------------------------------------------------------------------------------------------------------------------
]]
	if dOption ~= 4 then
		--print("beep");
		local enemies = player:getSpottedList()

		for i = 0, enemies:size() - 1 do
			local enemy = enemies:get(i)
			local isBandit = enemy:getVariableBoolean("Bandit");
			local isMainHeroFemale = player:isFemale();
			local isBanditFemale = enemy:isFemale();
			local isRapist = enemy:getModData().isRapist;
			local isSubdued = enemy:getModData().isSubdued;
			local brain = {};
			local sex = {};
			local phrase = {};
			
			if isBandit then
				brain = BanditBrain.Get(enemy);
			end
			
			if isMainHeroFemale ~= isBanditFemale then sex = "str"; end
			if isMainHeroFemale and isBanditFemale then sex = "les"; end
			if not isMainHeroFemale and not isBanditFemale then sex = "gay"; end

			if isBandit then
				if not Bandit.IsHostile(enemy) and brain.endurance > 0.06 and isRapist and not isSubdued then
					if enemy:DistTo(player) < maxDistance and player:isAlive() and enemy:isAlive() and not player:isProne() and not enemy:isProne() then 
						if brain.program.name == "Defeat" then
							if Bandit.GetProgram(enemy).name == "Defeat" then
								if not Bandit.HasTaskType(enemy, "Shove") then
									if not enemy:isProne() and not enemy:isKnockedDown() and not enemy:isStaggerBack() then
										if not player:isProne() and not player:isKnockedDown() and not player:isBumped() and not player:isBumpStaggered() and not player:isZombie() and not player:isNPC() then
											if not player:getModData().ZomboWinSexScene then
												if not isCooldown(player, enemy, cooldown) then
													player:getModData().bDcd = 0;
													if not isWearingClothes(enemy, player) then
														--print("bandit is set to shove");
														if sex == "str" then
															phrase = "strStrip";
														elseif sex == "les" then
															phrase = "lesStrip";
														elseif sex == "gay" then
															phrase = "gayStrip";
														end
														slayerSpeakChance(enemy, phrase);
														slayerShove(enemy, player, false);
													else
														--print("bandit is set to defeat");
														if sex == "str" then
															phrase = "strPerverse";
														elseif sex == "les" then
															phrase = "lesPerverse";
														elseif sex == "gay" then
															phrase = "gayPerverse";
														end
														if not slayerSpeakChance(enemy, phrase) then
															attemptBanditDefeat(enemy, player);
														else
															slayerShove(enemy, player, false);
														end
													end
												else
													slayerShove(enemy, player, true);
												end
											end
										end
									end
								end
							end
						end
					end
				end
			end
		end
	end

end

local function OnHitZombie(zombie, character, bodyPartType, handWeapon)
	--- debugging "dead or presumed dead" bandits ---
	local isBandit = zombie:getVariableBoolean("Bandit");
	local player = getPlayer();
	
	if character ~= player then return; end
	if not isBandit then 
		--print("debug: not bandit!"); 
	elseif isBandit then 
		--print("debug: isbandit"); 
		if zombie:getModData().isCruel then
			--print("debug: isCruel");
			zombie:getModData().cruelTick = -1;
			cruelNormalize(zombie);
		end
		if zombie:getModData().isOwner then
			--print("debug: isOwner");
			if ZombRand(3) == 0 then
				zombie:getModData().isCruel = true;
				zombie:getModData().cruelTick = -1;
				cruelNormalize(zombie);
				slayerSpeak(zombie, "banOwnerH")
			end
		else
			--print("debug: not cruel");
		end
	end
end

local function rewardRefresh()
	--[[local bandit = self;
	
	bandit:getModData().sFoods = bandit:getModData().sFoods or 0;
	if bandit:getModData().sFoods > 0 then
		bandit:getModData().sFoods = (bandit:getModData().sFoods - 1)
	end]]
end

Events.OnHitZombie.Add(OnHitZombie)
Events.OnZombieUpdate.Add(onBanditUpdate);
Events.OnZombieUpdate.Add(onZumbieUpdate);
Events.OnZombieDead.Add(onZombieReset)
Events.OnPlayerUpdate.Add(OnPlayerUpdate)
Events.EveryTenMinutes.Add(rewardRefresh);
--Events.EveryOneMinute.Add(rewardRefresh)