ActorRegister={}
TrackedPairs = {}
TrackedProduction = {}
ConditionRecord = {}
AP_upgradestate={} --atreides powers upgrade state log
AP_ConditionRecord={} --outpost airpower condition record
OriginalOwner={}
CrazeCount={}

ReinforcementSquads = {
	{"vet_light_inf","vet_light_inf","vet_light_inf","vet_trooper","vet_trooper"},
	{"quad","quad","mpsardaukar", "mpsardaukar"},
	{"guild_agent"}
	}
	
WorldLoaded = function()
	neut=Player.GetPlayer("Neutral")
	mp0=Player.GetPlayer("Multi0")
	mp1=Player.GetPlayer("Multi1")
	mp2=Player.GetPlayer("Multi2")
	mp3=Player.GetPlayer("Multi3")
	mp4=Player.GetPlayer("Multi4")
	mp5=Player.GetPlayer("Multi5")
	mp6=Player.GetPlayer("Multi6")
	mp7=Player.GetPlayer("Multi7")
	mp8=Player.GetPlayer("Multi8")
	mp9=Player.GetPlayer("Multi9")
	mp10=Player.GetPlayer("Multi10")
	mp11=Player.GetPlayer("Multi11")
	mp12=Player.GetPlayer("Multi12")
	mp13=Player.GetPlayer("Multi13")
	players={mp0,mp1,mp2,mp3,mp4,mp5,mp6,mp7, mp8, mp9, mp10, mp11, mp12, mp13}--non existent players will be "nil" and thus ignored by lua
	gd1=Player.GetPlayer("gas_dummy_1")
	gd2=Player.GetPlayer("gas_dummy_2")
	gd3=Player.GetPlayer("gas_dummy_3")

	Media.DisplayMessage("This map uses D2k Advanced rules.\nVisit moddb.com/mods/dune2k-advanced\nfor more information. Have fun!","Info",HSLColor.DarkGray)
	-- worm delay section
	wormDelay = Map.LobbyOption("WormsSpawnDelay")
	if tonumber(wormDelay) > 0
	then
		delay = tonumber(Map.LobbyOption("WormsSpawnDelay"))
		Trigger.AfterDelay(DateTime.Minutes(delay), function()
			wormSpawnFunction()  
		end)
	else
		wormSpawnFunction()
	end
	for _,player in pairs(players)
	do 
		if player.HasPrerequisites({"techlevel.house"})and not player.HasPrerequisites({"techlevel.vehicles"})
		then
			actors=player.GetActors()
			loc=CPos.New(actors[2].Location.X-1,actors[2].Location.Y-1)
			startpalace=nil
			if player.Faction=="harkonnen" then startpalace="palace.harkonnen" end
			if player.Faction=="ordos" then startpalace="palace.ordos" end
			if player.Faction=="atreides" then startpalace="palace.atreides" end
			Actor.Create("concreteb", true, { Owner = player, Location = loc }) 
			Actor.Create(startpalace, true, { Owner = player, Location = loc }) 
		end
	end
end

