#****************************************************************************
#**
#**  File     :  /lua/PhantomSim.lua
#**  Author(s):  novaprim3
#**
#**  Summary  :  Pirates vs Ninjas Mod for Forged Alliance
#**
#****************************************************************************
local modPath = 'Phantom-PvN_B4'

local cur = {
	innocents = {},
	phantoms = {},
	boss = nil,
	boss_dead = false,
	playerScores = {}, 
	delta = { mass = {}, energy = {} }, 
	trash = nil,
	tbot = nil,
	captureTime = 0,
	config = {
		vote = 7 * 60,
		declare = 8 * 60,
		#vote = 9,
		#declare = 10,
		tbot_spawn = 2 * 60,
		tbot_respawn = 1 * 60,
		#tbot_spawn = 5,
		#tbot_respawn = 10,
		tbot_timer = 20 * 60,
		#tbot_timer = 30,
		phantom_coefficient = 4/10,
		phantom_vote = false,
		min_vampire = 0.25,
		max_vampire = 0.5
	},
	stats = {
		phantom_count = 0,
		innocent_count = 0,
		phantoms_dead = 0,
		innocents_dead = 0
	},
	end_game = false,
	votes = {},
	mapSizeX = nil, 
	mapSizeZ = nil,
	civArmy = nil,
	civBrain = nil,
}


