----------------------------------------------------------------------------------------------------------------
-- Command helper functions
-- (c) 2003 Relic Entertainment Inc.

import("GroupCallers.scar")

-- Commands

--+ Cmd_MoveToMarker
--+ Cmd_MoveToEGroup
--+ Cmd_MoveToSGroup
--+ Cmd_AttackSGroup
--+ Cmd_AttackEntiyGroup
-- Cmd_Reinforce AtMarker? ask chris

-- Cmd_IsCaptured -- applies to entities and squads maybe?
-- Cmd_IsPlayerBuilding
-- Cmd_IsUnderAttack -- applies to entities and squads
-- Cmd_IsPlayerUnderAttack

----------------------------------------------------------------------------------------------------------------
-- Private implementation
_Cmd_Private = {
	-- 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 )
				
		return player
	end,
	
	-- checks if ALL or ANY items in a group have the active command
	IsCommandActive = function( group, groupcaller, command, all )		
		local count = groupcaller.GetCount( group )
		
		-- ignore empty group
		if( count <= 0 ) then return false end
		
		local IsItemCommandActive = function( groupid, itemindex, itemid )
			return ( command == groupcaller.GetItemActiveCommand( itemid ) )
		end
		
		local result = groupcaller.ForEachAllOrAnyEx( group, all, IsItemCommandActive, true, true )
		return result
	end,
}

--? @group Command

----------------------------------------------------------------------------------------------------------------
-- Commands

----------------------------------------------------------------------------------------------------------------------
-- MOVE COMMANDS
----------------------------------------------------------------------------------------------------------------------


--? @shortdesc Move a squad group to a given markers position.
--? @extdesc The move command will be issued by the player owner of the first item in the group.  This function does nothing if the group is empty.
--? @args SGroupID sgroup, MarkerID marker, [Boolean queued]
--? @result Void
function Cmd_MoveToMarker( sgroupid, markerid, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( SGroup_Count(sgroupid) >= 1 ) then											-- ignore empty group
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_Move,
			Marker_GetPosition ( markerid ),
			queued
		)
	end
	
end

--? @shortdesc Move a squad group to a given position. The sgroup can be deleted when in proximity of a marker if you pass in the marker as the 4th argument.
--? @extdesc The move command will be issued by the player owner of the first item in the group.  This function does nothing if the group is empty.
--? @args SGroupID sgroup, Pos position, [Boolean queued, MarkerID deleteWhenNearMarker]
--? @result Void
function Cmd_MoveToPos( sgroupid, pos, queued, marker )

	if ( queued == nil ) then
		queued = false
	end
	
	if ( SGroup_Count(sgroupid) >= 1 ) then											-- ignore empty group
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_Move,
			Util_GetPosition(pos),
			queued
		)
		
		if marker ~= nil then
			_AddSquadToBeDeletedNearMarker(sgroupid, marker)			
		end
	end
	
end

--? @shordesc Moves squads in group to a location offset by some distance around 'position'.
--? @extdesc The offset is a number from 0 to 7 and indicates a location around 'position' as follows:\n7   0   1\n6  pos  2\n5   4   3
--? @args SGroupID source, ScarPosition position, Integer offset, Float distance[, Boolean queued]
--? @result Void
function Cmd_MoveToPosOffset( sourceid, position, offset, distance, queued )

	if ( queued == nil ) then
		queued = false
	end
	
	if ( SGroup_Count(sourceid) >= 1 ) then	-- ignore empty groups
		
		local heading = World_Pos(0,0,1)
		
		Command_SquadPosOffset(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			SCMD_Move,
			position,
			heading,
			offset,
			distance,
			queued
		)
	end

end

function _AddSquadToBeDeletedNearMarker(sgroupid, marker)
	
	if scartype(marker) ~= ST_MARKER then fatal("_AddSquadToBeDeletedNearMarker: 'marker' is specified, but it's not a marker!") end
	
	if __t_SquadMarkerProxDeleter == nil then
		__t_SquadMarkerProxDeleter = {}
	end
	
	if table.getn(__t_SquadMarkerProxDeleter) == 0 then
		if TimeRule_Exists(Rule_SquadMarkerProxDeleter) then fatal("_AddSquadToBeDeletedNearMarker: Rule should not already exist!") end
		
		Rule_AddInterval(Rule_SquadMarkerProxDeleter, 0.5)
	else
		-- delete existing orders
		--for k, v in __t_SquadMarkerProxDeleter do
		for i = 1, table.getn(__t_SquadMarkerProxDeleter) do
			
			local v = __t_SquadMarkerProxDeleter[i]
			if v.group == sgroupid then
				table.remove(__t_SquadMarkerProxDeleter, i)
				break
			end
			
		end
	end
	
	table.insert(__t_SquadMarkerProxDeleter, {group = sgroupid, marker = marker })
	
end

function Rule_SquadMarkerProxDeleter()

	--for k, v in __t_SquadMarkerProxDeleter do
	for i = table.getn(__t_SquadMarkerProxDeleter), 1, -1 do
		
		local v = __t_SquadMarkerProxDeleter[i]
		local bRemoveEntry = 0
		
		if not SGroup_IsEmpty(v.group) then
			
			if SGroupCaller.IsNearMarker(v.group, v.marker, false) then
				
				bRemoveEntry = 2
				
				-- cya
				SGroup_DestroyAllSquads(v.group)
				
			end
			
		else
			bRemoveEntry = 1
		end
		
		if bRemoveEntry > 0 then
			table.remove(__t_SquadMarkerProxDeleter, i)
		end
		
	end
	
	--print("\t\t" .. table.getn(__t_SquadMarkerProxDeleter))
	if table.getn(__t_SquadMarkerProxDeleter) == 0 then
		Rule_RemoveMe()
	end
	
end

--? @shortdesc Move a squad group to a given markers position only after other previously queued command is done
--? @extdesc The queued move command will be issued by the player owner of the first item in the group.  This function does nothing if the group is empty.
--? @args SGroupID sgroup, MarkerID marker
--? @result Void
function Cmd_MoveToMarkerQueued( sgroupid, markerid )
	
	if ( SGroup_Count(sgroupid) >= 1 ) then											-- ignore empty group
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_Move,
			Marker_GetPosition ( markerid ),
			true -- queued command
		)
	end
	
end

--? @shortdesc Move a squad group to the center of an entity group.
--? @extdesc The move command will be issued by the player owner of the first item in the group.  This function does nothing if the group is empty.
--? @args SGroupID sgroup, EGroupID egroup, [Boolean queued]
--? @result Void
function Cmd_MoveToEGroup( sgroupid, egroupid, queued )

	if ( queued == nil ) then
		queued = false
	end

	if ( (SGroup_Count(sgroupid) >= 1) and (EGroup_Count(egroupid) >= 1) ) then		-- ignore empty groups
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_Move,
			EGroup_GetPosition( egroupid ),
			queued
		)
	end
	
end


--? @shortdesc Move a squad group to another squad group.  This function should track the target squad groups position. 
--? @extdesc The source squads will not stop moving until they reach the targeted squad group.
--? @args SGroupID sourcesgroup, SGroupID targetsgroup, [Boolean queued]
--? @result Void
function Cmd_MoveToSGroup( sourceid, targetid, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sourceid) >= 1) and (SGroup_Count(targetid) >= 1) ) then	-- ignore empty groups
		Command_SquadSquad(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			SCMD_Move,
			targetid,
			queued
		)
	end

end

--? @shortdesc Move a squad group to cover that's close a given position. If cover cannot be found, it just moves to the target position.
--? @extdesc The move command will be issued by the player owner of the first item in the group.  This function does nothing if the group is empty.
--? @args SGroupID sgroup, Pos position, Float searchRadius[, Boolean queued]
--? @result Void
function Cmd_MoveToCover( sourceid, pos, searchRadius, queued )
	
	if (SGroup_Count(sourceid) >= 1) then	-- ignore empty groups
		
		local coverPos = Squad_FindCover(SGroup_GetSpawnedSquadAt(sourceid, 1), pos, searchRadius)
		Cmd_MoveToPos(sourceid, coverPos, queued)
		
	end
	
end

--? @shordesc Moves squads in group to a location offset by some distance around the targert sgroup.
--? @extdesc The the target SGroup can only have one squad in it. The offset is a number from 0 to 7 and indicates a location around the target sgroup (oriented based on that group's orientation) as follows:\n7   0   1\n6  pos  2\n5   4   3
--? @args SGroupID source, SGroupID target, Integer offset, Float distance[, Boolean queued]
--? @result Void
function Cmd_MoveToSGroupOffset( sourceid, targetid, offset, distance, queued )

	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sourceid) >= 1) and (SGroup_CountSpawned(targetid) >= 1) ) then	-- ignore empty groups
		
		local targetHeading = Squad_GetHeading(SGroup_GetSpawnedSquadAt(targetid, 1))
		local targetPos = SGroup_GetPosition(targetid)
		
		Command_SquadPosOffset(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			SCMD_Move,
			targetPos,
			targetHeading,
			offset,
			distance,
			queued
		)
	end

