----------------------------------------------------------------------------------------------------------------
-- SGroup and EGroup helper functions
-- (c) 2003 Relic Entertainment Inc.

import("Squad.scar")
import("Entity.scar")
import("GroupCallers.scar")

-- Types
FILTER_KEEP = 0
FILTER_REMOVE = 1

----------------------------------------------------------------------------------------------------------------
-- Priv

_Groups_Private = {

	__skipsave = true,
	
	-- Create a group and return it if not found.
	CreateGroupIfNotFound = function( groupname, groupcaller )
		if( groupcaller.Exists( groupname ) ) then
			-- found, return existing
			return groupcaller.FromName( groupname )
		end
		
		-- not found, create new
		return groupcaller.Create( groupname )
	end,
	
	-- Returns true if group is empty
	IsGroupEmpty = function( group, groupcaller )
		return ( groupcaller.GetCount( group ) == 0 )
	end,
	
	-- Can ALL or ANY items in a group see ALL or ANY items in an egroup
	CanGroupSeeEGroup = function( group1, groupcaller1, egroup, all )
	
		local CheckCanSeeEGroup = function( groupid, groupindex, itemid )
			
			local CheckCanItemSeeEntity = function( egroupid, egroupindex, entityid )
				return groupcaller1.CanItemSeeEntity( itemid, entityid )
			end
			
			-- can this item see all or any items of an egroup?
			return EGroupCaller.ForEachAllOrAny( egroup, all, CheckCanItemSeeEntity )
			
		end
		
		return groupcaller1.ForEachAllOrAny( group1, all, CheckCanSeeEGroup )
	end,
	
	-- Can ALL or ANY items in a group see ALL or ANY items in an sgroup
	CanGroupSeeSGroup = function( group1, groupcaller1, sgroup, all )
	
		local CheckCanSeeSGroup = function( groupid, groupindex, itemid )
			
			local CheckCanItemSeeSquad = function( sgroupid, sgroupindex, squadid )
				return groupcaller1.CanItemSeeSquad( itemid, squadid )
			end
			
			-- can this item see all or any items of an egroup?
			return SGroupCaller.ForEachAllOrAny( sgroup, all, CheckCanItemSeeSquad )
			
		end
		
		return groupcaller1.ForEachAllOrAny( group1, all, CheckCanSeeSGroup )
	end,
	
	-- Are ALL or ANY items of a group in cover?
	IsGroupInCover = function( groupid, groupcaller, all )
		
		local CheckIsInCover = function( groupid, itemindex, itemid )
			return groupcaller.IsItemInCover( itemid, all )
		end
		
		return  groupcaller.ForEachAllOrAny( groupid, all, CheckIsInCover )
		
	end,
	
	-- Are ALL or ANY items of a group under attack?
	IsGroupUnderAttack = function( groupid, groupcaller, all, duration )
		
		local CheckIsItemUnderAttack = function ( groupid, itemindex, itemid )
			return groupcaller.IsItemUnderAttack( itemid, duration )
		end
		
		return  groupcaller.ForEachAllOrAny( groupid, all, CheckIsItemUnderAttack )
		
	end,
	
	-- Are ALL or ANY items of a group under attack?
	IsGroupAttacking = function( groupid, groupcaller, all, duration )
		
		local CheckIsItemAttacking = function ( groupid, itemindex, itemid )
			return groupcaller.IsItemAttacking( itemid, duration )
		end
		
		return  groupcaller.ForEachAllOrAny( groupid, all, CheckIsItemAttacking )
		
	end,
	
	GetAvgGroupHealth = function( groupid, groupcaller )
		
		local currentHealth = 0
		local maxHealth = 0
		
		local GetItemHealth = function( groupid, itemindex, itemid )
			currentHealth = currentHealth + groupcaller.GetItemHealth( itemid )
			maxHealth = maxHealth + groupcaller.GetItemHealthMax( itemid )
		end
		
		-- check health
		groupcaller.ForEach( groupid, GetItemHealth )
		
		
		local result = 0.0
		
		-- make sure max health is not 0
		if ( maxHealth > 0.0 ) then 
			result = currentHealth / maxHealth
		end
		
		if( (result < 0.0) or (result > 1.0) ) then
			print("this should never happen: " .. result .. " (" .. currentHealth .. " / " .. maxHealth .. ")")
		end
		
		return result
		
	end,
	
	SetAvgGroupHealth = function( groupid, groupcaller, healthPercent )
		
		-- clip health [0.0, 1.0]
		if( healthPercent < 0 ) then healthPercent = 0 end
		if( healthPercent > 1 ) then healthPercent = 1 end
	
		local GetItemHealth = function( groupid, itemindex, itemid )
			groupcaller.SetItemHealth( itemid, healthPercent )
		end
		
		-- set health
		groupcaller.ForEach( groupid, GetItemHealth )
		
	end,
	
	SetPlayerOwner = function( groupid, groupcaller, playerOwner )
	
		local SetOwner = function( groupid, itemindex, itemid )
			groupcaller.SetPlayerOwner( itemid, playerOwner )
		end
		
		groupcaller.ForEachEx( groupid, SetOwner, true, true )
	end,
	
	-- destroys all items in a group
	DestroyAllItems = function( groupid, groupcaller )
	
		local DestroyItem = function( groupid, itemindex, itemid )
			groupcaller.DestroyItem( itemid )
		end
		
		groupcaller.ForEachEx( groupid, DestroyItem, true, true )
		
	end,
	
	-- check if a group contains all or any of the blueprints in a blueprint list
	GroupContainsBlueprints = function( groupid, groupcaller, bpList, all )
			
		local ContainsBlueprints = function( bpname, count )
		
			local found = 0
			
			local HasBlueprints = function( groupid, itemindex, itemid )
				if( groupcaller.GetItemBlueprint( itemid ) == bpname ) then
					found = found + 1
				end
				
				return (found >= count)
			end
			
			-- do any of the items in the list have count bpnames?
			local result =  groupcaller.ForEachAllOrAny( groupid, false, HasBlueprints )
			return result
		end
		
		
		for i=1, table.getn( bpList ) do
			
			-- validate blueprint name
			if( groupcaller.BlueprintExists( bpList[i].name ) == false ) then
				fatal( "Invalid blueprint name "..bpList[i].name )
			end
			
			local hasBlueprints = ContainsBlueprints( bpList[i].name, bpList[i].count )
			
			-- check all condition
			if( all == true and hasBlueprints == false ) then return false end
			
			-- check any condition
			if( all == false and hasBlueprints == true ) then return true end
			
		end
		
		-- conditions not met; return true for all found or false for any not found
		return all
		
	end,
	
	-- check the invulnerability state of ALL or ANY items in a group
	GroupGetInvulnerable = function( groupid, groupcaller, all )
		
		local IsInvulnerable = function( groupid, itemindex, itemid )
			return groupcaller.GetInvulnerable( itemid )
		end
		
		local result = groupcaller.ForEachAllOrAny( groupid, all, IsInvulnerable )
		return result
		
	end,
	
	-- set the invulnerability state of ALL items in a group
	GroupSetInvulnerable = function( groupid, groupcaller, enabled, reset_time )
		
		local SetInvulnerable = function( groupid, itemindex, itemid )
			groupcaller.SetInvulnerable( itemid, enabled, reset_time )
		end
		
		groupcaller.ForEach( groupid, SetInvulnerable )
		
	end,
	
	-- 
	DoGroupSpawn = function ( groupid, groupcaller, spawn )
		local items = {}
		
		local DoItemSpawn = function( groupid, itemindex, itemid )
			table.insert( items, itemid )
		end
		
		-- when spawn is true, we want to iterate over all despawned items
		groupcaller.ForEachEx( groupid, DoItemSpawn, not spawn, spawn )
		
		for i = 1, table.getn( items ) do
			if( spawn ) then
				groupcaller.ReSpawnItem( items[i] )
			else
				groupcaller.DeSpawnItem( items[i] )
			end
		end
		
	end,
	
	-- set selectable state of all items in a group
	GroupSetSelectable = function( groupid, groupcaller, selectable )
		
		local SetSelectable = function( groupid, itemindex, itemid )
			return groupcaller.SetSelectable( itemid, selectable )
		end
		
		groupcaller.ForEach( groupid, SetSelectable )
		
	end,
}


----------------------------------------------------------------------------------------------------------------
-- EGroup

--? @group EGroup

--? @shortdesc Returns true if ALL or ANY entities are in cover.
--? @args EGroupID egroup, Boolean all
--? @result Boolean
function EGroup_IsInCover( egroupid, all )
	return _Groups_Private.IsGroupInCover(
				egroupid,
				EGroupCaller,
				all
			)	
end

--? @shortdesc Returns true if ALL or ANY entities are under attack within the time
--? @args EGroupID egroup, Boolean all, Float time
--? @result Boolean
function EGroup_IsUnderAttack( egroupid, all, duration )
	return _Groups_Private.IsGroupUnderAttack(
				egroupid,
				EGroupCaller,
				all,
				duration
			)	