function PhantomMainThread()
	cur.mapSizeX, cur.mapSizeZ = GetMapSize()
	cur.votes[1] = 0
	cur.votes[2] = 0
	cur.votes[3] = 0
	cur.trash = TrashBag()

	import('/lua/SimPlayerQuery.lua').AddQueryListener("PhantomDumpResources", dumpResources)
	import('/lua/SimPlayerQuery.lua').AddQueryListener("PhantomVote", vote)

	Sync.pSkin = true
	
	# Run through all players and make some adjustments
	for army1, brain1 in ArmyBrains do
		if ArmyIsCivilian(army1) == true then
			cur.civArmy = army1
			cur.civBrain = brain1
			LOG("Civ Army: "..army1)
		elseif ArmyIsOutOfGame(army1) == false then
			SetAlliedVictory(army1, false)
			brain1:SetResourceSharing(false)
			brain1.CalculateScore = PhantomCalculateScore
			
			# Spawn the player an SCU
			local armyUnits = brain1:GetListOfUnits(categories.LAND, false, false)
			for id, unit in armyUnits do 
				local loc = unit:GetPosition()
				local bpID = unit:GetBlueprint().BlueprintId
				local uFact = string.sub(bpID, 1, 3)
				local uType = string.sub(bpID, 4)
				if uType == '0001' then
					local scu = CreateUnitHPR(uFact..'0301', army1, loc[1]+3, loc[2], loc[3], 0, 0, 0)
					scu:PlayCommanderWarpInEffect()
				end
			end
			
			# Fill up their reserves (SCU gives extra storage)			
			brain1:GiveResource('MASS', 275)
			brain1:GiveResource('ENERGY', 5000)

			
			# Ally player with all other players
			for army2, brain2 in ArmyBrains do
				if ArmyIsCivilian(army2) == false and ArmyIsOutOfGame(army2) == false and army1 != army2 then
					SetAlliance(army1, army2, 'Ally')
				end
			end
		end
	end
	
	Sync.PAssignment = "Assignment Pending"
	
	# Delay until vote
	WaitSeconds(cur.config.vote)
	
	# Show vote diaglog, if applicable
	if cur.config.phantom_vote then
		Sync.pVote = true
	end
	
	# Delay until assignment
	WaitSeconds(cur.config.declare - cur.config.vote)

	# Move all elegiable players to the innocents table
	for army, brain in ArmyBrains do
		if ArmyIsCivilian(army) == false and ArmyIsOutOfGame(army) == false then
			table.insert(cur.innocents, army)
		end
	end
	
	# Count innocents and determine how many we will move to the phantom table
	# Check votes for a phantom count, this should select the lower of two even votes
	local voteMax = 0
	local voteIndex = 0
	for key, val in cur.votes do
		if val > voteMax then
			voteMax = val
			voteIndex = key
		end
	end
	if voteIndex > 0 then
		cur.stats.phantom_count = voteIndex
		if cur.stats.phantom_count >= table.getn(cur.innocents) then
			cur.stats.phantom_count = table.getn(cur.innocents) - 1
		end
		
	else
		cur.stats.phantom_count = math.ceil(table.getn(cur.innocents) * cur.config.phantom_coefficient)
	end
	cur.stats.innocent_count = table.getn(cur.innocents) - cur.stats.phantom_count
		
	# Move a few innocents over to phantoms
	local try = 0
	for i=1,cur.stats.phantom_count do
		# This is pretty messy, but I couldn't get table.remove working the way I wanted it to
		local ok = false
		local p = 0
		repeat
			ok = true
			p = math.random(table.getn(cur.innocents))
			for index, army in cur.phantoms do
				if cur.innocents[p] == army then
					ok = false
				end
			end
		until ok == true
		table.insert(cur.phantoms, cur.innocents[p])
	end
	
	# Remove phantoms from the innocents table
	for i=1,table.getn(cur.innocents) do
		for index, army in cur.phantoms do
			if cur.innocents[i] == army then
				cur.innocents[i] = nil
			end
		end
	end
	
	# Assign a pirate boss
	while cur.boss == nil do
		cur.boss = cur.innocents[math.random(table.getn(cur.innocents))]	
	end
	
	# Prepare data to send to the UI
	pData = {
		phantom_armies = {}, 
		innocent_armies = {}, 
		isPhantom = false
	}
	if IsPhantom(GetFocusArmy()) then
		Sync.pAlert = { "Designation: NINJA", "Assassinate the Pirate Boss" }
		Sync.PAssignment = "Ninja"
		pData.isPhantom = true
	else
		if cur.boss == GetFocusArmy() then
			Sync.pAlert = { "Designation: PIRATE BOSS", "Arrr, don't get dead" }
			Sync.PAssignment = "Pirate Boss"
		else
			Sync.pAlert = { "Designation: PIRATE", "Arrr, capture your booty" }
			Sync.PAssignment = "Pirate"
		end
		pData.isPhantom = false
	end
	
	pData.phantom_armies = cur.phantoms
	pData.innocent_armies = cur.innocents
	Sync.pData = pData
	
	# Fire up the eco thread
	cur.trash:Add(ForkThread(PhantomResourceThread))
	
	# Spawn a TBOT
	ForkThread(spawnTBot)
	
	# Main loop
	local stats_changed = true
	while cur.end_game == false do
		WaitSeconds(0.1)
				
		# Check if we need a new TBOT
		if cur.tbot != nil and cur.tbot:IsDead() == true then
			# Check if the TBOT was captured - search for a new TBOT
			cur.tbot = nil
			local tbotOwner = nil
			for army, brain in ArmyBrains do
				if ArmyIsCivilian(army) == false and ArmyIsOutOfGame(army) == false then
					local armyUnits = brain:GetListOfUnits(categories.BOT, false, false)
					for id, unit in armyUnits do 
						if unit:GetBlueprint().BlueprintId == 'tbot' and unit:IsDead() == false then
							cur.tbot = unit
							tbotOwner = brain
							break
						end
					end
				end
				if cur.tbot != nil then
					break
				end
			end
			
			if cur.tbot == nil then
				# TBOT was killed
				LOG("TBOT Killed")
				cur.captureTime = 0
				Sync.captureTime = cur.captureTime
				ForkThread(respawnTBot)
			else
				# TBOT was captured
				LOG("TBOT Captured")
				cur.captureTime = GetGameTimeSeconds() + cur.config.tbot_timer
				Sync.captureTime = cur.captureTime
				Sync.pAlert = { "TreasureBot Captured", "Timer reset" }
				
				# Teleport the TBOT to new owners ACU, if they have one
				local armyUnits = tbotOwner:GetListOfUnits(categories.COMMAND, false, false)
				for id, unit in armyUnits do 
					local bpID = unit:GetBlueprint().BlueprintId
					local uFact = string.sub(bpID, 1, 3)
					local uType = string.sub(bpID, 4)
					if uType == '0001' then
						local loc = unit:GetPosition()
						loc[1] = loc[1]-3
						cur.tbot:PlayUnitSound('Warp') 

						CreateAttachedEmitter( cur.tbot, 0, cur.tbot:GetArmy(), '/effects/emitters/generic_teleportin_01_emit.bp') 
						CreateAttachedEmitter( cur.tbot, 0, cur.tbot:GetArmy(), '/effects/emitters/generic_teleportin_02_emit.bp') 
						CreateAttachedEmitter( cur.tbot, 0, cur.tbot:GetArmy(), '/effects/emitters/generic_teleportin_03_emit.bp') 
						WaitSeconds(2)

        					CreateAttachedEmitter( cur.tbot, 0, cur.tbot:GetArmy(), '/effects/emitters/generic_teleportout_01_emit.bp') 
        					WaitSeconds(0.5)
        					
						Warp(cur.tbot, loc)
						
						cur.tbot:PlayCommanderWarpInEffect()
						
						
						break
					end
				end
			end
		end

		# TBot has been captured for hold time
		if cur.captureTime > 0 and GetGameTimeSeconds() >= cur.captureTime then
			LOG("TBOT held for timer duration")
			local tbotHolder = cur.tbot:GetArmy()
			local pirateHolder = false
			for index, army in cur.innocents do
				 if army == tbotHolder then
				 	pirateHolder = true
				 end
			end
			if pirateHolder == true then
				cur.captureTime = -1
				Sync.captureTime = cur.captureTime
				LOG("Pirates win by capture, destroying ninjas")
				Sync.pAlert = { "Pirates Capture Treasure", "aarrrrrr!" }
				# arrr, nuke them ninjas
				killTeam(cur.phantoms)
			else
				cur.captureTime = -2
				Sync.captureTime = cur.captureTime
				LOG("Ninjas hold TBOT")
				Sync.pAlert = { "Ninja Holds Treasure", false }
			end
		end

		local innocents_dead = 0
		local phantoms_dead = 0
		for index, army in cur.phantoms do
			if ArmyIsOutOfGame(army) then
				phantoms_dead = phantoms_dead + 1
			end
		end
		for index, army in cur.innocents do
			if ArmyIsOutOfGame(army) then
				innocents_dead = innocents_dead + 1
			end
		end
		
		# Ninja killed
		if phantoms_dead > cur.stats.phantoms_dead then
			local remain = cur.stats.phantom_count - phantoms_dead
			local txt = remain
			if remain > 1 then
				txt = txt .. " ninjas remain"
			else
				if remain == 0 then
					txt = "All ninjas killed"
				else
					txt = txt .. " ninja remains"
				end
			end
			WaitSeconds(2)
			Sync.pAlert = { "Ninja Assassinated", txt }
			WaitSeconds(2)
			stats_changed = true
		end

		# Pirate killed
		if innocents_dead > cur.stats.innocents_dead then
			if ArmyIsOutOfGame(cur.boss) then
				LOG("Pirate boss killed, destroying pirates")
				cur.boss_dead = true
				killTeam(cur.innocents)
			end
			local remain = cur.stats.innocent_count - innocents_dead
			local txt = remain
			if remain > 1 then
				txt = txt .. " pirates remain"
			else
				if remain == 0 then
					txt = "All pirates killed"
				else
					txt = txt .. " pirate remains"
				end
			end
			WaitSeconds(2)
			Sync.pAlert = { "Pirate Assassinated", txt }
			WaitSeconds(2)
			stats_changed = true
		end
		
		cur.stats.phantoms_dead = phantoms_dead
		cur.stats.innocents_dead = innocents_dead
		
		# Send stats to the UI
		if stats_changed then
			stats_changed = false
			Sync.pStats = cur.stats
		end
				
		# All Ninjas dead (Pirate victory)
		if cur.stats.innocents_dead < cur.stats.innocent_count and cur.stats.phantoms_dead == cur.stats.phantom_count then
			Sync.pAlert = { "Pirates Win", false }
			# Ally all Pirates and set allied victory to true
			for index1, army1 in cur.innocents do
				SetAlliedVictory(army1, true)
				for index2, army2 in cur.innocents do
					if ArmyIsOutOfGame(army1) == false and ArmyIsOutOfGame(army2) == false and army1 != army2 then
						SetAlliance(army1, army2, 'Ally')
					end
				end
			end
			cur.end_game = true
		end

		# Pirate Boss dead (Ninja victory)
		# All pirates are killed when boss dies, so wait until they're dead before declaring victory
		if cur.stats.phantoms_dead < cur.stats.phantom_count and cur.stats.innocents_dead == cur.stats.innocent_count then
			Sync.pAlert = { "Ninjas Win", false }
			# Ally all Ninjas and set allied victory to true
			for index1, army1 in cur.innocents do
				SetAlliedVictory(army1, true)
				for index2, army2 in cur.innocents do
					if ArmyIsOutOfGame(army1) == false and ArmyIsOutOfGame(army2) == false and army1 != army2 then
						SetAlliance(army1, army2, 'Ally')
					end
				end
			end
			cur.end_game = true
		end
		
		# Everyone fails
		if cur.boss_dead == true and cur.stats.phantoms_dead == cur.stats.phantom_count then
			Sync.pAlert = { "Nobody Wins", false }
			cur.end_game = true
		end
		
	end
	cur.trash:Destroy()