end

--? @shortdesc Move a squad group out of a position to a certain radius
--? @extdesc All squads in the group will move away from the centre position from its current position
--? @args SGroupID sgroup, Pos position, Int radius, [Boolean queued]
--? @result Void
function Cmd_MoveAwayFromPos( sgroupid, pos, radius, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 	-- ignore empty group
	
		local count = SGroup_CountSpawned( sgroupid )
	
		for i=1, count do
		
			local squad = SGroup_GetSpawnedSquadAt( sgroupid, i )
			
			-- Check how far away the squad if from the position
			local spos = Squad_GetPosition( squad )
			if ( World_DistancePointToPoint( spos, pos ) < radius ) then
		
				-- make sure our temporary sgroup hasn't been created by something else
				if ( SGroup_Exists( "sg_Cmd_MoveAwayFromPos" ) ) then
					fatal( "Cmd_MoveAwayFromPos: Unable to run command because 'sg_Cmd_MoveAwayFromPos' already exists" )
				end
		
				local tempsgroup = SGroup_Create( "tempsgroup" )
				SGroup_Add( tempsgroup, squad )

				-- Get new position to move to
				local vec = World_Pos( spos.x-pos.x, spos.y-pos.y, spos.z-pos.z )
				vec = Vector_Normalize( vec )
				vec.x = vec.x * radius
				vec.y = vec.y * radius
				vec.z = vec.z * radius
				pos.x = pos.x + vec.x
				pos.y = pos.y + vec.y
				pos.z = pos.z + vec.z
				
				Command_SquadPos(
					_Cmd_Private.GetGroupPlayerOwner( tempsgroup, SGroupCaller ),
					sgroupid,
					SCMD_Move,
					pos,
					queued
				)
				
				SGroup_Destroy( tempsgroup )
				
			end

		
		end
			
	end
	
end

--? @shortdesc Send a command to the squad to follow a path. Can wait at each waypoint. The sgroup can be deleted when in proximity of a marker if you pass in the marker as the 7th argument.
--? @args SGroupID sgroup, String pathName, Boolean bFromClosest, Boolean bLoop, Boolean bAttackMove, Float pauseTime[, MarkerID deleteWhenNearMarker]
--? @result Void
function Cmd_SquadPath(sgroup, pathName, bFromClosest, bLoop, bAttackMove, pauseTime, marker)

	if (pauseTime == nil) then
		pauseTime = 0
	end
	
	if (SGroup_Count(sgroup) >= 1) then -- ignore empty groups
		Command_SquadPath(
			_Cmd_Private.GetGroupPlayerOwner( sgroup, SGroupCaller ),
			sgroup,
			pathName,
			bFromClosest,
			bLoop,
			bAttackMove,
			pauseTime
		)
		
		if marker ~= nil then
			_AddSquadToBeDeletedNearMarker(sgroup, marker)
		end
	end

end


-- Squad patrol manager

--? @shortdesc Causes a squad to patrol a marker attacking any enemies that come within its radius. If used on circular markers, the radius must be at least 5. To stop the squad from patrolling the marker, use Cmd_StopSquads.
--? @args SGroupID sgroup, MarkerID marker
--? @result Void
function Cmd_SquadPatrolMarker(sgroup, marker)

	if (scartype(marker) ~= ST_MARKER) then
		fatal("Cmd_SquadPatrolMarker: invalid marker")
	end
	
	if Marker_GetProximityType(marker) == PT_Circle then
		
		if Marker_GetProximityRadius(marker) < 5 then
			fatal("Cmd_SquadPatrolMarker: circular markers must have a radius of at least 5 meters")
		end
		
	elseif Marker_GetProximityType(marker) == PT_Rectangle then
		
		--if Marker_GetNumberAttribute(marker, "ProxRectWidth") < 10 or Marker_GetNumberAttribute(marker, "ProxRectHeight") < 10 then
		--	fatal("Cmd_SquadPatrolMarker: rectangular markers must be at least 10x10")
		--end
	end
	
	if __t_SquadPatrolOrders == nil then __t_SquadPatrolOrders = {} end
	if __NextValidID == nil then __NextValidID = 1 end
	
	local T = {}
	
	T.Player = _Cmd_Private.GetGroupPlayerOwner( sgroup, SGroupCaller )
	T.SGroup = sgroup
	T.Marker = marker
	T.State = 0
	T.Target = SGroup_Create("Cmd_SquadPatrolMarker_" .. __NextValidID) -- a sgroup that target squads get put into (for isolating specific squads)

	__NextValidID = __NextValidID + 1
	
	table.insert(__t_SquadPatrolOrders, T)
	
	if (not TimeRule_Exists(__UpdateSquadPatrolOrders)) then
		Rule_AddInterval(__UpdateSquadPatrolOrders, 2)
	end

end

function __SquadStopPatrollingMarker(sgroup)
	if __t_SquadPatrolOrders ~= nil then
		for k, v in __t_SquadPatrolOrders do
			if (v.SGroup == sgroup) then
				SGroup_Destroy(v.Target)
				__t_SquadPatrolOrders[k] = nil
				if (table.getn(__t_SquadPatrolOrders) == 0) then
					Rule_Remove(__UpdateSquadPatrolOrders)
				end
				return
			end
		end
	end
end

function __UpdateSquadPatrolOrders()

	for k, v in __t_SquadPatrolOrders do
		
		local bRemoveEntry = 0
		
		if not SGroup_IsEmpty(v.SGroup) then
			
			-- 0 patrolling
			-- 1 attacking
			
			if (v.State == 0) then
				
				if ((v.Destination == nil) or (not SGroup_IsAttackMoving(v.SGroup, false))) then
					
					-- find a destination at least 10 meters away. the marker has been validated as having a radius of 5 or more, so this should never cause an infinite loop...
					local startpos = SGroup_GetPosition(v.SGroup)
					local destination = nil
					local tries = 0 -- give up after a while if things go horribly wrong
					repeat
						destination = Marker_GetRandomPosition(v.Marker)
						tries = tries + 1
					until (World_DistancePointToPoint(startpos, destination) >= 10 or tries >= 100)
					
					if (tries >= 100) then
						print("Could not find a destination 10+ meters away")
					end
					
					v.Destination = destination
					--print("Moving to destination (" .. math.floor(v.Destination.x) .. "," .. math.floor(v.Destination.y) .. "," .. math.floor(v.Destination.z) .. ")")
					--Cmd_MoveToPos(v.SGroup, v.Destination)
					Cmd_AttackMovePos(v.SGroup, v.Destination)
					
				end
				
				-- detect enemies within range
				local playerCount = World_GetPlayerCount()
				for i = 1, playerCount do
					
					local player = World_GetPlayerAt(i)
					if (player ~= v.Player and Player_GetRelationship(v.Player, player) == R_ENEMY) then
					
						-- get all of this player's squads near the marker
						local sgTemp = SGroup_Create("__UpdateSquadPatrolOrders_temp")
						Player_GetAllSquadsNearMarker(player, sgTemp, v.Marker)
						
						-- found anyone? then attack them
						if (SGroup_CountSpawned(sgTemp) >= 1) then
							
							SGroup_Clear(v.Target)
							SGroup_Add(v.Target, SGroup_GetSpawnedSquadAt(sgTemp, 1))
							
							v.State = 1
							
							print("Detected enemies within range of the marker")
							Cmd_AttackSGroup(v.SGroup, v.Target)
							
						end
						
						SGroup_Destroy(sgTemp)
					end
				end
				
			elseif (v.State == 1) then
				-- break out of attack state if enemy moves out of marker range or is dead
				if (SGroup_CountSpawned(v.Target) == 0) or (not Prox_AreSquadsNearMarker(v.Target, v.Marker, false)) then
					
					print("Going back to patrolling")
					-- go back to patrolling the marker
					v.State = 0
					v.Destination = nil
					
				end
			end
		else
			bRemoveEntry = 1
		end
		
		if (bRemoveEntry > 0) then
			
			print("Removing squad patrol order (reason: " .. bRemoveEntry .. ")")
			__SquadStopPatrollingMarker(v.SGroup)
			return
			
		end
	end
	
end

----------------------------------------------------------------------------------------------------------------------
-- ATTACK COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Order a squad group to attack another squad group.  This function should track the target squad groups position. 
--? @extdesc The source squads will not stop moving until they reach the targeted squad group.
--? @args SGroupID sourcesgroup, SGroupID targetsgroup, [Boolean queued]
--? @result Void
function Cmd_AttackSGroup( sourceid, targetid, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sourceid) >= 1) and (SGroup_Count(targetid) >= 1) ) then	-- ignore empty groups
		Command_SquadSquadAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			targetid,
			false,		-- Enemy in FOW should be attackable in SCAR
			false, 		-- Not a stationary attack
			"",			-- No attack plan override
			queued
		)
	end
	
end