end

--? @shortdesc Returns true if ALL or ANY entities are attacking within the time
--? @args EGroupID egroup, Boolean all, Float time
--? @result Boolean
function EGroup_IsDoingAttack( egroupid, all, duration )
	return _Groups_Private.IsGroupAttacking(
				egroupid,
				EGroupCaller,
				all,
				duration
			)	
end

--? @shortdesc Returns true if ALL or ANY entities in a group can see ALL or ANY entities in a given egroup.
--? @args EGroupID egroup, EGroupID targetegroup, Boolean all
--? @result Boolean
function EGroup_CanSeeEGroup( egroupid, targetegroupid, all )
	return _Groups_Private.CanGroupSeeEGroup(
				egroupid,
				EGroupCaller,
				targetegroupid,
				all
			)
end

--? @shortdesc Returns true if ALL or ANY entities in a group can see ALL or ANY squads in a given sgroup.
--? @args EGroupID egroup, SGroupID targetsgroup, Boolean all
--? @result Boolean
function EGroup_CanSeeSGroup( egroupid, targetsgroupid, all )
	return _Groups_Private.CanGroupSeeSGroup(
				egroupid,
				EGroupCaller,
				targetsgroupid,
				all
			)
end

--? @shortdesc Find a entity group from name.  Creates a new one with given name if it doesnt exist.
--? @args String egroupname
--? @result EGroupID
function EGroup_CreateIfNotFound( egroupname )
	return _Groups_Private.CreateGroupIfNotFound( egroupname, EGroupCaller )
end

--? @shortdesc Returns true if a named entity group contains no spawned or despawned entities
--? @args EGroupID egroup
--? @result Boolean
function EGroup_IsEmpty( egroupid )
	return _Groups_Private.IsGroupEmpty( egroupid, EGroupCaller )
end

--? @shortdesc Returns true if all or any strategic points in a group have been captured. Use ANY or ALL.
--? @extdesc This function will ignore all entities that cannot be captured and will return false if no entities in the group can be captured.
--? @args EGroupID egroup, PlayerID playerId, Boolean all
--? @result Boolean
function EGroup_IsCapturedByPlayer( egroup, playerid, all )
	local n = EGroup_Count( egroup )
	
	local CheckCapturedByPlayer = function ( groupid, itemindex, itemid )
		if( World_OwnsEntity( itemid ) == false ) then
		
			if( Entity_IsStrategicPoint( itemid ) ) then
				return Player_GetID( Entity_GetPlayerOwner( itemid ) ) == Player_GetID( playerid )
			end
			
		end
		
		return false
	end
	
	local result = EGroup_ForEachAllOrAny( egroup, all, CheckCapturedByPlayer )
	
	return result
end

--? @shortdesc Returns the average health of all units in a entity group as a weighted percent [0.0, 1.0].
--? @extdesc Example: A group of entities with health 20/100 and 100/1000 would return 120/1100 ( approx 11 % )
--? @args EGroupID egroup
--? @result Real
function EGroup_GetAvgHealth( egroupid )
	return _Groups_Private.GetAvgGroupHealth(
				egroupid,
				EGroupCaller
			)
end

--? @shortdesc Sets the health of each unit in an entity group to a given percent [0.0, 1.0].
--? @args EGroupID egroup, Real healthPercent
--? @result Real
function EGroup_SetAvgHealth( egroupid, healthPercent )
	
	return _Groups_Private.SetAvgGroupHealth(
					egroupid,
					EGroupCaller,
					healthPercent
				)
	
end

--? @shortdesc Changes the player owner for all spawned and despawned entities of an EGroup.
--? @extdesc Strategic/capturable point does not support setting player owner directly
--? @args EGroup egroup, PlayerID owner
--? @result Void
function EGroup_SetPlayerOwner( egroupid, owner )
	
	_Groups_Private.SetPlayerOwner(
			egroupid,
			EGroupCaller,
			owner
	)
	
end

--? @shortdesc Destroys all spawned and despawned entities in a group.
--? @extdesc 
--? Be careful not to confuse this with EGroup_Destroy which destroys the group and NOT the items it contains.  This function will destroy
--? spawned and despawned items in a group
--? @args EGroupID egroup
--? @result Void
function EGroup_DestroyAllEntities( egroupid )
	
	return _Groups_Private.DestroyAllItems(
					egroupid,
					EGroupCaller
				)
	
end

--? @shortdesc Check if a group contains ALL or ANY of the items in a blueprint table.
--? @args EGroupID egroup, BlueprintTable blueprints, Boolean all
--? @result Boolean
--? @extdesc See SGroup_ContainsBlueprints for more info.
function EGroup_ContainsBlueprints( egroupid, blueprintTable, all )

	if type(blueprintTable) ~= "table" then
		fatal("EGroup_ContainsBlueprints: must pass in a table.")
	end
	
	return _Groups_Private.GroupContainsBlueprints(
					egroupid,
					EGroupCaller,
					blueprintTable,
					all
				)
	
end

--? @shortdesc Enable/Disable invulnerablity for an entire entity group. Use true and false for simple on/off, or use a number between 0.0 and 1.0 for more precise control on how much damage an entity can take before it takes no more.
--? @result Void
--? @args EGroupID egroup, Boolean enabled[, Float reset_time]
--? @extdesc The optional reset_time is used to automatically remove invulnerability after a set time. If invulnerable, both health and critical damage are disabled.
function EGroup_SetInvulnerable(egroupid, enabled, reset_time)
	
	if reset_time == nil then
		reset_time = 0
	end
	
	if (scartype(egroupid) ~= ST_EGROUP) then fatal("EGroup_SetInvulnerable: egroupid is invalid") end
	if (scartype(enabled) ~= ST_BOOLEAN) and (scartype(enabled) ~= ST_NUMBER) then fatal("EGroup_SetInvulnerable: enabled is invalid") end
	if (scartype(reset_time) ~= ST_NUMBER) then fatal("EGroup_SetInvulnerable: reset_time is invalid") end
	
	_Groups_Private.GroupSetInvulnerable(
		egroupid,
		EGroupCaller,
		enabled,
		reset_time
	)
	
end

--? @shortdesc Set the minimum health for this entity
--? @result Void
--? @args EGroupID egroup, float minhealth
--? @extdesc This is usually set to zero, any higher value prevents the entity from having its health reduced below this given value
function EGroup_SetHealthMinCap( egroupid, minhealth )
	
	local EGSetHealthMinCap = function( a, b, itemid )
			Entity_SetHealthMinCap( itemid, minhealth )
	end
		
	EGroup_ForEach( egroupid, EGSetHealthMinCap )
	
end

--? @shortdesc Check invulnerablity state for ALL or ANY entity in an entity group.
--? @result Boolean
--? @args EGroupID egroup, Boolean all
--? @extdesc
--? Set all param to true to check for ALL or set to false to check for ANY.
function EGroup_GetInvulnerable( egroupid, all )

	local result = _Groups_Private.GroupGetInvulnerable(
		egroupid,
		EGroupCaller,
		all
	)
	
	return result

end

--? @shortdesc Respawn all despawned entities in a group.
--? @result Void
--? @args EGroupID egroup
function EGroup_ReSpawn( egroupid )
	_Groups_Private.DoGroupSpawn(
		egroupid, 
		EGroupCaller,
		true
	)
end

--? @shortdesc Despawn all spawned entities in a group.
--? @result Void
--? @args EGroupID egroup
function EGroup_DeSpawn( egroupid )
	_Groups_Private.DoGroupSpawn(
		egroupid, 
		EGroupCaller,
		false
	)
end

--? @shortdesc Get the number of alive entities (both spawned and despawned)
--? @result int
--? @args INT numAlive
function EGroup_CountAlive( egroupid )

	local aliveCount = 0

	local function FindAliveEntityCount( egroupid, itenindex, entityID )
		if ( Entity_IsAlive( entityID ) == 1 ) then
			aliveCount = aliveCount + 1
		end
	end
	
	EGroup_ForEachEx( egroupid, FindAliveEntityCount, true, true )
	
	return aliveCount
end

--? @shortdesc Get a random spawned entity from egroup
--? @args EGroup egroupid
--? @result EntityID
function EGroup_GetRandomSpawnedEntity( egroupid )

	local num = EGroup_CountSpawned(egroupid)
	
	if ( num == 0 ) then
		return 0
	end
	
	-- index is one based
	local index = World_GetRand( 1, num )
	
	-- get the random entity
	return EGroup_GetSpawnedEntityAt( egroupid, index )

end

--? @shortdesc Set player selectable state of entities in the egroup
--? @args EGroupID egroup, Bool selectable
--? @result Void
function EGroup_SetSelectable( egroupid, selectable )

	_Groups_Private.GroupSetSelectable( egroupid, EGroupCaller, selectable )
	
end