Tick = function()
	--check register for new actors and only call act.Type=="whatever" when necessary since it is expensive
	local new_actors = ActorsAddedToWorld(Map.ActorsInWorld,ActorRegister)
	
	for _,act in pairs(new_actors) 
	do
	--ACTOR TYPE CHECKS
		local T=act.Type
		--needed for airdrop powers
		if (T=="waypoint_dummy_a" or T=="waypoint_dummy_h" or T=="waypoint_dummy_o")
		then
			dropAirReinforcements(act.Location,act)
			--for atreides improved airdrop powers
			executeAtreidesPowerUpgrade(act.Owner,"drop")
		--needed for Sardaukar Invasion Superpower
		elseif T=="waypoint_dummy_invasion"
		then commenceSardaukarInvasion(act.Location,act.Owner)
		--needed for TRxx-5b Superpower
		elseif T=="camera.gas_target_zone"
		then crazeArea(act,gd1,gd2,gd3)
		--needed for Paul Atreides "superpower"
		elseif T=="paul_atreides"
		then
			Trigger.OnRemovedFromWorld(act,function(self)
				local worms = Map.ActorsInCircle(self.CenterPosition, WDist.New(1),function(w) 
					if w.Type=="sandworm" 
					then return w 
					else return nil 
					end 
					end)
				if worms[1] then transformPaulAtreides(self.Owner,self.Location) end
				end)
		--needed for improved Atreides air powers
		elseif T=="palace.atreides"
		then
			queueAtreidesPowerUpgrades(act.Owner)
			--Media.Debug("palace placed")
			Trigger.OnRemovedFromWorld(act, 
				function(victim) 
					if not victim.Owner.GetActorsByType("palace.atreides")[1] then 
						queueAtreidesPowerDegrade(victim.Owner) 			--Media.Debug("after triggering")
					end
				end
			)
			Trigger.OnCapture(act, 
				function(capped,captor,oldOwner,newOwner)
					queueAtreidesPowerUpgrades(newOwner)
				end
			)
		elseif T=="ornithopter"
		then 
			executeAtreidesPowerUpgrade(act.Owner,"strike")
		elseif T=="upgrade.hightech"
		then
			executeAtreidesPowerUpgrade(act.Owner,"strike")	
		elseif T=="upgrade.outpost"
		then
			executeAtreidesPowerUpgrade(act.Owner,"drop")	
		--...handle silo replacement on silo upgrade
		elseif T=="upgrade.silo"
		then
			replace_silos(act.Owner)
		--...Handle saboteur entering a harvester which is getting picked up by a carryall before the Demolition charge explodes (Both will die)
		elseif (T == "harvester" or T == "advanced_harvester_h"  or T == "advanced_harvester_a" or T == "advanced_harvester_o" or T == "harvester.starport") and TrackedPairs[tostring(act)]==nil
		then
			TrackedPairs[tostring(act)]='NoCarryall'
			Trigger.OnKilled(act, 
				function(harvester,killer)
					local MaybeCarryall = TrackedPairs[tostring(harvester)]
					if MaybeCarryall~='NoCarryall' and MaybeCarryall~=nil
					then
						if MaybeCarryall.HasProperty('Kill') 
						then 
							MaybeCarryall.Kill()
							Media.PlaySound('EXPLLG2.WAV')
						end
					else
					end
				end
			)
			Trigger.OnRemovedFromWorld(act, 
				function(harv)
					local carryalls = Map.ActorsInCircle(harv.CenterPosition, WDist.New(1), 
						function(carry) 
							if (carry.Type=="carryall" or carry.Type=="carryall.starport") then return carry else return nil end 
						end
					)
					if carryalls[1]~=nil 
					then 
						TrackedPairs[tostring(harv)]=carryalls[1]
					end
				end 
			)
			Trigger.OnAddedToWorld(act,function(harv)
					local carryalls = Map.ActorsInCircle(harv.CenterPosition, WDist.New(1), 
						function(carry) 
							if (carry.Type=="carryall" or carry.Type=="carryall.starport") then return carry else return nil end 
						end
					)
					if carryalls[1]~=nil
					then 
						TrackedPairs[tostring(harv)]='NoCarryall'
					end
				end 
			)
		--...handle guild agent unit-built icons on unit constructing buildings
		elseif (T=="barracks" or T=="light_factory" or T=="heavy_factory" or T=="high_tech_factory" or T=="starport") and TrackedProduction[tostring(act)]==nil
		then
			TrackedProduction[tostring(act)]= true
			Trigger.OnProduction(act, 
				function(prod,prodded) 
					grantProductionIcon(prod,prodded) 
				end
			)
		end	
	end
end

