----------------------------------------------------------------------------------------------------------------
-- Single player scar helper functions
-- (c) 2003 Relic Entertainment Inc.

import("Setup.scar")
import("Proximity.scar")
import("Timer.scar")
import("Player.scar")
import("Entity.scar")
import("Squad.scar")
import("Command.scar")
import("Groups.scar")
import("Camera.scar")
import("Restrict.scar")
import("Actor.scar")
import("Modifiers.scar")
import("Objectives.scar")
import("MoreUtils.scar")
import("CpuPlayerUtil.scar")
import("Stingers.scar")
import("RuleSystem.scar")
import("Deprecated.scar")
import("View.scar")
import("UI.scar")
import("DesignerLib.scar")
import("SyncWeapons.scar")
import("LuaConsts.scar")
import("UID.scar")
import("TechTreeSetup.scar")


-- Some Constants...
ALL = true
ANY = false		-- for use in "Boolean: all" parameters

-- Deprecate error function
error = fatal

function __Private_Util_DoNothing()
end

----------------------------------------------------------------------------------------------------------------
-- Private implementation
_ScarUtil_Private = {

	__skipsave = true,

	-- get the player owner of a group.  this function assumes that all items in a group belong to the same player
	-- also assumes that the group is not empty
	GetGroupPlayerOwner = function( group, groupcaller )
		if( groupcaller.GetCount( group ) <= 0 ) then
			fatal("Function cannot be called with empty group")
		end
		
		local player = nil
		
		local CheckPlayer = function( groupid, itemindex, itemid )
			player = groupcaller.GetItemPlayer( itemid )
			return true
		end
		
		groupcaller.ForEachAllOrAnyEx( group, false, CheckPlayer, true, true )
		
		if( player == nil ) then
			fatal("This should never happen")
		end
		
		return player
	end	
}
	
-- NOTES
-- use lowercase to start variables
-- use uppercase to start functions
-- restrict the use of "_" to grouping categories

-- TODO
-- Util_CreateEntitiesAtPosition
-- Util_CreateEntitiesAtMarker
-- Cleanup/Document the rest of Squad.Scar
-- Cleanup/Document the rest of this file


--? @group Util

--? @shortdesc Converts a 2D top down position to a 3D ScarPosition.
--? @extdesc
--? 3D ScarPositions have the x axis left to right, the z axis in to out, and the y axis down to up (y axis represents the height of the terrain).  Use this function to convert a top-down 2D position to a 3D world position.\n\n
--? Note: (0,0) is in the center of the map.
--? @result Position
--? @args Real xpos, Real ypos
function Util_ScarPos(xpos, ypos)
	return World_Pos(xpos, 0, ypos)
end


--? @shortdesc Creates a given number of entities at a location and adds them to an egroup. A PlayerID of nil will create the entities as world objects.
--? @args PlayerID player, EGroupID egroup, Integer blueprintID, MarkerID/Pos location, Integer numentities
--? @result Void
function Util_CreateEntitiesAtMarker(playerid, egroupid, blueprintID, pos, numentities)

	-- if we passed in a marker rather than a pos, then convert it now
	if (scartype(pos) == ST_MARKER) then
		pos = Marker_GetPosition(pos)
	end
	
	-- do some type checking
	if (scartype(egroupid) ~= ST_EGROUP) then fatal("Util_CreateEntitesAtMarker: EGroupID is invalid") end
	if (scartype(blueprintID) ~= ST_NUMBER) then fatal("Util_CreateEntitesAtMarker: Blueprint is invalid") end
	if (scartype(pos) ~= ST_SCARPOS) then fatal("Util_CreateEntitesAtMarker: Position/MarkerID is invalid") end
	if (scartype(numentities) ~= ST_NUMBER) then fatal("Util_CreateEntitesAtMarker: Number of entities is invalid") end
	
	for i = 1, numentities do
		
		local entityid = nil
		if playerid == nil then
			entityid = Entity_CreateENV(blueprintID, pos)								-- create the entity as a world object
		else
			entityid = Entity_Create(blueprintID, playerid, pos)						-- create the entity as a player unit
			Entity_Spawn(entityid)													-- spawn it
		end
		
		if (Entity_IsBuilding(entityid) == true) then								-- if it's a building then make it fully constructed
			Entity_ForceConstruct(entityid)
		end
		
		if (egroupid ~= nil) then
			EGroup_Add(egroupid, entityid)											-- add to group
		end
		
	end

end



--? @shortdesc Creates a given number of squads at a location and adds them to an sgroup.  You can optionally also specify the loadout (it will default to the min loadout if left off)
--? @result Void
--? @args PlayerID playerid, SGroupID sgroup, Integer blueprintID, MarkerID/Pos location, Integer numsquads[, Integer loadout]
function Util_CreateSquadsAtMarker(playerid, sgroupid, blueprintID, pos, numsquads, loadout)

	local direction = World_Pos(0, 0, 1)
	
	-- fill in default loadout value if it was skipped
	if (loadout == nil) then
		loadout = 0
	end
	
	-- if we passed in a marker rather than a pos, then convert it now
	if (scartype(pos) == ST_MARKER) then
		direction = Marker_GetDirection(pos)
		pos = Marker_GetPosition(pos)
	end

	-- do some type checking
	if (scartype(playerid) ~= ST_PLAYER) then fatal("Util_CreateSquadsAtMarker: PlayerID is invalid") end
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Util_CreateSquadsAtMarker: SGroupID is invalid") end
	if (scartype(blueprintID) ~= ST_NUMBER) then fatal("Util_CreateSquadsAtMarker: Blueprint is invalid") end
	if (scartype(pos) ~= ST_SCARPOS) then fatal("Util_CreateSquadsAtMarker: Position/MarkerID is invalid") end
	if (scartype(numsquads) ~= ST_NUMBER) then fatal("Util_CreateSquadsAtMarker: Number of squads is invalid") end
	if (scartype(loadout) ~= ST_NUMBER) then fatal("Util_CreateSquadsAtMarker: Loadout is invalid") end
	
	for i = 1, numsquads do
		local facingpos = World_Pos((pos.x + (direction.x*10)), pos.y, (pos.z + (direction.z*10)))
		local squadid = Squad_CreateAndSpawnToward( blueprintID, playerid, loadout, pos, facingpos )		-- create the squad
		if (sgroupid ~= nil) then
			SGroup_Add(sgroupid, squadid)															-- add to group
		else
			fatal( "Failed to create or spawn squad" )
		end

	end

end



--? @shortdesc Parachutes in a given number of squads at a location and adds them to an sgroup. You can optionally also specify the loadout (it will default to the min loadout if left off) 
--? @extdesc Units are not currently added into the specified SGroup - we'll implement that bit when we work out how.
--? @result Void
--? @args PlayerID player, SGroupID sgroup, Integer blueprintID, MarkerID/Pos location, Integer numsquads[, Integer loadout]
function Util_ParachuteSquadsAtMarker(playerid, sgroupid, blueprintID, pos, count, loadout)
	
	-- fill in default loadout value if it was skipped
	if (loadout == nil) then
		loadout = 0
	end
	
	-- if we passed in a marker rather than a pos, then convert it now
	if (scartype(pos) == ST_MARKER) then
		pos = Marker_GetPosition(pos)
	end
	
	-- do some type checking
	if (scartype(playerid) ~= ST_PLAYER) then fatal("Util_ParachuteSquadsAtMarker: PlayerID is invalid") end
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Util_ParachuteSquadsAtMarker: SGroupID is invalid") end
	if (scartype(blueprintID) ~= ST_NUMBER) then fatal("Util_ParachuteSquadsAtMarker: Blueprint is invalid") end
	if (scartype(pos) ~= ST_SCARPOS) then fatal("Util_ParachuteSquadsAtMarker: Position/MarkerID is invalid") end
	if (scartype(count) ~= ST_NUMBER) then fatal("Util_ParachuteSquadsAtMarker: Number of squads is invalid") end
	if (scartype(loadout) ~= ST_NUMBER) then fatal("Util_ParachuteSquadsAtMarker: Loadout is invalid") end
	
	for n = 1, count do
		Player_DoParadrop(playerid, sgroupid, pos, 100, 20, blueprintID, true, loadout, 0)
	end
	
end



