--Copyright Melons 2022, ask permission before use.

-- Ultimate AI Functions Start (Before WorldLoaded) 
difficulty = Map.LobbyOption("difficulty")
function setDifficulty() 

	toxicAdjustment =1

	if toxic_enabled == 1
	then 
		toxicAdjustment = 3
	end


	if difficulty == "noob" then
		difficultyHandicap = 0.6
		maxLimit = (6*HandicapAdjustment)*toxicAdjustment
		mcvLimit = (12*HandicapAdjustment)*toxicAdjustment
	elseif difficulty == "easy" then
		difficultyHandicap = 0.8
		maxLimit = (10*HandicapAdjustment)*toxicAdjustment
		mcvLimit = (14*HandicapAdjustment)*toxicAdjustment
	elseif difficulty == "normal" then
		difficultyHandicap = 1
		maxLimit = (14*HandicapAdjustment)*toxicAdjustment
		mcvLimit = (18*HandicapAdjustment)*toxicAdjustment
	elseif difficulty == "extreme" then
		difficultyHandicap = 1.2
		maxLimit = (20*HandicapAdjustment)*toxicAdjustment
		mcvLimit = (30*HandicapAdjustment)*toxicAdjustment
	else
		difficultyHandicap = 1.5
		maxLimit = 99
		mcvLimit = 99
	end
end


function theBots()
    return Player.GetPlayers(function(player)
        return player.IsBot == true;
    end);
end

function theUltimateBots()
    return Player.GetPlayers(function(player)
        return player.IsBot == true and player.HasPrerequisites({"ultimateaienabled"});
    end);
end

function theHumans()
    return Player.GetPlayers(function(player)
        return player.IsBot == false and player.IsNonCombatant == false;
    end);
end

function thePlayers()
    return Player.GetPlayers(function(player)
        return player.IsNonCombatant == false;
    end);
end

function thePlayersAndCreeps()
    return Player.GetPlayers(function(player)
        return true
    end);
end


--Random Testing
function getActorsAround(actorselected,actorsToFind, distance)
	actorList = Map.ActorsInCircle(actorselected.CenterPosition, WDist.FromCells(distance), function(a)
		return a.Type == actorsToFind
	end)

	return actorList
end

function getClosetActor(actorselected,actorsToFind)
	actorList = Map.ActorsInCircle(actorselected.CenterPosition, WDist.FromCells(50), function(a)
		return a.Type == actorsToFind
	end)

	return actorList[1]
end

function getAllActors(a)

	theActors = Map.ActorsInBox(Map.TopLeft, Map.BottomRight, function(actor)
		return actor.Type == a
	end)

	return theActors

end

function getAllCreeps()

	targets = Utils.Where(Creeps.GetActors(), function(actor)
		return
			actor.Type == "powr" or actor.Type == "silo"
	end)
	return targets

end

----START OF AI REINFORCEMENTS ---

function createNewMcv(quantity)
	if quantity == nil then
		quantity = 1
	end
	Trigger.AfterDelay(DateTime.Seconds(10), function()
		for z, thePlayer in ipairs(AIPlayers) do
			Actor.Create("mcv", true, { Owner = thePlayer, Location = thePlayer.HomeLocation + CVec.New(0, 3) });
		end
		quantity = quantity -1
		if quantity > 0 then
			createNewMcv(quantity)
		end
	end)
end