--? @shortdesc Order a squad group to attack another squad group.  This function should track the target squad groups position. 
--? @extdesc The source squads will not stop moving until they reach the targeted squad group.
--? @args SGroupID sourcesgroup, EGroupID targetegroup, [Boolean queued]
--? @result Void
function Cmd_AttackEGroup( sourceid, targetid, queued )
	
	if ( (SGroup_Count(sourceid) >= 1) and (EGroup_Count(targetid) >= 1) ) then 	-- ignore empty groups
		Command_SquadEntityAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			targetid,
			false, 		-- Should be able to attack squads in FOW in SCAR
			false, 		-- Not a stationary attack
			"",			-- No attack plan override
			false
		)
	end

end




--? @shortdesc Order a squad group to attack another squad group.  This function should track the target squad groups position. 
--? @extdesc The source squads will not stop moving until they reach the targeted squad group.
--? @args SGroupID sourcesgroup, SGroupID targetsgroup, String plan, [Boolean queued]
--? @result Void
function Cmd_AttackSGroupWithPlan( sourceid, targetid, plan, queued )
	
	if ( queued == nil ) then
		queued = false
	end
		
	if ( scartype( plan ) ~= ST_STRING ) then
		fatal( "Cmd_AttackSGroupWithPlan: 3rd parameter (plan) requires actual plan name")
	end

	if ( (SGroup_Count(sourceid) >= 1) and (SGroup_Count(targetid) >= 1) ) then	-- ignore empty groups
		Command_SquadSquadAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			targetid,
			false,			-- Enemy in FOW should be attackable in SCAR
			false,			-- Not a stationary attack
			plan,
			queued
		)
	end
	
end


--? @shortdesc Order a squad group to attack another squad group.  This function should track the target entity groups position. 
--? @extdesc The source squads will not stop moving until they reach the targeted squad group.
--? @args SGroupID sourcesgroup, EGroupID targetegroup, String plan, [Boolean queued]
--? @result Void
function Cmd_AttackEGroupWithPlan( sourceid, targetid, plan, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( scartype( plan ) ~= ST_STRING ) then
		fatal( "Cmd_AttackSGroupWithPlan: 3rd parameter (plan) requires actual plan name")
	end
	
	if ( (SGroup_Count(sourceid) >= 1) and (EGroup_Count(targetid) >= 1) ) then 	-- ignore empty groups
		Command_SquadEntityAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			targetid,
			false, 		-- Should be able to attack squads in FOW in SCAR
			false, 		-- Not a stationary attack
			plan,
			queued
		)
	end

end

--? @shortdesc Order a squad group to attack another squad group without moving.  This function should track the target squad groups position. 
--? @extdesc The source squads will not execute any plan and it will not move to attack.
--? @args SGroupID sourcesgroup, SGroupID targetsgroup, [Boolean queued]
--? @result Void
function Cmd_StationaryAttackSGroup( sourceid, targetid, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sourceid) >= 1) and (SGroup_Count(targetid) >= 1) ) then	-- ignore empty groups
		Command_SquadSquadAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			targetid,
			false,		-- Enemy in FOW should be attackable in SCAR
			true, 		-- Stationary attack
			"",			-- No attack plan override
			queued
		)
	end
	
end


--? @shortdesc Order a squad group to attack another squad group without moving.  This function should track the target squad groups position. 
--? @extdesc The source squads will not execute any plan and it will not move to attack.
--? @args SGroupID sourcesgroup, EGroupID targetegroup, [Boolean queued]
--? @result Void
function Cmd_StationaryAttackEGroup( sourceid, targetid, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sourceid) >= 1) and (EGroup_Count(targetid) >= 1) ) then 	-- ignore empty groups
		Command_SquadEntityAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			targetid,
			false, 		-- Should be able to attack squads in FOW in SCAR
			true, 		-- Stationary attack
			"",			-- No attack plan override
			queued
		)
	end

end

--? @shortdesc Order a squad group to attack a position without moving.  This function should track the target squad groups position. 
--? @extdesc The source squads will not execute any plan and it will not move to attack.
--? @args SGroupID sourcesgroup, MarkerID/Pos target, [Boolean queued]
--? @result Void
function Cmd_StationaryAttackPosition( sourceid, target, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if (scartype(target) == ST_MARKER) then
		target = Marker_GetPosition(target)
	end
	
	if (SGroup_Count(sourceid) >= 1) then 	-- ignore empty groups
		Command_SquadPositionAttack(
			_Cmd_Private.GetGroupPlayerOwner( sourceid, SGroupCaller ),
			sourceid,
			target,
			false, 		-- Should be able to attack squads in FOW in SCAR
			true, 		-- Stationary attack
			"",			-- No attack plan override
			queued
		)
	end

end

----------------------------------------------------------------------------------------------------------------------
-- ATTACK MOVE COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Order a squad group to attack move to a position. 
--? @args SGroupID sgroup, Position targetposition
--? @result Void
function Cmd_AttackMovePos( sgroupid, targetposition )
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_AttackMove,
			targetposition,
			false
		)
	end
end

--? @shortdesc Order a squad group to attack move to a position only after other queued commands are done.
--? @args SGroupID sgroup, Position targetposition
--? @result Void
function Cmd_AttackMovePosQueued( sgroupid, targetposition )
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_AttackMove,
			targetposition,
			true  -- queued command
		)
	end
end

--? @shortdesc Order a squad group to attack move to cover that's close to a position. 
--? @args SGroupID sgroup, Position targetposition, Float searchRadius
--? @result Void
function Cmd_AttackMoveToCover( sgroupid, targetposition, searchRadius )
	if SGroup_Count(sgroupid) >= 1 then
		local coverPos = Squad_FindCover(SGroup_GetSpawnedSquadAt(sgroupid, 1), targetposition, searchRadius)
		Cmd_AttackMovePos(sgroupid, coverPos)
	end
end

--? @shortdesc Order a squad group to attack move to cover that's close to a position, only after other queued commands are done. 
--? @args SGroupID sgroup, Position targetposition, Float searchRadius
--? @result Void
function Cmd_AttackMoveToCoverQueued( sgroupid, targetposition, searchRadius )
	if SGroup_Count(sgroupid) >= 1 then
		local coverPos = Squad_FindCover(SGroup_GetSpawnedSquadAt(sgroupid, 1), targetposition, searchRadius)
		Cmd_AttackMovePosQueued(sgroupid, coverPos)
	end
end

--? @shortdesc Order a squad group to ground move to a marker position. 
--? @args SGroupID sgroup, MarkerID marker
--? @result Void
function Cmd_AttackMoveMarker( sgroupid, markerid )
	Cmd_AttackMovePos( sgroupid, Marker_GetPosition(markerid) )
end

--? @shortdesc Order a squad group to ground move to a marker position only after other queued commands are done. 
--? @args SGroupID sgroup, MarkerID marker
--? @result Void
function Cmd_AttackMoveMarkerQueued( sgroupid, markerid )
	Cmd_AttackMovePosQueued( sgroupid, Marker_GetPosition(markerid) )
end



--? @shortdesc Order a squad group to ground move to a sgroup's position. 
--? @args SGroupID sgroup, SGroupID targetsgroup
--? @result Void
function Cmd_AttackMoveSGroup( sgroupid, targetsgroupid )
	Cmd_AttackMovePos( sgroupid, SGroup_GetPosition(targetsgroupid) )
end

--? @shortdesc Order a squad group to ground move to a sgroup's position only after other queued commands are done. 
--? @args SGroupID sgroup, SGroupID targetsgroup
--? @result Void
function Cmd_AttackMoveSGroupQueued( sgroupid, targetsgroupid )
	Cmd_AttackMovePosQueued( sgroupid, SGroup_GetPosition(targetsgroupid) )
end



--? @shortdesc Order a squad group to ground move to an egroup's position. 
--? @args SGroupID sgroup, EGroupID targetegroup
--? @result Void
function Cmd_AttackMoveEGroup( sgroupid, targetegroupid )
	Cmd_AttackMovePos( sgroupid, EGroup_GetPosition(targetegroupid) )
end

--? @shortdesc Order a squad group to ground move to an egroup's position only after other queued commands are done. 
--? @args SGroupID sgroup, EGroupID targetegroup
--? @result Void
function Cmd_AttackMoveEGroupQueued( sgroupid, targetegroupid )
	Cmd_AttackMovePosQueued( sgroupid, EGroup_GetPosition(targetegroupid) )
end


--? @shortdesc Order a squad group to attack move with custom plan to a position. 
--? @args SGroupID sgroup, Position targetposition, String plan
--? @result Void
function Cmd_AttackMovePosWithPlan( sgroupid, targetposition, plan )

	if ( scartype( plan ) ~= ST_STRING ) then
		fatal( "Cmd_AttackMovePosWithPlan: 3rd parameter (plan) requires actual plan name")
	end
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_SquadAttackMovePos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_AttackMove,
			targetposition,
			plan,
			false
		)
	end
end