--? @shortdesc Creates a given number of squads at a marker position (facing another marker) and adds them to an sgroup.  You can optionally also specify the loadout (it will default to the min loadout if left off)
--? @args PlayerID playerid, SGroupID sgroup, Integer blueprintID, MarkerID/Pos marker, MarkerID/Pos facingmarker, Integer numsquads[, Integer loadout]
--? @result Void
function Util_CreateSquadsAtMarkerFacing(playerid, sgroupid, blueprintID, pos, facing, numsquads, loadout)

	-- fill in default loadout value if it was skipped
	if (loadout == nil) then
		loadout = 0
	end

	-- if we passed in a marker rather than a pos, then convert it now
	if (scartype(pos) == ST_MARKER) then
		pos = Marker_GetPosition(pos)
	end
	if (scartype(facing) == ST_MARKER) then
		facing = Marker_GetPosition(facing)
	end
	
	-- do some type checking
	if (scartype(playerid) ~= ST_PLAYER) then fatal("Util_CreateSquadsAtMarkerFacing: PlayerID is invalid") end
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Util_CreateSquadsAtMarkerFacing: SGroupID is invalid") end
	if (scartype(blueprintID) ~= ST_NUMBER) then fatal("Util_CreateSquadsAtMarkerFacing: Blueprint is invalid") end
	if (scartype(pos) ~= ST_SCARPOS) then fatal("Util_CreateSquadsAtMarkerFacing: First Position/MarkerID is invalid") end
	if (scartype(facing) ~= ST_SCARPOS) then fatal("Util_CreateSquadsAtMarkerFacing: Second (Facing) Position/MarkerID is invalid") end
	if (scartype(numsquads) ~= ST_NUMBER) then fatal("Util_CreateSquadsAtMarkerFacing: Number of squads is invalid") end
	if (scartype(loadout) ~= ST_NUMBER) then fatal("Util_CreateSquadsAtMarkerFacing: Loadout is invalid") end

	for i = 1, numsquads do
		local squadid = Squad_CreateAndSpawnToward( blueprintID, playerid, loadout, pos, facing )	-- create the squad
		if (sgroupid ~= nil) then
			SGroup_Add(sgroupid, squadid)									-- add to group
		else
			fatal( "Failed to create or spawn squad" )
		end
	end

end



--? @shortdesc Creates a given number of squads and instant load them in to a hold and adds them to an sgroup. You can optionally also specify whether to overload the buiding or not & the squad loadout (default setting sets to NOT overload & uses normal loadout )
--? @args PlayerID playerid, SGroupID sgroup, Integer blueprintID, EGroupID/SGroupID hold, Integer numsquads[, Bool overload[, Integer loadout]]
--? @result Void
function Util_CreateSquadsAndGarrison(playerid, sgroupid, blueprintID, hold, numsquads, overload, loadout)

	-- fill in default overload value if it was skipped
	if overload == nil then
		overload = false
	end
	
	-- fill in default loadout value if it was skipped
	if loadout == nil then
		loadout = 0
	end
	
	-- do some type checking
	if (scartype(playerid) ~= ST_PLAYER) then fatal("Util_CreateSquadsAndGarrison: PlayerID is invalid") end
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Util_CreateSquadsAndGarrison: SGroupID is invalid") end
	if (scartype(blueprintID) ~= ST_NUMBER) then fatal("Util_CreateSquadsAndGarrison: Blueprint is invalid") end
	if (scartype(hold) ~= ST_EGROUP) and (scartype(hold) ~= ST_SGROUP) then fatal("Util_CreateSquadsAndGarrison: Hold EGroupID/SGroupID is invalid") end
	if (scartype(numsquads) ~= ST_NUMBER) then fatal("Util_CreateSquadsAndGarrison: Number of squads is invalid") end
	if (scartype(overload) ~= ST_BOOLEAN) then fatal("Util_CreateSquadsAndGarrison: Overload is invalid") end
	if (scartype(loadout) ~= ST_NUMBER) then fatal("Util_CreateSquadsAndGarrison: Loadout is invalid") end
	
	-- make a local sgroup for creating this set of guys in 
	sg_scarutiltemp = SGroup_CreateIfNotFound("sg_scarutiltemp")
	SGroup_Clear(sg_scarutiltemp)
	
	-- get approximate spawn location
	local pos = nil
	if (scartype(hold) == ST_EGROUP) then
		pos = EGroup_GetPosition( hold )
	elseif (scartype(hold) == ST_SGROUP) then
		pos = SGroup_GetPosition( hold )
	end
	
	for i = 1, numsquads do
		local squadid = Squad_CreateAndSpawn( blueprintID, playerid, loadout, pos )		-- create the squad
		if (sgroupid ~= nil) then
			SGroup_Add(sgroupid, squadid)									-- add to group
			SGroup_Add(sg_scarutiltemp, squadid)								-- add to another group for adding to the hold item
		else
			fatal( "Failed to create or spawn squad" )
		end
		
	end

	-- instant load everyone into the hold item
	if (scartype(hold) == ST_EGROUP) then
		Cmd_InstantGarrison( sg_scarutiltemp, hold, overload )
	elseif (scartype(hold) == ST_SGROUP) then
		Cmd_InstantLoad( sg_scarutiltemp, hold, overload )
	end
	
	-- clear the local group
	SGroup_Clear(sg_scarutiltemp)
	
end



--? @shortdesc Creates a given number of squads and instant load them in to a hold and adds them to an sgroup. It then exit them from the hold and move to a position. You can optionally also specify whether to overload the buiding or not & the squad loadout (default setting sets to NOT overload & uses normal loadout )
--? @extdesc To ask the exited squad to do any subsequent actions, please use queued commands
--? @args PlayerID playerid, SGroupID sgroup, Integer blueprintID, EGroupID hold_egroupid, Integer numsquads, MarkerID/Pos exitpos[, Bool overload[, Integer loadout]]
--? @result SGroupID
function Util_CreateSquadsAndGarrisonExit(playerid, sgroupid, blueprintID, hold_egroupid, numsquads, exitpos, overload, loadout)

	-- fill in default overload value if it was skipped
	if overload == nil then
		overload = false
	end
	
	-- fill in default loadout value if it was skipped
	if loadout == nil then
		loadout = 0
	end
	
	-- if we passed in a marker rather than a pos, then convert it now
	if (scartype(exitpos) == ST_MARKER) then
		exitpos = Marker_GetPosition(exitpos)
	end

	-- do some type checking
	if (scartype(playerid) ~= ST_PLAYER) then fatal("Util_CreateSquadsAndGarrisonExit: PlayerID is invalid") end
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Util_CreateSquadsAndGarrisonExit: SGroupID is invalid") end
	if (scartype(blueprintID) ~= ST_NUMBER) then fatal("Util_CreateSquadsAndGarrisonExit: Blueprint is invalid") end
	if (scartype(hold_egroupid) ~= ST_EGROUP) then fatal("Util_CreateSquadsAndGarrisonExit: Hold EGroupID is invalid") end
	if (scartype(numsquads) ~= ST_NUMBER) then fatal("Util_CreateSquadsAndGarrisonExit: Number of squads is invalid") end
	if (scartype(exitpos) ~= ST_SCARPOS) then fatal("Util_CreateSquadsAndGarrisonExit: Exit Pos/MarkerID is invalid") end
	if (scartype(overload) ~= ST_BOOLEAN) then fatal("Util_CreateSquadsAndGarrisonExit: Overload is invalid") end
	if (scartype(loadout) ~= ST_NUMBER) then fatal("Util_CreateSquadsAndGarrisonExit: Loadout is invalid") end

	-- create and garrison first
	Util_CreateSquadsAndGarrison( playerid, sgroupid, blueprintID, hold_egroupid, numsquads, overload, loadout )
	
	-- ask the squad to unload (queued after instant load command inside Util_CreateSquadsAndGarrison)
	Cmd_SquadUnload( sgroupid, exitpos, true )

	
end



--? @shortdesc If only one team is alive, players on that team win.  Returns true if one team left.
--? @args String win_condition
--? @result Boolean
function Util_CheckOneTeamLeft( win_condition )
	local count = World_GetPlayerCount();

	local team_first = -1;
	local player_alive;

	for i = 1, count
	do
		local player = World_GetPlayerAt(i);

		if( Player_IsAlive(player) ) then
			if( team_first == -1 ) then
				player_alive = player;
				team_first = Player_GetTeam( player );
			else
				local team = Player_GetTeam( player );
				if( team ~= team_first ) then
					return false;
				end
			end
		end
	end

	World_SetTeamWin( player_alive, win_condition);
	World_SetGameOver();

	return true;
end

-- TODO: make sure to document how to create the table etc
function Util_NearMkrGoMkr(playerId, tableName, mkr1, mkr2)
	local range = table.getn(tableName)
	for r = 1, range do
		if Prox_AllSquadsNearMarker(tableName[r], mkr1) then
			Cmd_MoveToMarker(tableName[r], mkr2)
		end
	end
end
--For a given player, checks a table of sgroups to see if they are near mkr1, and if they are, sends them to mkr2

-- this function orders the squad to move to a location, but to take its time
function Util_SGroupAmble( sgroup, finalMarker )

	local finalPosition = Marker_GetPosition( Marker_FromName( finalMarker, "basic_marker")  )
	if not finalPosition then
		print("SU_ERROR: finalPosition isnt valid")
		return false
	end
	local currentSquadPosition = SGroup_GetPosition( SGroup_FromName( sgroup ) )
	if not currentSquadPosition then
		print("SU_ERROR: currentSquadPosition isnt valid")
		return false
	end
	local currentDistanceToFinal = World_DistancePointToPoint ( finalPosition, currentSquadPosition )
	local n = 0
	-- we need to pick a new position that is closer to the final position
	while true do
		-- this is our failsafe.  If weve iterated too many times (10 is too many) we exit out of the loop.  Otherwise we risk falling into an infinite
		n = n + 1
		if n > 10 then
			print("SU_ERROR: too many iterations of the loop!")
			return false
		end

		if finalPosition.x < currentSquadPosition.x then
			newX = World_GetRand(finalPosition.x,currentSquadPosition.x)
		else
			newX = World_GetRand(currentSquadPosition.x,finalPosition.x)
		end
		if finalPosition.z < currentSquadPosition.z then
			newZ = World_GetRand(finalPosition.z,currentSquadPosition.z)
		else
			newZ = World_GetRand(currentSquadPosition.z,finalPosition.z)
		end

		local newAmblePosition = World_Pos( newX ,0, newZ )

		if World_DistancePointToPoint( newAmblePosition, currentSquadPosition ) < currentDistanceToFinal then
			return newAmblePosition
		end   -- exit the loop if the condition is true
	end