--? @shortdesc Change the ownership of a Strategic Point
--? @args EGroupID egroup, PlayerID player
--? @result Void
function EGroup_InstantCaptureStrategicPoint(egroupid, playerid)

	local _Capture = function (gid, idx, eid)
		Entity_InstantCaptureStrategicPoint(eid, playerid)
	end
	
	EGroup_ForEach(egroupid, _Capture)
	
end


--? @shortdesc Hide or show all entities in an EGroup
--? @extdesc Bool should be true to hide, false to show
--? @args EGroupID egroup, Bool hide
--? @result Void
function EGroup_Hide(group, bool)

	if ( group == nil or scartype( group ) ~= ST_EGROUP ) then
		return
	end
	
	local _HideEntity = function (gid, idx, eid)
		Entity_VisHide(eid, bool)
	end
	
	EGroup_ForEachEx(group, _HideEntity, true, true)
	
end



--? @shortdesc Kill all entities in an EGroup
--? @args EGroupID egroup
--? @result Void
function EGroup_Kill(group)

	if ( group == nil or scartype( group ) ~= ST_EGROUP ) then
		return
	end
	
	local _KillEntity = function (gid, idx, eid)
		Entity_Kill(eid)
	end
	
	EGroup_ForEachEx(group, _KillEntity, true, true)

end


--? @shortdesc Check if the entities are attacked by the player
--? @args EGroupID group, PlayerID attackerplayer, Float duration
--? @result Bool
function EGroup_IsUnderAttackByPlayer(groupid, attackerplayer, duration)

	local result = false;
	
	local _OneEntity = function (gid, idx, eid)
		if ( Entity_IsUnderAttackByPlayer(eid, attackerplayer, duration) ) then
			result = true
		end
	end
	
	EGroup_ForEach(groupid, _OneEntity)
	
	return result
	
end



--? @shortdesc Returns true if EGroup contains a particular EntityID
--? @args EGroupID egroup, EntityID entity
--? @result Boolean
function EGroup_ContainsEntity(group, entity)

	local myid = Entity_GetGameID(entity)
	
	local _CheckEntity = function (gid, idx, eid)
		if (Entity_GetGameID(eid) == myid) then
			return true
		end
	end

	return EGroup_ForEach(group, _CheckEntity)
	
end


--? @shortdesc Returns true if EGroup1 contains ANY or ALL of EGroup2
--? @args EGroupID egroup1, EGroupID egroup2, Boolean all
--? @result Boolean
function EGroup_ContainsEGroup(group1, group2, all)

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

	return EGroup_ForEachAllOrAny(group2, all, _CheckEntity)
	
end

--? @shortdesc Create and display kicker message on the each entity in the egroup to the player
--? @args EGroupID group, PlayerID, player, LocString textid
--? @result Void
function EGroup_CreateKickerMessage(group, player, textid)

	local _CreateEntityKickerMessage = function (gid, idx, eid)
		return UI_CreateEntityKickerMessage(player, eid, textid)
	end
	
	return EGroup_ForEachAllOrAny(group, ALL, _CreateEntityKickerMessage)
	
end

--? @shortdesc Returns true if ANY or ALL entities in an EGroup are moving.
--? @result Boolean
--? @args EGroupID egroupid, Boolean all
function EGroup_IsMoving(egroupid, all)

	local _IsEntityMoving = function (gid, idx, eid)
		
		return Entity_IsMoving(eid)
		
	end

	return EGroup_ForEachAllOrAnyEx(egroupid, all, _IsEntityMoving, true, false)

end


--? @shortdesc Creates an entity group containing a single entity
--? @extdesc
--? Creates an EGroup containing just one entity, creating the group if it doesn't exist and clearing it if it does. It returns the name of the EGroup.
--? @result EGroupID
--? @args EGroupID egroup, entityID entity
function EGroup_Single(groupID, entityID)

--	local groupID = EGroup_CreateIfNotFound(name)
	EGroup_Clear(groupID)
	
	EGroup_Add(groupID, entityID)

	return groupID
	
end

--? @shortdesc Filters an EGroup by blueprint.
--? @extdesc
--? Blueprints can be provided by name or by ID, and in a table if you want to filter on more than one type.
--? Setting filtertype to FILTER_KEEP results in the group only containing entities of the types listed in the blueprint table.
--? Setting filtertype to FILTER_REMOVE will strip those same entities out and leave those that aren't of the types listed.
--? @result Void
--? @args EGroupID egroup, String/ID/Table blueprint, Integer filtertype
function EGroup_Filter(groupid, blueprinttable, filtertype)

	-- stuff blueprint into a table if it isn't already
	if (type(blueprinttable) ~= "table") then
		blueprinttable = {blueprinttable}
	end
	
	-- convert all blueprint names to blueprint IDs
	for n = 1, table.getn(blueprinttable) do
		if (type(blueprinttable[n]) == "string") then
			blueprinttable[n] = Util_GetEntityBlueprintID(blueprinttable[n])
		end
	end
	
	local _FilterMe = function (gID, idx, eID)
		
		-- find this entities blueprint
		local thisentityblueprint = Entity_GetBlueprintID(eID)
		local mark = false
		
		-- check to see if it's in the blueprint table
		for n = 1, table.getn(blueprinttable) do
			if thisentityblueprint == blueprinttable[n] then
				mark = true
			end
		end
		
		if filtertype == FILTER_KEEP then
			-- KEEP, so remove if it wasn't found in the table
			if mark == false then
				EGroup_Remove(gID, eID)
			end
		elseif filtertype == FILTER_REMOVE then
			-- REMOVE, so remove if it was found in the table
			if mark == true then
				EGroup_Remove(gID, eID)
			end
		end
		
	end
	
	EGroup_ForEachEx(groupid, _FilterMe, true, true)
	
end


--? @shortdesc Filters an EGroup by construction status.
--? @extdesc
--? Setting filtertype to FILTER_KEEP results in the group only containing those entities that are in the process of being built.
--? Setting filtertype to FILTER_REMOVE will strip those same entities out and leave those that are complete.
--? @result Void
--? @args EGroupID egroup, Integer filtertype
function EGroup_FilterUnderConstruction(groupid, filtertype)

	local _FilterMe = function (gID, idx, eID)
		
		-- find this entities status
		local complete = false
		if (Entity_GetBuildingProgress(eID) == 1) then
			complete = true
		end
		
		if filtertype == FILTER_KEEP then
			-- KEEP, so remove if it is complete
			if complete == true then
				EGroup_Remove(gID, eID)
			end
		elseif filtertype == FILTER_REMOVE then
			-- REMOVE, so remove if it is under construction
			if complete == false then
				EGroup_Remove(gID, eID)
			end
		end
		
	end
	
	EGroup_ForEach(groupid, _FilterMe)
	
end

--? @shortdesc Duplicates an EGroup
--? @extdesc
--? Creates a copy of egroup1 in egroup2. The function will clear egroup2 beforehand if necessary.
--? @result Void
--? @args EGroupID egroupid1, EGroupID egroupid2
function EGroup_Duplicate(groupid1, groupid2)

	-- clear group2
	EGroup_Clear(groupid2)
	
	-- add group 1 to the empty group 2
	EGroup_AddEGroup(groupid2, groupid1)
	
end

--? @shortdesc Returns true if ANY or ALL (use those keywords) of the enities in the group are present onscreen. You can pass in a percentage of the screen to check, so 0.8 would be a centered rectangle occupying 80% of the screen.
--? @result Bool
--? @args PlayerID player, EGroupID group, Bool all[, Float percent]
function EGroup_IsOnScreen(playerid, groupid, all, pct)

	if pct == nil then pct = 1.0 end
	
	local _CheckEntity = function(gid, idx, eid)
		return Misc_IsEntityOnScreen(eid, pct)
	end
	
	return EGroup_ForEachAllOrAny(groupid, all, _CheckEntity)
	
end


--? @shortdesc Sets whether a weapon to auto-target things or not
--? @args EGroupID group, String hardpoint, Bool enable
--? @result Void
function EGroup_SetAutoTargetting(group, hardpoint, enable)

	-- create the appropriate modifier
	local modifier = 1
	if (enable == true) then
		modifier = Modifier_Create(MAT_Weapon, "modifiers\\auto_target_enable_weapon_modifier.lua", MUT_Enable, true, 1, hardpoint)
	else
		modifier = Modifier_Create(MAT_Weapon, "modifiers\\auto_target_enable_weapon_modifier.lua", MUT_Enable, true, -1, hardpoint)
	end
	
	-- apply this to each squad in the group
	local _ApplyModifier = function (gid, idx, eid)
		Modifier_ApplyToEntity(modifier, eid)
	end
	EGroup_ForEachEx(group, _ApplyModifier, true, true)
	
end


--? @shortdesc Returns the number of combat slots in total of a certain hold-able type 
--? @result Integer
--? @args EGroupID egroup, String holdTypeName
function EGroup_GetNumCombatSlots(egroupid, holdTypeName)
	
	local count = 0
	local _CountEntity = function(gid, idx, eid)
		count = count + Entity_GetNumCombatSlots(eid, holdTypeName)
	end
	
	EGroup_ForEach(egroupid, _CountEntity)
	return count

