-- $Id: //depot/Projects/StarWars_Expansion/Run/Data/Scripts/Library/PGTaskForce.lua#6 $
--/////////////////////////////////////////////////////////////////////////////////////////////////
--
-- (C) Petroglyph Games, Inc.
--
--
--  *****           **                          *                   *
--  *   **          *                           *                   *
--  *    *          *                           *                   *
--  *    *          *     *                 *   *          *        *
--  *   *     *** ******  * **  ****      ***   * *      * *****    * ***
--  *  **    *  *   *     **   *   **   **  *   *  *    * **   **   **   *
--  ***     *****   *     *   *     *  *    *   *  *   **  *    *   *    *
--  *       *       *     *   *     *  *    *   *   *  *   *    *   *    *
--  *       *       *     *   *     *  *    *   *   * **   *   *    *    *
--  *       **       *    *   **   *   **   *   *    **    *  *     *   *
-- **        ****     **  *    ****     *****   *    **    ***      *   *
--                                          *        *     *
--                                          *        *     *
--                                          *       *      *
--                                      *  *        *      *
--                                      ****       *       *
--
--/////////////////////////////////////////////////////////////////////////////////////////////////
-- C O N F I D E N T I A L   S O U R C E   C O D E -- D O   N O T   D I S T R I B U T E
--/////////////////////////////////////////////////////////////////////////////////////////////////
--
--              $File: //depot/Projects/StarWars_Expansion/Run/Data/Scripts/Library/PGTaskForce.lua $
--
--    Original Author: Brian Hayes
--
--            $Author: James_Yarrow $
--
--            $Change: 46700 $
--
--          $DateTime: 2006/06/21 14:40:55 $
--
--          $Revision: #6 $
--
--/////////////////////////////////////////////////////////////////////////////////////////////////

require("PGAICommands")

function FindStageArea(_target, taskforce)
	return _FindStageArea(PlayerObject, _target, taskforce);
end

function AssembleForce(taskforce, stage_at_strongest_space)
	if stage_at_strongest_space then
		stage = FindTarget.Reachable_Target(PlayerObject, "FriendlySpaceForceStrength", "Friendly | Enemy | Neutral", "Enemy_undefended", 1.0, AITarget)
		DebugMessage("%s -- Using the enemy_undefended reachable system %s with the strongest space force for staging.", tostring(Script), tostring(stage))
	else
		stage = taskforce.Get_Stage()
		DebugMessage("%s -- Using default system %s (nearest faction system) for staging.", tostring(Script), tostring(stage))
	end
	
	if not stage then 
		DebugMessage("%s -- Unable to find a staging area.  Abandonning plan.", tostring(Script))
		ScriptExit()
	end
	DebugMessage("%s -- Selected Stage: %s\n", tostring(Script), tostring(stage))
	BlockOnCommand(taskforce.Produce_Force(stage));
end

function SynchronizedAssemble(taskforce, stage_at_strongest_space)
	if stage_at_strongest_space then
		stage = FindTarget(taskforce, "FriendlySpaceForceStrength", "Friendly | Enemy | Neutral", 1.0)
		DebugMessage("%s -- Using the system %s with the strongest space force for staging.", tostring(Script), tostring(stage))
	else
		stage = taskforce.Get_Stage()
		DebugMessage("%s -- Using default system %s (nearest faction system) for staging.", tostring(Script), tostring(stage))
	end
	
	if not stage then 
		DebugMessage("%s -- Unable to find a staging area.  Abandonning plan.", tostring(Script))
		ScriptExit()
	end
	DebugMessage("%s -- Selected Stage: %s\n", tostring(Script), tostring(stage))
	BlockOnCommand(taskforce.Produce_Force(stage, true));
end

function WaitMoveForce(taskforce, planet)
	DebugMessage("Moving Taskforce %s to object %s.", tostring(taskforce), tostring(planet))
	BlockOnCommand(taskforce.Move_To(FindPlanet(planet)))
	DebugMessage("Moving Command finished")
end

function LandUnits(taskforce)
	DebugMessage("Landing on %s with taskforce %s.", tostring(Target), tostring(taskforce))
	BlockOnCommand(taskforce.Land_Units())
	DebugMessage("Land Units Command finished")