end


--? @group FOW
--? @shortdesc Reveals an area the size of a given markers proximity at that markers position for a given amount of time. Pass in a duration of 1 for indefinite duration. YOU SHOULD ONLY CALL THIS ONCE FOR EACH AREA. 
--? @extdesc This function will reveal the FOW for ALL alive players. This does not work with markers with rectangular proximity type
--? @args MarkerID marker, Real duration
--? @result Void
function FOW_RevealMarker( markerid, duration )

	-- only work with circular proximity markers
	if ( Marker_GetProximityType( markerid ) ~= PT_Circle ) then
		return;
	end
	
	local markerpos = Marker_GetPosition( markerid )
	local markerprox = Marker_GetProximityRadius( markerid )

	-- reveal the area
	FOW_RevealArea( markerpos.x, markerpos.z, markerprox, duration )

end


--? @group FOW
--? @shortdesc Unreveals an area the size of a given markers proximity at that markers position. YOU SHOULD ONLY CALL THIS ONCE FOR EACH AREA. 
--? @extdesc This does not work with markers with rectangular proximity type
--? @args MarkerID marker
--? @result Void
function FOW_UnRevealMarker( markerid )

	-- only work with circular proximity markers
	if ( Marker_GetProximityType( markerid ) ~= PT_Circle ) then
		return;
	end
	
	local markerpos = Marker_GetPosition( markerid )
	local markerprox = Marker_GetProximityRadius( markerid )

	-- reveal the area
	FOW_UnRevealArea( markerpos.x, markerpos.z, markerprox )

end



--? @group Util

--? @shortdesc Function takes multiple strings and returns a BlueprintTable ( used for SGroup_ContainsBlueprints )
--? @args Args args
--? @result BlueprintTable
--? @extdesc
--? Example:\n
--? \tlocal bpTable = Util_MakeBlueprintTable( "chaos_marine_squad", "chaos_marine_squad", "chaos_squad_bloodthirster" )\n
--? \t--Check if SGroup contains ALL items from the list (two chaos marines and a bloodthirster)\n
--? \tlocal result = SGroup_ContainsBlueprints( "sg_MyGroup", bpTable, true )\n\n
--? \t--Check if an SGroup contains ANY blueprints from the list
--? \tresult = SGroup_ContainsBlueprints( "sg_MyGroup", bpTable, false )\n
function Util_MakeBlueprintTable(...)
	local BlueprintTable = {}

	for i=1, table.getn( arg ) do
		local bp = arg[ i ]

		if( type( bp ) ~= "string" ) then
			fatal( "Arguments for Util_MakeBlueprintTable must be strings." )
		end

		-- make sure blueprint exists

		-- check if bp exists in table already
		local found = false
		for j=1, table.getn( BlueprintTable ) do
			local val = BlueprintTable[ j ]
			if( val.name == bp ) then
				val.count = val.count + 1
				found = true
			end
		end

		-- make new entry if one wasnt found
		if( found == false ) then
			local entry = { name = bp, count = 1 }
			table.insert( BlueprintTable, entry )
		end

	end

	-- return the table
	return BlueprintTable

end

--? @shortdesc Find all the squads with a given armour type in sourcegroup and add them to destgroup.
--? @result Void
--? @args SGroupID sourcegroup, SGroupID destgroup, String armourtype
--? @extdesc destgroup will be created if it does not already exist.  See Squad_HasArmourType for list of valid armour types.
function Util_GetSquadsByArmourType( sourcegroup, destgroup, armourtype )

	if( sourcegroup == destgroup ) then
		fatal( "Function does not support source and dest groups that are the same!" )
	end

	destgroupID = SGroup_CreateIfNotFound( destgroup )
	srcgroupID = SGroup_FromName( sourcegroup )

	local CheckArmourType = function( groupid, itemindex, itemid )
		if( Squad_HasArmourType( itemid, armourtype ) ) then
			-- add to dest group
			SGroup_Add( destgroupID, itemid )
		end
	end

	SGroup_ForEach( srcgroupID, CheckArmourType )

end

function _GetGroupByBPID( srcID, destID, groupcaller, bpID )
	if( srcID == destID ) then
		fatal( "Function does not support the same source and destination groups")
	end
	
	local CheckBP = function( groupid, itemindex, itemid )
		
		if( groupcaller.GetItemBlueprintID( itemid ) == bpID ) then
			-- blueprint matches, add to dest group
			groupcaller.AddItem( destID, itemid )
		end
	end

	groupcaller.ForEach( srcID, CheckBP )
end


--? @shortdesc Find all the squads with a given blueprint in sourcegroup and add them to destgroup.
--? @result Void
--? @args SGroupID sourcegroup, SGroupID destgroup, Integer bpID
--? @extdesc See also: SGroup_Filter()
function Util_GetSquadsByBP( sourcegroupid, destgroupid, bpID )

	_GetGroupByBPID(
		sourcegroupid,
		destgroupid,
		SGroupCaller,
		bpID
	)
end


--? @shortdesc Find all the entities with a given blueprint in sourcegroup and add them to destgroup.
--? @result Void
--? @args EGroupID sourcegroup, EGroupID destgroup, Integer bpID
--? @extdesc See also: EGroup_Filter()
function Util_GetEntitiesByBP( sourcegroupid, destgroupid, bpID )
	_GetGroupByBPID(
		sourcegroupid,
		destgroupid,
		EGroupCaller,
		bpID
	)
end

--? @shortdesc Returns trailing numbers from a string, if it exists, nil otherwise. E.G. "marker23" would return 23.
--? @result Number
--? @args String val
function Util_GetTrailingNumber( val )

	local trail = nil
	for d in string.gfind( val, "%a+(%d+)" ) do
		trail = d
	end

	return trail

end

--? @shortdesc Play an events file at a given markers location
--? @result void
--? @args String markername, String eventfile
function Util_MarkerFX( markername, eventfile )
	World_FXEvent( eventfile, Marker_GetPosition( Marker_FromName( markername, "basic_marker" ) ) )
end



--? @shortdesc Play an NIS. These have the highest priority, and will interrupt Intel Events and Stingers.
--? @extdesc This function should used instead of Event_Start because it handles priorities.
--? @result Void
--? @args LuaFunction func
function Util_StartNIS( func )
	UI_HideTacticalMap()
	Event_Start( func, 0 ) 			-- high priority
	--print("*** Util_StartNIS is deprecated. Please use Util_PlayOpeningNIS, Util_PlayMidMissionNIS or Util_PlayEndingNIS instead")
--	Game_StartNIS(func, __Private_Util_DoNothing)
end

--? @shortdesc Play an Intel Event. These are medium priority, and will interrupt a Stinger, but not an NIS.
--? @extdesc This function should used instead of Event_Start because it handles priorities.
--? @result Void
--? @args LuaFunction func
function Util_StartIntel( func )
	Event_Start( func, 1 ) 			-- medium priority
--	Game_StartIntel(func, __Private_Util_DoNothing)
end

--? @shortdesc Play a Speech Stinger. These are the lowest priority, and will be bumped by Intel Events or NIS's.
--? @extdesc This function should used instead of Event_Start because it handles priorities.
--? @result Void
--? @args LuaFunction func
function Util_StartStinger( func )
	Event_Start( func, 2 ) 			-- low priotity
--	Game_StartStinger(func, __Private_Util_DoNothing)
end