end


--? @shortdesc Returns whether ANY or ALL entities in an EGroup have the specified upgrade
--? @result Boolean
--? @args EGroupID egroup, UpgradeID upgrade, Boolean all
function EGroup_HasUpgrade(egroup, upgrade, all)

	local _CheckEntity = function (gid, idx, eid)
		return Entity_HasUpgrade(eid, upgrade)
	end
	
	return EGroup_ForEachAllOrAny(egroup, all, _CheckEntity)
	
end

--? @shortdesc Returns the distance from the centre of the group of the entity that furthest out. 
--? @result Real
--? @args EGroupID egroup
function EGroup_GetSpread(egroup)

	local centerpos = EGroup_GetPosition(egroup)
	local maxdist = 0
	
	local _CheckEntity = function (gid, idx, eid)
		
		local newpos = Entity_GetPosition(eid)
		local dist = World_DistancePointToPoint(centerpos, newpos)
		if dist > maxdist then
			maxdist = dist
		end
		
	end
	
	EGroup_ForEach(egroup, _CheckEntity)
	
	return maxdist
	
end


--? @shortdesc Builds a table of EGroupIDs that are named in a sequence. i.e. a name of "eg_building" will find groups "eg_building1", "eg_building2" and so on, up until it looks for a group that isn't there.
--? @args String name
--? @result Table
function EGroup_GetSequence(name)

	local num = 1
	local result = {}
	
	while EGroup_Exists(name..num) do
		table.insert(result, EGroup_FromName(name..num))
		num = num + 1
	end
	
	if num >= 2 then
		print("Retrieved sequence of EGroups: "..name.."1 to "..name..(num-1))
	end
	
	return result
	
end


--? @shortdesc Trigger animation action for an EGroup. Please only use this for simple animations
--? @args EGroupID egroup, String actionName
--? @result Void
function EGroup_SetAnimatorAction(egroup, actionName)

	local _SetEntity = function (gid, idx, eid)
		Entity_SetAnimatorAction(eid, actionName)
	end
	EGroup_ForEach(egroup, _SetEntity)
	
end


--? @shortdesc Set animation event for an EGroup. Please only use this for simple animations
--? @args EGroupID egroup, String eventName
--? @result Void
function EGroup_SetAnimatorEvent(egroup, eventName)

	local _SetEntity = function (gid, idx, eid)
		Entity_SetAnimatorEvent(eid, eventName)
	end
	EGroup_ForEach(egroup, _SetEntity)
	
end


--? @shortdesc Set animation state of a state machine for an EGroup. Please only use this for simple animations
--? @args EGroupID egroup, String stateMachineName, String stateName
--? @result Void
function EGroup_SetAnimatorState(egroup, stateMachineName, stateName)

	local _SetEntity = function (gid, idx, eid)
		Entity_SetAnimatorState(eid, stateMachineName, stateName)
	end
	EGroup_ForEach(egroup, _SetEntity)
	
end


--? @shortdesc Set animation variable value for an EGroup. Please only use this for simple animations
--? @args EGroupID egroup, String variableName, Real value
--? @result Void
function EGroup_SetAnimatorVariable(egroup, variableName, value)

	local _SetEntity = function (gid, idx, eid)
		Entity_SetAnimatorVariable(eid, variableName, value)
	end
	EGroup_ForEach(egroup, _SetEntity)
	
end

--? @shortdesc Returns whether any entity in an EGroup has a hold on anything
--? @args EGroupID egroup
--? @result Void
function EGroup_IsHoldingAny(egroup)

	local _EntityHoldingAny = function(gid, idx, eid)
		if Entity_IsHoldingAny(eid) then
			return true
		end
	end
	
	return EGroup_ForEach(egroup, _EntityHoldingAny)

end

--? @shortdesc Returns an sgroup containing all squads held by any entities in an egroup
--? @args EGroupID egroup, SGroupID sgroupRecipient
--? @result Void
function EGroup_GetSquadsHeld(egroup, sgroup)

	SGroup_Clear(sgroup)
	
	local _GetSquadsHeld = function(gid, idx, eid)
		Entity_GetSquadsHeld(eid, sgroup)
	end
	
	EGroup_ForEach(egroup, _GetSquadsHeld)
	
end

----------------------------------------------------------------------------------------------------------------
-- SGroup

--? @group SGroup

--? @shortdesc Returns the percentage of the SGroup members that are in cover. Alternatively, specify ANY or ALL as a second parameter to return true/false.
--? @args SGroupID sgroup[, Boolean all]
--? @result Real/Boolean
function SGroup_IsInCover( sgroupid, all )
	
	local total = 0
	local totalincover = 0
	
	local _CheckSquad = function (gid, idx, sid)
		
		local count = Squad_Count(sid)
		
		for n = 1, count do
			if (Entity_GetCoverValue(Squad_EntityAt(sid, n-1)) > 0) then
				totalincover = totalincover + 1
			end
		end
		total = total + count
		
	end
	
	SGroup_ForEach(sgroupid, _CheckSquad)
	
	if (all == ANY) then					-- return true if ANY are in cover
		if (totalincover >= 1) then
			return true
		else
			return false
		end
	elseif (all == ALL) then				-- return true if ALL are in cover
		if (total == totalincover) then
			return true
		else
			return false
		end
	else									-- return the raw percentage
		return (totalincover/total)
	end
	
end




---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns true if ALL or ANY squads are under attack within the time.
--? @args SGroupID sgroup, Boolean all, Float time
--? @result Boolean
function SGroup_IsUnderAttack( sgroupid, all, duration )
	return _Groups_Private.IsGroupUnderAttack(
				sgroupid,
				SGroupCaller,
				all,
				duration
			)	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns true if ALL or ANY squads are attacking within the time.
--? @args SGroupID sgroup, Boolean all, Float time
--? @result Boolean
function SGroup_IsDoingAttack( sgroupid, all, duration )
	return _Groups_Private.IsGroupAttacking(
				sgroupid,
				SGroupCaller,
				all,
				duration
			)	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns true if ALL or ANY squads in a group can see ALL or ANY squads in a target sgroup.
--? @args SGroupID sgroup, SGroupID targetsgroup, Boolean all
--? @result Boolean
function SGroup_CanSeeSGroup( sgroupid, targetsgroupid, all )
	return _Groups_Private.CanGroupSeeSGroup(
				sgroupid,
				SGroupCaller,
				targetsgroupid,
				all
			)
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns true if ALL or ANY squads in a group are infiltrated
--? @args SGroupID sgroup, Boolean all
--? @result Boolean
function SGroup_IsInfiltrated( sgroupid, all )
	
	local IsInfil = function( groupid, idx, id )
		return Squad_IsInfiltrated( id )
	end
	
	return SGroup_ForEachAllOrAny( sgroupid, all, IsInfil )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Find a squad group from name.  Creates a new one with given name if it doesnt exist.
--? @args String name
--? @result SGroupID
function SGroup_CreateIfNotFound( name )
	return _Groups_Private.CreateGroupIfNotFound( name, SGroupCaller )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns true if a named squad group is empty
--? @args SGroupID sgroup
--? @result Boolean
function SGroup_IsEmpty( sgroupid )
	return _Groups_Private.IsGroupEmpty( sgroupid, SGroupCaller )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns the average health of all units in a squad group as a weighted percent [0.0, 1.0].
--? @extdesc Example: A group of squads with health 20/100 and 100/1000 would return 120/1100 ( approx 11 % )
--? @args SGroupID sgroup
--? @result Real
function SGroup_GetAvgHealth( sgroupid )

	return _Groups_Private.GetAvgGroupHealth(
					sgroupid,
					SGroupCaller
				)
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Sets the health of each squad in a squad group to a given percent [0.0, 1.0].
--? @args SGroupID sgroup, Real healthpercent
--? @result Real
function SGroup_SetAvgHealth( sgroupid, healthPercent )
	
	return _Groups_Private.SetAvgGroupHealth(
					sgroupid,
					SGroupCaller,
					healthPercent
				)
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Returns the average loadout of all squads in a group as a percent [0.0, 1.0].
--? @extdesc Example: A group of squads with loadouts of 4/8 and 1/1 would return 0.75
--? @args SGroup sgroup
--? @result Real
function SGroup_GetAvgLoadout( sgroupid )

	local currentLoadout = 0
	local maxLoadout = 0

	local GetSquadLoadout = function( groupid, itemindex, itemid )
		currentLoadout = currentLoadout + Squad_Count( itemid )
		maxLoadout = maxLoadout + Squad_GetMax( itemid )
	end
	
	-- check health
	SGroup_ForEach( sgroupid, GetSquadLoadout )
	
	local result = currentLoadout / maxLoadout
	
	if( (result < 0.0) or (result > 1.0) ) then
		fatal("this should never happen")
	end
	
	return result