end

function LaunchUnits(taskforce)
	DebugMessage("Launching units into orbit with taskforce %s.", tostring(taskforce))
	
	block = taskforce.Launch_Units()
	if not block then
		return false
	end
	BlockOnCommand(block)
	BlockOnCommand(taskforce.Form_Units())
	DebugMessage("Launch Units Command finished")
	
	return true
end

function Invade(taskforce)
	DebugMessage("Invading %s with taskforce %s.", tostring(Target), tostring(taskforce))

	InvasionActive = true
	invade_status = taskforce.Invade()
	if invade_status and BlockOnCommand(invade_status) then
		DebugMessage("Invasion successful.  Waiting for planet conversion.");
		taskforce.Set_As_Goal_System_Removable(false)
		LandUnits(taskforce)
		InvasionActive = false
		return true
	else
		DebugMessage("Invasion failed!")
		InvasionActive = false
		return false
	end
end

function FundBases(player, target)

	--Prefer starbases since they block enemy movement
	DebugMessage("%s -- giving desire bonus to build starbase", tostring(Script))
	BlockOnCommand(GiveDesireBonus(player, "Build_Initial_Starbase_Only", target, 15, 5)) 
	DebugMessage("%s -- waiting for starbase", tostring(Script))
	BlockOnCommand(WaitForStarbase(Target, 1))
	DebugMessage("%s -- giving desire bonus to build groundbase", tostring(Script))
	BlockOnCommand(GiveDesireBonus(player, "Build_Initial_Groundbase_Only", target, 15, 5))
end


-- Generally follow the task force we're escorting and attack it's attackers.
-- Conservative style will avoid confrontation with non fighters/bombers
function Escort(taskforce, target, conservative_style)

	if not TestValid(target) then
		Sleep(1)
		return 
	end
	
	lib_enemy = FindDeadlyEnemy(target)
	
	if lib_enemy then
		if Get_Game_Mode() == "Space" then
			if not conservative_style or (lib_enemy.Is_Category("Fighter") or lib_enemy.Is_Category("Bomber")) then
	
				-- Lure them away from our escorted target if we can.
				Try_Ability(taskforce,"LURE")
				
				-- Weave a bit.
				Weave(taskforce, lib_enemy)
	
				-- engage the attackers for a duration
				if not TestValid(lib_enemy) then return end
				DebugMessage("%s -- In Escort: Attacking lib_enemy: %s for Target: %s", tostring(Script), tostring(lib_enemy), tostring(target))
				BlockOnCommand(taskforce.Attack_Target(lib_enemy), 15)
			else
			
				-- run away a bit, then hold ground before resuming escort
				DebugMessage("%s -- In Escort: attempting to divert lib_enemy: %s for Target: %s", tostring(Script), tostring(lib_enemy), tostring(target))
				BlockOnCommand(taskforce.Move_To(Project_By_Unit_Range(lib_enemy, taskforce)), 15)
				
				if not TestValid(target) then return end
				
				BlockOnCommand(taskforce.Guard_Target(target, 5))
			end
		else 
			BlockOnCommand(taskforce.Attack_Target(lib_enemy), 20)
		end
		return
	end

	if not TestValid(target) then
		Sleep(1)
		return 
	end
    
	-- Make sure we're assisting the target provided we're not already assisting
	-- because reissuing a guard will cause the assisting escort to break off attack.
	-- Note: may receive a non tf "target" param, for example EscortPlan passes a unit
	if not Tf_Has_Attack_Target(taskforce) then
		
		-- Escort forces should have attack priorities that allow them to attack structures by this point
		DebugMessage("%s on %s -- In Escort: Resuming escort of %s", tostring(Script), tostring(AITarget), tostring(target))
		taskforce.Guard_Target(target)
	end
	
	Sleep(1)
end


-- Does any member of the given task force have an attack target?
function Tf_Has_Attack_Target(tf)

	if TestValid(tf) then 
		for i, unit in tf.Get_Unit_Table() do
			if unit.Has_Attack_Target() == true then
				return true
			end
		end
	else
		DebugMessage("%s -- task force %s isn't valid", tostring(Script), tostring(tf))
	end
	
	return false