end

# This function changes the way the score works
function PhantomCalculateScore(self)
	local score = 0
	
	if table.getn(cur.playerScores) > 0 then
		if cur.stats.phantom_count + cur.stats.innocent_count - cur.stats.phantoms_dead - cur.stats.innocents_dead < 2 then
			score = 100
		else
			score = cur.playerScores[self:GetArmyIndex()]
		end
	end
	return score

	#local massTotal = self:GetArmyStat("Economy_TotalConsumed_Mass",0.0).Value
	#local massLost = self:GetArmyStat("Units_MassValue_Lost",0.0).Value
	
	#return math.floor(massTotal - massLost) 

	#local massValueDestroyed = self:GetArmyStat("Enemies_MassValue_Destroyed",0.0).Value
	#local massValueLost = self:GetArmyStat("Units_MassValue_Lost",0.0).Value
	#local energyValueDestroyed = self:GetArmyStat("Enemies_EnergyValue_Destroyed",0.0).Value
	#local energyValueLost = self:GetArmyStat("Units_EnergyValue_Lost",0.0).Value

	#local energyValueCoefficient = 0.02
	#return math.floor((massValueDestroyed - massValueLost) + (energyValueDestroyed - energyValueLost) * energyValueCoefficient)
end

# This functions gives the resource bonus to all players
function PhantomResourceThread()
	local minScore = 0
	local maxScore = 0
	local score = 0
	local playerScores = {}
	local coefDelta = cur.config.max_vampire - cur.config.min_vampire
	local scoreDelta = 0
	local coef = 0
	while true do
		WaitSeconds(0.1)
		minScore = -1
		maxScore = -1
		
		# First, work out where everyone is coming - only players in the game can effect this
		for army, brain in ArmyBrains do
			if ArmyIsOutOfGame(army) == false then
				score = brain:GetArmyStat("Economy_TotalConsumed_Mass",0.0).Value - brain:GetArmyStat("Units_MassValue_Lost",0.0).Value
				playerScores[army] = score
				#cur.playerScores[army] = math.floor(score)
				if minScore == -1 or score < minScore then
					minScore = score
				end
				if maxScore == -1 or score > maxScore then
					maxScore = score
				end
			end
		end
		
		# Now we know min and max scores, allocate resources
		scoreDelta = maxScore - minScore
		
		for army, brain in ArmyBrains do
			if ArmyIsOutOfGame(army) == false then
				score = playerScores[army] - minScore
				coef = cur.config.max_vampire - ((score / scoreDelta) * coefDelta)
				cur.playerScores[army] = math.floor(coef * 100)
				
				local mass = brain:GetArmyStat("Enemies_MassValue_Destroyed",0.0).Value
				local energy = brain:GetArmyStat("Enemies_EnergyValue_Destroyed",0.0).Value
				if cur.delta.mass[army] > 0 then
					local deltaMass = (mass - cur.delta.mass[army]) * coef
					local deltaEnergy = (energy - cur.delta.energy[army]) * coef

					brain:GiveResource('MASS', deltaMass)
					brain:GiveResource('ENERGY', deltaEnergy)
				end
				cur.delta.mass[army] = mass
				cur.delta.energy[army] = energy
			end
		end
	end