function crazeArea(act,dummy1,dummy2,dummy3)
--wait for bomb detonating on the ground...
Trigger.AfterDelay(32,function()
	--make all units in area attack each other temporarily
		local crazyUnits = Map.ActorsInCircle(act.CenterPosition, WDist.New(3584),function(unit) 
			if (unit.HasProperty("Move") and unit.Type~="paul_atreides" and unit.Type~="worm_rider") and not unit.HasProperty("Land") --dirty hack to discard buildings and aircraft from selection
			then return unit 
			else return nil 
			end 
			end)
		
		if crazyUnits[1] --power didn't "miss"
		then	
			local dummy={dummy1,dummy2,dummy3}
			for _,u in pairs(crazyUnits) 
			do 
				local ustr=tostring(u)
				
				if not OriginalOwner[ustr]  --unit isn't under the effect of another craze
				then  OriginalOwner[ustr]=u.Owner 
				end
				
				 --craze unit
				u.Owner=Utils.Random(dummy)
				u.Stop()
				if u. HasProperty("AttackMove") then u.AttackMove(u.Location) end
				
				--track the ammount of stacked crazes on this unit
				if CrazeCount[ustr] 
				then CrazeCount[ustr]=CrazeCount[ustr]+1 
				else CrazeCount[ustr]=1 
				end
				
				--return to owner
				Trigger.AfterDelay(DateTime.Seconds(13),function()
					CrazeCount[ustr]=CrazeCount[ustr]-1
					if CrazeCount[ustr]==0 
					then 
						CrazeCount[ustr]=nil
						if u.IsInWorld
						then
							--and player wasn't defeated meanwhile
							local oldowner=OriginalOwner[tostring(u)]
							if oldowner.HasNoRequiredUnits() 
							then oldowner=neut
							end 
							u.Owner=oldowner
							OriginalOwner[ustr]=nil
						end
					end	
				end)
			end
		end
		end)
end

function transformPaulAtreides(player,location)
Trigger.AfterDelay(40,function() 
	Actor.Create("worm_rider", true, { Owner = player, Location = location })
	end)
end

function ActorsAddedToWorld(newlist,register)
	local new_actor_list={}
	for key,act in pairs(newlist)
	do
		if not register[tostring(act)]
		then
			register[tostring(act)]=true
			table.insert(new_actor_list,act)
		end
	end
	return new_actor_list
end

function queueAtreidesPowerDegrade(player)
	--Media.Debug("Degrade:")
	--Media.Debug(tostring(AP_upgradestate[tostring(player)]))
	local state = AP_upgradestate[tostring(player)]
	if state=="notstrike" or state=="notdrop" or state==nil then return
	else AP_upgradestate[tostring(player)]="queued_undo"
	end
end

function queueAtreidesPowerUpgrades(player)
	--Media.Debug("Upgrade:")
	--Media.Debug(tostring(AP_upgradestate[tostring(player)]))
	local state = AP_upgradestate[tostring(player)]
	if state=="strike" or state=="drop" or state=="both" then return
	else AP_upgradestate[tostring(player)]="queued"
	end
end

function executeAtreidesPowerUpgrade(player,power)
	--Media.Debug("Execute:")
	--Media.Debug(tostring(AP_upgradestate[tostring(player)]))
	local  state = AP_upgradestate[tostring(player)]
	local undo=false
	if not state 		 	then return --no palace
	elseif state=="both" 	then return --power allready upgraded
	elseif state==power or state=="not"..power then return
	elseif state=="queued_undo" then AP_upgradestate[tostring(player)]="not"..power undo=true --no palace and no degrades done
	elseif state=="queued"  then AP_upgradestate[tostring(player)]=power --palace, but no upgrades done
	elseif string.sub(state,1,3)=="not" and string.sub(state,4)~=power then AP_upgradestate[tostring(player)]=nil undo=true --other power upgrade degrade done
	elseif string.sub(state,1,3)~="not" and state~=power then AP_upgradestate[tostring(player)]="both" --other power upgrade done
	else Media.Debug("should not happen")
	end
	--Media.Debug(power.." executing "..tostring(AP_upgradestate[tostring(player)]))
	local PowerProviders
	if power=="strike" then PowerProviders = player.GetActorsByType("high_tech_factory")
	elseif power=="drop" then PowerProviders = player.GetActorsByType("outpost")
	end
	if not undo 
	then
		for _,powerprovider in pairs(PowerProviders) 
		do 
			AP_ConditionRecord[tostring(powerprovider)] = powerprovider.GrantCondition("improved_"..power) 
		end
	else
		for _,powerprovider in pairs(PowerProviders)
		do
			if AP_ConditionRecord[tostring(powerprovider)] 
			then
				powerprovider.RevokeCondition(AP_ConditionRecord[tostring(powerprovider)])
				AP_ConditionRecord[tostring(powerprovider)]=nil
			end
		end
	end