--[[

 -- WORK IN PROGRESS - DO NOT DELETE
 
--? @shortdesc Plays a mission's opening NIS with appropriate fades, letterboxes, etc. Provides 2 callbacks: OnScreenFaded is called when the screen is faded to black right after the NIS (but still during the event), and OnComplete is called after the event is over.
--? @args String NISname, LuaFunction OnScreenFaded, LuaFunction OnComplete
--? @result Void
function Util_PlayOpeningNIS( NISname, OnScreenFaded, OnComplete )

	g_Util_NIS_Name = NISname
	g_Util_OnScreenFaded = OnScreenFaded
	
	if OnComplete == nil then
		OnComplete = __Private_Util_DoNothing
	end
	
	Game_StartNIS(__Private_Event_PlayOpeningNIS, OnComplete)
	
	-- must start letterbox here to hide the taskbar in time
	Game_Letterbox(true, 1)
	
end

--? @shortdesc Plays a mid mission NIS with appropriate fades, letterboxes, etc. Provides 2 callbacks: OnScreenFaded is called when the screen is faded to black right after the NIS (but still during the event), and OnComplete is called after the event is over.
--? @args String NISname, LuaFunction OnScreenFaded, LuaFunction OnComplete
--? @result Void
function Util_PlayMidMissionNIS( NISname, OnScreenFaded, OnComplete )

	g_Util_NIS_Name = NISname
	g_Util_OnScreenFaded = OnScreenFaded
	
	if OnComplete == nil then
		OnComplete = __Private_Util_DoNothing
	end
	
	Game_StartNIS(__Private_Event_PlayMidMissionNIS, OnComplete)

end

--? @shortdesc Plays a mission's closing NIS with appropriate fades, letterboxes, etc. Provides 1 callback: OnComplete is called after the event is over (this would be where to set the winner)
--? @args String NISname, LuaFunction OnComplete
--? @result Void
function Util_PlayClosingNIS( NISname, OnComplete )

	g_Util_NIS_Name = NISname
	
	if OnComplete == nil then
		OnComplete = __Private_Util_DoNothing
	end
	
	Game_StartNIS(__Private_Event_PlayClosingNIS, OnComplete)
	
end

function __Private_Event_PlayOpeningNIS()

	-- instant fade to black
	--Game_FadeToBlack(FADE_OUT, 0)
	FOW_Enable(false)
	
	-- since the Event only starts 1 frame into the game, letterboxing here is not early enough (you see the taskbar for 1 frame).
	-- to fix that, letterbox is enabled before the event starts.
	-- this event still disables letterbox at the end though.
	--CTRL.Game_Letterbox(true, 1)
	CTRL.Scar_PlayNIS(g_Util_NIS_Name)
	CTRL.WAIT()
	
	FOW_Enable(true)
	
	-- fade to black to give the script (via callback) a chance to set things up while the player can't see
	if g_Util_OnScreenFaded ~= nil then
		g_Util_OnScreenFaded()
	end
	
	-- back to normal gameplay
	--CTRL.Game_FadeToBlack(FADE_IN, 1)
	CTRL.Game_Letterbox(false, 1)
	CTRL.WAIT()
	
end

function __Private_Event_PlayMidMissionNIS()

	--CTRL.Game_FadeToBlack(FADE_OUT, 1)
	CTRL.Game_Letterbox(true, 1)
	CTRL.WAIT()
	
	FOW_Enable(false)
	
	CTRL.Scar_PlayNIS(g_Util_NIS_Name)
	CTRL.WAIT()
	
	if g_Util_OnScreenFaded ~= nil then
		--CTRL.Game_FadeToBlack(FADE_OUT, 1)
		--CTRL.WAIT()
		g_Util_OnScreenFaded()
	end
	
	-- back to normal gameplay
	FOW_Enable(true)
	--CTRL.Game_FadeToBlack(FADE_IN, 1)
	CTRL.Game_Letterbox(false, 1)
	CTRL.WAIT()
	
end

function __Private_Event_PlayClosingNIS()

	--CTRL.Game_FadeToBlack(FADE_OUT, 1)
	CTRL.Game_Letterbox(true, 1)
	CTRL.WAIT()
	
	FOW_Enable(false)
	
	CTRL.Scar_PlayNIS(g_Util_NIS_Name)
	CTRL.WAIT()
	
	-- mission over trigger?
	
end

]]


--? @shortdesc Returns the ability ID, given its name (full path name to the ability bag).
--? @args String blueprint_name
--? @extdesc Example input would be "abilities/ally_air_strike_ability.lua"
--? @result Int
function Util_GetAbilityID( abilityname )
	return World_GetPropertyBagGroupID( abilityname )
end

--? @shortdesc Returns the upgrade ID, given its name (full path name to the upgrade bag).
--? @args String blueprint_name
--? @extdesc Example input would be "upgrade/allies/item/allies_squad_item_bazooka.lua"
--? @result Int
function Util_GetUpgradeID( abilityname )
	return World_GetPropertyBagGroupID( abilityname )
end

--? @shortdesc Returns the critical bag ID, given its name (full path name to the critical bag).
--? @args String blueprint_name
--? @extdesc Example input would be "critical/soldier_killed.lua"
--? @result Int
function Util_GetCriticalID( criticalname )
	return World_GetPropertyBagGroupID( criticalname )
end

--? @shortdesc Returns the damage bag ID, given its name (full path name to the critical bag).
--? @args String blueprint_name
--? @extdesc The possible names are "damage/damage_green.lua", "damage/damage_yellow.lua" and "damage/damage_red.lua"
--? @result Int
function Util_GetDamageID( criticalname )
	return World_GetPropertyBagGroupID( criticalname )
end

--? @shortdesc Returns the ebp ID, given its name (full path name to the entity blueprint).
--? @args String blueprint_name
--? @extdesc Example input would be "ebps/races/allies/buildings/checkpoint.lua"
--? @result Int
function Util_GetEntityBlueprintID( ebpname )
	return World_GetPropertyBagGroupID( ebpname )
end

--? @shortdesc Returns the sbp ID, given its name (full path name to the squad blueprint).
--? @args String blueprint_name
--? @extdesc Example input would be "sbps/races/allies/soldiers/rifleman_squad.lua"
--? @result Int
function Util_GetSquadBlueprintID( sbpname )
	return World_GetPropertyBagGroupID( sbpname )
end

--? @shortdesc Returns the slot item ID, given its name (full path name to the slot item bag).
--? @args String blueprint_name
--? @extdesc Example input would be "slot_item/allies_grenades.lua"
--? @result Int
function Util_GetSlotItemID( itemname )
	return World_GetPropertyBagGroupID( itemname )
end

--? @shortdesc Returns the camouflage stance ID, given its name (full path name to the stance bag).
--? @args String blueprint_name
--? @extdesc Example input would be "camouflage_stance_type/hold_fire_stance.lua"
--? @result Int
function Util_GetCamouflageStanceID( itemname )
	return World_GetPropertyBagGroupID( itemname )
end

--? @shortdesc Play the mission title fade.
--? @result Void
--? @args LocString title
function Util_MissionTitle( title )
	
	-- NOTE: this function could easily be made to wait for 2 seconds if designers want to wait on it
	local gameTitle = function()
		-- params: loc_str, fade_in_secs, lifetime_secs, fade_out_secs
		CTRL.Game_TextTitleFade( title, .5, 3, 2, "" )
		CTRL.WAIT()
	end
	
	-- we will try it as an Intel Event first
	-- the IE's and NIS's have different sets of priorities
	Util_StartIntel(gameTitle)
end

--? @shortdesc Library function to trigger NIS event under a certain sets of conditions.  NOTE: if checking against a marker DO NOT specify a range.  The range of the marker set in the WorldBuilder will be used.
--? @extdesc triggering event when one of the player's squads come near a particular SGroup, EGroup, Marker, or Position and that squad is onscreen, and that squad is not in combat.
--? @extdesc 6 arguments when using marker, 7 arguments when using position, egroup and sgroup with the additional range value
--? @extdesc non_combat set to TRUE means the squad cannot be in combat if the event is to be triggered.
--? @extdesc onscreen_only set to TRUE means the squad must be onscreen if the event is to be triggered.
--? @result Void
--? @args PlayerID playerid, Marker/EGroup/SGroup/ScarPos position, Int range, LuaFunction func, Boolean non_combat, Boolean onscreen_only, Int onscreen_duration

