718 lines
21 KiB
Ucode
718 lines
21 KiB
Ucode
/**
|
|
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
|
|
*
|
|
* Manages adding appropriate crowd population around player
|
|
* Agents will be spawned/recycled at any available active GameCrowdDestination
|
|
*
|
|
*/
|
|
class GameCrowdPopulationManager extends CrowdPopulationManagerBase
|
|
implements(Interface_NavigationHandle)
|
|
dependson(SeqAct_GameCrowdSpawner)
|
|
native;
|
|
|
|
var CrowdSpawnInfoItem CloudSpawnInfo;
|
|
var array<CrowdSpawnInfoItem> ScriptedSpawnInfo;
|
|
|
|
var GameCrowdInfoVolume ActiveCrowdInfoVolume;
|
|
var array<GameCrowdDestination> GlobalPotentialSpawnPoints;
|
|
|
|
/** How much to reduce number by in splitscreen */
|
|
var float SplitScreenNumReduction;
|
|
|
|
/** How far ahead to compute predicted player position for spawn prioritization */
|
|
var float PlayerPositionPredictionTime;
|
|
|
|
/** Offset used to validate spawn by checking above spawn location to see if head/torso would be visible */
|
|
var float HeadVisibilityOffset;
|
|
|
|
/** Navigation Handle used by agents requesting pathing */
|
|
var class<NavigationHandle> NavigationHandleClass;
|
|
var NavigationHandle NavigationHandle;
|
|
|
|
/** Agent requesting navigation handle use */
|
|
var GameCrowdAgent QueryingAgent;
|
|
|
|
var array<CrowdSpawnerPlayerInfo> PlayerInfo;
|
|
var float LastPlayerInfoUpdateTime;
|
|
|
|
var(Debug) bool bDebugSpawns;
|
|
var(Debug) bool bPauseCrowd;
|
|
|
|
cpptext
|
|
{
|
|
virtual UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
|
|
virtual void TickSpawnInfo( FCrowdSpawnInfoItem& Item, FLOAT DeltaTime );
|
|
|
|
virtual void GetAlwaysRelevantDynamics( AGameCrowdAgent* Agent );
|
|
|
|
/** Interface_NavigationHandle implementation to grab search params */
|
|
virtual void SetupPathfindingParams( FNavMeshPathParams& out_ParamCache );
|
|
virtual void InitForPathfinding() {}
|
|
virtual INT ExtraEdgeCostToAddWhenActive(FNavMeshEdgeBase* Edge) { return 0; }
|
|
virtual FVector GetEdgeZAdjust(FNavMeshEdgeBase* Edge);
|
|
|
|
UBOOL GetSpawnInfoItem( USeqAct_GameCrowdPopulationManagerToggle* inAction, FCrowdSpawnInfoItem*& out_Spawner, UBOOL bCreateIfNotFound = 0 );
|
|
}
|
|
|
|
function PostBeginPlay()
|
|
{
|
|
local GameCrowdDestination GCD;
|
|
|
|
Super.PostBeginPlay();
|
|
|
|
if( !bDeleteMe )
|
|
{
|
|
WorldInfo.PopulationManager = self;
|
|
}
|
|
|
|
if( NavigationHandleClass != None )
|
|
{
|
|
NavigationHandle = new(self) NavigationHandleClass;
|
|
}
|
|
|
|
// add spawn points that have already begun play
|
|
foreach AllActors(class'GameCrowdDestination', GCD)
|
|
{
|
|
AddSpawnPoint(GCD);
|
|
}
|
|
}
|
|
|
|
// Interface_navigationhandle stub - called when path edge is deleted that this controller is using
|
|
event NotifyPathChanged();
|
|
|
|
function AddSpawnPoint( GameCrowdDestination GCD )
|
|
{
|
|
if( GCD.MyPopMgr != None || !GCD.bAllowCloudSpawning )
|
|
{
|
|
return;
|
|
}
|
|
GCD.MyPopMgr = self;
|
|
GlobalPotentialSpawnPoints[GlobalPotentialSpawnPoints.Length] = GCD;
|
|
|
|
if( ActiveCrowdInfoVolume == None )
|
|
{
|
|
CloudSpawnInfo.PotentialSpawnPoints[CloudSpawnInfo.PotentialSpawnPoints.Length] = GCD;
|
|
}
|
|
}
|
|
|
|
function RemoveSpawnPoint(GameCrowdDestination GCD)
|
|
{
|
|
local int Idx, AgentIdx;
|
|
|
|
GCD.MyPopMgr = None;
|
|
|
|
// remove from potential spawnpoints and prioritized spawn points list
|
|
// also remove agents moving toward unloaded spawn point
|
|
CloudSpawnInfo.PotentialSpawnPoints.RemoveItem( GCD );
|
|
CloudSpawnInfo.PrioritizedSpawnPoints.RemoveItem( GCD );
|
|
GlobalPotentialSpawnPoints.RemoveItem( GCD );
|
|
for( AgentIdx = 0; AgentIdx < CloudSpawnInfo.ActiveAgents.Length; AgentIdx++ )
|
|
{
|
|
if( CloudSpawnInfo.ActiveAgents[AgentIdx].CurrentDestination == GCD )
|
|
{
|
|
CloudSpawnInfo.ActiveAgents[AgentIdx].Destroy();
|
|
}
|
|
}
|
|
|
|
|
|
for( Idx = 0; Idx < ScriptedSpawnInfo.Length; Idx++ )
|
|
{
|
|
ScriptedSpawnInfo[Idx].PotentialSpawnPoints.RemoveItem( GCD );
|
|
ScriptedSpawnInfo[Idx].PrioritizedSpawnPoints.RemoveItem( GCD );
|
|
|
|
for( AgentIdx = 0; AgentIdx < ScriptedSpawnInfo[Idx].ActiveAgents.Length; AgentIdx++ )
|
|
{
|
|
if( ScriptedSpawnInfo[Idx].ActiveAgents[AgentIdx].CurrentDestination == GCD )
|
|
{
|
|
ScriptedSpawnInfo[Idx].ActiveAgents[AgentIdx].Destroy();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function SetCrowdInfoVolume( GameCrowdInfoVolume Vol )
|
|
{
|
|
if( Vol != ActiveCrowdInfoVolume )
|
|
{
|
|
ActiveCrowdInfoVolume = Vol;
|
|
if( Vol != None )
|
|
{
|
|
CloudSpawnInfo.PotentialSpawnPoints = Vol.PotentialSpawnPoints;
|
|
}
|
|
else
|
|
{
|
|
CloudSpawnInfo.PotentialSpawnPoints = GlobalPotentialSpawnPoints;
|
|
}
|
|
|
|
CloudSpawnInfo.PrioritizedSpawnPoints.Length = 0;
|
|
CloudSpawnInfo.PrioritizationIndex = 0;
|
|
CloudSpawnInfo.PrioritizationUpdateIndex = 0;
|
|
}
|
|
}
|
|
|
|
event int CreateSpawner( SeqAct_GameCrowdPopulationManagerToggle inAction )
|
|
{
|
|
local int Idx;
|
|
Idx = ScriptedSpawnInfo.Length;
|
|
ScriptedSpawnInfo.Length = Idx + 1;
|
|
ScriptedSpawnInfo[Idx].SeqSpawner = inAction;
|
|
return Idx;
|
|
}
|
|
|
|
/** Instantly destroy all active agents controlled by this manager. Useful for debugging. */
|
|
event FlushAgents( CrowdSpawnInfoItem Item )
|
|
{
|
|
local int AgentIdx;
|
|
|
|
for( AgentIdx = 0; AgentIdx < Item.ActiveAgents.Length; AgentIdx++ )
|
|
{
|
|
Item.ActiveAgents[AgentIdx].Destroy();
|
|
}
|
|
Item.ActiveAgents.Length = 0;
|
|
}
|
|
event FlushAllAgents()
|
|
{
|
|
local int Idx;
|
|
FlushAgents( CloudSpawnInfo );
|
|
for( Idx = 0; Idx < ScriptedSpawnInfo.Length; Idx++ )
|
|
{
|
|
FlushAgents( ScriptedSpawnInfo[Idx] );
|
|
}
|
|
}
|
|
|
|
function AgentDestroyed( GameCrowdAgent Agent )
|
|
{
|
|
local int SpawnerIdx;
|
|
local int i;
|
|
|
|
SpawnerIdx = ScriptedSpawnInfo.Find('SeqSpawner', SeqAct_GameCrowdPopulationManagerToggle(Agent.MySpawner));
|
|
if( SpawnerIdx >= 0 )
|
|
{
|
|
// now modify the CurrSpawned amount for this archetype since we just destroyed one
|
|
for( i = 0; i < ScriptedSpawnInfo[SpawnerIdx].AgentArchetypes.Length; i++ )
|
|
{
|
|
if( GameCrowdAgent(ScriptedSpawnInfo[SpawnerIdx].AgentArchetypes[i].AgentArchetype) == Agent.MyArchetype )
|
|
{
|
|
ScriptedSpawnInfo[SpawnerIdx].AgentArchetypes[i].CurrSpawned--;
|
|
//`log( GetFuncName() @ `showvar(AgentArchetypes[i].AgentArchetype) @ `showvar(AgentArchetypes[i].CurrSpawned) );
|
|
}
|
|
}
|
|
|
|
ScriptedSpawnInfo[SpawnerIdx].ActiveAgents.RemoveItem( Agent );
|
|
}
|
|
else if( Agent.MySpawner != None )
|
|
{
|
|
// now modify the CurrSpawned amount for this archetype since we just destroyed one
|
|
for( i = 0; i < CloudSpawnInfo.AgentArchetypes.Length; i++ )
|
|
{
|
|
if( GameCrowdAgent(CloudSpawnInfo.AgentArchetypes[i].AgentArchetype) == Agent.MyArchetype )
|
|
{
|
|
CloudSpawnInfo.AgentArchetypes[i].CurrSpawned--;
|
|
//`log( GetFuncName() @ `showvar(AgentArchetypes[i].AgentArchetype) @ `showvar(AgentArchetypes[i].CurrSpawned) );
|
|
}
|
|
}
|
|
|
|
CloudSpawnInfo.ActiveAgents.RemoveItem( Agent );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use 'GameDebug' console command to show this debug info
|
|
* Useful to show general debug info not tied to a particular concrete actor.
|
|
*/
|
|
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
|
|
{
|
|
local Canvas Canvas;
|
|
local int RenderedNum, LOSNum, SimNum, ActualCount, DistanceBucket[20], i, RVONum;
|
|
local Actor HitActor;
|
|
local vector HitNormal, HitLocation;
|
|
local GameCrowdAgent GCA;
|
|
local float Dist;
|
|
local array<GameCrowdAgent> AgentList;
|
|
local int PlayerIdx, SpawnIdx;
|
|
local bool bHasLOS;
|
|
local float BucketSize;
|
|
|
|
Canvas = HUD.Canvas;
|
|
Canvas.SetDrawColor(255,255,255);
|
|
|
|
Canvas.SetPos(4,out_YPos);
|
|
Canvas.DrawText("---- GameCrowdPopulationManager ---");
|
|
out_YPos += out_YL;
|
|
|
|
if( !GetPlayerInfo() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// calculate number of agents being rendered, simulated, and in player's LOS
|
|
ForEach DynamicActors(class'GameCrowdAgent', GCA)
|
|
{
|
|
if( !GCA.bDeleteMe )
|
|
{
|
|
AgentList[AgentList.Length] = GCA;
|
|
}
|
|
}
|
|
|
|
BucketSize = (2.f*CloudSpawnInfo.MaxSpawnDist) / ArrayCount(DistanceBucket);
|
|
foreach AgentList(GCA)
|
|
{
|
|
ActualCount++;
|
|
if( GCA.Health > 0 )
|
|
{
|
|
if( GCA.bSimulateThisTick )
|
|
{
|
|
SimNum++;
|
|
}
|
|
if( `TimeSince(GCA.LastRenderTime) < 1.0 && (GCA.LastRenderTime != GCA.InitialLastRenderTime) )
|
|
{
|
|
bHasLOS = TRUE;
|
|
RenderedNum++;
|
|
}
|
|
else
|
|
{
|
|
bHasLOS = FALSE;
|
|
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
|
|
{
|
|
HitActor = Trace( HitLocation, HitNormal, GCA.Location, PlayerInfo[PlayerIdx].ViewLocation, FALSE );
|
|
if( HitActor == None )
|
|
{
|
|
bHasLOS = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if( bHasLOS )
|
|
{
|
|
LOSNum++;
|
|
if( GCA.bSimulateThisTick )
|
|
{
|
|
RVONum++;
|
|
}
|
|
}
|
|
|
|
GCA.bSimulateThisTick = FALSE;
|
|
}
|
|
Dist = 999999.f;
|
|
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
|
|
{
|
|
Dist = FMin(VSize(PlayerInfo[PlayerIdx].ViewLocation - GCA.Location), Dist);
|
|
}
|
|
DistanceBucket[Min(19, int(Dist/BucketSize))]++;
|
|
}
|
|
|
|
Canvas.DrawText("TotalCount: "$ActualCount );
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
|
|
Canvas.DrawText("Cloud:"@CloudSpawnInfo.ActiveAgents.Length@"Active:"@CloudSpawnInfo.bSpawningActive );
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
for( SpawnIdx = 0; SpawnIdx < ScriptedSpawnInfo.Length; SpawnIdx++ )
|
|
{
|
|
Canvas.DrawText("Scripted: "$ScriptedSpawnInfo[SpawnIdx].ActiveAgents.Length@ScriptedSpawnInfo[SpawnIdx].SeqSpawner@"Active:"@ScriptedSpawnInfo[SpawnIdx].bSpawningActive );
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
}
|
|
|
|
Canvas.DrawText("Agents Rendered:"@RenderedNum);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
Canvas.DrawText("Agents LOS:"@LOSNum);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
Canvas.DrawText("Agents Simulated:"@SimNum);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
Canvas.DrawText("Agents RVO:"@RVONum);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
|
|
Canvas.DrawText("Distance Buckets");
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
for ( i=0; i<19; i++ )
|
|
{
|
|
if( DistanceBucket[i] > 0 )
|
|
{
|
|
Canvas.DrawText(" (<"$BucketSize * (i+1)$")"$DistanceBucket[i]);
|
|
out_YPos += out_YL;
|
|
Canvas.SetPos(4,out_YPos);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/** returns whether we want spawning to currently be active */
|
|
function bool IsSpawningActive()
|
|
{
|
|
local int SpawnerIdx;
|
|
|
|
if( CloudSpawnInfo.bSpawningActive )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
for( SpawnerIdx = 0; SpawnerIdx < ScriptedSpawnInfo.Length; SpawnerIdx++ )
|
|
{
|
|
if( ScriptedSpawnInfo[SpawnerIdx].bSpawningActive )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
simulated function bool ShouldDebugDestinations()
|
|
{
|
|
return bDebugSpawns;
|
|
}
|
|
|
|
/**
|
|
* FIXMESTEVE - Nativize?
|
|
*/
|
|
function Tick( float DeltaTime )
|
|
{
|
|
`if(`notdefined(FINAL_RELEASE))
|
|
local GameCrowdDestination PickedSpawnPoint;
|
|
local int Idx;
|
|
local int PlayerIdx;
|
|
// local Color C;
|
|
`endif
|
|
|
|
`if(`notdefined(FINAL_RELEASE))
|
|
if( ShouldDebugDestinations() && GetPlayerInfo() )
|
|
{
|
|
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
|
|
{
|
|
DrawDebugBox( PlayerInfo[PlayerIdx].PredictLocation, vect(20,20,20), 255, 0, 0 );
|
|
DrawDebugBox( PlayerInfo[PlayerIdx].ViewLocation, vect(10,10,10), 255, 255, 255 );
|
|
DrawDebugLine( PlayerInfo[PlayerIdx].ViewLocation, PlayerInfo[PlayerIdx].ViewLocation + Vector(PlayerInfo[PlayerIdx].ViewRotation) * 64, 255, 255, 255 );
|
|
// DrawDebugSphere( PlayerInfo[PlayerIdx].ViewLocation, GetMaxSpawnDist(), 20, 255, 255, 255 );
|
|
}
|
|
|
|
for( Idx = 0; Idx < CloudSpawnInfo.PotentialSpawnPoints.Length; Idx++ )
|
|
{
|
|
PickedSpawnPoint = CloudSpawnInfo.PotentialSpawnPoints[Idx];
|
|
if( PickedSpawnPoint == None )
|
|
{
|
|
continue;
|
|
}
|
|
PickedSpawnPoint.AnalyzeSpawnPoint( PlayerInfo, CloudSpawnInfo.MaxSpawnDistSq, CloudSpawnInfo.bForceNavMeshPathing, NavigationHandle );
|
|
PickedSpawnPoint.PrioritizeSpawnPoint( PlayerInfo, CloudSpawnInfo.MaxSpawnDist );
|
|
PickedSpawnPoint.DrawDebug( PlayerInfo );
|
|
|
|
if( !ValidateSpawnAt( CloudSpawnInfo, PickedSpawnPoint) )
|
|
{
|
|
DrawDebugCylinder( PickedSpawnPoint.Location, PickedSpawnPoint.Location, PickedSpawnPoint.CylinderComponent.CollisionRadius, PickedSpawnPoint.CylinderComponent.CollisionHeight, 255, 0, 0 );
|
|
}
|
|
}
|
|
|
|
// for( Idx = 0; Idx < CloudSpawnInfo.ActiveAgents.Length; Idx++ )
|
|
// {
|
|
// C = CloudSpawnInfo.ActiveAgents[Idx].DebugAgentColor;
|
|
// DrawDebugLine( CloudSpawnInfo.ActiveAgents[Idx].DebugSpawnDest.Location, CloudSpawnInfo.ActiveAgents[Idx].Location, C.R, C.G, C.B );
|
|
// }
|
|
}
|
|
`endif
|
|
|
|
if( !bPauseCrowd && IsSpawningActive() )
|
|
{
|
|
UpdateAllSpawners( DeltaTime );
|
|
}
|
|
}
|
|
|
|
native function UpdateAllSpawners( float DeltaTime );
|
|
|
|
event bool UpdateSpawner( out CrowdSpawnInfoItem Item, float DeltaTime )
|
|
{
|
|
local GameCrowdDestination PickedSpawnPoint;
|
|
local GameCrowdAgent A;
|
|
local int NumSpawned;
|
|
|
|
if( !Item.bSpawningActive || Item.ActiveAgents.Length >= Item.SpawnNum )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if( Item.SeqSpawner != None )
|
|
{
|
|
Item.SeqSpawner.LastSpawnedList.Length = 0;
|
|
}
|
|
|
|
Item.Remainder += FMin( DeltaTime, 0.05f ) * Item.SpawnRate;
|
|
if( Item.Remainder > 1.f )
|
|
{
|
|
// Prioritize based on potential visibility and recently spawned agents
|
|
PrioritizeSpawnPoints( Item, DeltaTime );
|
|
|
|
// Spawn new agents for this tick
|
|
while( Item.Remainder > 1.f && Item.ActiveAgents.Length < Item.SpawnNum)
|
|
{
|
|
PickedSpawnPoint = PickSpawnPoint( Item );
|
|
if( PickedSpawnPoint != None )
|
|
{
|
|
PickedSpawnPoint.LastSpawnTime = WorldInfo.TimeSeconds;
|
|
A = SpawnAgent( Item, PickedSpawnPoint);
|
|
if( A != None )
|
|
{
|
|
NumSpawned++;
|
|
if( Item.SeqSpawner != None )
|
|
{
|
|
Item.SeqSpawner.LastSpawnedList.AddItem( A );
|
|
}
|
|
}
|
|
Item.Remainder -= 1.f;
|
|
}
|
|
else
|
|
{
|
|
Item.Remainder = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (NumSpawned>0);
|
|
}
|
|
|
|
/**
|
|
* @RETURN best spawn point to spawn next crowd agent
|
|
*/
|
|
event GameCrowdDestination PickSpawnPoint( out CrowdSpawnInfoItem Item )
|
|
{
|
|
local int StartingIndex, SpawnIdx;
|
|
local GameCrowdDestination Candidate;
|
|
|
|
// Go down prioritized list, make sure currently valid (still not visible if not prioritize frame)
|
|
StartingIndex = Min(Item.PrioritizationIndex, Item.PrioritizedSpawnPoints.Length);
|
|
for( SpawnIdx = 0; SpawnIdx < Item.PrioritizedSpawnPoints.Length; SpawnIdx++ )
|
|
{
|
|
Item.PrioritizationIndex = (StartingIndex + SpawnIdx) % Item.PrioritizedSpawnPoints.Length;
|
|
Candidate = Item.PrioritizedSpawnPoints[Item.PrioritizationIndex];
|
|
if( ValidateSpawnAt( Item, Candidate ) )
|
|
{
|
|
return Candidate;
|
|
}
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
native simulated function bool GetPlayerInfo();
|
|
native static simulated function bool StaticGetPlayerInfo( out array<CrowdSpawnerPlayerInfo> out_PlayerInfo );
|
|
|
|
/**
|
|
* Prioritize GameCrowdDestinations as potential spawn points
|
|
*/
|
|
event PrioritizeSpawnPoints( out CrowdSpawnInfoItem Item, float DeltaTime )
|
|
{
|
|
local int UpdateNum;
|
|
|
|
if( Item.PotentialSpawnPoints.Length == 0 || !GetPlayerInfo() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// calculate number of potential spawn points to prioritize this tick
|
|
UpdateNum = Max(1, DeltaTime * float(Item.PotentialSpawnPoints.Length)/Item.SpawnPrioritizationInterval);
|
|
|
|
// Analyze and prioritize a number of spawn points
|
|
AnalyzeSpawnPoints( Item, Item.PrioritizationUpdateIndex, UpdateNum );
|
|
Item.PrioritizationUpdateIndex = (Item.PrioritizationUpdateIndex + UpdateNum) % Item.PotentialSpawnPoints.Length;
|
|
}
|
|
|
|
function AnalyzeSpawnPoints( out CrowdSpawnInfoItem Item, int StartIndex, int NumToUpdate )
|
|
{
|
|
local int UpdateIdx, Idx, NumUpdated;
|
|
local GameCrowdDestination GCD;
|
|
|
|
if( StartIndex >= Item.PotentialSpawnPoints.Length || !GetPlayerInfo() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// determine potential visibility of all GameCrowdDestinations
|
|
NumUpdated = 0;
|
|
for( UpdateIdx = 0; NumUpdated < NumToUpdate && UpdateIdx < Item.PotentialSpawnPoints.Length; UpdateIdx++ )
|
|
{
|
|
Idx = (StartIndex + UpdateIdx) % Item.PotentialSpawnPoints.Length;
|
|
GCD = Item.PotentialSpawnPoints[Idx];
|
|
if( GCD == None )
|
|
{
|
|
Item.PotentialSpawnPoints.Remove(UpdateIdx--,1);
|
|
continue;
|
|
}
|
|
|
|
Item.PrioritizedSpawnPoints.RemoveItem(GCD);
|
|
if( GCD.AnalyzeSpawnPoint( PlayerInfo, Item.MaxSpawnDistSq, Item.bForceNavMeshPathing, NavigationHandle ) )
|
|
{
|
|
NumUpdated++;
|
|
|
|
// add GCD back to list if is potential spawn point
|
|
if( GCD.bCanSpawnHereNow )
|
|
{
|
|
AddPrioritizedSpawnPoint( Item, GCD );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prioritize passed in GameCrowdDestination and insert it into ordered PrioritizedSpawnPoints list, offset from current starting point
|
|
*/
|
|
function AddPrioritizedSpawnPoint( out CrowdSpawnInfoItem Item, GameCrowdDestination GCD )
|
|
{
|
|
local int SpawnIdx, StartingIndex;
|
|
|
|
GCD.PrioritizeSpawnPoint( PlayerInfo, Item.MaxSpawnDist );
|
|
|
|
// insert GCD into prioritized list
|
|
StartingIndex = Min(Item.PrioritizationIndex, Item.PrioritizedSpawnPoints.Length);
|
|
for( SpawnIdx = 0; SpawnIdx < Item.PrioritizedSpawnPoints.Length; SpawnIdx++ )
|
|
{
|
|
Item.PrioritizationIndex = (StartingIndex + SpawnIdx) % Item.PrioritizedSpawnPoints.Length;
|
|
if( Item.PrioritizedSpawnPoints[Item.PrioritizationIndex].Priority < GCD.Priority )
|
|
{
|
|
Item.PrioritizedSpawnPoints.Insert(Item.PrioritizationIndex, 1);
|
|
Item.PrioritizedSpawnPoints[Item.PrioritizationIndex] = GCD;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// add right at current index (and increment index since this one should be last
|
|
Item.PrioritizedSpawnPoints.Insert(StartingIndex, 1);
|
|
Item.PrioritizedSpawnPoints[StartingIndex] = GCD;
|
|
Item.PrioritizationIndex = (StartingIndex + 1) % Item.PrioritizedSpawnPoints.Length;
|
|
}
|
|
|
|
/**
|
|
* Determine whether candidate spawn point is currently valid
|
|
*/
|
|
function bool ValidateSpawnAt( out CrowdSpawnInfoItem Item, GameCrowdDestination Candidate)
|
|
{
|
|
local Actor HitActor;
|
|
local vector HitLocation, HitNormal;
|
|
local float DistSq, MinDistFromViewSq;
|
|
local float DestDotView;
|
|
local int PlayerIdx;
|
|
|
|
// make sure candidate not at capacity
|
|
if( !Candidate.bIsEnabled || !Candidate.bAllowsSpawning || Candidate.AtCapacity() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if( Candidate.bAllowVisibleSpawning )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// check that spawn point is not visible to player
|
|
if( GetPlayerInfo() )
|
|
{
|
|
MinDistFromViewSq = MaxInt;
|
|
for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ )
|
|
{
|
|
// if candidate is beyond max (normal) spawn dist, it's a special case and we don't mind if it is visible
|
|
// also don't mind if far away and not in view frustrum
|
|
DistSq = VSizeSq(Candidate.Location - PlayerInfo[PlayerIdx].ViewLocation);
|
|
MinDistFromViewSq = FMin( DistSq, MinDistFromViewSq );
|
|
if( DistSq < Item.MaxSpawnDistSq )
|
|
{
|
|
DestDotView = Normal(Candidate.Location - PlayerInfo[PlayerIdx].ViewLocation) DOT Vector(PlayerInfo[PlayerIdx].ViewRotation);
|
|
if( DistSq < Item.MinBehindSpawnDistSq || DestDotView > 0.7f )
|
|
{
|
|
HitActor = Trace(HitLocation, HitNormal, Candidate.Location + HeadVisibilityOffset*vect(0,0,1), PlayerInfo[PlayerIdx].ViewLocation, FALSE,,, TRACEFLAG_Bullet);
|
|
if( HitActor == None )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( MinDistFromViewSq < Item.MaxSpawnDistSq )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Actually create a new CrowdAgent actor, and initialise it
|
|
*/
|
|
native function GameCrowdAgent SpawnAgentByIdx( int SpawnerIdx, GameCrowdDestination SpawnLoc );
|
|
native function GameCrowdAgent SpawnAgent( out CrowdSpawnInfoItem Item, GameCrowdDestination SpawnLoc );
|
|
native function bool Warmup( out CrowdSpawnInfoItem Item, int WarmupNum );
|
|
|
|
/**
|
|
* Create new GameCrowdAgent and initialize it
|
|
*/
|
|
event GameCrowdAgent CreateNewAgent( out CrowdSpawnInfoItem Item, GameCrowdDestination SpawnLoc, GameCrowdAgent AgentTemplate, GameCrowdGroup NewGroup)
|
|
{
|
|
local GameCrowdAgent Agent;
|
|
local rotator SpawnRot;
|
|
local vector SpawnPos;
|
|
local int i;
|
|
|
|
// GameCrowdSpawnInterface provides spawn location (can be line/circle/volume/etc. based)
|
|
GameCrowdSpawnInterface(SpawnLoc).GetSpawnPosition(none, SpawnPos, SpawnRot);
|
|
if( !GetPlayerInfo() )
|
|
{
|
|
return None;
|
|
}
|
|
|
|
Agent = Spawn( AgentTemplate.Class,,,SpawnPos,SpawnRot,AgentTemplate);
|
|
Agent.SetLighting(Item.bEnableCrowdLightEnvironment, Item.AgentLightingChannel, Item.bCastShadows);
|
|
|
|
if( Item.bForceObstacleChecking )
|
|
{
|
|
Agent.bCheckForObstacles = TRUE;
|
|
}
|
|
if( Item.bForceNavMeshPathing )
|
|
{
|
|
Agent.bUseNavMeshPathing = TRUE;
|
|
}
|
|
|
|
// don't prefer visible paths on spawn if on soon to be visible start
|
|
if( SpawnLoc.bWillBeVisible )
|
|
{
|
|
Agent.bPreferVisibleDestinationOnSpawn = Agent.bPreferVisibleDestination;
|
|
}
|
|
|
|
Agent.MySpawner = GameCrowdSpawnerInterface(Item.SeqSpawner);
|
|
Item.ActiveAgents[Item.ActiveAgents.Length] = Agent;
|
|
Agent.InitializeAgent(SpawnLoc, PlayerInfo, AgentTemplate, NewGroup, Item.AgentWarmUpTime*2.0*FRand(), (Item.AgentWarmupTime>0.f), TRUE );
|
|
|
|
// now find the archetype and update the CurrSpawned
|
|
for( i = 0; i < Item.AgentArchetypes.Length; i++ )
|
|
{
|
|
if( GameCrowdAgent(Item.AgentArchetypes[i].AgentArchetype) == Agent.MyArchetype )
|
|
{
|
|
Item.AgentArchetypes[i].CurrSpawned++;
|
|
}
|
|
}
|
|
|
|
return Agent;
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
NavigationHandleClass=class'NavigationHandle'
|
|
|
|
SplitScreenNumReduction=0.5
|
|
|
|
PlayerPositionPredictionTime=5.0
|
|
|
|
HeadVisibilityOffset=40.0
|
|
|
|
RemoteRole=ROLE_None
|
|
NetUpdateFrequency=10
|
|
bHidden=TRUE
|
|
bOnlyDirtyReplication=TRUE
|
|
bSkipActorPropertyReplication=TRUE
|
|
}
|