end

# Determines if an army is a phantom
function IsPhantom(army)
	if table.getn(cur.phantoms) == 0 then
		return false
	else
		for index, p in cur.phantoms do
			if p == army then
				return true
			end
		end
		return false
	end
end

# Dump Resources (from UI)
function dumpResources(data)
	brain = ArmyBrains[data.From]
	brain:TakeResource(data.Args, math.floor(brain:GetEconomyStored(data.Args) * 0.9))	
end

# Vote (from UI)
function vote(data)
	cur.votes[data.Args.Vote] = cur.votes[data.Args.Vote] + 1
	if data.Args.Vote == 1 then
		print(data.Args.Nick.." voted for a single ninja")
	else
		print(data.Args.Nick.." voted for "..data.Args.Vote.." ninjas")
	end
end

# Spawn a TBOT (first time)
function spawnTBot()
	WaitSeconds(cur.config.tbot_spawn)
	if cur.end_game == false then
		Sync.pAlert = { "TreasureBot Arriving", "T-Bot will arrive near a random player" }
		cur.tbot = spawnBotByRandom()
	end
end

# Respawn a TBOT
function respawnTBot()
	WaitSeconds(cur.config.tbot_respawn)
	if cur.end_game == false then
		Sync.pAlert = { "TreasureBot Respawning", "T-Bot reallocated to a random player" }
		cur.tbot = spawnBotByRandom()
	end