function Util_TriggerEvent(...) 

	-- grab the arguments
	local playerid = arg[1]
	local marker = nil
	local pos
	local egroupid
	local sgroupid
	local range
	local i
	
	if ( scartype( playerid ) ~= ST_PLAYER ) then fatal("playerid is not a valid player ID") 				end

	if (table.getn(arg) == 6) then
	
		local marker = arg[2]
		
		if ( scartype( marker ) ~= ST_MARKER ) then fatal("MarkerID is invalid") end
		
		marker = pos
		pos = nil
		
		i = 2
		
	elseif (table.getn(arg) == 7) then
		
		local target = arg[2]
		
		-- Check what type of target is passed in
		if (scartype(target) == ST_SCARPOS) then
			pos = target
		elseif (scartype(target) == ST_SGROUP) then
			sgroupid = target
		elseif (scartype(target) == ST_EGROUP) then
			egroupid = target
		else
			fatal("target is neither ScarPos, SGroupID or EGroupID")
		end

		-- Find range
		range = arg[3]
	
		if (scartype(range) ~= ST_NUMBER) then fatal("Range is invalid") end
		
		i = 3
	else
		fatal("Wrong number of arguments - should be 6 if using a marker, 7 if using a position, sgroup or egroup")
	end

	-- get the rest of the parameters
	local event = arg[i+1]
	local non_combat = arg[i+2]
	local onscreen_only = arg[i+3]
	local onscreen_duration = arg[i+4]
	
	-- do final parameter check
	if ( scartype( event ) 				~= ST_FUNCTION ) 	then fatal("event is not a valid funciton") 					end
	if ( scartype( non_combat ) 		~= ST_BOOLEAN ) 	then fatal("non_combat is not a valid boolean flag") 			end
	if ( scartype( onscreen_only ) 		~= ST_BOOLEAN ) 	then fatal("on_screen_only is not a valid boolean flag") 		end
	if ( scartype( onscreen_duration ) 	~= ST_NUMBER ) 		then fatal("on_screen_duration is not a valid boolean flag") 	end
	
	-- onscreen_duration is not used if onscreen_only is not set
	if ( onscreen_only == false ) then
		onscreen_duration = 0
	end

	------------------
	-- the rule to run
	function Rule_TriggerEvent()
		print( onscreen_duration )
		
		local success = false
		
		-- process sgroup target
		if (sgroupid ~= nil) then
		
			if ( SGroup_CountSpawned( sgroupid ) == 0 ) then
				Rule_RemoveMe()
				return
			end
			
			squad = SGroup_GetSpawnedSquadAt( sgroupid, 1 )
			
			if ( Prox_PlayerSquadsInProximityOfSquads( playerid, sgroupid, range, ANY, squad ) and
			     ( onscreen_only == false or Misc_IsSquadOnScreen( SGroup_GetSpawnedSquadAt( sgroupid, 1 ), 1.0 ) ) 
			   ) then
			   
			   success = true
			   
			   -- if the player squads needs to be non-combat, check for this
				if ( non_combat ) then
					local temp_sgroup = SGroup_Create( "temp_sgroup" )
					World_GetSquadsNearPoint( playerid, temp_sgroup, SGroup_GetPosition( sgroupid ), range, OT_Ally )
					
					if ( SGroup_IsUnderAttack( temp_sgroup, ANY, 2.0 ) ) then
						success = false
					end
					
					SGroup_Destroy( temp_sgroup )
				end
			end
		-- process egroup target
		elseif (egroupid ~= nil) then
			if ( Prox_PlayerSquadsInProximityOfEntities( playerid, egroupid, range, ANY ) and
				 ( onscreen_only == false or Misc_IsEntityOnScreen( EGroup_GetSpawnedEntityAt( egroupid, 1 ), 1.0 ) ) 
			   ) then
			   
			   success = true
			   
			   -- if the player squads needs to be non-combat, check for this
				if ( non_combat ) then
					local temp_sgroup = SGroup_Create( "temp_sgroup" )
					World_GetSquadsNearPoint( playerid, temp_sgroup, EGroup_GetPosition( egroupid ), range, OT_Ally )
					
					if ( SGroup_IsUnderAttack( temp_sgroup, ANY, 2.0 ) ) then
						success = false
					end
					
					SGroup_Destroy( temp_sgroup )
				end
			end
		-- process marker target
		elseif (marker ~= nil) then
			if ( Prox_IsPlayerNearMarker( playerid, marker, ANY ) and
				 ( onscreen_only == false or Misc_IsPosOnScreen( pos ) ) 
			   ) then
			   success = true
			   
			   -- if the player squads needs to be non-combat, check for this
				if ( non_combat ) then
					local temp_sgroup = SGroup_Create( "temp_sgroup" )
					World_GetSquadsNearMarker( playerid, temp_sgroup, marker, OT_Ally )
					
					if ( SGroup_IsUnderAttack( temp_sgroup, ANY, 2.0 ) ) then
						success = false
					end
					
					SGroup_Destroy( temp_sgroup )
				end
			end
		-- finally process positional target
		elseif (pos ~= nil) then
			if ( Prox_IsPlayerNearMarker( playerid, pos, range, ANY ) and
				 ( onscreen_only == false or Misc_IsPosOnScreen( pos ) ) 
			   ) then
			   success = true
			   
			   -- if the player squads needs to be non-combat, check for this
				if ( non_combat ) then
					local temp_sgroup = SGroup_Create( "temp_sgroup" )
					World_GetSquadsNearPoint( playerid, temp_sgroup, pos, range, OT_Ally )
					
					if ( SGroup_IsUnderAttack( temp_sgroup, ANY, 2.0 ) ) then
						success = false
					end
					
					SGroup_Destroy( temp_sgroup )
				end
			end
		end
		
		if ( success ) then
						
			-- count down the duration if successful
			onscreen_duration = onscreen_duration - 1
					
			if ( onscreen_duration <= 0 ) then
				Util_StartNIS(event)
			end
		else
			-- failed to keep the target on screen, will quit
			onscreen_duration = 0
		end
			
		--
		if ( onscreen_duration <= 0 ) then
			Rule_RemoveMe()
		end
		
	end
	
	Rule_AddInterval( Rule_TriggerEvent, 1 )
end

--? @shortdesc Try to garrison a loadable building within radius that is closer to the first squad of the sgroup than enemy
--? @extdesc Returns the entity id of the building that the squad try to garrison into. Return nil if nothing is found
--? @extdesc The squad suppression would be resetted before fallling back so that they would not succumb to pinned state
--? @result EntityID
--? @args SGroupID sgroupid, Int radius
function Util_FallBackToGarrisonBuilding( sgroupid, radius )

	-- check if the sgroup is empty or not
	if ( SGroup_CountSpawned( sgroupid ) == 0 ) then
		
		print("*** WARNING in Util_FallBackToGarrisonBuilding: SGroup is empty ***")
		
	else
		-- get the first squad of the sgroup 
		local squad = SGroup_GetSpawnedSquadAt( sgroupid, 1 )
		
		-- get the sgroup owner player
		local player = _ScarUtil_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller )
		
		-- get sgroup centre position
		local centre = SGroup_GetPosition( sgroupid )
		
		-- find all loadable buildings within the radius
		local eg = EGroup_Create( "temp" )
		World_GetEntitiesNearPoint( player, eg, centre, radius, OT_Neutral )
		World_GetEntitiesNearPoint( player, eg, centre, radius, OT_Ally )
		
		--print( "Entities found: "..EGroup_Count( eg ) )
		
		local building = nil
		
		local CheckEntity = function( groupid, itemindex, itemid )
			
			-- skip entities that are not loadable
			if ( Entity_CanLoadSquad( itemid, squad ) == false ) then
				--print( "Entity cannot load squad. Next!" )
				return false
			end
			
			local sg = SGroup_Create( "temp" )
			
			-- find the number of enemies near the building
			local numEnemies = World_GetSquadsNearPoint( player, sg, Entity_GetPosition( itemid ), radius, OT_Enemy )
			
			SGroup_Destroy( "temp" )		
			
			-- If the building doesn't have enemy near it, then the squad is save to load
			if ( numEnemies == 0 ) then
				
				-- print( "No enemy found nearby. Garrisoning..." )
				
				-- save the building EntityID
				building = itemid 
				
				local eg_building = EGroup_Create( "temp2" )
				EGroup_Add( eg_building, itemid )
				
				-- make sure the squad is not pinned right now
				SGroup_SetSuppression( sgroupid, 0 )
				
				-- garrison this building here
				Cmd_Garrison( sgroupid, eg_building, false )
				
				EGroup_Destroy( eg_building )
				
				-- do not continue the for loop
				return true
			end
			
			-- print( "Enemy nearby the building. Next!" )
			
			return false
		end
		
		EGroup_ForEach( eg, CheckEntity )
		
		EGroup_Destroy( eg )
		
		return building
		
	end
	
end



--? @shortdesc Returns a position that is distance metres from point A, headed in the direction of point B.
--? @extdesc You can also pass in a percentage (0.0 to 1.0) instead of a distance in metres.
--? @result Position
--? @args MarkerID/Pos a, MarkerID/Pos b, Real distance
function Util_GetPositionFromAtoB(a, b, distance)

	
	-- if either a or b are markers, convert them to positions
	if (scartype(a) == ST_MARKER) then
		a = Marker_GetPosition(a)
	end
	if (scartype(b) == ST_MARKER) then
		b = Marker_GetPosition(b)
	end
	
	if scartype(a) ~= ST_SCARPOS then fatal("Util_GetPositionFromAtoB: Position A invalid") end
	if scartype(b) ~= ST_SCARPOS then fatal("Util_GetPositionFromAtoB: Position B invalid") end
	if scartype(distance) ~= ST_NUMBER then fatal("Util_GetPositionFromAtoB: No distance specified") end
	
	local distbetweenpoints = World_DistancePointToPoint(a, b)
	
	if (distbetweenpoints > 0.05) then
		
		-- if asked for distance in metres, work out the percentage
		if (distance >= 1) then
			distance = (distance / distbetweenpoints)
		end
		
		local deltax = (b.x - a.x) * distance
		local deltay = (b.y - a.y) * distance
		local deltaz = (b.z - a.z) * distance
		
		return World_Pos((a.x + deltax), (a.y + deltay), (a.z + deltaz))
		
	end
	
	-- failsafe
	print("*** WARNING in Util_GetPositionFromAtoB: Positions A and B are too close together to function properly ***")
	return a
	
end

--? @shortdesc Reloads the running scar script. Current running rules would also be updated to the redefined functioin.
function Util_ReloadScript()

	-- reload scar script and refresh the rule systems
	Scar_ReloadScripts()
	TimeRule_Refresh()
	EventRule_Refresh()

end