--? @shortdesc Order a squad group to attack move with custom plan to a position only after other queued commands are done.
--? @args SGroupID sgroup, Position targetposition, String plan
--? @result Void
function Cmd_AttackMovePosQueuedWithPlan( sgroupid, targetposition, plan )

	if ( scartype( plan ) ~= ST_STRING ) then
		fatal( "Cmd_AttackMovePosQueuedWithPlan: 3rd parameter (plan) requires actual plan name")
	end
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_SquadAttackMovePos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_AttackMove,
			targetposition,
			plan,
			true  -- queued command
		)
	end
end


----------------------------------------------------------------------------------------------------------------------
-- DIG IN COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Order a squad group to dig in the first position and facing the second position
--? @args SGroupID sgroup, Position targetposition, Position facingposition
--? @result Void
function Cmd_DigIn( sgroupid, targetposition, facingposition )
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_SquadPosPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_DigIn,
			targetposition,
			facingposition
		)
	end

end

--? @shortdesc Order a squad group to dig in instantly at the current position and facing
--? @extdesc This order should only be used in the beginning of missions since it skips all the dig in animation. Also, if any of the squad member is on terrain that doesn't allow dig in, the whole squad would refuse to instant dig in.
--? @args SGroupID sgroup
--? @result Void
function Cmd_InstantDigIn( sgroupid )
	if ( SGroup_Count(sgroupid) >= 1 ) then 	-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_InstantDigIn,
			false
		)
	end

end


--? @shortdesc Order a squad group to dig out instantly
--? @args SGroupID sgroup
--? @result Void
function Cmd_InstantDigOut( sgroupid )
	if ( SGroup_Count(sgroupid) >= 1 ) then 	-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_InstantDigOut,
			false
		)
	end

end


--? @shortdesc Order a squad group to dig out if they are already dug in
--? @args SGroupID sgroup
--? @result Void
function Cmd_DigOut( sgroupid )
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_DigOut,
			false
		)
	end

end

----------------------------------------------------------------------------------------------------------------------
-- Retreat COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Order a squad group to retreat to the nearest building
--? @args SGroupID sgroup
--? @result Void
function Cmd_Retreat( sgroupid )
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_Retreat,
			false
		)
	end

end


--? @shortdesc Order a squad group to retreat to the nearest building
--? @args SGroupID sgroup, Position destination
--? @result Void
function Cmd_RetreatToLocation( sgroupid, destination )
	
	if ( SGroup_Count(sgroupid) >= 1 ) then 										-- ignore empty groups
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_Retreat,
			destination,
			false
		)
	end

end


----------------------------------------------------------------------------------------------------------------------
-- LOAD & GARRISON COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Order a squad group to load instantly at an random squad of the second squad group
--? @extdesc This order should only be used in the beginning of missions since it skips all the loading animation.
--? @extdesc overload is a flag that will allow the hold entity to ignore maximum slot check
--? @args SGroupID fromsgroupid, SGroupID tosgroupid, [Bool overload, Bool queued]
--? @result SquadID id of the destination squad
function Cmd_InstantLoad( ... )

	-- grab the arguments
	local fromsgroupid = arg[1]
	local tosgroupid = arg[2]
	local overload = false		-- do not overload by default
	if (table.getn(arg) == 3) then
		overload = arg[3]
	end
	local queued = false		-- do not queue command by default
	if (table.getn(arg) > 3) then
		queued = arg[4]
	end
	
	if ( SGroup_Count(tosgroupid) == 0 ) then
		return
	end

	-- make sure our temporary sgroup hasn't been created by something else
	if ( SGroup_Exists( "tempsgroup" ) ) then
	    fatal( "Cmd_InstantLoad: Unable to run command because tempsgroup already exists" )
	end
	
	-- get one random squad from the target sgroup
	local squad = SGroup_GetRandomSpawnedSquad( tosgroupid )
	if ( squad == 0 ) then
		fatal( "Cmd_InstantLoad: Unable to get random squad from the destination sgroup" )
	end

	local tempsgroup = SGroup_Create( "tempsgroup" )
	SGroup_Add( tempsgroup, squad )
	
	if ( SGroup_Count(fromsgroupid) >= 1 ) then 	-- ignore empty groups
		Command_SquadSquadLoad(
			_Cmd_Private.GetGroupPlayerOwner( fromsgroupid, SGroupCaller ),
			fromsgroupid,
			SCMD_InstantLoad,
			tempsgroup,
			overload,
			queued
		)
	end

	-- remove the temporary sgroup
	SGroup_Destroy( tempsgroup )
	
	return squad
end

--? @shortdesc Order a squad group to load at an random squad of the second squad group
--? @extdesc overload is a flag that will allow the hold entity to ignore maximum slot check
--? @args SGroupID fromsgroupid, SGroupID tosgroupid, [Bool overload, Bool queued]
--? @result SquadID id of the destination squad
function Cmd_Load( ... )

	-- grab the arguments
	local fromsgroupid = arg[1]
	local tosgroupid = arg[2]
	local overload = false		-- do not overload by default
	if (table.getn(arg) > 3) then
		overload = arg[3]
	end
	local queued = false		-- do not queue command by default
	if (table.getn(arg) > 3) then
		queued = arg[4]
	end
	
	if ( SGroup_Count(tosgroupid) == 0 ) then
		return
	end

	-- make sure our temporary sgroup hasn't been created by something else
	if ( SGroup_Exists( "tempsgroup" ) ) then
	    fatal( "Cmd_Load: Unable to run command because tempsgroup already exists" )
	end
	
	-- get one random squad from the target sgroup
	local squad = SGroup_GetRandomSpawnedSquad( tosgroupid )
	if ( squad == 0 ) then
		fatal( "Cmd_Load: Unable to get random squad from the destination sgroup" )
	end

	local tempsgroup = SGroup_Create( "tempsgroup" )
	SGroup_Add( tempsgroup, squad )
	
	if ( SGroup_Count(fromsgroupid) >= 1 ) then 	-- ignore empty groups
		Command_SquadSquadLoad(
			_Cmd_Private.GetGroupPlayerOwner( fromsgroupid, SGroupCaller ),
			fromsgroupid,
			SCMD_Load,
			tempsgroup,
			overload,
			queued
		)
	end

	-- remove the temporary sgroup
	SGroup_Destroy( tempsgroup )
	
	return squad
end

--? @shortdesc Order a squad group to garrison instantly at an random entity (mostly buildings) of the entity group
--? @extdesc This order should only be used in the beginning of missions since it skips all the garrisoning animation.
--? @extdesc overload is a flag that will allow the hold entity to ignore maximum slot check
--? @args SGroupID fromsgroupid, EGroupID toegroupid, [Bool overload, Bool queued]
--? @result EntityID id of the destination entity
function Cmd_InstantGarrison( ... )

	-- grab the arguments
	local fromsgroupid = arg[1]
	local toegroupid = arg[2]
	local overload = false		-- do not overload by default
	if (table.getn(arg) == 3) then
		overload = arg[3]
	end
	local queued = false		-- do not queue command by default
	if (table.getn(arg) > 3) then
		queued = arg[4]
	end
	
	if ( scartype( fromsgroupid ) ~= ST_SGROUP ) then
		fatal( "Cmd_InstantGarrison: 1st parameter (fromsgroupid) requires actual garrisoning squad group.")
	end

	if ( scartype( toegroupid ) ~= ST_EGROUP ) then
		fatal( "Cmd_InstantGarrison: 2nd parameter (toegroupid) requires actual garrison building entity group.")
	end
	
	if ( EGroup_Count(toegroupid) == 0 ) then
		return
	end

	-- make sure our temporary egroup hasn't been created by something else
	if ( EGroup_Exists( "tempegroup" ) ) then
	    fatal( "Cmd_InstantGarrison: Unable to run command because tempegroup already exists" )
	end
	
	-- get one random entity from the target egroup
	local entity = EGroup_GetRandomSpawnedEntity( toegroupid )
	if ( entity == 0 ) then
		fatal( "Cmd_InstantGarrison: Unable to get random entity from the destination egroup" )
	end

	local tempegroup = EGroup_Create( "tempegroup" )
	EGroup_Add( tempegroup, entity )
	
	if ( SGroup_Count(fromsgroupid) >= 1 ) then 	-- ignore empty groups
		Command_SquadEntityLoad(
			_Cmd_Private.GetGroupPlayerOwner( fromsgroupid, SGroupCaller ),
			fromsgroupid,
			SCMD_InstantLoad,
			tempegroup,
			overload,
			queued
		)
	end

	-- remove the temporary sgroup
	EGroup_Destroy( tempegroup )
	
	return entity
end

