-- Author SolarEdge 2024

local zombieUpdate = {};

local ZomboWin = require("ZomboWin/ZomboWin")
local ZWex = false;

require("ZomboWin/Temptation")
require("ZomboWin/ZLMain");

if getActivatedMods():contains("ZomboWinDefeatStrip") == true then
	ZWex = true;
	require("ZomboWinDefeatStrip/ZombieHandler");
	require("ZomboWinDefeatStrip/ZomboWinDefeatConfig");
end

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 = 10
local lastTickUpdated = 0
local anotherTick = 0;
local banditsDcd = 60;
local cruelTimer = 15;


local banditsEnabled = false;

if getActivatedMods():contains("Bandits") == 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 ZomboWinDefeatConfig.ModOptions.options.box18 and enemy:getModData().wasSubdued 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 ZomboWinDefeatConfig.ModOptions.options.box18 and enemy:getModData().wasSubdued 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 convertBandits(bandit)
		if not banditsEnabled then return; end
		if not bandit:getVariableBoolean("Bandit") then return; end
		---- [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, false)
		
		local function func(bandit)
			--print("rapists is called");
			if not Bandit.IsHostile(bandit) then Bandit.SetHostile(bandit, true); end
			bandit:getModData().wasSubdued = 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
				func(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] ----
		print("normalize is called");
		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
		bandit:getModData().wasSubdued = 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 forceNormalize(bandit)
		if not banditsEnabled then return; end
		if not bandit:getVariableBoolean("Bandit") then return; end
		--print("force normalize is called");
		Bandit.SetHostile(bandit, true);
		bandit:getModData().wasSubdued = 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("normalize is called");
		if bandit:getModData().isPartner then bandit:getModData().isPartner = false; end
		bandit:getModData().wasSubdued = 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
	
	local function isCooldown(player, cooldown)
		--- pbDcd = players' bandit defeat cool down
		local pbDcd = player:getModData().bDcd or cooldown;
		if pbDcd > cooldown then
			--print("cooldown finished");
			return false
		end
		
		return true;
	end
	
local function OnZombieUpdateMisc(zombie)
	if not ZomboWinConfig.ModOptions.options.box1 then return; end
	if not zombie:isAlive() then return end
	if ZWex then local dOption = (ZomboWinDefeatConfig.ModOptions.options.dropdown13); end
	local isBandit = zombie:getVariableBoolean("Bandit");
	local isSubdued = zombie:getModData().isSubdued;
	local wasSubdued = zombie:getModData().wasSubdued;
	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;
			forceNormalize(zombie);
		end
	end
	
--- tick counter ---
	anotherTick = anotherTick or tickUpdateRate;
	anotherTick = anotherTick - 1
	if anotherTick > 0 then return; elseif anotherTick <= 0 then anotherTick = tickUpdateRate; end
	--print("boop");
	
--- isCruel ---
	if banditsEnabled and ZWex and dOption ~= 2 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