end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Sets the moral of each squad in a squad group to a given percent [0.0, 1.0].
--? @args SGroup sgroup, Real moralepercent
--? @result Real
function SGroup_SetAvgMorale( sgroupid, moralePercent )
	
	local SetMorale = function( groupid, itemindex, itemid )
		Squad_SetMorale( itemid, moralePercent )
	end
	
	SGroup_ForEach( sgroupid, SetMorale )
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Changes the player owner of spawned and despawned squads in an SGroup.
--? @args SGroupID sgroup, PlayerID owner
--? @result Void
function SGroup_SetPlayerOwner( sgroupid, owner )
	
	return _Groups_Private.SetPlayerOwner(
			sgroupid,
			SGroupCaller,
			owner
	)
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Destroys all spawned and despawned squads in a group.
--? @extdesc 
--? Be careful not to confuse this with SGroup_Destroy which destroys the group and NOT the squads it contains.  This function will destroy
--? spawned and despawned items in a group
--? @args SGroupID sgroup
--? @result Void
function SGroup_DestroyAllSquads( sgroupid )
	
	return _Groups_Private.DestroyAllItems(
					sgroupid,
					SGroupCaller
				)
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Check if a group contains ALL or ANY of the items in a blueprint table.
--? @args SGroupID sgroup, BlueprintTable blueprints, Boolean all
--? @result Boolean
--? @extdesc 
--? This function will validate all blueprint names.\n
--? Set all to true to check for all blueprints in the blueprintTable or set to false to check for any.\n
--? A blueprint table containing two marines and a bloodthirster looks like so:\n
--? BlueprintTable = {\n
--? \t{ name = "chaos_marine_squad", count = 2 }\n
--? \t{ name = "chaos_squad_bloodthirster", count = 1 }\n
--? }\n\n
--? You could also call Util_MakeBlueprintTable:\n
--? BlueprintTable = Util_MakeBlueprintTable( "chaos_marine_squad", "chaos_marine_squad", "chaos_squad_bloodthirster")\n\n
--? This function will take care of setting up the table for you.
function SGroup_ContainsBlueprints( sgroupid, blueprintTable, all )

	if type(blueprintTable) ~= "table" then
		fatal("SGroup_ContainsBlueprints: must pass in a table.")
	end
	
	return _Groups_Private.GroupContainsBlueprints(
					sgroupid,
					SGroupCaller,
					blueprintTable,
					all
				)
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Enable/Disable invulnerablity for an entire SGroup. Use true and false for simple on/off, or use a number between 0.0 and 1.0 for more precise control on how much damage a squad can take before it takes no more.
--? @result Void
--? @args SGroupID sgroup, Boolean/Real enabled[, Float reset_time]
--? @extdesc The optional reset_time is used to automatically remove invulnerability after a set time. If invulnerable, both health and critical damage are disabled.
function SGroup_SetInvulnerable(sgroupid, enabled, reset_time)
	
	if reset_time == nil then
		reset_time = 0
	end
	
	if (scartype(sgroupid) ~= ST_SGROUP) then fatal("SGroup_SetInvulnerable: sgroupid is invalid") end
	if (scartype(enabled) ~= ST_BOOLEAN) and (scartype(enabled) ~= ST_NUMBER) then fatal("SGroup_SetInvulnerable: enabled is invalid") end
	if (scartype(reset_time) ~= ST_NUMBER) then fatal("SGroup_SetInvulnerable: reset_time is invalid") end
	
	_Groups_Private.GroupSetInvulnerable(
		sgroupid,
		SGroupCaller,
		enabled,
		reset_time
	)
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Check invulnerablity state for ALL or ANY squads in a squad group.
--? @result Boolean
--? @args SGroupID sgroup, Boolean all
--? @extdesc
--? Set all param to true to check for ALL or set to false to check for ANY.
function SGroup_GetInvulnerable( sgroupid, all )

	local result = _Groups_Private.GroupGetInvulnerable(
		sgroupid,
		SGroupCaller,
		all
	)
	
	return result

end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Check if ALL or ANY squads in a sgroup have a leader
--? @result Boolean
--? @args SGroupID sgroup, Boolean all
--? @extdesc
--? Set all param to true to check for ALL or set to false to check for ANY.
function SGroup_HasLeader( sgroupid, all )
	local HasLeader = function( groupid, itemindex, itemid )
		return Squad_HasLeader( itemid )
	end
	
	local result = SGroup_ForEachAllOrAny( sgroupid , all, HasLeader )
	return result
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Destroys all items in a group that are in proximity to a given marker.
--? @result Void
--? @args SGroupID sgroup, MarkerID marker
function SGroup_DestroyAllInMarker( sgroupid, markerid )	
	
	local squadsNearMarker = {}
	
	local CheckNearMarker = function ( groupid, itemindex, itemid )
		if ( Marker_InProximity(markerid, Squad_GetPosition(itemid)) ) then
			table.insert( squadsNearMarker, itemid )
		end
	end
	
	--
	SGroup_ForEach( sgroupid, CheckNearMarker )
	
	--
	for i = 1, table.getn( squadsNearMarker ) do
		Squad_Destroy( squadsNearMarker[i] ) -- squad will be removed from all groups when it is destroyed
	end
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Adds a leader to all squads in a group that can take a leader.
--? @extdesc This function will bypass all cost and queue prereqs
--? @result Void
--? @args SGroupID sgroup
function SGroup_AddLeaders( sgroupid )	
	local AddLeader = function( groupid, itemindex, itemid )
		Squad_AddLeader( itemid )
	end
	
	SGroup_ForEach( sgroupid, AddLeader )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Makes two SGroups face each other
--? @result Void
--? @args SGroupID sgroup1, SGroupID sgroup2
function SGroup_FaceEachOther( sgroupid1, sgroupid2 )
	-- group1 face group2
	SGroup_FacePosition( sgroupid1,  SGroup_GetPosition( sgroupid2 ) )
	
	-- group2 face group1
	SGroup_FacePosition( sgroupid2,  SGroup_GetPosition( sgroupid1 ) )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Makes two SGroups face each other at no time
--? @result Void
--? @args SGroupID sgroup1, SGroupID sgroup2
function SGroup_SnapFaceEachOther( sgroupid1, sgroupid2 )
	-- group1 face group2
	SGroup_SnapFacePosition( sgroupid1,  SGroup_GetPosition( sgroupid2 ) )
	
	-- group2 face group1
	SGroup_SnapFacePosition( sgroupid2,  SGroup_GetPosition( sgroupid1 ) )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Makes a SGroup face a marker.
--? @result Void
--? @args SGroupID sgroup, MarkerID marker
function SGroup_FaceMarker( sgroupid, markerid )
	local pos = Marker_GetPosition( markerid )
	SGroup_FacePosition( sgroupid, pos )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Respawn all despawned squads in a group.
--? @result Void
--? @args SGroupID groupid
function SGroup_ReSpawn( sgroupid )
	_Groups_Private.DoGroupSpawn(
		sgroupid, 
		SGroupCaller,
		true
	)
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Despawn all spawned squads in a group.
--? @result Void
--? @args SGroupID groupid
function SGroup_DeSpawn( sgroupid )
	_Groups_Private.DoGroupSpawn(
		sgroupid, 
		SGroupCaller,
		false
	)
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Merge first squads in the SGroups. The resulting squad will be in both SGroups
--? @extdesc Merging only works on squads with the same blueprint type. Squad loadout limit is ignored.
--? @args SGroup sgroupid1, SGroup sgroupid2
function SGroup_MergeFirstSquad( sgroupid1, sgroupid2 )

	if ( (SGroup_CountSpawned(sgroupid1) == 0) or (SGroup_CountSpawned(sgroupid2) == 0) ) then
		fatal("Cannot merge squads from empty squad groups")
	end

	-- get the first squads
	local dest = SGroup_GetSpawnedSquadAt( sgroupid1, 1 )
	local source = SGroup_GetSpawnedSquadAt( sgroupid2, 1 )
	
	-- merge the squads
	Squad_Merge( dest, source )
	
	-- add the merged squad to the second sgroup as well
	SGroup_Add( sgroupid2, dest )
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Split first squad into a new squad with size "num"
--? @extdesc The new squad would not have any upgrads or modifiers from the original squad
--? @args SGroup sgroupid1, INT num
--? @result SquadID
function SGroup_SplitFirstSquad( sgroupid, num )

	if ( SGroup_CountSpawned(sgroupid) == 0 ) then
		fatal("Cannot split squad from empty squad group")
	end

	-- get the first squads
	local squad = SGroup_GetSpawnedSquadAt( sgroupid, 1 )
	
	-- merge the squads
	return Squad_Split( squad, num )