--? @shortdesc Order a squad group to load at a random entity of the egroup
--? @extdesc overload is a flag that will allow the hold entity to ignore maximum slot check
--? @args SGroupID fromsgroupid, EGroupID toegroupid, [Bool overload, Bool queued]
--? @result EntityID id of the destination entity
function Cmd_Garrison( ... )

	-- grab the arguments
	local fromsgroupid = arg[1]
	local toegroupid = arg[2]
	local overload = false		-- do not overload by default
	if (table.getn(arg) == 3) then
		overload = arg[3]
	end
	local queued = false		-- do not queue command by default
	if (table.getn(arg) > 3) then
		queued = arg[4]
	end
	
	if ( EGroup_Count(toegroupid) == 0 ) then
		return
	end

	-- make sure our temporary egroup hasn't been created by something else
	if ( EGroup_Exists( "tempegroup" ) ) then
	    fatal( "Cmd_Garrison: Unable to run command because tempegroup already exists" )
	end
	
	-- get one random entity from the target egroup
	local entity = EGroup_GetRandomSpawnedEntity( toegroupid )
	if ( entity == 0 ) then
		fatal( "Cmd_Garrison: Unable to get random entity from the destination egroup" )
	end

	local tempegroup = EGroup_Create( "tempegroup" )
	EGroup_Add( tempegroup, entity )
	
	if ( SGroup_Count(fromsgroupid) >= 1 ) then 	-- ignore empty groups
		Command_SquadEntityLoad(
			_Cmd_Private.GetGroupPlayerOwner( fromsgroupid, SGroupCaller ),
			fromsgroupid,
			SCMD_Load,
			tempegroup,
			overload,
			queued
		)
	end

	-- remove the temporary sgroup
	EGroup_Destroy( tempegroup )
	
	return entity
end

--? @shortdesc Order all squads from the building to ungarrison into destinated location
--? @args EGroupID egroupid, Position destination
function Cmd_Ungarrison( egroupid, destination )

	if ( EGroup_Count(egroupid) == 0 ) then
		return
	end
	
--~ 	local entity = EGroup_GetSpawnedEntityAt(egroupid, 1)
--~ 	if World_OwnsEntity(entity) then
--~ 		return
--~ 	end

	local _UngarrisonEntity = function(gid, item, entity)
		
		if World_OwnsEntity(entity) then
			return
		end
		
		EGroup_Add(gid, entity)
		Command_EntityPos(
			Entity_GetPlayerOwner(entity),
			gid,
			CMD_UnloadSquads,
			destination
		)
	end
	
	EGroup_ForEach(egroupid, _UngarrisonEntity)
end

--? @shortdesc Order first squad in the sgroup from the building to ungarrison into destinated location
--? @args EGroupID egroupid, Position destination, SGroupID sgroupid
function Cmd_UngarrisonSquad( egroupid, destination, sgroupid )

	if ( SGroup_CountSpawned(sgroupid) == 0 ) then
		return
	end
	
	if ( EGroup_Count(egroupid) == 0 ) then
		return
	end
	
	local entity = EGroup_GetSpawnedEntityAt(egroupid, 1)
	if World_OwnsEntity(entity) then
		return
	end
		
	Command_EntityPosSquad(
		_Cmd_Private.GetGroupPlayerOwner( egroupid, EGroupCaller ),
		egroupid,
		CMD_UnloadSquads,
		destination,
		sgroupid
	)
end

--? @shortdesc Order all squads from the vehicle in the SGroup to unload when the vehicle reaches the destination
--? @args SGroupID sgroupid, MarkerID/Pos destination
function Cmd_Unload( sgroupid, destination )

	if ( SGroup_Count(sgroupid) == 0 ) then
		return
	end
	
	if (scartype(destination) == ST_MARKER) then
		destination = Marker_GetPosition(destination)
	end
	
	Command_SquadPos(
		_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
		sgroupid,
		SCMD_UnloadSquads,
		destination,
		false
	)
end

--? @shortdesc Order all squads in the SGroup to unload from vehicle or building that they are currently in
--? @extdesc DO NOT confuse this with other unload commands. This only works on the squads inside a hold while the other unload command is issued to BUILDINGS and VEHICLES!!!
--? @args SGroupID sgroupid, MarkerID/Pos destination, [Bool queued]
function Cmd_SquadUnload( sgroupid, destination, queued )

	if ( SGroup_Count(sgroupid) == 0 ) then
		return
	end
	
	if (scartype(destination) == ST_MARKER) then
		destination = Marker_GetPosition(destination)
	end
	
	if ( queued == nil ) then
		queued = false
	end
	
	Command_SquadPos(
		_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
		sgroupid,
		SCMD_Unload,
		destination,
		queued
	)
end

----------------------------------------------------------------------------------------------------------------------
-- MISC COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc apply critical hit to the entities
--? @extdesc Critical id is the critical hit to apply
--? @extdesc Damage id is which damage bucket to put the critical in (green, yellow, or red)
--? @extdesc Player id doesn't have to be the owner as the applied entities but need to make sure the player is still alive
--? @args PlayerID playerid, SGroupID sgroupid, CriticalID criticalid, DamageID damageid
--? @result Void
function Cmd_SGroupCriticalHit( playerid, sgroupid, criticalid, damageid )

	if ( SGroup_Count( sgroupid ) == 0 ) then
		return
	end
	
	Command_PlayerSquadCriticalHit(
		playerid,
		sgroupid,
		PCMD_CriticalHit,
		criticalid, 0, 
		damageid )
end




--? @shortdesc apply critical hit to the entities
--? @extdesc Critical id is the critical hit to apply
--? @extdesc Damage id is which damage bucket to put the critical in (green, yellow, or red)
--? @extdesc Player id doesn't have to be the owner as the applied entities but need to make sure the player is still alive
--? @extdesc Applies critical to every entity in the EGroup
--? @args PlayerID playerid, SGroupID sgroupid, CriticalID criticalid, DamageID damageid
--? @result Void
function Cmd_EGroupCriticalHit( playerid, egroupid, criticalid, damageid )

	local _hitEntity = function(gid, idx, eid)
		Command_PlayerEntityCriticalHit(playerid, eid, PCMD_CriticalHit, criticalid, 0, damageid)
	end
	
	EGroup_ForEach(egroupid, _hitEntity)

end





--? @shortdesc apply critical hit a single entity
--? @extdesc This special function only takes entity id, not egroup because we want to support entities in a squad
--? @extdesc Player id doesn't have to be the owner as the applied entities but need to make sure the player is still alive
--? @args PlayerID playerid, EntityID entityid, CriticalID criticalid, DamageID damageid
--? @result Void
function Cmd_SingleCriticalHit( playerid, entityid, criticalid, damageid )

	Command_PlayerEntityCriticalHit(
		playerid,
		entityid,
		PCMD_CriticalHit,
		criticalid, 0, 
		damageid )
end

--? @shortdesc Sends an camouflage stance command to all squads in a group.  stanceid should be the number returned by Util_GetCamouflageStanceID( stancename )
--? @args SGroupID sgroup, CamouflageStanceID stanceid
--? @result Void
function Cmd_SquadCamouflageStance( sgroupid, stanceid )
		
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
		-- send commands
		Command_SquadExt(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_SetCamouflageStance,
			stanceid,
			0
		)
	end
end

--~ --? @shortdesc Order a squad group set their stance.  Use STANCE_Hold, STANCE_StandGround, STANCE_Burn, STANCE_CeaseFire, or STANCE_Attack.
--~ --? @extdesc The order will be issued by the owner of the squad group.  This function does nothing if the squad group is empty.
--~ --? @args String squadgroupname, Integer stance
--~ --? @result Void
--~ function Cmd_SetStance( sgroupname, stance )
--~ 	local squads = SGroup_FromName( sgroupname )
--~ 	
--~ 	-- ignore empty groups
--~ 	if( SGroup_Count( squads )  <= 0 ) then return end
--~ 	
--~ 	Command_SquadExt(
--~ 		_Cmd_Private.GetGroupPlayerOwner( squads, SGroupCaller ),
--~ 		squads,
--~ 		SCMD_Stance,
--~ 		stance,
--~ 		0
--~ 	)
--~ end

--? @shortdesc Order a squad group set their stance.  Use MSTANCE_Assault, MSTANCE_Ranged, MSTANCE_Versatile.
--? @extdesc The order will be issued by the owner of the squad group.  This function does nothing if the squad group is empty.
--? @args String squadgroupname, Integer stance
--? @result Void
--~ function Cmd_SetMeleeStance( sgroupname, stance )
--~ 	local squads = SGroup_FromName( sgroupname )
--~ 	
--~ 	-- ignore empty groups
--~ 	if( SGroup_Count( squads )  <= 0 ) then return end
--~ 	
--~ 	Command_SquadExt(
--~ 		_Cmd_Private.GetGroupPlayerOwner( squads, SGroupCaller ),
--~ 		squads,
--~ 		SCMD_StanceMelee,
--~ 		stance,
--~ 		0
--~ 	)
--~ end

----------------------------------------------------------------------------------------------------------------------
-- REINFORCEMENT COMMANDS
----------------------------------------------------------------------------------------------------------------------

DO_NOTHING = 0
SPAWN_ATMARKER = 1
SPAWN_ATSQUAD = 2