end

function Weave(tf, enemy)
	if not TestValid(enemy) then return end
	BlockOnCommand(tf.Attack_Target(enemy), 10)
	if not TestValid(enemy) then return end
	BlockOnCommand(tf.Move_To(Project_By_Unit_Range(enemy, tf)), 5)
	if not TestValid(enemy) then return end
	BlockOnCommand(tf.Attack_Target(enemy), 2)
	if not TestValid(enemy) then return end
	BlockOnCommand(tf.Move_To(Project_By_Unit_Range(enemy, tf)), 4)
end

function WaitForAllReinforcements(taskforce, target)
	block = taskforce.Reinforce(target)
	if block then
		while block and not BlockOnCommand(block) do
			block = taskforce.Reinforce(target)
		end
	end
end



-- Keep a task force from being idle
-- expects lib_tf_to_anti_idle and lib_enemy_to_avoid to be defined before use
function Anti_Idle_Taskforce()
	DebugMessage("%s-- AntiIdle attempt", tostring(Script))
	if lib_tf_to_anti_idle and TestValid(lib_enemy_to_avoid) then
		DebugMessage("%s-- AntiIdle lib vars are defined", tostring(Script))

		if lib_new_anti_idle == true then
			DebugMessage("%s-- AntiIdling %s", tostring(Script), tostring(lib_tf_to_anti_idle))
			lib_anti_idle_start_time = GetCurrentTime()
			lib_anti_idle_block = lib_tf_to_anti_idle.Move_To(Project_By_Unit_Range(lib_enemy_to_avoid, lib_tf_to_anti_idle))
			lib_new_anti_idle = false 
		elseif lib_anti_idle_block then
			PumpEvents()
			
			-- Reissue a new command if the old one has expired or some time has passed
			-- This ensures that newly landed reinforcements on the task force will also anti-idle
			if lib_anti_idle_block.IsFinished() == true
				or 2 + lib_anti_idle_start_time < GetCurrentTime() then
				lib_new_anti_idle = true
			end
		end
	else
		DebugMessage("%s-- needed global vars aren't defined for anti idle", tostring(Script))
	end
end


-- Try to get units on the map in the most reasonable manner.
-- Passing an escort_force is optional
function QuickReinforce(player, target, tf_to_reinforce, second_try_tf)

	if not player then
		DebugMessage("%s-- received nil player", tostring(Script))
		return 
	end