--- isRapist ---
	if banditsEnabled and ZWex and dOption ~= 2 then		
		if isBandit then
			if Bandit.IsHostile(zombie) then
				if not isSubdued then
					--- conditions before a bandit goes into defeat mode
					--if zombie:getModData().ZomboWinSexScene then print("they grape!"); end
					
					local closestPlayer = getPlayer();		--BanditUtils.GetClosestPlayerLocation(bandit, true)
					local isMainHeroFemale = closestPlayer:isFemale();
					local banditIsFemale = zombie:isFemale();
					local perchance = 0
					local maxDist = (((ZomboWinDefeatConfig.ModOptions.options.dropdown11) * 4) + 2)
					local maxHP = (ZomboWinDefeatConfig.ModOptions.options.dropdown12)
					if maxHP == 1 then maxHP = 101 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 banditIsFemale or not isMainHeroFemale and not banditIsFemale then return; end	--- samesex breaks defeats, so enabled
					
					if closestPlayer ~= nil then
						if zombie:DistTo(closestPlayer) > maxDist then 
							if wasSubdued then
								normalize(zombie, closestPlayer);
							end
						return;
						end
					else
						if wasSubdued then
							normalize(zombie, closestPlayer);
						end
						return;
					end
					
					local currentHp = closestPlayer:getBodyDamage():getHealth();
					if currentHp < maxHP and closestPlayer:isAlive() then 
						if not zombie:isProne() and not zombie:isKnockedDown() and not zombie:isStaggerBack() and zombie:DistTo(closestPlayer) < maxDist then
							local isSexing = closestPlayer:getModData().ZomboWinSexScene
							local brain = BanditBrain.Get(zombie)
							if brain.program.name ~= "Defeat" then
								if not isSexing then 
									local dChance = (ZomboWinDefeatConfig.ModOptions.options.dropdown10)
									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;
									convertBandits(zombie);
								end
							
								if perchance ~= -1 and perchance == 0 and not isCruel then
									convertBandits(zombie);
								else
									--print("conditions met, roll failed: " .. tostring(perchance));
								end
							end
						end
					else
						if wasSubdued then
							normalize(zombie, closestPlayer);
						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 ZWex and dOption == 2 then
		if isBandit then optionNormalize(zombie); end
	end
	
	if zombie:isAlive() and zombie:getModData().isWatching then
		--- bandits already excluded from isWatching ---
		if not zombie:isUseless() then zombie:setUseless(true);	end
	end
	
	if zombie:isAlive() and zombie:getModData().isSubdued then
	
		local function forceUseless(zombie)
			if not zombie:isCanWalk() then zombie:setCanWalk(false); end
			if not zombie:isUseless() then zombie:setUseless(true);	end
			if not zombie:isSitAgainstWall() and zombie:getModData().isSubdued then zombie:setSitAgainstWall(true); end
			if zombie:isCrawling() then zombie:setCrawler(false); end
		end
		
		if banditsEnabled then
			--print("bandits mod detected.");
			local isBandit = zombie:getVariableBoolean("Bandit");
			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); 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");
					end
				end	-- this works too but seems less efficient
				--if not Bandit.IsForceStationary(zombie) then Bandit.ForceStationary(zombie, true); end	--- this seems more efficient
				forceUseless(zombie);
			else
				--print("bandit mod detected, a zombie is set to knock down.");
				forceUseless(zombie);
			end
		else
			--print("bandit mod not detected, a zombie is set to knock down.");
			forceUseless(zombie);
		end
	end
	---- end ----
end

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

local function OnPlayerUpdate(player)
	if not ZomboWinConfig.ModOptions.options.box1 then return; end
	--if player:getModData().ZomboWinSexScene then print("sexscene!") end
	local cooldown = 0;
	if banditsEnabled and ZWex then
		cooldown = (ZomboWinDefeatConfig.ModOptions.options.dropdown14 * banditsDcd) - banditsDcd
	end
	if cooldown < 0 then cooldown = 0 end
	
	--if not banditsEnabled then print("bandits not detected"); end
	--if not ZWex then print("defeat not detected"); end
	
	if banditsEnabled and ZWex and dOption ~= 2 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

	lastTickUpdated = lastTickUpdated or tickUpdateRate;
	lastTickUpdated = lastTickUpdated - 1
	if lastTickUpdated > 0 then return; elseif lastTickUpdated <= 0 then lastTickUpdated = tickUpdateRate; end
	
	if banditsEnabled and ZWex and dOption ~= 2 then
		--print("beep");
		local enemies = player:getSpottedList()
		local maxDistance = 1.0
		local phrase = {};

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

			if isBandit then
				if Bandit.IsHostile(enemy) and brain.endurance > 0.06 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, cooldown) then
													player:getModData().bDcd = 0;
													if not CheckAndRemoveClothing(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
															--- IDs the acting bandit ---
															enemy:getModData().isPartner = true;
															attemptToDefeatTarget(enemy, player);
														else
															slayerShove(enemy, player, false);
														end
													end
												else
													slayerShove(enemy, player, true);
												end
											else
												--Bandit.ClearTasks(enemy)
												--Bandit.ClearMoveTasks(enemy)
												--local task = {action="Time", anim="Smoke", time=250}
												--table.insert(tasks, task)
												--Bandit.Say(enemy, "DEATH")
												--slayerSpeakChance(enemy, phrase);
											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;
			forceNormalize(zombie);
		end
		if zombie:getModData().isOwner then
			--print("debug: isOwner");
			if ZombRand(3) == 0 then
				zombie:getModData().isCruel = true;
				zombie:getModData().cruelTick = -1;
				forceNormalize(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(OnZombieUpdateMisc);
Events.OnZombieDead.Add(OnZombieDead)
Events.OnPlayerUpdate.Add(OnPlayerUpdate)
Events.EveryTenMinutes.Add(rewardRefresh);
--Events.EveryOneMinute.Add(rewardRefresh)