--? @shortdesc Sends a reinforce command to all squads in a group.  count represents the number of commands to send.
--? @extdesc Note: To reinforce squad bypassing the pre-reqs and costs use Cmd_InstantReinforceUnit.
--? @args SGroupID sgroup, Integer count
--? @result Void
function Cmd_ReinforceUnit( sgroupid, count )
	
	if count == nil then count = 1 end												-- default parameter
	
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
		-- send commands
		for i=1, count do
			Command_Squad(
				_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
				sgroupid,
				SCMD_ReinforceUnit,
				false
			)
		end
	end
end


--? @shortdesc Sends a reinforce command to all squads in a group.  count represents the number of commands to send. spawnlocation is where the reinforced unit will spawn. You can optionally find a hidden position by specifying a checktype (CHECK_OFFCAMERA, CHECK_IN_FOW or CHECK_BOTH), and what to do if a hidden position can't be found (SPAWN_ATMARKER, SPAWN_ATSQUAD, or DO_NOTHING) - SPAWN_ATMARKER is the default.
--? @extdesc To reinforce squads bypassing the pre-reqs and costs use Cmd_InstantReinforceUnit 
--? @args SGroupID sgroup, Integer count, MarkerID/Pos spawnlocation[, Integer checktype[, Integer failtype]]
--? @result Void
function Cmd_ReinforceUnitPos(sgroupid, count, spawnlocation, checktype, fail)
	
	if fail == nil then
		fail = SPAWN_ATMARKER
	end

	if scartype(spawnlocation) == ST_MARKER then										-- if we passed in a marker, turn it into a pos
		spawnlocation = Marker_GetPosition(spawnlocation)
	end

	-- do some type checking
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Cmd_ReinforceUnitPos: SGroupID is invalid") end
	if (scartype(spawnlocation) ~= ST_SCARPOS) then fatal("Cmd_ReinforceUnitPos: Position/MarkerID is invalid") end
	if (scartype(count) ~= ST_NUMBER) then fatal("Cmd_ReinforceUnitPos: Reinforce count is invalid") end
	
	if (SGroup_CountSpawned(sgroupid) >= 1) and (count >= 1) then								-- ignore empty groups
		
		for n = 1, SGroup_CountSpawned(sgroupid) do
			
			local squadid = SGroup_GetSpawnedSquadAt(sgroupid, n)
			local playerid = Squad_GetPlayerOwner(squadid)
			local destination = Squad_GetPosition(squadid)
			local temppos = nil
			local failed = false
			
			sg_reinforcetempgroup = SGroup_CreateIfNotFound("sg_reinforcetempgroup")
			SGroup_Clear(sg_reinforcetempgroup)	
			SGroup_Add(sg_reinforcetempgroup, squadid)
			
			if (checktype ~= nil) then														-- if we want a hidden position, calculate it now
				temppos = World_GetHiddenPositionOnPath(playerid, spawnlocation, destination, checktype)
				if (temppos ~= nil) then													-- only use this position if a hidden pos was found
					spawnlocation = temppos
				else																		-- else use one of the fallback scenarios 
					failed = true
					if (fail == SPAWN_ATMARKER) then										-- (default is SPAWN_ATMARKER)
						spawnlocation = spawnlocation
					elseif (fail == SPAWN_ATSQUAD) then
						spawnlocation = destination
					elseif (fail == DO_NOTHING) then
						count = 0
					end
				end
			end
			
			for i=1, count do																-- repeat command however many times
				Command_SquadPosExt(														
					playerid,
					sg_reinforcetempgroup,
					SCMD_ReinforceUnit,
					spawnlocation,
					0,
					false
				)
			end
			
		end
		
	end
	
end

--? @shortdesc Sends a instant reinforce command to all squads in a group.  count represents the number of commands to send.
--? @extdesc Note: This function bypasses pre-reqs, costs and the production queue
--? @args SGroupID sgroup, Integer count
--? @result Void
function Cmd_InstantReinforceUnit( sgroupid, count )
	
	if count == nil then count = 1 end												-- default parameter
	
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
		-- send commands
		for i=1, count do
			Command_Squad(
				_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
				sgroupid,
				SCMD_InstantReinforceUnit,
				false
			)
		end
	end
end


--? @shortdesc Sends a instant reinforce command to all squads in a group.  count represents the number of commands to send. spawnlocation is where the reinforced unit will spawn. You can optionally find a hidden position by specifying a checktype (CHECK_OFFCAMERA, CHECK_IN_FOW or CHECK_BOTH), and what to do if a hidden position can't be found (SPAWN_ATMARKER, SPAWN_ATSQUAD, or DO_NOTHING) - SPAWN_ATMARKER is the default.
--? @extdesc Note: This function bypasses pre-reqs and costs and the production queue
--? @args SGroupID sgroup, Integer count, MarkerID/Pos spawnlocation[, Integer checktype[, Integer failtype]]
--? @result Void
function Cmd_InstantReinforceUnitPos(sgroupid, count, spawnlocation, checktype, fail)
	
	if fail == nil then
		fail = SPAWN_ATMARKER
	end
	
	if scartype(spawnlocation) == ST_MARKER then										-- if we passed in a marker, turn it into a pos
		spawnlocation = Marker_GetPosition(spawnlocation)
	end
	
	-- do some type checking
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("Cmd_InstantReinforceUnitPos: SGroupID is invalid") end
	if (scartype(spawnlocation) ~= ST_SCARPOS) then fatal("Cmd_InstantReinforceUnitPos: Position/MarkerID is invalid") end
	if (scartype(count) ~= ST_NUMBER) then fatal("Cmd_InstantReinforceUnitPos: Reinforce count is invalid") end

	if (SGroup_CountSpawned(sgroupid) >= 1) and (count >= 1) then								-- ignore empty groups
		
		for n = 1, SGroup_CountSpawned(sgroupid) do
			
			local squadid = SGroup_GetSpawnedSquadAt(sgroupid, n)
			local playerid = Squad_GetPlayerOwner(squadid)
			local destination = Squad_GetPosition(squadid)
			local temppos = nil
			local failed = false
			
			sg_reinforcetempgroup = SGroup_CreateIfNotFound("sg_reinforcetempgroup")
			SGroup_Clear(sg_reinforcetempgroup)	
			SGroup_Add(sg_reinforcetempgroup, squadid)
			
			if (checktype ~= nil) then														-- if we want a hidden position, calculate it now
				temppos = World_GetHiddenPositionOnPath(playerid, spawnlocation, destination, checktype)
				if (temppos ~= nil) then													-- only use this position if a hidden pos was found
					spawnlocation = temppos
				else																		-- else use one of the fallback scenarios 
					failed = true
					if (fail == SPAWN_ATMARKER) then										-- (default is SPAWN_ATMARKER)
						spawnlocation = spawnlocation
					elseif (fail == SPAWN_ATSQUAD) then
						spawnlocation = destination
					elseif (fail == DO_NOTHING) then
						count = 0
					end
				end
			end
			
			for i=1, count do																-- repeat command however many times
				Command_SquadPosExt(														
					playerid,
					sg_reinforcetempgroup,
					SCMD_InstantReinforceUnit,
					spawnlocation,
					0,
					false
				)
			end
			
		end
		
	end
end

----------------------------------------------------------------------------------------------------------------------
-- UPGRADE COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Sends an upgrade command to all squads in a group.  upgradeid should be the number returned by Util_GetUpgradeID( upgradename )
--? @args SGroupID sgroup, UpgradeID upgradeid[, Integer count]
--? @result Void
function Cmd_SquadUpgrade( sgroupid, upgradeid, count )
	
	if (count == nil) then count = 1 end
	
	if( SGroup_Count( sgroupid )  >= 1 ) then	-- ignore empty groups
		-- send commands
		for n = 1, count do
			Command_SquadExt(
				_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
				sgroupid,
				SCMD_Upgrade,
				upgradeid,
				0
			)
		end
	end
end

--? @shortdesc Sends an instant upgrade command to all squads in a group.  upgradeid should be the number returned by Util_GetUpgradeID( upgradename )
--? @args SGroupID sgroup, UpgradeID upgradeid[, Integer count]
--? @result Void
function Cmd_InstantSquadUpgrade( sgroupid, upgradeid, count )
	
	if (count == nil) then count = 1 end
	
	if( SGroup_Count( sgroupid )  >= 1 ) then	-- ignore empty groups
		-- send commands
		for n = 1, count do
			Command_SquadExt(
				_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
				sgroupid,
				SCMD_InstantUpgrade,
				upgradeid,
				0
			)
		end
	end
end

--? @shortdesc Sends an upgrade command to all entities in a group.  upgradeid should be the number returned by Util_GetUpgradeID( upgradename )
--? @args EGroupID egroup, UpgradeID upgradeid[, Integer count]
--? @result Void
function Cmd_EntityUpgrade( egroupid, upgradeid, count )
	
	if (count == nil) then count = 1 end
	
	if( EGroup_Count( egroupid )  >= 1 ) then	-- ignore empty groups
		
		local player = _Cmd_Private.GetGroupPlayerOwner( egroupid, EGroupCaller )
		if ( player == nil ) then
			return
		end
		
		for n = 1, count do
			-- send commands
			Command_EntityExt(
				player,
				egroupid,
				CMD_Upgrade,
				upgradeid,
				0
			)
		end
	end