--? @shortdesc Play sound on the first entity of the squad in sgroup
--? @result Void
--? @args String soundpathname, SGroupID sgroupid
function Sound_PlayOnSquad( soundpathname, sgroupid)

	if( SGroup_Count( sgroupid ) <= 0 ) then
		return
	end

	local squad = SGroup_GetSpawnedSquadAt( sgroupid, 1 )
	
	if ( Squad_Count( squad ) <= 0 ) then
		return
	end
	
	local entity = Squad_EntityAt( squad, 0 )
	
	Sound_Play3D( soundpathname, entity )
	
end


FADE_OUT = true
FADE_IN = false

--? @shortdesc Fades the screen to black - FADE_OUT to fade to black, FADE_IN to fade back in
--? @result Void
--? @args Bool direction, Real length
function Game_FadeToBlack(direction, length)
	
	if (direction == FADE_OUT) then
		Game_ScreenFade(0, 0, 0, 1, length)
		UI_ScreenFade(0, 0, 0, 1, length, true)
	elseif (direction == FADE_IN) then
		Game_ScreenFade(0, 0, 0, 0, length)
		UI_ScreenFade(0, 0, 0, 0, length, false)
	end

end



--? @group World

--? @shortdesc Kill off a specific player's dead bodies (enter ALL to clean them all up)
--? @result Void
--? @args PlayerID player
function World_CleanUpTheDead(player)

	local _CleanUpPlayerDeadBodies = function (gid, idx, eid)
		if (Entity_IsBuilding(eid) == false) and (Entity_IsAlive(eid) == 0 or Entity_GetHealth(eid) == 0) then
			Entity_Destroy(eid)
		end
	end
	
	if (player == ALL) then
		for n = 1, World_GetPlayerCount() do
			local deadbodiestempgroup = Player_GetEntities(World_GetPlayerAt(n))
			EGroup_ForEach(deadbodiestempgroup, _CleanUpPlayerDeadBodies)
			EGroup_Destroy(deadbodiestempgroup)
		end
	else
		local deadbodiestempgroup = Player_GetEntities(player)
		EGroup_ForEach(deadbodiestempgroup, _CleanUpPlayerDeadBodies)
		EGroup_Destroy(deadbodiestempgroup)
	end
	
end



--? @group Camera

--? @shortdesc Helper function to set the default camera parameters
--? @extdesc If the parameter is nil, the particular property is not modified
--? @result Void
--? @args Float height, Float declination, Float angle
function Camera_SetDefault( height, declination, angle )

	if ( height ~= nil and scartype(height) == ST_NUMBER ) then
		Camera_SetTuningValue( TV_DefaultHeight, height )
	end

	if ( declination ~= nil and scartype(declination) == ST_NUMBER ) then
		Camera_SetTuningValue( TV_DefaultDeclination, declination )
	end

	if ( angle ~= nil and scartype(angle) == ST_NUMBER ) then
		Camera_SetTuningValue( TV_DefaultAngle, (angle ) )
	end

end

--? @group Util

--? @shortdesc Returns a position from entity/marker/pos/egroup/sgroup/squad
--? @args Variable var
--? @result Position
function Util_GetPosition(variable)

	local type = scartype(variable)
	
	if type == ST_ENTITY then
		return Entity_GetPosition(variable)
	elseif type == ST_MARKER then
		return Marker_GetPosition(variable)
	elseif type == ST_SCARPOS then
		return variable
	elseif type == ST_EGROUP then
		return EGroup_GetPosition(variable)
	elseif type == ST_SGROUP then
		return SGroup_GetPosition(variable)
	elseif type == ST_SQUAD then
		return Squad_GetPosition(variable)
	else
		error("Util_GetPosition: unsupported type")
	end
end

--? @shortdesc Returns a formatted localized string.
--? @extdesc Use this function to format localized text. ie %1PLAYERNAME% is going to win.
--? @args Integer FormatID, ...
--? @result LocString
function Loc_FormatText(LocID, ...)
	
	local argc = table.getn(arg)
	
	if argc == 1 then
		return Loc_FormatText1(LocID, arg[1])
	elseif argc == 2 then
		return Loc_FormatText2(LocID, arg[1], arg[2])
	elseif argc == 3 then
		return Loc_FormatText3(LocID, arg[1], arg[2], arg[3])
	elseif argc == 4 then
		return Loc_FormatText4(LocID, arg[1], arg[2], arg[3], arg[4])
	else
		fatal("Loc_FormatText: Too many arguments (" .. argc .. ")")
	end
	
end

--? @shortdesc Kills ALL world entities near a marker
--? @args MarkerID marker
--? @result Void
function World_KillAllNeutralEntitesNearMarker(marker)

	egTemp = EGroup_CreateIfNotFound("egTemp_World_KillNeutralEntitesNearMarker")
	World_GetNeutralEntitiesNearMarker(egTemp, marker)
	
	local _KillEntity = function(gid, idx, eid)
		Entity_Kill(eid)
	end
	
	EGroup_ForEach(egTemp, _KillEntity)

end

--? @shortdesc Returns the closest MarkerID to the entity/marker/pos/egroup/sgroup/squad from the table of markers provided
--? @args Variable var, Table markers
--? @result MarkerID
function Util_GetClosestMarker(var, markers) 

	local dist
	local closest = 99999
	local result

	for i=1, table.getn(markers) do 
		dist = World_DistancePointToPoint(Util_GetPosition(var), Marker_GetPosition(markers[i]))
		if dist < closest then
			closest = dist
			result = markers[i]
		end
	end
	
	return result
end


--? @group UI
--? @shortdesc Returns true if ANY or ALL of the SGroup is selected
--? @args SGroupID sgroup, Boolean all
--? @result Boolean
function Misc_IsSGroupSelected(sgroup, all)

	local _CheckSquad = function(gid, idx, sid)
		return Misc_IsSquadSelected(sid)
	end

	return SGroup_ForEachAllOrAny(sgroup, all, _CheckSquad)
	
end


--? @group UI
--? @shortdesc Returns true if ANY or ALL of the EGroup is selected
--? @args EGroupID egroup, Boolean all
--? @result Boolean
function Misc_IsEGroupSelected(egroup, all)

	local _CheckEntity = function(gid, idx, eid)
		return Misc_IsEntitySelected(eid)
	end

	return EGroup_ForEachAllOrAny(egroup, all, _CheckEntity)
	
end



--? @group SGroup
--? @shortdesc Returns true if ANY or ALL of the squads in an SGroup are carrying some kind of team weapon
--? @args SGroupID sgroup, Boolean all
--? @result Boolean
function SGroup_HasTeamWeapon(sgroup, all)

	local _CheckSquad = function (gid, idx, sid)
		return Squad_HasTeamWeapon(sid)
	end
	
	return SGroup_ForEachAllOrAny(sgroup, all, _CheckSquad)
	
end


--? @group Squad
--? @shortdesc Returns true if the squad is carrying some kind of team weapon
--? @args SquadID squad
--? @result Boolean
function Squad_HasTeamWeapon(squad)

	for n = 1, Squad_Count(squad) do
		if Entity_IsSyncWeapon(Squad_EntityAt(squad, n-1)) then
			return true
		end
	end
	
	return false

end

--? @group Util
--? @shortdesc Ends the single player game (win/lose). You can optionally specify a message that will show up in the gameover dialog box
--? @args Boolean win[, LocString message, Boolean nis]
--? @result Void
function Game_EndSP(win, message, nis)

	-- fail everything you haven't completed
	if __t_Objectives ~= nil then
		for k, v in pairs(__t_Objectives) do
			if Obj_GetState(v.ID) == OS_Incomplete then
				Objective_Fail(v, false)
			end
		end
	end

	-- message that appears at the top of the
	-- game summary window
	if message == nil then
		message = 0
	end
	-- defaults to 'win'
	if win == nil then
		win = true
	end
	-- win/lose title
	local title = 0 
	if win == true then
		title = 39257
	else
		title = 39258
	end
	-- local NIS
	local GameOverNIS = function()
		
		-- rotate around current position
		Camera_AutoRotate(Camera_GetTargetPos(), 35, 43, 1)
		Game_SetMode(UI_Fullscreen)
		UI_ToggleDecorators()
		Misc_SetDefaultCommandsEnabled(false)
		Misc_SetSelectionInputEnabled(false)
		
		-- Fade out speech / sfx as win screen comes up
		Sound_SetVolume("Speech", 0.0, 2)
		Sound_SetVolume("SFXmaster", 0.0, 2)
		
		-- Mission failed
--		CTRL.Util_MissionTitle(title)
		CTRL.Game_TextTitleFade( title, .5, 3, 2, "" )
		CTRL.WAIT()
		
		UI_SetGameOverMessage(message)
		World_EndSP(win)
		World_SetGameOver()
		
	end
	
	-- play NIS
	if nis == true then
		Event_Start(GameOverNIS, 0)
		
	-- don't play the NIS, just end it
	else
		
		Game_SetMode(UI_Fullscreen)
		UI_SetGameOverMessage(message)
		World_EndSP(win)
		World_SetGameOver()
		
	end
	
end