function createRandomInfantryPerBuilding(quantity)
	if quantity == nil then
		quantity = 1
	end
	
	countLimit = 0

			for z, thePlayer in ipairs(AIPlayers) do
				theProducers = thePlayer.GetActorsByTypes({ "barr", "tent" });
				
				Utils.Do(theProducers, function(theUnits)
					if countLimit < maxLimit then
						if (difficulty == "easy" or difficulty == "noob") then
							toProduceTanks = { "e1", "e1", "e1",  "e1", "e1", "e1", "e3"  }
						else
							toProduceTanks = { "e1", "e1", "e1", "e3"  }
						end
						randomUnit = Utils.RandomInteger(1,#toProduceTanks+1)
						randomTank =  toProduceTanks[randomUnit];
						-- Media.DisplayMessage("hello1", "Mission", HSLColor.Red)
						theTank = Actor.Create(randomTank, true, { Owner = thePlayer, Location = theUnits.Location + CVec.New(0, 2) });
						theTank.Move(theUnits.RallyPoint)
						countLimit = countLimit+1
					end
				end)
				quantity = quantity -1
			end
			if quantity > 0 then
				--Media.DisplayMessage("hello2", "Mission", HSLColor.Red)
				createRandomInfantryPerBuilding(quantity)
			end
end




function createRandomTankPerBuilding(quantity)
	if quantity == nil then
		quantity = 1
	end

	countLimit = 0

			for z, thePlayer in ipairs(AIPlayers) do
				theProducers = thePlayer.GetActorsByTypes({ "weap" });

				Utils.Do(theProducers, function(theUnits)
					if countLimit < maxLimit then
						if (difficulty == "easy" or difficulty == "noob") then
							toProduceTanks = { "jeep", "apc", "1tnk", "ftrk", "jeep", "apc", "1tnk", "ftrk", "jeep", "apc", "1tnk", "ftrk", "4tnk", "ctnk", "v2rl", "2tnk", "2tnk", "1tnk", "3tnk", "mrj", "2tnk", "arty", "mgg", "mrj", "v2rl", "2tnk", "arty", "3tnk", "2tnk", "stnk", "dtrk" }
						elseif (difficulty == "normal") then
							toProduceTanks = { "apc", "apc", "1tnk", "1tnk", "ftrk", "4tnk", "3tnk", "3tnk", "ctnk", "v2rl", "2tnk", "2tnk", "2tnk", "1tnk", "3tnk", "2tnk", "arty", "mgg", "mrj", "v2rl", "2tnk", "arty", "3tnk", "mrj", "stnk", "dtrk"}
						else
							toProduceTanks = { "apc", "1tnk", "ftrk", "4tnk", "4tnk", "ctnk", "v2rl", "2tnk", "2tnk", "1tnk", "3tnk", "mrj", "2tnk", "arty", "mgg", "mrj", "v2rl", "2tnk", "arty", "3tnk", "mrj", "stnk", "dtrk", "dtrk"}
						end
						randomUnit = Utils.RandomInteger(1,#toProduceTanks+1)
						randomTank =  toProduceTanks[randomUnit];
						-- Media.DisplayMessage("hello1", "Mission", HSLColor.Red)
						theTank = Actor.Create(randomTank, true, { Owner = thePlayer, Location = theUnits.Location + CVec.New(0, 2) });
						theTank.Move(theUnits.RallyPoint)
						countLimit = countLimit+1
					end
				end)
				quantity = quantity -1

			end
			if quantity > 0 then
				--Media.DisplayMessage("hello2", "Mission", HSLColor.Red)
				createRandomTankPerBuilding(quantity)
			end
end




function spawnUnits(spawnUnitType, spawnUnitOwner, spawnUnitLocation)
	theActor = Actor.Create(spawnUnitType, true, { Owner = spawnUnitOwner, Location =  spawnUnitLocation});

	if spawnUnitType ~= "mcv" then
		if spawnUnitType == "qtnk" or spawnUnitType == "mgg" or spawnUnitType == "mrj" or spawnUnitType == "thf" then
			--theActor.Move(CaptureMiddle.Location)
		else
			--theActor.AttackMove(CaptureMiddle.Location)
		end
	end
	
end

function sendRepeatWave(unitsTier,quantity,repeatTime)
	
	if repeatTime == nil then
		repeatTime = 120
	end

	sendAttackWave(unitsTier,quantity)

			--Set Next Wave
			Trigger.AfterDelay(DateTime.Seconds(repeatTime), function()
				sendRepeatWave(unitsTier,quantity,repeatTime)
			end)
end


function sendAttackWave(unitsTier, quantity)

	if repeatWave == nil then
		local repeatWave = 0
	end
	if unitsTier == nil then
		unitsTier = tier1Tanks
	end

	--bug for ai production
		for z, thePlayer in ipairs(AIPlayers) do

			if #thePlayer.GetActors() > 1 then

				if (unitsTier == tierMcv) then
					if (countMcvs(thePlayer) >= mcvLimit) then
						return
					end
				end

				if quantity == nil or quantity == 0 then
					quantityToProduce = 7
				else
					quantityToProduce = quantity
				end

				currentSpawn = thePlayer.HomeLocation + CVec.New(-5,-5)

				while quantityToProduce > 0 do
					randomUnit = Utils.RandomInteger(1,#unitsTier+1)
					--Media.DisplayMessage("".. randomUnit, "Mission", HSLColor.Red)

					spawnUnits(unitsTier[randomUnit],thePlayer, currentSpawn)

					quantityToProduce = quantityToProduce -1

					if currentSpawn == spawnPoint1 then
						currentSpawn = spawnPoint2
					elseif  currentSpawn == spawnPoint2 then
						currentSpawn = spawnPoint3
					elseif  currentSpawn == spawnPoint3 then
						currentSpawn = spawnPoint4
					elseif  currentSpawn == spawnPoint4 then
						currentSpawn = spawnPoint1
					end
				end
			end
		end

end



function createHarv()
	if quantity == nil then
		quantity = 1
	end
	if toxic_enabled then
		Trigger.AfterDelay(DateTime.Seconds(10), function()
			for z, thePlayer in ipairs(AIPlayers) do
				if thePlayer.Cash < 2500 then
					Actor.Create("harv", true, { Owner = thePlayer, Location = thePlayer.HomeLocation + CVec.New(0, 2) });
				end
			end
			createHarv()
		end)
	else
		Trigger.AfterDelay(DateTime.Seconds(25), function()
			for z, thePlayer in ipairs(AIPlayers) do
				if thePlayer.Cash < 1500 then
					Actor.Create("harv", true, { Owner = thePlayer, Location = thePlayer.HomeLocation + CVec.New(0, 2) });
				end
			end
			createHarv()
		end)

	end
end

function createEngineers(quantity)--
	if quantity == nil then
		quantity = 1
	end

				sendAttackWave(tierEngineers,#theHumans())
				sendAttackWave(tierThief,#theHumans())
				
				if (difficulty ~= "easy" and difficulty ~= "noob" and difficulty ~= "normal") then
					sendAttackWave(tierSpy,#theHumans())
				end

			Trigger.AfterDelay(DateTime.Seconds(5), function()
				sendEngineers()
				if (difficulty ~= "easy" and difficulty ~= "noob") then
				end
			end)
			if quantity > 0 then
				Trigger.AfterDelay(DateTime.Seconds(60), function()
					--Media.DisplayMessage("hello2", "Mission", HSLColor.Red)
					createEngineers(quantity)
					sendThief()
					sendSpy()
				end)
			end
end

--send engineers to cap middle
function sendThief()
	
	for z, thePlayer in ipairs(AIPlayers) do
        local theUnits = thePlayer.GetActorsByType("thf");

		actorToTarget = { "proc"}

		if #theUnits > 0 then
			Utils.Do(theUnits, function(unit)
				unitToTarget = findClosestTargetAi(unit,actorToTarget)

				if unitToTarget ~= nil then
					--Media.DisplayMessage("hello5", "Mission", HSLColor.Red)
					unit.Stop()
					unit.Move(unitToTarget.Location)
					unit.Infiltrate(unitToTarget)
				end
			end)
		end
	end
end

--send engineers to cap middle
function sendSpy()
	
	for z, thePlayer in ipairs(AIPlayers) do
        local theUnits = thePlayer.GetActorsByType("spy");

		actorToTarget = { "powr", "apwr", "dome" }

		if #theUnits > 0 then
			Utils.Do(theUnits, function(unit)
				unitToTarget = findClosestTargetAi(unit,actorToTarget)
	
				if unitToTarget ~= nil then
					--Media.DisplayMessage("hello5", "Mission", HSLColor.Red)
					unit.Stop()
					unit.DisguiseAsType("e1",unitToTarget.EffectiveOwner)
					unit.Move(unitToTarget.Location)
					unit.Infiltrate(unitToTarget)
				end
			end)
		end
	end
end

function findClosestTargetAi(searchActor, actorTypes)
     closestActor = nil
     distnceCloset = 250000;
    
    
    for rr, enemyPlayer in ipairs(HumanPlayers) do
        if enemyPlayer ~= searchActor.EffectiveOwner then
            if enemyPlayer.Team ~= searchActor.EffectiveOwner.Team or enemyPlayer.Team == 0 or searchActor.EffectiveOwner.Team == 0 then
                local actors = enemyPlayer.GetActorsByTypes(actorTypes);
                for rr, actor in ipairs(actors) do
                     theDistance = actor.Location - searchActor.Location;
                     distanceMath = math.sqrt((theDistance.X * theDistance.X) + (theDistance.Y * theDistance.Y));
                    if distanceMath <= distnceCloset then
                        distnceCloset = distanceMath;
                        closestActor = actor;
                    end
                end
            end
        end
    end
    return closestActor;
end


--send engineers to cap middle
function sendEngineers()
	for z, thePlayer in ipairs(AIPlayers) do
        local engineers = thePlayer.GetActorsByType("e6");


		-- Utils.Do(engineers, function(unit)

		-- 	if CaptureMiddle.Owner ~= unit.Owner and CaptureMiddle.EffectiveOwner.Team  ~= unit.EffectiveOwner.Team then
		-- 		unit.Stop()
		-- 		unit.Move(CaptureMiddle.Location)
		-- 		unit.Capture(CaptureMiddle)
		-- 	end
		-- end)
	end
end


function aiCash()

	if toxic_enabled then
		Trigger.AfterDelay(DateTime.Seconds(10), function()
			for z, thePlayer in ipairs(AIPlayers) do
				if thePlayer.Cash < 4000 then
					thePlayer.Cash =  thePlayer.Cash +2000
				end
			end
			aiCash()
		end)
	else
		Trigger.AfterDelay(DateTime.Seconds(20), function()
			for z, thePlayer in ipairs(AIPlayers) do
				if thePlayer.Cash < 4000 then
					thePlayer.Cash =  thePlayer.Cash +500
				end
			end
			aiCash()
		end)
	end
	
end



--Tanks every 60s
function continuousProductionTanks()

	if (difficulty == "noob") then
		continuousDelay = 90
	elseif (difficulty == "easy") then
		continuousDelay = 74
	elseif (difficulty == "normal") then
		continuousDelay = 65
	else
		continuousDelay = 45
	end

	createRandomTankPerBuilding((4*HandicapAdjustment))
	Trigger.AfterDelay(DateTime.Seconds(continuousDelay), continuousProductionTanks)
end

--Infantry every 15s
function continuousProductionInfantry()

	if (difficulty == "noob" ) then
		continuousDelay = 30
	elseif (difficulty == "easy") then
		continuousDelay = 25
	elseif (difficulty == "normal") then
		continuousDelay = 20
	else
		continuousDelay = 15
	end

	createRandomInfantryPerBuilding((4*HandicapAdjustment))
	Trigger.AfterDelay(DateTime.Seconds(20), continuousProductionInfantry)
end

function continuousProduction()
	continuousProductionTanks()
	continuousProductionInfantry()
end

function countMcvs(isPlayer)
	mcvCount = countbuildings("mcv", isPlayer)
	factCount = countbuildings("fact", isPlayer)
	totalMcvs = mcvCount + factCount
	return totalMcvs
end


-- Ultimate AI Functions (Before WorldLoaded) End

WorldLoaded = function()

	----START OF AI SETUP CODE After setToxic() ---
		
	AllPlayersIncludingCreeps = thePlayersAndCreeps();
	AllPlayers = thePlayers();
	AIPlayers = theUltimateBots();
	UltimateBotsCount = #theUltimateBots();
	AllBots = theBots();
	HumanPlayers = theHumans();
	HandicapAdjustment = #theHumans()/3
	HumansCount = #theHumans()
	ToxicHandicap = 1
			
	tierEngineers = { "e6", "e6", "e6", "e6"}
	tierThief = { "thf", "thf", "thf", "thf"}
	tierSpy = { "spy", "spy", "spy", "spy"}
	tier1Tanks = { "jeep", "apc", "1tnk", "ftrk"}
	tier2Tanks = { "v2rl", "2tnk", "arty", "mgg", "mrj", "ttnk"}
	tier3Tanks = { "3tnk", "mrj", "ctnk", "stnk" }
	tier4Tanks = { "4tnk", "3tnk", "mrj", "dtrk", "ctnk", "stnk" }
	tier0infantry = {"e1"}
	tier1infantry = { "e1", "medi", "dog", "e1", "e2", "e3", "e3r1", "e4", "mech", "gnrl", "shok" }
	tier2infantry = { "spy", "thf" }
	tier3infantry = { "e7", "sniper"}
	tierMcv = { "mcv"}

	if toxic_enabled == 1 	then 
		ToxicHandicap = HandicapAdjustment*4
	end
	----END OF AI SETUP CODE After setToxic() ---



	----START OF AI REFINFORCEMENT CODE ---
	if UltimateBotsCount > 0 then
		

			-- MCVs on repeat
		Trigger.AfterDelay(DateTime.Seconds(30), function()

			if (difficulty == "noob" ) then
				timerDelay = 70
			elseif (difficulty == "easy") then
				timerDelay = 50
			elseif (difficulty == "normal") then
				timerDelay = 40
			else
				timerDelay = 30
			end

			sendRepeatWave(tierMcv,1,timerDelay/HandicapAdjustment)
		end) 

		--One off attacks
		Trigger.AfterDelay(DateTime.Seconds(110), function()
			Media.DisplayMessage( "Enemy Troops Inbound", "Mission", HSLColor.Red)
			if (difficulty == "noob") then
				sendAttackWave(tier0infantry,(15*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "easy") then
				sendAttackWave(tier0infantry,(20*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "normal") then
				sendAttackWave(tier0infantry,(30*HandicapAdjustment)*difficultyHandicap)
			else
				sendAttackWave(tier0infantry,(50*HandicapAdjustment)*difficultyHandicap)
			end
		end)


		Trigger.AfterDelay(DateTime.Seconds(220), function()
			Media.DisplayMessage( "Enemy Troops Inbound", "Mission", HSLColor.Red)
			if (difficulty == "noob") then
				sendAttackWave(tier0infantry,(10*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "easy") then
				sendAttackWave(tier0infantry,(15*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "normal") then
				sendAttackWave(tier0infantry,(20*HandicapAdjustment)*difficultyHandicap)
			else
				sendAttackWave(tier0infantry,(25*HandicapAdjustment)*difficultyHandicap)
			end
		end)

		Trigger.AfterDelay(DateTime.Seconds(330), function()
			Media.DisplayMessage( "Enemy Troops Inbound", "Mission", HSLColor.Red)
			if (difficulty == "easy" or difficulty == "noob") then
				sendAttackWave(tier0infantry,(60*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier1Tanks,(8*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "normal") then
				sendAttackWave(tier0infantry,(60*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier1Tanks,(8*HandicapAdjustment)*difficultyHandicap)
			else
				sendAttackWave(tier1infantry,(60*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(8*HandicapAdjustment)*difficultyHandicap)
			end
		end)

		Trigger.AfterDelay(DateTime.Seconds(420), function()
			--Start of Tank Waves
			Media.DisplayMessage( "Enemy Tanks Inbound", "Mission", HSLColor.Red)

			if (difficulty == "easy" or difficulty == "noob") then
				--Infantry
				sendAttackWave(tier1infantry,(80*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier1Tanks,(8*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(8*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "normal") then
				--Infantry
				sendAttackWave(tier1infantry,(90*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier1Tanks,(8*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(8*HandicapAdjustment)*difficultyHandicap)
			else
				--Infantry
				sendAttackWave(tier1infantry,(100*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(10*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier3Tanks,(10*HandicapAdjustment)*difficultyHandicap)
			end
			-- sendAttackWave(tier4Tanks,2*HandicapAdjustment)*difficultyHandicap)
		end)

		Trigger.AfterDelay(DateTime.Seconds(540), function()

			Media.DisplayMessage( "Enemy Tanks Inbound", "Mission", HSLColor.Red)

			if (difficulty == "easy" or difficulty == "noob") then
				--Infantry
				sendAttackWave(tier1infantry,(100*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2infantry,(4*HandicapAdjustment)*difficultyHandicap)
				--Tanks
				sendAttackWave(tier1Tanks,(6*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(6*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier3Tanks,(4*HandicapAdjustment)*difficultyHandicap)
				--sendAttackWave(tier4Tanks,(2*HandicapAdjustment)*difficultyHandicap)
			elseif (difficulty == "normal") then
				--Infantry
				sendAttackWave(tier1infantry,(100*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2infantry,(4*HandicapAdjustment)*difficultyHandicap)
				--Tanks
				sendAttackWave(tier1Tanks,(8*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(8*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier3Tanks,(6*HandicapAdjustment)*difficultyHandicap)
				--sendAttackWave(tier4Tanks,(2*HandicapAdjustment)*difficultyHandicap)
			else
				--Infantry
				sendAttackWave(tier1infantry,(120*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2infantry,(8*HandicapAdjustment)*difficultyHandicap)
				--Tanks
				sendAttackWave(tier1Tanks,(10*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier2Tanks,(8*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier3Tanks,(6*HandicapAdjustment)*difficultyHandicap)
				sendAttackWave(tier4Tanks,(4*HandicapAdjustment)*difficultyHandicap)
			end
			
		end)


		Trigger.AfterDelay(DateTime.Seconds(1), function()


			Trigger.AfterDelay(DateTime.Seconds(1), function()
				setupRefs()


				if (difficulty == "noob") then
					removeactors(getAllActors("gap.defend"))
				end
			end)


		end)

		Trigger.AfterDelay(DateTime.Seconds(2), function()
			aiCash() -- Stream of cash for AI
			createHarv() -- If ran out of money then create Harvester
			createEngineers()
		end)



		if (difficulty == "noob") then
			continuousCountdown = 720
		elseif (difficulty == "easy" ) then
			continuousCountdown = 600
		elseif (difficulty == "normal") then
			continuousCountdown = 600
		else
			continuousCountdown = 480
		end

			Trigger.AfterDelay(DateTime.Seconds(continuousCountdown), function()
				Media.DisplayMessage( "WARNING: Ultimate AI has ramped up production!", "Mission", HSLColor.Red)
				continuousProduction()
			end)

		setDifficulty()
	end
	----END OF AI REFINFORCEMENT CODE ---
end