end

--? @shortdesc Sends an instant upgrade command to all entities in a group.  upgradeid should be the number returned by Util_GetUpgradeID( upgradename )
--? @args EGroupID egroup, UpgradeID upgradeid[, Integer count]
--? @result Void
function Cmd_InstantEntityUpgrade( egroupid, upgradeid, count )

	if (count == nil) then count = 1 end
	
	if( EGroup_Count( egroupid )  >= 1 ) then	-- ignore empty groups
		
		local player = _Cmd_Private.GetGroupPlayerOwner( egroupid, EGroupCaller )
		if ( player == nil ) then
			return
		end
		
		for n = 1, count do
			-- send commands
			Command_EntityExt(
				player,
				egroupid,
				CMD_InstantUpgrade,
				upgradeid,
				0
			)
		end
	end
end

--? @shortdesc Sends an upgrade command to the player. upgradeid should be the number returned by Util_GetUpgradeID( upgradename )
--? @args PlayerID playerid, UpgradeID upgradeid[, Integer count]
--? @result Void
function Cmd_PlayerUpgrade( playerid, upgradeid, count )
	
	if (count == nil) then count = 1 end
		
	-- check for invalid player id
	if( World_GetPlayerIndex( playerid ) == -1) then return end

	-- send commands
	for n = 1, count do
		Command_PlayerExt(
			playerid,
			playerid,
			PCMD_Upgrade,
			upgradeid,
			0
		)
	end
end

--? @shortdesc Sends an instant upgrade command to the player.  upgradeid should be the number returned by Util_GetUpgradeID( upgradename )
--? @args PlayerID playerid, UpgradeID upgradeid[, Integer count]
--? @result Void
function Cmd_InstantPlayerUpgrade( playerid, upgradeid, count )
	
	if (count == nil) then count = 1 end
		
	-- check for invalid player id
	if( World_GetPlayerIndex( playerid ) == -1) then return end

	-- send commands
	for n = 1, count do
		Command_PlayerExt(
			playerid,
			playerid,
			PCMD_InstantUpgrade,
			upgradeid,
			0
		)
	end
end

----------------------------------------------------------------------------------------------------------------------
-- ABILITY COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Sends an ability command without additional target to all squads in a group.  abilityid should be the number returned by Util_GetAbilityID( abilityname )
--? @args SGroupID sgroup, AbilityID abilityid, [Bool skipCostPrereq]
--? @result Void
function Cmd_SquadAbility( sgroupid, abilityid, skipCostPrereq )
		
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
	
		if ( skipCostPrereq == nil ) then
			skipCostPrereq = false
		end
	
		-- send commands
		Command_SquadAbility(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			abilityid,
			skipCostPrereq
		)
	end
end

--? @shortdesc Command squad to do ability on a position.
--? @args SGroupID sgroupid, AbilityID abilityid, Position position, [Bool skipCostPrereq]
--? @result Void
function Cmd_SquadAbilityPos( sgroupid, abilityid, position, skipCostPrereq )
	
	print( "inside Cmd_SquadAbilityPos" )
	
	-- no need to check for spawned sgroup in order for team weapon abilities to work
	-- team weapons are not spawned before set up
	print( "send command from Cmd_SquadAbilityPos" )
	
	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
		-- send commands
		Command_SquadPosAbility(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			position,
			abilityid,
			skipCostPrereq
		)
	end
end

--? @shortdesc Command squad to do ability on an entity.
--? @args SGroupID sgroupid, AbilityID abilityid, EGroupID to_egroupid, [Bool skipCostPrereq]
--? @result Void
function Cmd_SquadAbilityEntity( sgroupid, abilityid, to_egroupid, skipCostPrereq )
	
	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
		
		-- send commands
		Command_SquadEntityAbility(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			to_egroupid,
			abilityid,
			skipCostPrereq
		)
	end
end

--? @shortdesc Command squad to do ability on a squad.
--? @args SGroupID sgroupid, AbilityID abilityid, SGroupID to_sgroupid, [Bool skipCostPrereq]
--? @result Void
function Cmd_SquadAbilitySquad( sgroupid, abilityid, to_sgroupid, skipCostPrereq )
	
	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	if( SGroup_Count( sgroupid )  >= 1 ) then								-- ignore empty groups
		-- send commands
		Command_SquadSquadAbility(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			to_sgroupid,
			abilityid,
			skipCostPrereq
		)
	end
end

----------------------------------------------------------------------------------------------------------------------
-- QUERIES
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Returns true if ALL or ANY squads in a group are currently calling for reinforcments.
--? @extdesc Set all to true to check for ALL or set to false to check for ANY
--? @args SGroupID sgroup, Boolean all
--? @result Boolean
function Cmd_IsReinforcing( sgroupid, all )
	
	local CheckIsReinforcing = function( groupid, itemindex, itemid )
		return Squad_IsReinforcing( itemid )
	end
	
	return SGroup_ForEachAllOrAnyEx( sgroupid, all, CheckIsReinforcing, true, true )
end


--? @shortdesc Returns true if ALL or ANY squads in a group are currently upgrading. You can pass in nil for upgradeid if it doesn't matter what is being upgraded.
--? @extdesc Set all to true to check for ALL or set to false to check for ANY
--? @args SGroupID sgroup, UpgradeID upgradeid, Boolean all
--? @result Boolean
function Cmd_IsUpgrading( sgroupid, upgradeid, all )
	
	if upgradeid == nil then
		upgradeid = -1
	end
	
	local CheckIsUpgrading = function( groupid, itemindex, itemid )
		return Squad_IsUpgrading( itemid, upgradeid )
	end
	
	return SGroup_ForEachAllOrAnyEx( sgroupid, all, CheckIsUpgrading, true, true )
end


--? @shortdesc Returns true if ALL or ANY squads in a group are currently capturing something.
--? @extdesc Set all to true to check for ALL or set to false to check for ANY
--? @args SGroupID sgroup, Boolean all
--? @result Boolean
function Cmd_IsCapturing( sgroupid, all )
	return _Cmd_Private.IsCommandActive(
				sgroupid,
				SGroupCaller,
				SQUADSTATEID_Capture,
				all
			)
end


--? @shortdesc Returns true if ALL or ANY squads in a group are currently constructing a building.
--? @extdesc Set all to true to check for ALL or set to false to check for ANY
--? @args SGroup sgroup, Boolean all
--? @result Boolean
function Cmd_IsConstructingBuilding( sgroupid, all )
	return _Cmd_Private.IsCommandActive(
				sgroupid,
				SGroupCaller,
				SQUADSTATEID_Construction,
				all
			)
end


--? @shortdesc Returns true if ALL or ANY entities in a group are currently producing squads
--? @extdesc Set all to true to check for ALL or set to false to check for ANY
--? @args EGroupID egroup, Boolean all
--? @result Boolean
function Cmd_IsProducingSquads( egroupid, all )

	local IsProducingSquads = function( groupid, itemindex, itemid )
		return Entity_HasProductionQueue(itemid) and (Entity_GetProductionQueueSize(itemid) > 0) 
	end
	
	return EGroup_ForEachAllOrAnyEx( egroupid, all, IsProducingSquads, true, true )
end


--? @shortdesc Attach the squad from sgroupnameAttachee to sgroupname.  Both SGroups must contain only one squad.
--? @args SGroupID sgroup, SGroupID sgroupAttachee
--? @result Void
function Cmd_AttachSquads( sgroupid, sgroupidAttachee )
	
	-- validate squad groups are different
	if( sgroupid == sgroupidAttachee ) then
		fatal("Cannot attach squads from the same group!")
	end
	
	-- validate squad groups only have one squad
	if( SGroup_Count( sgroupid ) ~= 1 or SGroup_Count( sgroupidAttachee ) ~= 1 ) then
		fatal( "SGroups must contain only one squad" )
	end
		
	-- validate squads have same player owner
	if( Player_GetID( Squad_GetPlayerOwner( SGroup_GetSpawnedSquadAt( sgroupid, 1 ) ) ) ~= Player_GetID( Squad_GetPlayerOwner( SGroup_GetSpawnedSquadAt( sgroupidAttachee, 1 ) ) ) ) then
		fatal( "Cannot attach squads belonging to seperate players" )
	end
	
	-- issue order
	Command_SquadSquad( 
		Squad_GetPlayerOwner( SGroup_GetSpawnedSquadAt( sgroupid, 1 ) ),
		sgroupidAttachee, 
		SCMD_Attach, 
		sgroupid,
		false
	)	
	
end

----------------------------------------------------------------------------------------------------------------------
-- STOP COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Command an sgroup to stop.  This is the same as issuing a stop from the taskbar
--? @args SGroupID sgroup
--? @result Void
function Cmd_StopSquads( sgroupid )
	
	-- ignore empty groups
	if( SGroup_CountSpawned( sgroupid ) <= 0) then return end

	Command_Squad(
		Squad_GetPlayerOwner( SGroup_GetSpawnedSquadAt( sgroupid, 1 ) ),
		sgroupid,
		SCMD_Stop,
		false
	)

	__SquadStopPatrollingMarker(sgroupid)
	