-- Global plans can pass a nil target and rely on failsafes below to find a target
--	if not target then
--		DebugMessage("%s-- received nil target", tostring(Script))
--		return 
--	end

	if not tf_to_reinforce then
		DebugMessage("%s-- received nil tf_to_reinforce", tostring(Script))
		return 
	end

	
	-- Space mode will attempt various reinforce attempts with timeouts,
	-- then simply fail after the last option is exhausted.
	if Get_Game_Mode() == "Space" then

		lib_reinforce_timeout = 3
	
		-- If some units have landed, try land the remainder near the rest of the main tf
		if tf_to_reinforce.Get_Force_Count() ~= 0 then
	
			-- Define some global vars for the anti idle
			lib_tf_to_anti_idle = tf_to_reinforce
			lib_enemy_to_avoid = Find_Nearest(tf_to_reinforce, PlayerObject, false)
			lib_new_anti_idle = true
	
			-- Reinforce new units and anti-idle existing units waiting for the reinforce
			if BlockOnCommand(tf_to_reinforce.Reinforce(tf_to_reinforce), lib_reinforce_timeout, Anti_Idle_Taskforce) then
				DebugMessage("%s-- reinforced by task force", tostring(Script))
				lib_tf_to_anti_idle = nil
				lib_enemy_to_avoid = nil
				return
			end
		end
	
		-- If some of the escort force exists, try to land there
		if second_try_tf and second_try_tf.Get_Force_Count() ~= 0 then
	
			-- Define some global vars for the anti idle
			lib_tf_to_anti_idle = second_try_tf
			lib_enemy_to_avoid = Find_Nearest(second_try_tf, PlayerObject, false)
			lib_new_anti_idle = true
	
			-- Reinforce new units and anti-idle existing units waiting for the reinforce
			if BlockOnCommand(tf_to_reinforce.Reinforce(second_try_tf), lib_reinforce_timeout, Anti_Idle_Taskforce) then
				DebugMessage("%s-- reinforced by secondary task force", tostring(Script))
				lib_tf_to_anti_idle = nil
				lib_enemy_to_avoid = nil
				return
			end
		end
	
		-- Try to land near the default starting point, or some base building, or finally the target itself.
		lib_start_loc = FindTarget(tf_to_reinforce, "Is_Friendly_Start", "Tactical_Location", 1.0)
		if lib_start_loc then
			DebugMessage("%s-- Trying to reinforce by start point", tostring(Script))
			BlockOnCommand(tf_to_reinforce.Reinforce(lib_start_loc), lib_reinforce_timeout)
		else
			lib_base_list = Find_All_Objects_Of_Type(player, "Structure")
			if lib_base_list[1] then
				DebugMessage("%s-- Trying to reinforce by friendly structure", tostring(Script))
				BlockOnCommand(tf_to_reinforce.Reinforce(lib_base_list[1]), lib_reinforce_timeout)
			else
				lib_base_list = Find_All_Objects_Of_Type(player, "Capital")
				if lib_base_list[1] then
					DebugMessage("%s-- Trying to reinforce by friendly capital", tostring(Script))
					BlockOnCommand(tf_to_reinforce.Reinforce(lib_base_list[1]), lib_reinforce_timeout)
				else
					DebugMessage("%s-- Trying to reinforce by plan target", tostring(Script))
					BlockOnCommand(tf_to_reinforce.Reinforce(target), lib_reinforce_timeout)
				end
			end
		end
		
		-- If we were unable to bring in any reinforcements within a reasonable amount of time
		-- then just abandon the plan; it's not worth holding up other units for too long
		if tf_to_reinforce.Get_Force_Count() == 0 then
			ScriptExit()
		end
	
	-- Land mode: this will just return if the units are all available; 
	-- otherwise we'll reinforce for a long time and then the plan will fail because it may no longer be relevant.
	else
	
		if tf_to_reinforce.Are_All_Units_On_Free_Store() then
			
			DebugMessage("%s-- All units available, proceeding with plan", tostring(Script))

			-- form up any spread out infantry
			tf_to_reinforce.Activate_Ability("SPREAD_OUT", false)
		else

			DebugMessage("%s-- Reinforcements required--waiting for them.", tostring(Script))

			-- Generally, we want things reinforced near the target, if possible
			-- But this will try all of the reinforcement points, if needed.
			-- This typically takes a long time.
			if AITarget then
				WaitForAllReinforcements(tf_to_reinforce, target)
			else
				
				-- find something to reinforce near, for global plans with a nil target
				friendly_loc = FindTarget(tf_to_reinforce, "Space_Area_Is_Hidden", "Tactical_Location", 1.0)
				WaitForAllReinforcements(tf_to_reinforce, friendly_loc)
			end			

			--WaitForAllReinforcements(tf_to_reinforce, tf_to_reinforce)
			DebugMessage("%s-- Reinforcements arrived.  Abandonning plan for reproposal if still relevant", tostring(Script))

			-- If the plan is still relevant, it will be proposed again.
			ScriptExit()
		end
	end

end





function SetClassPriorities(tf, priority_set_type)

	priority_set_name = "Fighter" .. "_" .. priority_set_type
	tf.Set_Targeting_Priorities(priority_set_name, "Fighter")

	priority_set_name = "Bomber" .. "_" .. priority_set_type
	tf.Set_Targeting_Priorities(priority_set_name, "Bomber")

	priority_set_name = "Corvette" .. "_" .. priority_set_type
	tf.Set_Targeting_Priorities(priority_set_name, "Corvette")

	priority_set_name = "Frigate" .. "_" .. priority_set_type
	tf.Set_Targeting_Priorities(priority_set_name, "Frigate")

	priority_set_name = "Capital" .. "_" .. priority_set_type
	tf.Set_Targeting_Priorities(priority_set_name, "Capital")

end