end

grantProductionIcon = function(producer,unit)
	if ConditionRecord[tostring(producer)] 
	then
		producer.RevokeCondition(ConditionRecord[tostring(producer)])
		ConditionRecord[tostring(producer)]=nil
	end

	local T=unit.Type
	if producer.Type == 'barracks' 
	then if T=='light_inf' or T=='trooper' or T=='engineer' or T=='thumper' or T=='assassin' or T=='loyalist' or T=='mpsardaukar' or T=='saboteur' or T=='propaganda_corps' or T=='guild_agent'
	then ConditionRecord[tostring(producer)] = producer.GrantCondition(tostring(unit.Type)..'_ICO') end
	elseif producer.Type == 'light_factory'
	then if T=='raider' or T=='trike_a' or T=='quad_hmg' or T=='quad' or T=='stealth_raider' or T=='troop_crawler'
	then ConditionRecord[tostring(producer)] = producer.GrantCondition(tostring(unit.Type)..'_ICO') end
	
	elseif producer.Type == 'heavy_factory'
	then if T=='advanced_harvester_o' or T=='advanced_harvester_a' or T=='advanced_harvester_h' or T=='combat_tank_a' or T=='combat_tank_h' or T=='combat_tank_o' or T=='siege_tank_o' or T=='siege_tank_a' or T=='deviator' or T=='missile_tank_a' or T=='missile_tank_o' or T=='sonic_tank' or T=='devastator' or T=='mcv' or T=='missile_tank_h' or T=='siege_tank_h'
	then ConditionRecord[tostring(producer)] =producer.GrantCondition(tostring(unit.Type)..'_ICO') end
	
	elseif producer.Type == 'high_tech_factory'
	then if T=='carryall' or T=='light_thopter' 
	then ConditionRecord[tostring(producer)] =producer.GrantCondition(tostring(unit.Type)..'_ICO') end
	
	elseif producer.Type == 'starport'
	then if T=='carryall.starport' or T=='harvester.starport' or T=='combat_tank_m.starport' or T=='missile_tank.starport' or T=='siege_tank.starport' or T=='trike.starport' or T=='quad.starport' or T=='mobile_crane.starport'
	then ConditionRecord[tostring(producer)] =producer.GrantCondition(string.sub(tostring(unit.Type),1,-10)..'_ICO') end
	
	end
end

function replace_silos(player)
	local res = player.Resources
	for _, actor in pairs(player.GetActorsByType("silo")) do
		local loc=actor.Location
		actor.Destroy()
		Actor.Create("advanced_silo", true, { Owner = player, Location = loc })
	end
	--give the game time to contemplate consequences of rapid player.ResourceCapacity changes, then correct.
	Trigger.AfterDelay(DateTime.Seconds(0.1),
					function() 
						player.Resources = res
					end
				)
end

function dropAirReinforcements(location,dummy)
	local squad
	if dummy.Type=="waypoint_dummy_a" then squad=1
	elseif dummy.Type=="waypoint_dummy_h" then squad=2
	elseif dummy.Type=="waypoint_dummy_o" then squad=3 
	end
	--Carryall reinforcements
	local base = dummy.Owner.GetActorsByType("outpost")[1]
	local edge = getMapEdge(base.Location, location)
	local path = {edge,location}
	local units = Reinforcements.ReinforceWithTransport(dummy.Owner, "carryall.controllable", ReinforcementSquads[squad], path, {path[2], path[1]})
	local carryall = units[1]
	Trigger.OnPassengerExited(carryall,
		function(carry, pass) 
			if not carry.HasPassengers 
			then 
				carry.Move(path[1]) 
				carry.Destroy() 
			end
		end
	)
end