end


--? @shortdesc Command an egroup to stop.  This is the same as issuing a stop from the taskbar
--? @args EGroupID egroup
--? @result Void
function Cmd_StopEntities( egroupid )
	
	-- ignore empty groups
	if( EGroup_Count( egroupid ) <= 0) then return end

	Command_Entity(
		Entity_GetPlayerOwner( EGroup_GetSpawnedEntityAt( egroupid, 1 ) ),
		egroupid,
		CMD_Stop
	)
end


--? @shortdesc Moves a squad group to the closest marker in a list/table of MarkerIDs.
--? @args SGroupID sgroup, Table markertable
--? @result markerID
function Cmd_MoveToClosestMarker( sgroupid, markeridtable )
	
	if( SGroup_Count( sgroupid ) == 0 ) then
		fatal( "Cannot issue order on empty group" )
	end
	
	local range = table.getn(markeridtable)
	local distance = {}
	local closest = 9999
	local closestPoint = 0
	local startPoint = 0
	for r = 1, range do
		table.insert(distance, r, World_DistanceSGroupToPoint( sgroupid, Marker_GetPosition(markeridtable[r]), 1) )
	end

	for r = 1, range do
		if distance[r] < closest then
			closest = distance[r]
			closestPoint = r
			startPoint = markeridtable[closestPoint]
--			print("the closest point currently is "..tableName[closestPoint])
		end
	end
--	print("the starting marker is "..startPoint)
	if startPoint == markeridtable[closestPoint] then
		Command_SquadPos( 
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid, 
			SCMD_Move,
			Marker_GetPosition( startPoint ),
			false
		)
		
		return startPoint
	end
end

----------------------------------------------------------------------------------------------------------------------
-- ABILITY COMMANDS
----------------------------------------------------------------------------------------------------------------------

--? @shortdesc Command player to do ability onto itself.
--? @args PlayerID playerid, AbilityID abilityid, [Bool skipCostPrereq]
--? @result Void
function Cmd_PlayerAbility( playerid, abilityid, skipCostPrereq )
	
	-- check for invalid player id
	if( World_GetPlayerIndex( playerid ) == -1) then return end

	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	Command_PlayerAbility(
		playerid,
		playerid,
		abilityid,
		skipCostPrereq
	)

end

--? @shortdesc Command player to do ability on a position.
--? @args PlayerID playerid, AbilityID abilityid, Position position, [Bool skipCostPrereq]
--? @result Void
function Cmd_PlayerAbilityPos( playerid, abilityid, position, skipCostPrereq )
	
	-- check for invalid player id
	if( World_GetPlayerIndex( playerid ) == -1) then return end

	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	Command_PlayerPosAbility(
		playerid,
		playerid,
		position,
		abilityid,
		skipCostPrereq
	)

end

--? @shortdesc Command player to do ability on a position with direction
--? @args PlayerID playerid, AbilityID abilityid, Position position, Position direction, [Bool skipCostPrereq]
--? @result Void
function Cmd_PlayerAbilityPosDir( playerid, abilityid, position, direction, skipCostPrereq )
	
	-- check for invalid player id
	if( World_GetPlayerIndex( playerid ) == -1) then return end

	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	Command_PlayerPosDirAbility(
		playerid,
		playerid,
		position,
		direction,
		abilityid,
		skipCostPrereq
	)

end

--? @shortdesc Command player to do ability on a marker position.
--? @args PlayerID playerid, AbilityID abilityid, MarkerID marker, [Bool skipCostPrereq]
--? @result Void
function Cmd_PlayerAbilityMarker( playerid, abilityid, marker, skipCostPrereq )
	
	-- check for invalid player id
	if( World_GetPlayerIndex( playerid ) == -1) then return end

	if ( skipCostPrereq == nil ) then
		skipCostPrereq = false
	end
	
	Command_PlayerPosAbility(
		playerid,
		playerid,
		Marker_GetPosition(marker),
		abilityid,
		skipCostPrereq
	)

end

----------------------------------------------------------------------------------------------------------------------
-- LIBRARY COMMANDS
----------------------------------------------------------------------------------------------------------------------

_activeAttackerTable = {}

--? @shortdesc Command attacker sgroup to attack move to strategic point target; when it is capturable, the sgroup would capture it
--? @args SGroupID attacker, EGroupID target, [ Boolean queued ]
--? @result Void
function Cmd_AttackMoveThenCapture( attacker, target, queued )

	if ( SGroup_IsEmpty(attacker) ) then
		print("Cmd_AttackMoveThenCapture: 'attacker' is an empty group")
		return
	end
	
	if ( queued == nil ) then
		queued = false
	end
	
	table.insert(_activeAttackerTable, {attacker = attacker, target = target, queued = queued})
	
	if ( queued == false ) then
		Cmd_AttackMoveEGroup(attacker, target)
	else
		Cmd_AttackMoveEGroupQueued(attacker, target)
	end

	Command_SquadEntity(
		_Cmd_Private.GetGroupPlayerOwner( attacker, SGroupCaller ),
		attacker,
		SCMD_Capture,
		target,
		true -- must always be queued, since it's preceded by the move command
	)
	
end

--? @shortdesc Order a squad group to capture team weapon entity group.
--? @args SGroupID sgroupid, EGroupID targetid, [Boolean queued]
function Cmd_CaptureTeamWeapon( sgroupid, targetid, queued )

	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sgroupid) >= 1) and (EGroup_Count(targetid) >= 1) ) then 	-- ignore empty groups
		Command_SquadEntity(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_CaptureTeamWeapon,
			targetid,
			queued
		)
	end

end

--? @shortdesc Order a squad group to abandon their current team weapon if they have it and they could (tuning value in attribute editor)
--? @args SGroupID sgroupid, [Boolean queued]
function Cmd_AbandonTeamWeapon( sgroupid, queued )

	if ( queued == nil ) then
		queued = false
	end
	
	if (SGroup_Count(sgroupid) >= 1) then 	-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_AbandonTeamWeapon,
			queued
		)
	end

end

--? @shortdesc Order a squad group to instant setup their team weapon
--? @args SGroupID sgroupid, [Boolean queued]
function Cmd_InstantSetupTeamWeapon( sgroupid, queued )

	if ( queued == nil ) then
		queued = false
	end
	
	if (SGroup_Count(sgroupid) >= 1) then 	-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_InstantSetupTeamWeapon,
			queued
		)
	end

end

--? @shortdesc Order a squad group to execute default reaction squad AI plan
--? @args SGroupID sgroupid, [ Boolean queued ]
--? @result Void
function Cmd_DoDefaultPlan( sgroupid, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if (SGroup_Count(sgroupid) >= 1) then 	-- ignore empty groups
		Command_Squad(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_DoPlan,
			queued
		)
	end
end

--? @shortdesc Order a squad group to execute default reaction squad AI plan towards a position
--? @args SGroupID sgroupid, Pos pos, [ Boolean queued ]
--? @result Void
function Cmd_DoDefaultPlanTowardsPos( sgroupid, pos, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if (SGroup_Count(sgroupid) >= 1) then 	-- ignore empty groups
		Command_SquadPos(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_DoPlan,
			pos,
			queued
		)
	end
end

--? @shortdesc Order a squad group to execute default reaction squad AI plan
--? @args SGroupID sgroupid, String planfile, [ Boolean queued ]
--? @result Void
function Cmd_DoCustomPlan( sgroupid, planfile, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if (SGroup_Count(sgroupid) >= 1) then 	-- ignore empty groups
		Command_SquadDoCustomPlan(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			planfile,
			queued
		)
	end
end

--? @shortdesc Order a squad group to execute default reaction squad AI plan towards a position
--? @args SGroupID sgroupid, String planfile, Pos pos, [ Boolean queued ]
--? @result Void
function Cmd_DoCustomPlanTowardsPos( sgroupid, planfile, pos, queued )
	
	if ( queued == nil ) then
		queued = false
	end
	
	if (SGroup_Count(sgroupid) >= 1) then 	-- ignore empty groups
		Command_SquadDoCustomPlanTarget(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			pos,
			planfile,
			queued
		)
	end
end

--? @shortdesc Order a squad group to revert occupied building
--? @args SGroupID sgroupid, EGroupID targetid, [Boolean queued]
function Cmd_RevertOccupiedBuilding( sgroupid, targetid, queued )

	if ( queued == nil ) then
		queued = false
	end
	
	if ( (SGroup_Count(sgroupid) >= 1) and (EGroup_Count(targetid) >= 1) ) then 	-- ignore empty groups
		Command_SquadEntity(
			_Cmd_Private.GetGroupPlayerOwner( sgroupid, SGroupCaller ),
			sgroupid,
			SCMD_RevertFieldSupport,
			targetid,
			queued
		)
	end

end