-- Move task force to asteroids or nebulae if within range and the asteroids aren't near an enemy
-- Returns true if the task force is already obscured or this function obscured it
function Obscure(tf, range, use_nebulae, path_through_fields)
	tf_unit_list = tf.Get_Unit_Table()
	if not UnitListIsObscured(tf_unit_list) then
		asteroids = Find_Nearest_Space_Field(tf, "Asteroid")
		
		if asteroids and tf.Get_Distance(asteroids) < range then
			enemy_nearest_loc = Find_Nearest(asteroids, PlayerObject, false)
			if not TestValid(enemy_nearest_loc) or enemy_nearest_loc.Get_Distance(asteroids) < 750 then

				--MessageBox("%s -- moving to asteroids:%s", tostring (Script), path_through_fields)
				BlockOnCommand(tf.Move_To(asteroids, 10, path_through_fields))
				return true
			end
		elseif use_nebulae then
			nebula = Find_Nearest_Space_Field(tf, "Nebula")
			if nebula and tf.Get_Distance(nebula) < range then
				enemy_nearest_loc = Find_Nearest(nebula, PlayerObject, false)
				if not TestValid(enemy_nearest_loc) or enemy_nearest_loc.Get_Distance(nebula) < 750 then

					--MessageBox("%s -- moving to nebulae:%s", tostring (Script), path_through_fields)
					BlockOnCommand(tf.Move_To(nebula, 10, path_through_fields))
					return true
				end	
			end
		end
		return false -- We're not obscured, and there's nothing in range to obscure us.
	end
	
	return true
end


-- Fix the unit or hide if appropriate.
-- Return true if the unit was fixed.
function ConsiderHeal(unit)
	unit_hull = unit.Get_Hull()
	if (unit_hull < 0.8) then
		bacta = Find_Nearest(unit, "HealsInfantry", PlayerObject, true)
		if TestValid(bacta) then
			if (unit_hull < 0.4) or (unit.Get_Distance(bacta) < 700) then
				unit.Activate_Ability("STEALTH", true)
				unit.Activate_Ability("FORCE_CLOAK", true)
				BlockOnCommand(unit.Move_To(bacta))
				while unit.Get_Hull() < 0.9 do
					DebugMessage("%s-- Waiting to heal", tostring(Script))
					BlockOnCommand(unit.Guard_Target(bacta), 2)
				end
				return true
			end
		end
	end
	return false		
end

-- Fix the unit or hide if appropriate.
-- Return true if the unit was fixed.
function ConsiderRepair(unit)
	unit_hull = unit.Get_Hull()
	if (unit_hull < 0.8) then
		repair_station = Find_Nearest(unit, "HealsVehicles", PlayerObject, true)
        if TestValid(repair_station) then 
			if (unit_hull < 0.4) or (unit.Get_Distance(repair_station) < 700) then
		
				BlockOnCommand(unit.Move_To(repair_station))
				while unit.Get_Hull() < 0.9 do
					BlockOnCommand(unit.Guard_Target(repair_station), 2)
				end
				return true
			end
		end
	end
	return false
end