end

# Picks a player and spawns a TBOT
function spawnBotByRandom()
	# This function will screw up if no players have an ACU, but it should never be called in such a condition
	
	# Reset the capture timer
	cur.captureTime = GetGameTimeSeconds() + cur.config.tbot_timer
	Sync.captureTime = cur.captureTime
	
	# Pick a player
	local suitableArmies = {}
	local locations = {}
	for army, brain in ArmyBrains do
		if ArmyIsCivilian(army) == false and ArmyIsOutOfGame(army) == false then
			# Player is alive, check they still have an ACU
			local armyUnits = brain:GetListOfUnits(categories.COMMAND, false, false)
			for id, unit in armyUnits do 
				local bpID = unit:GetBlueprint().BlueprintId
				local uType = string.sub(bpID, 4)
				if uType == '0001' then
					# Got an ACU, all good
					local loc = unit:GetPosition()
					table.insert(suitableArmies, army)
					table.insert(locations, loc)
					break
				end
			end
		end
	end
	local pos = math.random(table.getn(suitableArmies))
	local army = suitableArmies[pos]
	local loc = locations[pos]
	
	# Spawn TBOT
	local tbot = CreateUnitHPR('tbot', army, loc[1]-3, loc[2], loc[3], 0, 0, 0)
	
	# Warp in
	tbot:PlayCommanderWarpInEffect()
	
	return tbot
end

# Kills every unit that a team has (ninja or phantom) and sets all it's players out of the game
function killTeam(team)
	for index, army in team do
		if army != nil and ArmyIsOutOfGame(army) == false then
			local armyUnits = ArmyBrains[army]:GetListOfUnits(categories.LAND, false, false)
			for id, unit in armyUnits do 
				unit:Kill()
			end
			SetArmyOutOfGame(army)
		end
	end
end