end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Get a random spawned squad from sgroup
--? @args SGroup sgroupid
--? @result SquadID
function SGroup_GetRandomSpawnedSquad( sgroupid )

	local num = SGroup_CountSpawned(sgroupid)
	
	if ( num == 0 ) then
		return 0
	end

	-- index is one based
	local index = World_GetRand( 1, num )
	
	-- get the random squad
	return SGroup_GetSpawnedSquadAt( sgroupid, index )

end

--? @shortdesc Set player selectable state of squads in the sgroup
--? @args SGroup sgroupid, bool selectable
function SGroup_SetSelectable( sgroupid, selectable )

	_Groups_Private.GroupSetSelectable( sgroupid, SGroupCaller, selectable )
	
end

---------------------------------------------------------------------------------------------------
--
--

--? @shortdesc Sets whether a weapon to auto-target things or not
--? @args SGroupID group, String hardpoint, Bool enable
--? @result Void
function SGroup_SetAutoTargetting(group, hardpoint, enable)

	-- create the appropriate modifier
	local modifier = 1
	if (enable == true) then
		modifier = Modifier_Create(MAT_Weapon, "modifiers\\auto_target_enable_weapon_modifier.lua", MUT_Enable, true, 1, hardpoint)
	else
		modifier = Modifier_Create(MAT_Weapon, "modifiers\\auto_target_enable_weapon_modifier.lua", MUT_Enable, true, -1, hardpoint)
	end
	
	-- apply this to each squad in the group
	local _ApplyModifier = function (gid, idx, sid)
		Modifier_ApplyToSquad(modifier, sid)
	end
	SGroup_ForEachEx(group, _ApplyModifier, true, true)
	
end

---------------------------------------------------------------------------------------------------
--
--

MOVETYPE_SAFE = "safe_move"
MOVETYPE_NORMAL = "normal_move"
MOVETYPE_FAST = "fast_move"

--? @shortdesc Set the move type for an SGroup - move type is either MOVETYPE_SAFE, MOVETYPE_NORMAL, MOVETYPE_FAST
--? @args SGroupID group, String movetype
--? @result Void
function SGroup_SetMoveType(groupid, movetype)

	local _OneSquad = function (gid, idx, sid)
		Squad_SetMoveType(sid, movetype)
	end
	
	SGroup_ForEach(groupid, _OneSquad)
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Get the number of slot items with the same ID that the squads in the sgroup own
--? @args SGroupID group, Int itemID
--? @result Int
function SGroup_GetNumSlotItem(groupid, itemID)

	local count = 0
	
	local _OneSquad = function (gid, idx, sid)
		count = count + Squad_GetNumSlotItem(sid, itemID)
	end
	
	SGroup_ForEach(groupid, _OneSquad)
	
	return count
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Check if the squads are attacked by the player
--? @args SGroupID group, PlayerID attackerplayer, Float duration
--? @result Bool
function SGroup_IsUnderAttackByPlayer(groupid, attackerplayer, duration)

	local result = false
	
	local _OneSquad = function (gid, idx, sid)
		if ( Squad_IsUnderAttackByPlayer(sid, attackerplayer, duration) ) then
			result = true
		end
		
	end
	
	SGroup_ForEach(groupid, _OneSquad)
	
	return result
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Add to the list of slot items to drop when any one of the squads is wiped out
--? @extdesc Drop chance is percentage chance the item will drop (0.0-1.0)
--? @extdesc exlusive means the squad would only drop this item, thus erasing all previous items in list
--? @extdesc example:	local item = Util_GetSlotItemID( "slot_item/allies_m9bazooka.lua" )
--? @extdesc			SGroup_AddSlotItemToDropOnDeath( squadid, item, false )
--? @args SGroupID groupid, Int itemid, Float drop_chance, Bool exclusive
--? @result Void
function SGroup_AddSlotItemToDropOnDeath(groupid, itemid, drop_chance, exclusive)

	local _OneSquad = function (gid, idx, sid)
		Squad_AddSlotItemToDropOnDeath(sid, itemid, drop_chance, exclusive)
	end
	
	SGroup_ForEach(groupid, _OneSquad)
	
	return result
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Get the entity id of the building that any squad of the sgroup is garrisoned in
--? @args SGroupID groupid
--? @result EntityID
function SGroup_GetGarrisonedBuildingEntity(sgroupid)

	if ( SGroup_Count( sgroupid ) > 0 ) then
	
		local countSpawned = SGroup_CountSpawned( sgroupid )
		for i=1, countSpawned do 
			local squad = SGroup_GetSpawnedSquadAt( sgroupid, i )
			if ( Squad_IsInHoldEntity( squad ) ) then
				return Squad_GetHoldEntity( squad )
			end
		end
		
		local countDespawned = SGroup_CountDeSpawned( sgroupid )
		for i=1, countDespawned do 
			local squad = SGroup_GetDespawnedSquadAt( sgroupid, i )
			if ( Squad_IsInHoldEntity( squad ) ) then
				return Squad_GetHoldEntity( squad )
			end
		end
		
	end
	
	return 0
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Get the squad id of the vehicle squad that any squad of the sgroup is loaded in
--? @args SGroupID groupid
--? @result SquadID
function SGroup_GetLoadedVehicleSquad(sgroupid)

	if ( SGroup_Count( sgroupid ) > 0 ) then
	
		local countSpawned = SGroup_CountSpawned( sgroupid )
		for i=1, countSpawned do 
			local squad = SGroup_GetSpawnedSquadAt( sgroupid, i )
			if ( Squad_IsInHoldSquad( squad ) ) then
				return Squad_GetHoldSquad( squad )
			end
		end
		
		local countDespawned = SGroup_CountDeSpawned( sgroupid )
		for i=1, countDespawned do 
			local squad = SGroup_GetDespawnedSquadAt( sgroupid, i )
			if ( Squad_IsInHoldSquad( squad ) ) then
				return Squad_GetHoldSquad( squad )
			end
		end
		
	end
	
	return 0
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Get the suppression level for the first squad in the sgroup
--? @args SGroupID groupid
--? @result Float
function SGroup_GetSuppression(sgroupid)

	if ( SGroup_CountSpawned( sgroupid ) > 0 ) then
		return Squad_GetSuppression( SGroup_GetSpawnedSquadAt( sgroupid, 1 ) ) 
	end
	
	return 0.0
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Set the suppression level for all squads in the sgroup
--? @args SGroupID groupid, Float suppression
--? @result Void
function SGroup_SetSuppression(sgroupid, suppression)

	local _OneSquad = function (gid, idx, sid)
		Squad_SetSuppression(sid, suppression)
	end
	
	SGroup_ForEach(sgroupid, _OneSquad)
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Enables or disables the surprise feature for an sgroup
--? @args SGroupID groupid, Boolean enable
--? @result Void
function SGroup_EnableSurprise(sgroupid, bool)

	local _OneSquad = function (gid, idx, sid)
		Squad_EnableSurprise(sid, bool)
	end
	
	SGroup_ForEach(sgroupid, _OneSquad)
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Get the veterancy rank for the first squad in the sgroup
--? @args SGroupID groupid
--? @result Int
function SGroup_GetVeterancyRank(sgroupid)

	if ( SGroup_Count( sgroupid ) > 0 ) then
		return Squad_GetVeterancyRank( SGroup_GetSpawnedSquadAt( sgroupid, 1 ) ) 
	end
	
	return 0
end


---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Get the veterancy experience value for the first squad in the sgroup
--? @args SGroupID groupid
--? @result Float
function SGroup_GetVeterancyExperience(sgroupid)

	if ( SGroup_Count( sgroupid ) > 0 ) then
		return Squad_GetVeterancyExperience( SGroup_GetSpawnedSquadAt( sgroupid, 1 ) ) 
	end
	
	return 0.0
end


---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Increase squad veterancy rank for all squads in the sgroup. By default, increases rank by 1. Can do silent promotion (no sound/UI. ex: mass rank insrease at mission start)
--? @args SGroupID groupid[, Integer numranks, Boolean silent]
--? @result Void
function SGroup_IncreaseVeterancyRank(sgroupid, numranks, silent)

	if numranks == nil then
		numranks = 1
	end
	
	if silent == nil then
		silent = false
	end
	
	local _OneSquad = function (gid, idx, sid)
		Squad_IncreaseVeterancyRank(sid, numranks, silent)
	end
	
	SGroup_ForEach(sgroupid, _OneSquad)
end


---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Increase squad veterancy experience for all squads in the sgroup. Can do silent promotion (no sound/UI). Can skip modifiers, giving you direct control of experience gained.
--? @args SGroupID groupid, Float experience[, Boolean silent, Boolean applyModifiers]
--? @result Void
function SGroup_IncreaseVeterancyExperience(sgroupid, experience, silent, applyModifiers)

	if silent == nil then
		silent = false
	end
	
	if applyModifiers == nil then
		applyModifiers = true
	end
	
	local _OneSquad = function (gid, idx, sid)
		Squad_IncreaseVeterancyExperience(sid, experience, silent, applyModifiers)
	end
	
	SGroup_ForEach(sgroupid, _OneSquad)
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Hide or show all entities in all squads in an SGroup
--? @extdesc Bool should be true to hide, false to show
--? @args SGroupID sgroup, Bool hide
--? @result Void
function SGroup_Hide(group, bool)

	local _HideSquad = function (gid, idx, sid)
		for n = 1, Squad_Count(sid) do
			Entity_VisHide(Squad_EntityAt(sid, n-1), bool)
		end
	end
	
	SGroup_ForEachEx(group, _HideSquad, true, true)
	