-- Note, this is safe for event response (because it doesn't block)
function Release_To_Hide(tf, unit)

	flee_loc = Find_Nearest(unit, "HealsInfantry", PlayerObject, true)
	if not flee_loc then
		flee_loc = FindTarget(tf, "Space_Area_Is_Hidden", "Tactical_Location", 1.0, 5000.0)
	end
	if flee_loc then
		unit.Move_To(flee_loc)
		unit.Lock_Current_Orders()
		tf.Release_Unit(unit)
		return true
	end
end


-- This is for some give and take pacing.  The AI is allowed to attack without difficulty-based pause
-- 1 to N times, then it must wait for the player to win 1 to N times before attacking without pause.
function GalacticAttackAllowed(difficulty, ai_territories_just_gained)

	num_ai_attacks_left = GetGlobalValueOrZero(PlayerObject, "num_ai_attacks_left")
	if num_ai_attacks_left > 0 then

		-- If the AI has allowed attacks left, permit the attack and decrement
		GlobalValue.Set(PlayerSpecificName(PlayerObject, "num_ai_attacks_left"), num_ai_attacks_left - 1)
		--MessageBox("%s -- ai has %d attacks left; not pausing before next attack", tostring(Script), num_ai_attacks_left)
		return true
	else
		
		-- AI has to wait for player wins		

		-- Difficulty will dictate aggressiveness and amount of contrived breathing room the AI gives for free
		min_player_wins_unresponded = 4
		max_player_wins_unresponded = 8
		max_ai_attacks_allowed = 2
		if difficulty == "Normal" then
			min_player_wins_unresponded = 3
			max_player_wins_unresponded = 6
			max_ai_attacks_allowed = 2
		elseif difficulty == "Hard" then
			min_player_wins_unresponded = 1
			max_player_wins_unresponded = 2
			max_ai_attacks_allowed = 4
		end
		
		-- Track the player's progress
		num_land_enemy_controlled = EvaluatePerception("Num_Enemy_Land_Territories", PlayerObject)
		num_space_enemy_controlled = EvaluatePerception("Num_Enemy_Space_Territories", PlayerObject)
		--MessageBox("%s -- num_land_enemy_controlled:%d num_space_enemy_controlled:%d", tostring(Script), num_land_enemy_controlled, num_space_enemy_controlled)

		player_gains_before_attacking = GetGlobalValueOrZero(PlayerObject, "player_gains_before_attacking")
        if player_gains_before_attacking == 0 then
		
			-- Establish how far the player can go unresponded
			player_gains_before_attacking = GameRandom(min_player_wins_unresponded, max_player_wins_unresponded)
			GlobalValue.Set(PlayerSpecificName(PlayerObject, "player_gains_before_attacking"), player_gains_before_attacking)
			--MessageBox("%s -- Determined %d player gains allowed and recording current player territories.", tostring(Script), player_gains_before_attacking)

			-- Record the state of the galaxy
			GlobalValue.Set(PlayerSpecificName(PlayerObject, "recent_land_enemy_controlled"), num_land_enemy_controlled)
			GlobalValue.Set(PlayerSpecificName(PlayerObject, "recent_space_enemy_controlled"), num_space_enemy_controlled)
		end

		-- Check the player's progress (before our current attack)
		player_territories_gained = num_land_enemy_controlled + num_space_enemy_controlled
									- GlobalValue.Get(PlayerSpecificName(PlayerObject, "recent_land_enemy_controlled"))
									- GlobalValue.Get(PlayerSpecificName(PlayerObject, "recent_space_enemy_controlled"))
									--		+ ai_territories_just_gained
		
		if player_territories_gained >= player_gains_before_attacking then
		
			-- Player has won all that he's allowed without AI response, time to start attacking again
			num_ai_attacks_left = GameRandom(0, max_player_wins_unresponded - 1)
			GlobalValue.Set(PlayerSpecificName(PlayerObject, "num_ai_attacks_left"), num_ai_attacks_left)

			-- Also reset the losses allowed, so that it will be randomized next time
			GlobalValue.Set(PlayerSpecificName(PlayerObject, "player_gains_before_attacking"), 0)

			--MessageBox("%s -- player has enough wins unresponded (%d); not pausing before next attack. \n AI gets %d attacks without pause", tostring(Script), player_territories_gained, num_ai_attacks_left)
			return true
		else

			--MessageBox("%s -- %d player territories gained, %d allowed before responding so pausing before next attack"
			--							, tostring(Script), player_territories_gained, player_gains_before_attacking)
			if player_territories_gained < 0 then

				-- Record the state of the galaxy
				--MessageBox("%s -- player lost ground; resetting recent territories records", tostring(Script))
				GlobalValue.Set(PlayerSpecificName(PlayerObject, "recent_land_enemy_controlled"), num_land_enemy_controlled)
				GlobalValue.Set(PlayerSpecificName(PlayerObject, "recent_space_enemy_controlled"), num_space_enemy_controlled)
			end 
		end
	
	end
	
	return false
end


function DifficultyBasedMinPause(difficulty)

	sleep_duration = 300
	if difficulty == "Normal" then
		sleep_duration = 200
	elseif difficulty == "Hard" then
		sleep_duration = 45
	end

	return sleep_duration	
end

function GetGlobalValueOrZero(player, value_name)
	ret_value = GlobalValue.Get(PlayerSpecificName(player, value_name))
	if ret_value == nil then
		return 0.0
	end
	return ret_value
end