--? @group Util
--? @shortdesc If there's a squad under the mouse cursor, this adds it to a unique SGroup. Return value is the SGroup it was added to, or nil if there was no squad under the mouse cursor. Pass in true for 'clearGroup' to clear the sgroup before adding the squad.
--? @args [Boolean clearGroup]
--? @result SGroupID
function Util_AddMouseoverSquadToSGroup(clearGroup)

	local sg = Util_GetMouseoverSGroup()
		
	if (Misc_IsMouseOverEntity()) then
		
		local entity = Misc_GetMouseOverEntity()
		if (Entity_IsPartOfSquad( entity )) then
			
			local squad = Entity_GetSquad(entity)
			if clearGroup == true then
				SGroup_Clear(sg)
			end
			SGroup_Add(sg, squad)
			return sg
			
		end
	end
	
	-- is this redundant in Lua?
	return nil
	
end

--? @group Util
--? @shortdesc Returns a unique SGroup used to hold mouseover squads obtained from Util_AddMouseoverSquadToSGroup
--? @result Void
function Util_GetMouseoverSGroup()
	if sg_mouseover == nil then
		sg_mouseover = SGroup_Create("sg_mouseover")
	end
	
	return sg_mouseover
end

function Player_KillEnemyOwnedUnits(player)

	for i = 1, World_GetPlayerCount() do
		
		local p = World_GetPlayerAt(i)
		if Player_GetRelationship(player, p) == R_ENEMY then
		
			-- delete entities that are not starting positions
			local todelete = {}
			
			local eg = Player_GetEntities(player)
			for i = 1, EGroup_CountSpawned(eg) do
				local ent = EGroup_GetSpawnedEntityAt(eg, i)
				if not Entity_IsStartingPosition(ent) then
					table.insert(todelete, Entity_GetGameID(ent))
				end
			end
			
			for i = 1, table.getn(todelete) do
				local ent = Entity_FromWorldID(todelete[i])
				Entity_Destroy(ent)
			end
			
			local sg = Player_GetSquads(player)
			SGroup_DestroyAllSquads(sg)
			
		end
		
	end
	
end



--? @group Util
--? @shortdesc Hides all of a player's squads and/or buildings
--? @extdesc Doesn't hide buildings a player is in, or any base structures. Only items like sandbags, tanktraps, mg nests, etc. Put multiple playerIDs in a table to hide many players together, or use ALL for the playerID to apply to all players at once.
--? @args PlayerID player, Bool hide
--? @result Void
function Util_HidePlayerForNIS(player, hide)

	if scartype(player) == ST_PLAYER then
		
		player = {player}
		
	elseif player == ALL then
		
		player = {}
		for n = 1, World_GetPlayerCount() do
			table.insert(player, World_GetPlayerAt(n))
		end
		
	end
	
	for n = 1, table.getn(player) do
		
		local filterlist = {EBP.ALLIED_BARBEDWIRE, EBP.ALLIED_BARBEDWIRE_INF, EBP.ALLIED_SANDBAG, EBP.ALLIED_SANDBAG_RIFLEMEN, EBP.ALLIED_MINES, EBP.ALLIED_MINES_INF, EBP.ALLIED_TANKTRAP, EBP.ALLIED_TANKTRAP_RIFLEMEN, EBP.ALLIED_MGPOST, EBP.ALLIED_MEDICSTATION, EBP.AXIS_BARBEDWIRE, EBP.AXIS_SANDBAG, EBP.AXIS_TANKTRAP, EBP.AXIS_BUNKER}
		_sg_hideplayerfornis = SGroup_CreateIfNotFound("_sg_hideplayerfornis")
		_eg_hideplayerfornis = EGroup_CreateIfNotFound("_eg_hideplayerfornis")
		
		Player_GetAll(player[n], _sg_hideplayerfornis, _eg_hideplayerfornis)
		EGroup_Filter(_eg_hideplayerfornis, filterlist, FILTER_KEEP)
		SGroup_Hide(_sg_hideplayerfornis, hide)
		EGroup_Hide(_eg_hideplayerfornis, hide)
		
	end
	
end


--? @group Util
--? @shortdesc ReSpawns or DeSpawns sgroups (and egroups) for all players or the indicated player.
--? @args Boolean despawn, Boolean allPlayers [or Int playerNum], Boolean egroups
--? @result Void
--? @extdesc examples: Util_DespawnAll(true, true, false) or Util_DespawnAll(true, 1, false) or Util_DespawnAll(false, true, false)
function Util_DespawnAll(boolean, everybody, everything)
	local SGspawnFunction = function(sgroup)
		if boolean == true then
			SGroup_DeSpawn(sgroup)
		else
			SGroup_ReSpawn(sgroup)
		end
	end
	local EGspawnFunction = function(egroup)
		if boolean == true then
			EGroup_DeSpawn(egroup)
		else
			EGroup_ReSpawn(egroup)
		end
	end
	
	if everybody == true then
		for i = 1, World_GetPlayerCount() do 
			Player_GetAll(World_GetPlayerAt(i))
			SGspawnFunction(sg_allsquads)
			if everything == true then
				EGspawnFunction(eg_allentities)
			end
		end
	elseif everybody ~= nil and everybody <= World_GetPlayerCount() then
		Player_GetAll(World_GetPlayerAt(everybody))
		SGspawnFunction(sg_allsquads)
		if everything == true then
			EGspawnFunction(eg_allentities)
		end
	end

end

--? @group Util
--? @shortdesc Checks the entity count for the world and returns true or false depending on the result. A specific value can be passed in to override the default amount.
--? @args [Integer entityLimit]
--? @result Boolean
function Util_EntityLimit(int)
	if int == nil then
		int = 290
	end
	if Util_UnitCounts(true).entity <= int then
		return true
	else
		return false
	end
end
--[[
Example:
function now()
	local t = Util_UnitCounts(player1)
	print(t.squad)
	print(t.entity)
	print(t.vehicle)
	if t.squad < 50 then
		print("yippee")
	else
		print("darn")
	end
end
]]

--? @group Util
--? @shortdesc Returns a table containing either the total or a specific player's squad count, entity count, and vehicle count.
--? @extdesc table can be accessed as scene below
--? Get Player Example:\n
--? local t = Util_UnitCounts(player1)\n
--? print(t.squad)\n
--? print(t.entity)\n
--? print(t.vehicle)\n
--? Get World Example:\n
--? local t = Util_UnitCounts(true)\n
--? print(t.squad)\n
--? print(t.entity)\n
--? print(t.vehicle)\n
--? Alternate Format:\n
--? print(Util_UnitCounts(true).squad)\n
--? @args Boolean world OR playerID player
--? @result Lua Table
function Util_UnitCounts(countWho)
	
	-- true means return for all players
	if countWho == true then
		
		-- each player
		local playercount = World_GetPlayerCount()
		local total_squadcount = 0
		local total_entitycount_actual = 0
		local total_entitycount_reported = 0 -- count vehicles as 5
		local total_vehiclecount = 0
		
		for i = 1, playercount do
			
			local thisplayer = World_GetPlayerAt(i)
			local thisplayer_name = Player_GetRaceName(thisplayer)
			local thisplayer_squadcount = Player_GetSquadCount(thisplayer)
			local sgroupID = Player_GetSquads(thisplayer)
			
			local thisplayer_entitycount_actual = 0
			local thisplayer_entitycount_reported = 0 -- counts vehicles as 5 entities
			local thisplayer_vehiclecount = 0
			
			local _EachSquad = function(gid, idx, sid)
				-- count how many vehicles in this squad
				local thissquad_vehiclecount = 0
				for i = 1, Squad_Count(sid) do
					local entity = Squad_EntityAt(sid, i - 1)
					if Entity_IsVehicle(entity) then
						thissquad_vehiclecount = thissquad_vehiclecount + 1
					end
				end
				
				thisplayer_entitycount_actual = thisplayer_entitycount_actual + Squad_Count(sid)
				thisplayer_vehiclecount = thisplayer_vehiclecount + thissquad_vehiclecount
			end
			
			SGroup_ForEach(sgroupID, _EachSquad)
			
			thisplayer_entitycount_reported = thisplayer_entitycount_actual
			thisplayer_entitycount_reported = thisplayer_entitycount_reported + (thisplayer_vehiclecount * 4) -- each vehicle adds 4 extra entities
			
			total_squadcount = total_squadcount + thisplayer_squadcount
			total_entitycount_actual = total_entitycount_actual + thisplayer_entitycount_actual
			total_entitycount_reported = total_entitycount_reported + thisplayer_entitycount_reported
			total_vehiclecount = total_vehiclecount + thisplayer_vehiclecount
		end
		
		-- player totals
		local str = "Total: " .. total_squadcount .. " squads, " .. total_entitycount_reported .. " entities"
		if total_vehiclecount > 0 then
			str = str .. " (" .. total_vehiclecount .. " vehicles)"
		end
		print(str)
		return {squad = total_squadcount, entity = total_entitycount_reported, vehicle = total_vehiclecount}
		
		--[[ leftover world-owned entities
		local num_worldentities = World_GetNumEntities() - total_entitycount_actual
		str = "World-owned entities: " .. num_worldentities
		ypos = ypos + 0.02
		dr_text2d("unit_count", xpos, ypos, str, 255, 255, 255)
		]]
		
	elseif countWho ~= nil then
		
		--[[ start the work on just one ]]
			local thisplayer = countWho
			local thisplayer_name = Player_GetRaceName(thisplayer)
			local thisplayer_squadcount = Player_GetSquadCount(thisplayer)
			local sgroupID = Player_GetSquads(thisplayer)
			
			local thisplayer_entitycount_actual = 0
			local thisplayer_entitycount_reported = 0 -- counts vehicles as 5 entities
			local thisplayer_vehiclecount = 0
			
			local _EachSquad = function(gid, idx, sid)
				-- count how many vehicles in this squad
				local thissquad_vehiclecount = 0
				for i = 1, Squad_Count(sid) do
					local entity = Squad_EntityAt(sid, i - 1)
					if Entity_IsVehicle(entity) then
						thissquad_vehiclecount = thissquad_vehiclecount + 1
					end
				end
				
				thisplayer_entitycount_actual = thisplayer_entitycount_actual + Squad_Count(sid)
				thisplayer_vehiclecount = thisplayer_vehiclecount + thissquad_vehiclecount
			end
			
			SGroup_ForEach(sgroupID, _EachSquad)
			
			thisplayer_entitycount_reported = thisplayer_entitycount_actual
			thisplayer_entitycount_reported = thisplayer_entitycount_reported + (thisplayer_vehiclecount * 4) -- each vehicle adds 4 extra entities
			
			local str = thisplayer_name .. ": " .. thisplayer_squadcount .. " squads, " .. thisplayer_entitycount_reported .. " entities"
			if thisplayer_vehiclecount > 0 then
				str = str .. " (" .. thisplayer_vehiclecount .. " vehicles)"
			end
			print(str)
			return {squad = thisplayer_squadcount, entity = thisplayer_entitycount_reported, vehicle = thisplayer_vehiclecount}
			
		--[[ end of just one ]]
	else
		fatal("Util_UnitCounts: 'countWho' is neither 'true' nor a valid playerID")
	end