function commenceSardaukarInvasion(location,player)
	--spend all of Baron Vladimir Harkonnens money, to the last cent
	local frigatecount=math.floor((player.Resources+player.Cash)/2000)
	local rest=(player.Resources+player.Cash)%2000

	local invasionforce
	if rest < 500 then invasionforce={"mpsardaukar","mpsardaukar"}
	elseif rest < 1000 then invasionforce={"mpsardaukar","mpsardaukar","combat_tank_h"}
	elseif rest < 1500 then invasionforce={"mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","combat_tank_h"}
	else --full
	invasionforce={"mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","combat_tank_h"}
	end
	player.Resources=0
	player.Cash=0
	
	--Route Frigate reinforcements
	local base = player.GetActorsByType("palace.harkonnen")[1]
	local edge = getMapEdge(base.Location, location)
	local path = {edge,location}
	for i=0,frigatecount 
	do
		local units = Reinforcements.ReinforceWithTransport(player, "frigate.controllable", invasionforce, path, {path[2], path[1]})
		local frigate = units[1]
		Trigger.OnPassengerExited(frigate,
			function(frig, pass) 
				if not frig.HasPassengers 
				then 
					frig.Move(path[1]) 
					frig.Destroy() 
				end
			end
		)
		if i==0 
		then 
			invasionforce={"mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","mpsardaukar","combat_tank_h"} 
		end
	end
end

function getMapEdge(posB,posA)
	local bnds = {}
	bnds.Top=(Map.TopLeft.Y)/1024
	bnds.Bottom=(Map.BottomRight.Y+1)/1024
	bnds.Left=(Map.TopLeft.X)/1024
	bnds.Right=(Map.BottomRight.X+1)/1024
	--catch zero devision errors
	if posB.X-posA.X == 0 
	then 
		if posB.Y<posA.Y then return CPos.New(posA.X,bnds.Top)
		else return CPos.New(posA.X,bnds.Bottom)
		end
	elseif posB.Y-posA.Y == 0 
	then 
		if posB.X<posA.X then return CPos.New(bnds.Left,posA.Y)
		else return CPos.New(bnds.Right,posA.Y)
		end
	end
	
	--compute all boundary intersections
	local slope = (posB.Y-posA.Y)/(posB.X-posA.X)
	local topX   = posA.X - (posA.Y-bnds.Top)/slope
	local bottomX= posA.X + (bnds.Bottom-posA.Y)/slope
	local rightY = posA.Y + (bnds.Right-posA.X)*slope
	local leftY =  posA.Y - (posA.X-bnds.Left)*slope
	
	local top = CPos.New(math.floor(topX),bnds.Top)
	local bottom = CPos.New(math.floor(bottomX),bnds.Bottom)
	local right = CPos.New(bnds.Right, math.floor(rightY))
	local left = CPos.New(bnds.Left, math.floor(leftY))	
	
	--decide which of them is reasonable
	local positiveDelX = 0 < (posB.X-posA.X)
	local positiveSlope= 0 < slope 
	
	if positiveDelX and positiveSlope
	then 
		if bottom.X<=bnds.Right then return bottom
		else return right
		end
	elseif positiveDelX and not positiveSlope
	then
		if top.X<=bnds.Right then return top
		else return right
		end
	elseif not positiveDelX and positiveSlope
	then
		if top.X>=bnds.Left then return top
		else return left
		end
	elseif not positiveDelX and not positiveSlope
	then
		if bottom.X>=bnds.Left then return bottom
		else return left
		end
	end
	Media.DisplayMessage(" in Function getMapEdge, no Edge matched, returning default. (0,0)","Error")
	return CPos.New(0,0)
end

function wormSpawnFunction()
    creepPlayer = Player.GetPlayer("Creeps")
    actorsSpawner = creepPlayer.GetActorsByType("wormspawner")
  if actorsSpawner ~= nil
  then
    for i, spawner in pairs(actorsSpawner)
    do
      if spawner.AcceptsCondition("WormSpamDelayed")
      then
        spawner.GrantCondition("WormSpamDelayed") 	
      end
    end
  end
end