end



---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Remove from the first SGroup all squads contained in the second SGroup. SGroup2 remains untouched.
--? @args SGroupID group, SGroupID grouptoremove
--? @result Void
function SGroup_RemoveGroup(group, grouptoremove)

	local _RemoveSquad = function (gid, idx, sid)
		SGroup_Remove(group, sid)
	end
	
	SGroup_ForEach(grouptoremove, _RemoveSquad)
	
end





---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Returns true if ANY or ALL of the squads in the SGroup have the specified critical
--? @args SGroupID group, CriticalID critical, Boolean all
--? @result Boolean
function SGroup_HasCritical(group, critical, all)

	local _CheckSquad = function (gid, idx, sid)
		return Squad_HasCritical(sid, critical)
	end
	
	return SGroup_ForEachAllOrAny(group, all, _CheckSquad)
	
end




---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Returns true if ANY or ALL of the squads in the SGroup is dug in (or in the process of digging in)
--? @args SGroupID group, Boolean all
--? @result Boolean
function SGroup_IsDugIn(group, all)

	local _CheckSquad = function (gid, idx, sid)
		return Squad_IsDiggingOrDugIn(sid)
	end
	
	return SGroup_ForEachAllOrAny(group, all, _CheckSquad)
	
end


---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Enable or disable decorators on all squads in the sgroup 
--? @args SGroupID group, Boolean enable
--? @result Void
function SGroup_EnableUIDecorator(group, enable)

	local _SetSquadDecorator = function (gid, idx, sid)
		UI_EnableSquadDecorator(sid, enable)
	end
	
	SGroup_ForEach(group, _SetSquadDecorator)
--~ 	SGroup_ForEachAllOrAny(group, ALL, _SetSquadDecorator)
end




---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Enable or disable minimap indicator on all squads in the sgroup
--? @args SGroupID group, Boolean enable
--? @result Void
function SGroup_EnableMinimapIndicator(group, enable)

	local _SetSquadMinimapIndicator = function (gid, idx, sid)
		UI_EnableSquadMinimapIndicator(sid, enable)
	end
	
	SGroup_ForEach(group, _SetSquadMinimapIndicator)
--~ 	SGroup_ForEachAllOrAny(group, ALL, _SetSquadMinimapIndicator)
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Set team weapon in the squads to be capturable or not
--? @args SGroupID group, Boolean enable
--? @result Void
function SGroup_SetTeamWeaponCapturable(group, enable)

	local _SetSquadTeamWeaponCapturable = function (gid, idx, sid)
		return Squad_SetTeamWeaponCapturable(sid, enable)
	end
	
	SGroup_ForEachAllOrAny(group, ALL, _SetSquadTeamWeaponCapturable)
end


---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Returns true is ANY or ALL of a group is suppressed
--? @args SGroupID group, Boolean all
--? @result Boolean
function SGroup_IsSuppressed(group, all)

	local _CheckSquad = function (gid, idx, sid)
		return Squad_IsSuppressed(sid)
	end
	
	return SGroup_ForEachAllOrAny(group, all, _CheckSquad)
	
end


---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Returns true if ANY or ALL of a group is pinned
--? @args SGroupID group, Boolean all
--? @result Boolean
function SGroup_IsPinned(group, all)

	local _CheckSquad = function (gid, idx, sid)
		return Squad_IsPinned(sid)
	end
	
	return SGroup_ForEachAllOrAny(group, all, _CheckSquad)
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Set soldier mood mode. Mode could be MM_Auto, MM_ForceCalm or MM_ForceTense
--? @args SGroupID group, Integer mode
--? @result Void
function SGroup_SetMoodMode(group, mode)

	local _SetSquadMoodMode = function (gid, idx, sid)
		return Squad_SetMoodMode(sid, mode)
	end
	
	return SGroup_ForEachAllOrAny(group, ALL, _SetSquadMoodMode)
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Return true if ANY or ALL of a group can reinforce now
--? @args SGroupID group, Boolean all
--? @result Boolean
function SGroup_CanInstantReinforceNow(group, all)

	local _CheckSquad = function (gid, idx, sid)
		return Squad_CanInstantReinforceNow(sid)
	end
	
	return SGroup_ForEachAllOrAny(group, all, _CheckSquad)
	
end

---------------------------------------------------------------------------------------------------
--
--
--? @shortdesc Create and display kicker message on the each squad in the sgroup to the player
--? @args SGroupID group, PlayerID player, LocString textid
--? @result Void
function SGroup_CreateKickerMessage(group, player, textid)

	local _CreateSquadKickerMessage = function (gid, idx, sid)
		return UI_CreateSquadKickerMessage(player,sid,textid)
	end
	
	return SGroup_ForEachAllOrAny(group, ALL, _CreateSquadKickerMessage)
	
end

--? @shortdesc Returns true if ANY or ALL squads in an SGroup are moving.
--? @result Boolean
--? @args SGroupID sgroup, Boolean all
function SGroup_IsMoving(sgroupid, all)

	local _IsSquadMoving = function (gid, idx, sid)
		
		return Squad_IsMoving(sid)
		
	end

	return SGroup_ForEachAllOrAnyEx(sgroupid, all, _IsSquadMoving, true, false)
	
end

--? @shortdesc Filters an SGroup by blueprint.
--? @extdesc
--? Blueprints can be provided by name or by ID, and in a table if you want to filter on more than one type.
--? Setting filtertype to FILTER_KEEP results in the group only containing squads of the types listed in the blueprint table.
--? Setting filtertype to FILTER_REMOVE will strip those same squads out and leave those that aren't of the types listed.
--? @result Void
--? @args SGroupID sgroup, String/ID/Table blueprint, Integer filtertype
function SGroup_Filter(groupid, blueprinttable, filtertype)

	-- stuff blueprint into a table if it isn't already
	if (type(blueprinttable) ~= "table") then
		blueprinttable = {blueprinttable}
	end
	
	-- convert all blueprint names to blueprint IDs
	for n = 1, table.getn(blueprinttable) do
		if (type(blueprinttable[n]) == "string") then
			blueprinttable[n] = Util_GetSquadBlueprintID(blueprinttable[n])
		end
	end
	
	local _FilterMe = function (gID, idx, sID)
		
		-- find this squad's blueprint
		local thissquadblueprint = Squad_GetBlueprintID(sID)
		local mark = false
		
		-- check to see if it's in the blueprint table
		for n = 1, table.getn(blueprinttable) do
			if thissquadblueprint == blueprinttable[n] then
				mark = true
			end
		end
		
		if filtertype == FILTER_KEEP then
			-- KEEP, so remove if it wasn't found in the table
			if mark == false then
				SGroup_Remove(gID, sID)
			end
		elseif filtertype == FILTER_REMOVE then
			-- REMOVE, so remove if it was found in the table
			if mark == true then
				SGroup_Remove(gID, sID)
			end
		end
		
	end
	
	SGroup_ForEachEx(groupid, _FilterMe, true, true)
	
end

--? @shortdesc Pass in a group and it will filter it down to the indicated number
--? @result Void
--? @args SGroupID sgroup1, Int groupSize
function SGroup_FilterCount(groupid, count)
	
	local _FilterMe = function (gID, idx, sID)
		
		local ct = SGroup_Count(gID)
		
		if ct > count then 
			SGroup_Remove(gID, sID)
		else
			return true
		end
		
	end
	
	SGroup_ForEach(groupid, _FilterMe)
	
end


--? @shortdesc Pass in a group to command to 'stop'. Pass in booleans for capturing and building
--? @result Void
--? @args SGroupID sgroup1[, Boolean stopCapture, Boolean stopBuild]
function Cmd_StopSquadsExcept(groupid, capture, build)
	
	local _FilterMe = function (gID, idx, sID)
		
		-- filter out capturing squads
		if capture == true then 
			if Squad_GetActiveCommand(sID) == SQUADSTATEID_Capture then
				SGroup_Remove(gID, sID)
			end
		end
		
		-- filter out constructing squads
		if build == true then 
			if Squad_GetActiveCommand(sID) == SQUADSTATEID_Construction then
				SGroup_Remove(gID, sID)
			end
		end
		
	end
	
	SGroup_ForEach(groupid, _FilterMe)
	Cmd_StopSquads(groupid)
	
end