end

-- Entity Count Util Functions 
function _CountAvg_Track()
	
	local t = Util_UnitCounts(true)
	
	if _avg == nil then
		_avg = t.entity
	else
		_avg = (_avg+t.entity)/2
	end
	
	if _high == nil then
		_high = {}
	end
	
	if t.entity >= 250 then
		table.insert(_high, {count = t.entity, time = World_GetGameTime()})
	end
	
end

-- Entity Count Util Functions 
function _CountAvg_Report()
	-- the average
	print("CountAvg_Report: the Average Unit Count = ".._avg)
	
	-- the highs
	for k,v in pairs(_high) do
		print(k.." - "..v.count.." entities counted at time "..v.time)
	end
	
	print("Report Time:"..World_GetGameTime())
end

--? @shortdesc Clears any vehicle wrecks from a given area
--? @extdesc Area can be a marker (with or without a range override), a position and range combo, or a territory sector ID
--? @args MarkerID/Pos/SectorID position[, Real range]
--? @result Void
function Util_ClearWrecksFromMarker(pos, range)

	eg_clearwrecksgroup = EGroup_CreateIfNotFound("eg_clearwrecksgroup")
	
	-- get all the neutral entitied (different methods for different class types)
	if scartype(pos) == ST_MARKER then
		if range == nil then
			World_GetNeutralEntitiesNearMarker(eg_clearwrecksgroup, pos)
		else
			World_GetNeutralEntitiesNearPoint(eg_clearwrecksgroup, Marker_GetPosition(pos), range)
		end
	elseif scartype(pos) == ST_SCARPOS then
		World_GetNeutralEntitiesNearPoint(eg_clearwrecksgroup, pos, range)
	elseif scartype(pos) == ST_NUMBER then
		World_GetNeutralEntitiesWithinTerritorySector(eg_clearwrecksgroup, pos)
	end
	
	-- filter out anything that isn't a wreck, and destroy all entities that are left
	EGroup_Filter(eg_clearwrecksgroup, EBP.ALLWRECKS, FILTER_KEEP)
	EGroup_DestroyAllEntities(eg_clearwrecksgroup)

end

--? @shortdesc Trigger function immediately after the opening NIS to set starting resources for the player
--? @extdesc Features values for Easy, Normal, and Hard/Expert
--? @args Integer missionNumber
--? @result Void
function Util_SetStartingResources(int)
	
	local mission = {
		--[[M01 Omaha Beach]]
		{{man = 700, mun = 200, fuel = 0},	-- Easy
		{man = 700, mun = 200, fuel = 0}, 	-- Normal
		{man = 700, mun = 200, fuel = 0},},	-- Hard/Expert

		--[[M02 Vierville]]
		{{man = 800, mun = 250, fuel = 0},
		{man = 700, mun = 175, fuel = 0},
		{man = 500, mun = 125, fuel = 0},},

		--[[M03 Carentan]]
		{{man = 900, mun = 700, fuel = 0},
		{man = 900, mun = 700, fuel = 0},
		{man = 900, mun = 700, fuel = 0},},

		--[[M04 Carentan Counterattack]]
		{{man = 1000, mun = 500, fuel = 50},
		{man = 1000, mun = 500, fuel = 50},
		{man = 1000, mun = 500, fuel = 50},},

		--[[M05 Montebourg]]
		{{man = 700, mun = 200, fuel = 0},
		{man = 700, mun = 200, fuel = 0},
		{man = 700, mun = 200, fuel = 0},},

		--[[M06 Cherbourg]]
		{{man = 800, mun = 250, fuel = 0},
		{man = 800, mun = 250, fuel = 0},
		{man = 800, mun = 250, fuel = 0},},

		--[[M07 Sottevast]]
		{{man = 600, mun = 300, fuel = 100},
		{man = 600, mun = 300, fuel = 100},
		{man = 600, mun = 300, fuel = 100},},

		--[[M08 St. Fromond]]
		{{man = 500, mun = 200, fuel = 50},
		{man = 500, mun = 200, fuel = 50},
		{man = 500, mun = 200, fuel = 50},},

		--[[M09 Hill 192]]
		{{man = 800, mun = 300, fuel = 300},
		{man = 800, mun = 300, fuel = 300},
		{man = 800, mun = 300, fuel = 300},},

		--[[M10 St. Lo]]
		{{man = 800, mun = 500, fuel = 120},
		{man = 800, mun = 500, fuel = 120},
		{man = 800, mun = 500, fuel = 120},},

		--[[M11 Hebecrevon (Death of a Division)]]
		{{man = 600, mun = 300, fuel = 200},
		{man = 600, mun = 300, fuel = 200},
		{man = 600, mun = 300, fuel = 200},},

		--[[M12 Mortain]]
		{{man = 950, mun = 500, fuel = 50},
		{man = 950, mun = 500, fuel = 50},
		{man = 950, mun = 500, fuel = 50},},

		--[[M13 Mortain Counterattack]]
		{{man = 420, mun = 100, fuel = 25},
		{man = 420, mun = 100, fuel = 25},
		{man = 420, mun = 100, fuel = 25},},

		--[[M14 Autry (Cornered Tiger)]]
		{{man = 420, mun = 500, fuel = 90},
		{man = 420, mun = 500, fuel = 90},
		{man = 420, mun = 500, fuel = 90},},

		--[[M15 Chambois]]
		{{man = 600, mun = 650, fuel = 250},
		{man = 600, mun = 650, fuel = 250},
		{man = 600, mun = 650, fuel = 250},},
	}
	
	Player_SetResource(player1, RT_Manpower, Util_DifVar(mission[int]).man)
	Player_SetResource(player1, RT_Munition, Util_DifVar(mission[int]).mun)
	Player_SetResource(player1, RT_Fuel, Util_DifVar(mission[int]).fuel)
	
end



--? @shortdesc Plays music from the Data:Sound folder, and stores the music track so it can be resumed after a save/load
--? @extdec The new music will phase out the old one. There can only be one music playing at anytime besides the transition fade is the time to fade in the music; delay is the time in seconds to wait until the new music is started. Example: Sound_PlayMusic( "Music/GreatMusic", 0.0, 0.0 )
--? @args String name, Real fade, Real delay
--? @result Void
function Util_PlayMusic(name, fade, delay)

	Sound_PlayMusic(name, fade, delay)
	_current_music_filename = name	

end


--? @shortdesc Resumes playing the music track that was last triggered i.e. after a save/load
--? @args Void
--? @result Void
function Util_RestoreMusic()
	
	if _current_music_filename ~= nil then
		Sound_PlayMusic(_current_music_filename, 0, 0)
	end
	
end

--? @shortdesc Enabling this function will mute the ambient sound (NOT all sound).
--? @args Boolean enable, [Real fade]
--? @result Void
function Util_MuteAmbientSound(enable, fade_time)

	if enable == false then
		
		if fade_time == nil then
			fade_time = 1.5
		end
	
		Sound_SetVolumeDefault("Speech", fade_time)
		Sound_SetVolumeDefault("SFXmaster", fade_time)
	else
	
		if fade_time == nil then
			fade_time = 0
		end
	
		Sound_SetVolume("Speech", 0.0, fade_time)
		Sound_SetVolume("SFXmaster", 0.0, fade_time)
	end

end