--? @shortdesc Duplicates an SGroup
--? @extdesc
--? Creates a copy of sgroup1 in sgroup2. The function will clear sgroup2 beforehand if necessary.
--? @result Void
--? @args SGroupID sgroup1, SGroupID sgroup2
function SGroup_Duplicate(groupid1, groupid2)

	-- clear group2
	SGroup_Clear(groupid2)
	
	-- add group 1 to the empty group 2
	SGroup_AddGroup(groupid2, groupid1)
	
end


--? @shortdesc Returns the total count of all members of all the squads in a given SGroup.
--? @result Integer
--? @args SGroupID sgroup
function SGroup_TotalMembersCount(sgroupid)

	local membercount = 0									-- initialise the tally counter
	
	local _CountThisGroup = function (gID, idx, sID)		-- add one group's member count to the tally
		membercount = membercount + Squad_Count(sID)
	end
	
	SGroup_ForEachEx(sgroupid, _CountThisGroup, true, true)	-- call the above function for each squad in the group
	
	return membercount										-- return the final tally
	
end

--? @shortdesc Returns true if ANY or ALL (use those keywords) of the squads in the group are present onscreen. You can pass in a percentage of the screen to check, so 0.8 would be a centered rectangle occupying 80% of the screen.
--? @result Bool
--? @args PlayerID player, SGroupID group, Bool all[, Float percent]
function SGroup_IsOnScreen(playerid, groupid, all, pct)

	if pct == nil then
		pct = 1.0
	end
	
	local _CheckSquad = function(gid, idx, sid)
		return Misc_IsSquadOnScreen(sid, pct)
	end
	
	return SGroup_ForEachAllOrAny(groupid, all, _CheckSquad)
	
end


--? @shortdesc Returns true if ANY or ALL squads in an SGroup are attack moving.
--? @result Boolean
--? @args SGroupID sgroup, Boolean all
function SGroup_IsAttackMoving(sgroupid, all)

	if (  SGroup_Count( sgroupid ) > 0 ) then
	
		local _IsSquadAttackMoving = function (gid, idx, sid)
		
			if ( Squad_HasActiveCommand( sid ) == false ) then
				return false
			end
			
			return (Squad_GetActiveCommand(sid) == SQUADSTATEID_AttackMove)
		end
		
		return SGroup_ForEachAllOrAnyEx(sgroupid, all, _IsSquadAttackMoving, true, true)
	end
	
	-- squad has no one
	return false
end

--? @shortdesc Tells an SGroup to eject themselves from whatever they're inside (building or vehicle)
--? @result Void
--? @args SGroupID sgroup, Position destination
function SGroup_Eject(sgroupid, pos)

	local eid = SGroup_GetGarrisonedBuildingEntity(sgroupid)
	local sid = SGroup_GetLoadedVehicleSquad(sgroupid)
	
	if (eid ~= 0) then
		Cmd_Ungarrison(EGroup_Single(eg_single, eid), pos)
	elseif (sid ~= 0) then
		Cmd_Unload(SGroup_Single(sg_single, sid), pos)
	end
	
end

--? @shortdesc Kills all squads in an SGroup. This kills them 'naturally', as opposed to SGroup_DestroyAllSquads() which makes them blink out of existance.
--? @result Void
--? @args SGroupID sgroup
function SGroup_Kill(sgroupid)

	local _KillSquad = function (gid, idx, sid)
		Squad_Kill(sid)
	end
	
	SGroup_ForEach(sgroupid, _KillSquad)
	
end


--? @shortdesc Returns whether ANY or ALL squads in an SGroup have the specified upgrade
--? @result Boolean
--? @args SGroupID sgroup, UpgradeID upgrade, Boolean all
function SGroup_HasUpgrade(sgroup, upgrade, all)

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


--? @shortdesc Returns the distance from the centre of the group of the unit that furthest out. 
--? @result Real
--? @args SGroupID sgroup
function SGroup_GetSpread(sgroup)

	local centerpos = SGroup_GetPosition(sgroup)
	local maxdist = 0
	
	local _CheckSquad = function (gid, idx, sid)
		
		for n = 1, Squad_Count(sid) do
			local newpos = Entity_GetPosition(Squad_EntityAt(sid, n-1))
			local dist = World_DistancePointToPoint(centerpos, newpos)
			if dist > maxdist then
				maxdist = dist
			end
		end
		
	end
	
	SGroup_ForEach(sgroup, _CheckSquad)
	
	return maxdist
	
end

--? @shortdesc Builds a table of SGroupIDs that are named in a sequence. i.e. a name of "sg_killer" will find groups "sg_killer1", "sg_killer2" and so on, up until it looks for a group that isn't there.
--? @args String name
--? @result Table
function SGroup_GetSequence(name)

	local num = 1
	local result = {}
	
	while SGroup_Exists(name..num) do
		table.insert(result, SGroup_FromName(name..num))
		num = num + 1
	end
	
	if num >= 2 then
		print("Retrieved sequence of SGroups: "..name.."1 to "..name..(num-1))
	end
	
	return result
	
end

--? @shortdesc Creates a squad group containing a single squad
--? @extdesc
--? Creates an SGroup containing just one squad, creating the group if it doesn't exist and clearing it if it does. It returns the name of the SGroup.
--? @result SGroupID
--? @args SGroupID groupID, squadID squad
function SGroup_Single(groupID, squadID)

--	local groupID = SGroup_CreateIfNotFound(name)
	SGroup_Clear(groupID)
	
	SGroup_Add(groupID, squadID)

	return groupID
	
end







--? @shortdesc Disables all current combat plans for the squads in the sgroup
--? @args SGroupID groupID
--? @result Void
function SGroup_DisableCombatPlans(sgroupID)

	if (not SGroup_IsEmpty(sgroupID) ) then
		
		local _SetEmptyPlans = function(gid, idx, sid)
			
			-- make this squad act dumb.
			Squad_SetAttackPlan(sid, "empty-plan")
			Squad_SetRetaliationPlan(sid, "empty-plan")
			Squad_SetReactionPlan(sid, "empty-plan")
			
		end
		
		SGroup_ForEachEx(sgroupID, _SetEmptyPlans, true, false)
		
	end
end

--? @shortdesc Restore all current combat plans for the squads in the sgroup
--? @args SGroupID sgroupID
--? @result Void
function SGroup_RestoreCombatPlans(sgroupID)

	if (not SGroup_IsEmpty(sgroupID)) then
		
		local _RestorePlans = function(gid, idx, sid)
			
			-- restore default plans
			Squad_SetAttackPlan(sid, "")
			Squad_SetRetaliationPlan(sid, "")
			Squad_SetReactionPlan(sid, "")
			
		end
		
		SGroup_ForEachEx(sgroupID, _RestorePlans, true, false)
	end
	
end

--? @shortdesc Checks if ANY or ALL squads in an SGroup are garrisoned in an entity (building) 
--? @result Boolean
--? @args SGroupID sgroup, Boolean ALL
function SGroup_IsInHoldEntity(sgroup, all)
            
	local _CheckSquad = function(gid, idx, sid)
		if Squad_IsInHoldEntity(sid) then
			return true
		end
	end
   
	return SGroup_ForEachAllOrAny(sgroup, all, _CheckSquad)
end


--? @shortdesc Gets the last attacker(s) for all the squads in an SGroup
--? Gets the last attacker for all the squads in an SGroup and stores that in 
--? @result Void
--? @args SGroup SGroupVictim, SGroup SGroupAttacker
function SGroup_GetLastAttacker(SGroupVictim, SGroupAttacker)
	
	local _CheckSquad = function(gid, idx, sid)
		Squad_GetLastAttacker(sid, SGroupAttacker)
	end
	
	SGroup_ForEach(SGroupVictim, _CheckSquad)

end

--? @shortdesc 	Add a rule to be executed when the event of 'eventType' has happened on the 'EGroup'
--?	Event types are: GE_EntityKilled
--? @result Void
--? @args LuaFunction fule, EGroup egroup, Integer eventtype
function Rule_AddEGroupEvent(rule, egroup, eventtype)

	local _CheckEGroup = function(gid, idx, eid)
		Rule_AddEntityEvent(rule, eid, eventtype)
	end
	
	EGroup_ForEach(egroup, _CheckEGroup)

end

--? @shortdesc Returns whether any entity in an SGroup has a hold on anything
--? @args SGroupID sgroup
--? @result Void
function SGroup_IsHoldingAny(sgroup)

	local _SquadHoldingAny = function(gid, idx, sid)
		if Squad_IsHoldingAny(sid) then
			return true
		end
	end
	
	return SGroup_ForEach(sgroup, _SquadHoldingAny)
	
end

--? @shortdesc Returns an sgroup containing all squads held by any squad in an sgroup
--? @args SGroupID sgroup, SGroupID sgroupRecipient
--? @result Void
function SGroup_GetSquadsHeld(sgroupIn, sgroupOut)

	SGroup_Clear(sgroupOut)
	
	local _GetSquadsHeld = function(gid, idx, sid)
		Squad_GetSquadsHeld(sid, sgroupOut)
	end
	
	SGroup_ForEach(sgroupIn, _GetSquadsHeld)
	
end
