/** * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ class GameCrowdAgent extends CrowdAgentBase native abstract hidecategories(Advanced) hidecategories(Attachment) hidecategories(Collision) hidecategories(Object) implements(Interface_RVO) dependson(GameCrowdAgentBehavior) placeable; // placeable only so LDs can create archetypes /** Agent group this agent is part of */ var GameCrowdGroup MyGroup; /** Velocity in the absence of other agent interactions */ var Vector PreferredVelocity; /** Velocity we will take next physics tick */ var Vector PendingVelocity; /** Current destination */ var GameCrowdDestination CurrentDestination; /** Last destination where performed Kismet/Behavior. Cleared when have new destination. Used to keep from looping kismet/behavior at destination. */ var GameCrowdDestination BehaviorDestination; /** where agent is coming from */ var GameCrowdDestination PreviousDestination; /** If conforming to ground, this is how much to move the agent each frame between line-trace updates. */ var float InterpZTranslation; /** Current health of agent */ var() int Health; /** How long dead body stays around */ var(Behavior) float DeadBodyDuration; /** Pointer to LightEnvironment */ var const editconst DynamicLightEnvironmentComponent LightEnvironment; /** Used to count how many frames since the last conform trace. */ var transient int ConformTraceFrameCount; /** Nearby pawns and agents. Updated periodically using main Octree */ var transient array NearbyDynamics; /** Whether to use same scale variation in all axes */ var bool bUniformScale; enum EConformType { CFM_NavMesh, CFM_BSP, CFM_World, CFM_None, }; /** How agent conforms to surfaces */ var(Movement) EConformType ConformType; /** Whether to have obstacle mesh block agents */ var(Pathing) bool bCheckForObstacles; /** How far to trace to conform agent to the bsp/world. */ var(Movement) float ConformTraceDist; /** Every how many frames the ground conforming line check is done. */ var(Movement) int ConformTraceInterval; /** Current conform interval */ var int CurrentConformTraceInterval; /** TEMP for debugging - last ground conform hit normal Z*/ var float LastGroundZ; // Agent force stuff /** Controls how far around an agent the system looks when finding the average speed. */ var(Pathing) float AwareRadius; /** The radius used to check overlap between agents (basically how big an agent is). */ var(Pathing) float AvoidOtherRadius; struct native AvoidOtherSampleItem { var() int RotOffset; var() byte NumMagSamples; var() bool bFallbackOnly; }; var(Pathing) array AvoidOtherSampleList; var(Pathing) float PENALTY_COEFF_ANGLETOGOAL; var(Pathing) float PENALTY_COEFF_ANGLETOVEL; var(Pathing) float PENALTY_COEFF_MAG; var(Pathing) float MIN_PENALTY_THRESHOLD; var(Pathing) float LastProgressTime; var(Pathing) float LastFallbackActiveTime; /** If TRUE, use navmesh for pathing */ var(Pathing) bool bUseNavMeshPathing; var(Pathing) float MaxPathLaneValue; var(Pathing) float CurrentPathLaneValue; var(Pathing) int ExtraPathCost; /** When a 'target' action occurs, agent will rotate to face the CrowdAttractor. This controls how fast that turn happens */ var(Movement) float RotateToTargetSpeed; /** Crowd agents rotate to face the direction they are travelling. This value limits how quickly they turn to do this, to avoid them spinning too quickly */ var(Movement) float MaxYawRate; /** Min 3D drawscale to apply to the agent mesh */ var(Rendering) vector MeshMinScale3D; /** Max 3D drawscale to apply to the agent mesh */ var(Rendering) vector MeshMaxScale3D; /** Note currently only checks if see player when being rendered */ var bool bWantsSeePlayerNotification; /** Eye Z offset from location */ var float EyeZOffset; /** Distance to LOD out proximity checks for non-visible agents */ var(LOD) float ProximityLODDist; /** Distance to LOD out proximity checks for visible agents */ var(LOD) float VisibleProximityLODDist; /** Last position validated by collision trace */ var vector LastKnownGoodPosition; /** Distance from ground to agent center (used to adjust foot positioning) */ var(Rendering) float GroundOffset; /** Current movement destination intermediate to reaching CurrentDestination */ var vector IntermediatePoint; /** bounding box to use for pathing queries */ var vector SearchExtent; // Whether agent is allowed to pitch as he rotates toward his current velocity var(Movement) bool bAllowPitching; /** Navigation Handle used by agents requesting pathing */ var class NavigationHandleClass; var NavigationHandle NavigationHandle; /** flags set for debugging (set each tick) */ var bool bHitObstacle, bBadHitNormal; var int ObstacleCheckCount; /** Used for accessing potential obstacles - not an obstacle if hitnormal.Z > WalkableFloorZ */ var float WalkableFloorZ; /** Last time pathing was attempted for this agent */ var float LastPathingAttempt; /** Used to limit update frequency of agents that are not visible */ var float LastUpdateTime; /** Whether to perform crowd simulation this tick on this agent ( updated using ShouldPerformCrowdSimulation() )*/ var bool bSimulateThisTick; /** how long to wait before killing this agent when it isn't visible */ var(LOD) float NotVisibleLifeSpan; /** Archetype used to spawn this agent */ var GameCrowdAgent MyArchetype; /** Max walking speed (if not using root motion velocity)*/ var(Movement) float MaxWalkingSpeed; /** Max running speed (if not using root motion velocity)*/ var(Movement) float MaxRunningSpeed; /** Current max speed */ var float MaxSpeed; struct native RecentInteraction { var Name InteractionTag; var float InteractionDelay; }; var array RecentInteractions; /** Max distance to draw debug beacon */ var float BeaconMaxDist; /** Debug beacon offset from Location */ var vector BeaconOffset; /** Background texture for debug beacon */ var const Texture2D BeaconTexture; /** Beacon background color */ var const LinearColor BeaconColor; /** Ambient Sound cue played by this agent */ var() soundcue AmbientSoundCue; /** Ambient sound being played */ var AudioComponent AmbientSoundComponent; /** Current applied behavior instance */ var GameCrowdAgentBehavior CurrentBehavior; var float CurrentBehaviorActivationTime; /** Describes a behavior type and its frequency */ struct native BehaviorEntry { /** Archetype based on a GameCrowdAgentBehavior class */ var() GameCrowdAgentBehavior BehaviorArchetype; /** Optional actor to look at when performing this behavior */ var() Actor LookAtActor; /** How often this behavior is picked = BehaviorFrequency/(sum of BehaviorFrequencies) */ var() float BehaviorFrequency; /** If true, agent will never repeat this behavior */ var() bool bNeverRepeat; /** Whether this behavior has been used by this agent */ var bool bHasBeenUsed; /** Temp Cache whether this behavior can be used */ var bool bCanBeUsed; structdefaultproperties { BehaviorFrequency=1.0 } }; /** Behaviors to choose from when encounter another agent (only if no current behavior) */ var(Behavior) array EncounterAgentBehaviors; /** Set when updating dynamics if agent is potential encounter for updating agent - only valid in HandlePotentialAgentEncounter() event.*/ var bool bPotentialEncounter; /** Behaviors to choose from when see player (only if no current behavior) */ var(Behavior) array SeePlayerBehaviors; /** Calculated from behaviors in SeePlayerList */ var float MaxSeePlayerDistSq; /** How often see player event can be triggered. If 0, never retriggers */ var(Behavior) float SeePlayerInterval; /** Behaviors to choose from when agent spawns. */ var(Behavior) array SpawnBehaviors; /** Behaviors to choose from when agent panicks. */ var(Behavior) array UneasyBehaviors; var(Behavior) array AlertBehaviors; var(Behavior) array PanicBehaviors; /** Behaviors to choose from randomly at RandomBehaviorInterval. */ var(Behavior) array RandomBehaviors; /** Behaviors to choose from when the agent takes damage. */ var(Behavior) array TakeDamageBehaviors; /** Average time between random behavior attempt (only if visible to player and no current behavior) */ var(Behavior) float RandomBehaviorInterval; /** only used for animation now. Need to replace with more appropriately named property */ var bool bIsPanicked; /** World time when agent was spawned or last rendered */ var float ForceUpdateTime; /** Random variation in how closely agent must reach destination (0.5 to 1.0)*/ var float ReachThreshold; /** Whether should idle and wait for other group members */ var bool bWantsGroupIdle; /** Behaviors to choose from when waiting for other group members. */ var(Behavior) array GroupWaitingBehaviors; /** Try to keep Members this close together - probably will be obsolete when have formations */ var(Behavior) float DesiredGroupRadius; /** Keep square of DesiredGroupRadius for faster testing */ var float DesiredGroupRadiusSq; /** If true, agent will prefer destinations with line of sight to player if starting from non-L.O.S. destination */ var() bool bPreferVisibleDestination; /** If true, prefer visible destination only for first destination chosen after spawn */ var() bool bPreferVisibleDestinationOnSpawn; /** Max distance to keep agents around if they haven't been rendered in NotVisibleLifeSpan, but are still in player's potential line of sight */ var float MaxLOSLifeDistanceSq; /** Actor with GameCrowdSpawnerInterface which spawned this agent */ var GameCrowdSpawnerInterface MySpawner; /** True if already notified spawner about destruction */ var bool bHasNotifiedSpawner; /** true if agent is currently sitting in pop mgr's agent pool */ var bool bIsInSpawnPool; /** Used for keeping groups spawned together */ var vector SpawnOffset; /** Initial setting of LastRenderTime (used to see if agent was ever actually rendered) */ var float InitialLastRenderTime; var(Debug) bool bPaused; var(Debug) Color DebugAgentColor; var(Debug) GameCrowdDestination DebugSpawnDest; cpptext { virtual void PreBeginPlay(); virtual void PostBeginPlay(); virtual void PostScriptDestroyed(); virtual void GetActorReferences(TArray& ActorRefs, UBOOL bIsRemovingLevel); virtual UBOOL Tick( FLOAT DeltaSeconds, ELevelTick TickType ); virtual void performPhysics(FLOAT DeltaTime); virtual UBOOL ShouldTrace(UPrimitiveComponent* Primitive, AActor *SourceActor, DWORD TraceFlags); virtual void TickSpecial( FLOAT DeltaSeconds ); /** * If desired, take the current location, and using a line check, update its Z to match the ground better. * Returns FALSE if could not be conformed, and agent was killed. */ UBOOL UpdateInterpZTranslation(const FVector& NewLocation); /** * Update NearbyDynamics, RelevantAttractors * Also checks ReportOverlapsWithClass and calls OverlappedActorEvent if necessary */ void UpdateProximityInfo(); void CheckSeePlayer(); virtual void UpdatePendingVelocity( FLOAT DeltaTime ); UBOOL IsVelocityWithinConstraints( const FRotator& Dir, FLOAT Speed, FLOAT DeltaTime ); UBOOL VerifyDestinationIsClear(); UBOOL IsDestinationObstructed( const FVector& Dest ); virtual UBOOL IsValidNearbyDynamic( AActor* A ); virtual FLOAT GetInfluencePct( INT PriB ); virtual UBOOL WantsOverlapCheckWith(AActor* TestActor); /** * This will allow subclasses to implement specialized behavior for whether or not to actually simulate. * Example: You have hundreds of crowd agents and not all can be seen. So doing a distance check before simulating them * could save CPU. (distance check in stead of LastRender as you might want them moving before the viewer sees them). **/ virtual UBOOL ShouldPerformCrowdSimulation(FLOAT DeltaTime); /** Whether agent should end idling now */ virtual UBOOL ShouldEndIdle(); /** Check if reached intermediate point in route to destination */ UBOOL ReachedIntermediatePoint(); /** Clamp velocity to reach destination exactly */ virtual void ExactVelocity(FLOAT DeltaTime); /** Interface_NavigationHandle implementation to grab search params */ virtual void SetupPathfindingParams( FNavMeshPathParams& out_ParamCache ); virtual void InitForPathfinding(); virtual INT ExtraEdgeCostToAddWhenActive(FNavMeshEdgeBase* Edge); virtual FVector GetEdgeZAdjust(FNavMeshEdgeBase* Edge); /** * This function actually does the work for the GetDetailInfo and is virtual. * It should only be called from GetDetailedInfo as GetDetailedInfo is safe to call on NULL object pointers **/ virtual FString GetDetailedInfoInternal() const; virtual UBOOL IsActiveObstacle() { return TRUE; } virtual FLOAT GetAvoidRadius(); virtual INT GetInfluencePriority(); virtual FColor GetDebugAgentColor(); } native function Vector GetCollisionExtent(); /** called when the actor falls out of the world 'safely' (below KillZ and such) */ simulated event FellOutOfWorld(class dmgType) { Health = -1; Lifespan = -0.1; } /** * @RETURNS whether this agent is panicked (true if agent has a CurrentBehavior and CurrentBehavior.bIsPanicked==true */ native function bool IsPanicked(); /** * Pick a behavior from the BehaviorList, for a camera at BestCameraLoc, * and activate this behavior * Caller is responsible for setting bHasBeenUsed on picked behavior entry. * * @RETURNS true if new behavior was activated */ function bool PickBehaviorFrom(array BehaviorList, optional vector BestCameraLoc=vect(0,0,0) ) { local vector CameraLoc; local rotator CameraRot; local PlayerController PC; local float BestDistSq, NewDistSq; local int i; local float FreqSum, RandPick; if ( BestCameraLoc == vect(0,0,0) ) { // if camera location not passed in, find closest camera BestDistSq = 90000000.0; ForEach LocalPlayerControllers(class'PlayerController', PC) { PC.GetPlayerViewPoint(CameraLoc, CameraRot); NewDistSq = VSizeSq(CameraLoc - Location); if ( NewDistSq < BestDistSq ) { BestDistSq = NewDistSq; BestCameraLoc = CameraLoc; } } } // Pick a behavior to activate for ( i=0; i= FreqSum ) { return false; } // Activate the selected behavior for ( i=0; i DesiredGroupRadiusSq) && ((MyGroup.Members[i].Velocity dot (Location - MyGroup.Members[i].Location)) > 0.0)) { CurrentBehavior.ActionTarget = MyGroup.Members[i]; break; } } } } event SetCurrentDestination(GameCrowdDestination NewDest) { if( NewDest != CurrentDestination ) { if ( CurrentBehavior != None ) { CurrentBehavior.ChangingDestination(NewDest); } CurrentDestination = NewDest; CurrentDestination.IncrementCustomerCount(self); ReachThreshold = CurrentDestination.bSoftPerimeter ? 0.5 + 0.5*FRand() : 1.0; } } /** Set maximum movement speed */ function SetMaxSpeed() { MaxSpeed = IsPanicked()? MaxRunningSpeed : MaxWalkingSpeed; } simulated function PostBeginPlay() { local vector AgentScale3D; local int i; local float MaxSeePlayerDist; super.PostBeginPlay(); if ( bDeleteMe ) { return; } WorldInfo.bHaveActiveCrowd = true; // Randomize scale if( bUniformScale ) { AgentScale3D = MeshMinScale3D + (FRand() * (MeshMaxScale3D - MeshMinScale3D)); } else { AgentScale3D.X = RandRange(MeshMinScale3D.X, MeshMaxScale3D.X); AgentScale3D.Y = RandRange(MeshMinScale3D.Y, MeshMaxScale3D.Y); AgentScale3D.Z = RandRange(MeshMinScale3D.Z, MeshMaxScale3D.Z); } SetDrawScale3D(AgentScale3D); // assume starting point is valid LastKnownGoodPosition = Location; LastKnownGoodPosition.Z += EyeZOffset; ForceUpdateTime = WorldInfo.TimeSeconds; // init max speed SetMaxSpeed(); // init ambient sound if ( AmbientSoundCue != None ) { AmbientSoundComponent = new(self) class'AudioComponent'; if( AmbientSoundComponent != none ) { AttachComponent(AmbientSoundComponent); AmbientSoundComponent.SoundCue = AmbientSoundCue; AmbientSoundComponent.Play(); } } // init see player notification bWantsSeePlayerNotification = (SeePlayerBehaviors.Length > 0); for ( i=0; i 0 ) { settimer((0.8+0.4*FRand())*RandomBehaviorInterval, true, 'TryRandomBehavior'); } } /** * Kill this agent or add it to population manager's spawn pool */ event KillAgent() { if ( bIsInSpawnPool ) { return; } LifeSpan = -0.1; // make sure to tick right away to destroy TimeSinceLastTick = 1000.0; } /** * Agent is coming out of pool, so rev him up */ function ResetPooledAgent() { bIsInSpawnPool = false; SetHidden(false); BehaviorDestination = None; PreviousDestination = None; LifeSpan = 0.0; Health = default.Health; TimeSinceLastTick = 0.0; LastKnownGoodPosition = Location; LastKnownGoodPosition.Z += EyeZOffset; ForceUpdateTime = WorldInfo.TimeSeconds; SetMaxSpeed(); if ( RandomBehaviors.Length > 0 ) { settimer((0.8+0.4*FRand())*RandomBehaviorInterval, true, 'TryRandomBehavior'); } } simulated function Destroyed() { super.Destroyed(); if ( (MySpawner != None) && !bHasNotifiedSpawner ) { bHasNotifiedSpawner = true; MySpawner.AgentDestroyed(self); } if ( CurrentDestination != None ) { CurrentDestination.DecrementCustomerCount(self); CurrentDestination = None; } if ( MyGroup != None ) { MyGroup.RemoveMember(self); } } simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos) { local string T; local Canvas Canvas; super.DisplayDebug(HUD, out_YL, out_YPos); Canvas = HUD.Canvas; Canvas.SetPos(4, out_YPos); Canvas.SetDrawColor(255,0,0); T = GetDebugName(); if( bDeleteMe ) { T = T$" DELETED (bDeleteMe == true)"; } if( T != "" ) { Canvas.DrawText(T, FALSE); out_YPos += out_YL; Canvas.SetPos(4, out_YPos); } Canvas.SetDrawColor(255,255,255); Canvas.DrawText("Location:"@Location@"Rotation:"@Rotation@" Speed: "$VSize(Velocity)@"ZVel"@Velocity.Z, FALSE); out_YPos += out_YL; Canvas.SetPos(4,out_YPos); Canvas.DrawText("Hit obestacle:"@bHitObstacle@"BadHitNormal:"@bBadHitNormal@"count"@ObstacleCheckCount, FALSE); out_YPos += out_YL; Canvas.SetPos(4,out_YPos); Canvas.DrawText("Current conform interval:"@CurrentConformTraceInterval@"Base Conform Interval:"@ConformTraceInterval@" Last Ground Z "@LastGroundZ, FALSE); out_YPos += out_YL; Canvas.SetPos(4,out_YPos); if ( CurrentDestination == None ) { Canvas.DrawText("NO DESTINATION", FALSE); out_YPos += out_YL; Canvas.SetPos(4,out_YPos); } else { if ( NavigationHandle != None ) { NavigationHandle.DrawPathCache(); } T = "DESTINATION "$CurrentDestination; if ( MyGroup != None ) { T = T$" Group "$MyGroup; DrawDebugLine(MyGroup.Members[0].Location, Location, 255, 128, 0, FALSE); } Canvas.DrawText(T, FALSE); out_YPos += out_YL; Canvas.SetPos(4,out_YPos); if ( IntermediatePoint == CurrentDestination.Location ) { DrawDebugLine(IntermediatePoint, Location, 0, 128, 255, FALSE); } else { DrawDebugLine(IntermediatePoint, Location, 0, 255, 0, FALSE); DrawDebugLine(CurrentDestination.Location, Location, 255, 255, 0, FALSE); } } } /** * Set agent lighting * @PARAM bEnableLightEnvironment controls whether light environment is enabled * @PARAM AgentLightingChannel is the lighting channel to use (GameCrowdAgentSkeletal only) * @PARAM bCastShadows controls whether agent casts shadows (GameCrowdAgentSkeletal only) */ simulated function SetLighting(bool bEnableLightEnvironment, LightingChannelContainer AgentLightingChannel, bool bCastShadows) { // If desired, enable light env if(bEnableLightEnvironment) { LightEnvironment.SetEnabled(TRUE); } // If not, detach to stop it even getting updated else { DetachComponent(LightEnvironment); } } simulated function Vector GetAttemptedSpawnLocation( float Pct, Vector CurPos, float CurRadius, Vector DestPos, float DestRadius ) { local float MaxLateralOffset, LateralOffset; local Vector LateralDir; MaxLateralOffset = CurRadius + Pct * (DestRadius - CurRadius); LateralDir = Normal((CurPos - DestPos) CROSS vect(0,0,1)); LateralOffset = RandRange( -MaxLateralOffset, MaxLateralOffset ); return (Pct * DestPos) + ((1.f-Pct)*CurPos) + (LateralOffset * LateralDir); } /** * Initialize agent archetype, group, destination, and behavior */ simulated function InitializeAgent( Actor SpawnLoc, const out array PlayerInfo, GameCrowdAgent AgentTemplate, GameCrowdGroup NewGroup, float AgentWarmUpTime, bool bWarmupPosition, bool bCheckWarmupVisibility ) { local bool bGroupDestination, bRealPreferVisible; local GameCrowdDestination SpawnDest; local float TryPct, MaxSpawnDist, DestDist, StartDist; local vector TryLoc; local Actor HitActor; local vector HitLocation, HitNormal, NearestViewLocation, YAdjust; local bool bVisibleTryLoc, bFoundOption; local int CheckCnt, MaxCheckCnt, OptionIdx; local array TryOptions; local float SpawnDestRadius, TravelDestRadius; local int PlayerIdx; local float NearestViewDistSq, ViewDistSq; local bool bVisibleOption; MyArchetype = AgentTemplate; // let agent "warm up" and simulate a little before going to sleep LastRenderTime = WorldInfo.TimeSeconds + AgentWarmUpTime * (0.5 + FRand()); InitialLastRenderTime = LastRenderTime; // set group - maybe get destination from there if ( NewGroup != None ) { NewGroup.AddMember(self); if ( NewGroup.Members.Length > 1 ) { //already have leader, get destination from him bGroupDestination = true; SetCurrentDestination(NewGroup.Members[0].CurrentDestination); } } if ( !bGroupDestination ) { SpawnDest = GameCrowdDestination(SpawnLoc); if( SpawnDest != None ) { DebugSpawnDest = SpawnDest; // already at destination - pick a next destination from it SetCurrentDestination(SpawnDest); // ask for a new destination - with spawn preference for visible destination bRealPreferVisible = bPreferVisibleDestination; bPreferVisibleDestination = bPreferVisibleDestinationOnSpawn || !SpawnDest.bWillBeVisible; LastRenderTime = WorldInfo.TimeSeconds; // Update the agent's render time, so it doesn't get killed on initialization for having not been rendered in a while CurrentDestination.ReachedDestination(self); bPreferVisibleDestination = bRealPreferVisible; if ( CurrentDestination == None ) { `warn("INITIALIZING - NO CURRENTDESTINATION AFTER REACHING "$SpawnDest); } if ( bWarmupPosition ) { for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx++ ) { ViewDistSq = VSizeSq(PlayerInfo[PlayerIdx].ViewLocation-SpawnDest.Location); if( NearestViewDistSq == 0.f || ViewDistSq < NearestViewDistSq ) { NearestViewDistSq = ViewDistSq; NearestViewLocation = PlayerInfo[PlayerIdx].ViewLocation; } } if( NewGroup == None || NewGroup.Members.Length == 1 ) { // group leader or individual agent determines spawn offset // try randomizing position somewhere between spawn position and destination TryPct = FRand(); MaxSpawnDist = (MySpawner != None) ? MySpawner.GetMaxSpawnDist() : 0.0; if( SpawnDest.bIsBeyondSpawnDistance && MySpawner != None ) { DestDist = VSize(CurrentDestination.Location - NearestViewLocation); if ( CurrentDestination.bIsBeyondSpawnDistance || (DestDist > MaxSpawnDist) ) { TryPct = (DestDist < VSizeSq(SpawnDest.Location - NearestViewLocation)) ? 1.0 : 0.0; } else { // get close to max spawn dist StartDist = VSize(SpawnDest.Location - NearestViewLocation); if ( StartDist > DestDist ) { TryPct = 1.0 - (MaxSpawnDist - DestDist)/(StartDist - DestDist); TryPct *= 0.9; } else { TryPct = 0.0; } } } else if( !SpawnDest.bWillBeVisible ) { // bias spawning closer to point that will soon be visible, unless player is about to view this area TryPct = 0.5*TryPct + 0.5; } else { TryPct *= 0.9; } SpawnDestRadius = SpawnDest.GetDestinationRadius(); TravelDestRadius = CurrentDestination != None ? CurrentDestination.GetDestinationRadius() : SpawnDestRadius; TryLoc = GetAttemptedSpawnLocation( TryPct, SpawnDest.Location, SpawnDestRadius, CurrentDestination.Location, TravelDestRadius ); // make sure no player can see this intermediate spawn position bVisibleTryLoc = FALSE; if( NavigationHandle != None ) { bFoundOption = FALSE; CheckCnt = 0; MaxCheckCnt = 4; while( CheckCnt < MaxCheckCnt && !bFoundOption ) { TryOptions.Length = 0; NavigationHandle.GetValidPositionsForBox( TryLoc, 128.f, GetCollisionExtent(), FALSE, TryOptions, 1 ); for( OptionIdx = 0; OptionIdx < TryOptions.Length; OptionIdx++ ) { bVisibleOption = FALSE; for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx ++ ) { HitActor = Trace(HitLocation, HitNormal, PlayerInfo[PlayerIdx].ViewLocation, TryOptions[OptionIdx], FALSE); if( HitActor == None ) { // DrawDebugLine( PlayerInfo[PlayerIdx].ViewLocation, TryOptions[OptionIdx], 255, 0, 0, TRUE ); bVisibleOption = TRUE; break; } else { // DrawDebugLine( PlayerInfo[PlayerIdx].ViewLocation, TryOptions[OptionIdx], 0, 255, 0, TRUE ); } } if( !bVisibleOption ) { // not visible - allow spawn bFoundOption = TRUE; TryLoc = TryOptions[OptionIdx]; break; } } if( !bFoundOption ) { TryPct *= 0.5f; TryLoc = GetAttemptedSpawnLocation( TryPct, SpawnDest.Location, SpawnDestRadius, CurrentDestination.Location, TravelDestRadius ); CheckCnt++; } } bVisibleTryLoc = !bFoundOption; } if ( !bVisibleTryLoc ) { SpawnOffset = TryLoc; // most of the time, agent is fine any way, otherwise will just fall quickly out of the world SetLocation(TryLoc); // randomly turn some agents around if their spawn loc is about to become visible, and their current destination is visible if ( SpawnDest.bWillBeVisible && CurrentDestination.bIsVisible && (FRand()<0.5) ) { PreviousDestination = CurrentDestination; CurrentDestination.DecrementCustomerCount(self); CurrentDestination = None; BehaviorDestination = None; SetCurrentDestination(SpawnDest); } } else { // If current position is visible, teleport to spawn location, which we know was not visible for( PlayerIdx = 0; PlayerIdx < PlayerInfo.Length; PlayerIdx ++ ) { HitActor = Trace(HitLocation, HitNormal, Location, PlayerInfo[PlayerIdx].ViewLocation, FALSE); if( HitActor == None ) { SetLocation( SpawnDest.Location ); break; } } } } else { // group leader already determined offset, other group members will use same offset TryLoc = SpawnOffset; // try offsetting the agent so they aren't all in a line TryPct = 2.0 * FRand() - 1.0; YAdjust = TryLoc + TryPct * AvoidOtherRadius * Normal((CurrentDestination.Location - SpawnDest.Location) cross Vect(0,0,1)); HitActor = Trace(HitLocation, HitNormal, YAdjust, CurrentDestination.Location, false); if ( HitActor == None ) { TryLoc = YAdjust; } // find floor below candidate position, and adjust agent there HitActor = Trace(HitLocation, HitNormal, TryLoc - vect(0.0,0.0,250.0), TryLoc, false); if ( HitActor != None ) { TryLoc.Z = HitLocation.Z + GroundOffset + 5.0; } // most of the time, agent is fine any way, otherwise will just fall quickly out of the world SetLocation(TryLoc); } } } } LastKnownGoodPosition = Location; LastKnownGoodPosition.Z += EyeZOffset; // apply spawn behavior to agent if ( SpawnBehaviors.Length > 0 ) { PlaySpawnBehavior(); } UpdateIntermediatePoint(); InitDebugColor(); } simulated function OnPlayAgentAnimation(SeqAct_PlayAgentAnimation Action) { // non-skeletal agent can't play animation, so get next destination CurrentDestination.ReachedDestination(self); } /** * Play a looping idle animation */ simulated event PlayIdleAnimation(); simulated event StopIdleAnimation(); /** * Called when agent encounters another agent * NearbyDynamics list has been updated with agents, and potential encounters have their bPotentialEncounter set to true */ event HandlePotentialAgentEncounter() { if ( CurrentBehavior == None ) { PickBehaviorFrom(EncounterAgentBehaviors); } } /** * Called when agent spawns and has SpawnBehaviors set */ function PlaySpawnBehavior() { if ( CurrentBehavior == None ) { PickBehaviorFrom(SpawnBehaviors); } } /** * Called when see PC's pawn where PC is a local playercontroller, * Notification only occurs if bHasSeePlayerKismet=true */ event NotifySeePlayer(PlayerController PC) { local bool bFoundBehavior; local int i; bWantsSeePlayerNotification = false; // FIXMESTEVE - should check if current behavior can be overwritten and/or paused, and if so just pause it (keep it in current state) if ( CurrentBehavior == None ) { if ( !PickBehaviorFrom(SeePlayerBehaviors, PC.Pawn.Location) ) { // maybe all behaviors have been used and can't be re-used for ( i=0; i 0.0 ) { SetTimer( (0.8+0.4*FRand())*SeePlayerInterval, false, 'ResetSeePlayer'); } } /** * Called when random behavior timer expires * If not currently in behavior AND player can see me, do a random behavior. */ function TryRandomBehavior() { local bool bFoundBehavior; local int i; // FIXMESTEVE - should check if current behavior can be overwritten and/or paused, and if so just pause it (keep it in current state) if ( (CurrentBehavior == None) && (WorldInfo.TimeSeconds - LastRenderTime < 0.1) ) { if ( !PickBehaviorFrom(RandomBehaviors) ) { // maybe all behaviors have been used and can't be re-used for ( i=0; i DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { if ( Health > 0 ) { Health -= DamageAmount; if(Health <= 0) { Health = -1; SetCollision(FALSE, FALSE, FALSE); // Turn off all collision when dead. PlayDeath(normal(Momentum) * DamageType.default.KDamageImpulse + Vect(0,0,1)*DamageType.default.KDeathUpKick); } else { if ( CurrentBehavior == None ) { // Agent is still alive and there is no current behavior, start a take damage behavior PickBehaviorFrom(TakeDamageBehaviors); } } } } /** Called when crowd agent overlaps something in the ReportOverlapsWithClass list */ event OverlappedActorEvent(Actor A); /** spawn and init Navigation Handle */ event InitNavigationHandle() { if( NavigationHandleClass != None ) { NavigationHandle = new(self) NavigationHandleClass; } } /** * Generate a path to Goal on behalf of the QueryingAgent */ event vector GeneratePathToActor( Actor Goal, optional float WithinDistance, optional bool bAllowPartialPath ) { local vector NextDest; LastPathingAttempt = WorldInfo.TimeSeconds; NextDest = Goal.Location; // make sure we have a valid navigation handle if ( NavigationHandle == None ) { InitNavigationHandle(); } if( (NavigationHandle != None) && !NavigationHandle.ActorReachable(Goal) ) { class'NavMeshPath_Toward'.static.TowardGoal( NavigationHandle, Goal ); class'NavMeshGoal_At'.static.AtActor( NavigationHandle, Goal, WithinDistance, bAllowPartialPath ); if ( NavigationHandle.FindPath() ) { NavigationHandle.GetNextMoveLocation(NextDest, SearchExtent.X); } NavigationHandle.ClearConstraints(); } return NextDest; } simulated native function NativePostRenderFor(PlayerController PC, Canvas Canvas, vector CameraPosition, vector CameraDir); /** PostRenderFor() Hook to allow agents to render HUD overlays for themselves. Called only if the agent was rendered this tick. Assumes that appropriate font has already been set */ simulated event PostRenderFor(PlayerController PC, Canvas Canvas, vector CameraPosition, vector CameraDir) { local float NameXL, TextXL, BehavXL, TextYL, YL, XL; local vector ScreenLoc; local string ScreenName, DestString, BehaviorString; local FontRenderInfo FontInfo; // make sure not clipped out screenLoc = Canvas.Project(Location + BeaconOffset); if (screenLoc.X < 0 || screenLoc.X >= Canvas.ClipX || screenLoc.Y < 0 || screenLoc.Y >= Canvas.ClipY) { return; } ScreenName = "Agent"@self; if ( MyGroup != None ) { ScreenName = ScreenName$" Group "$MyGroup; DrawDebugLine(MyGroup.Members[0].Location, Location, 255, 0, 255, FALSE); } ScreenName = ScreenName@"Last Rendered"@(WorldInfo.TimeSeconds - LastRenderTime); Canvas.StrLen(ScreenName, NameXL, TextYL); XL = FMax(XL, NameXL); YL += TextYL; DestString = GetDestString(); Canvas.StrLen(DestString, TextXL, TextYL); XL = FMax(XL, TextXL); YL += TextYL; BehaviorString = GetBehaviorString(); Canvas.StrLen(BehaviorString, BehavXL, TextYL); XL = FMax(XL, BehavXL); YL += TextYL; Canvas.SetPos(ScreenLoc.X-0.7*XL, ScreenLoc.Y-1.8*YL); Canvas.DrawTile(BeaconTexture, 1.4*XL, 1.2*YL, 0,0,31,31, BeaconColor); Canvas.DrawColor = class'HUD'.default.GreenColor; Canvas.SetPos(ScreenLoc.X-0.5*NameXL,ScreenLoc.Y-1.7*YL); FontInfo.bClipText = true; Canvas.DrawText(ScreenName, true,,, FontInfo); Canvas.SetPos(ScreenLoc.X-0.5*TextXL,ScreenLoc.Y-1.7*YL + 1.1*TextYL); FontInfo.bClipText = true; Canvas.DrawText(DestString, true,,, FontInfo); Canvas.SetPos(ScreenLoc.X-0.5*BehavXL,ScreenLoc.Y-1.7*YL + 2.2*TextYL); FontInfo.bClipText = true; Canvas.DrawText(BehaviorString, true,,, FontInfo); // draw line to current destination if ( CurrentDestination != None ) { DrawDebugLine(Location, CurrentDestination.Location, 255, 255, 0 , false); } } /** * Get debug string about agent destination and movement status */ function string GetDestString() { local string DestString; DestString = (CurrentDestination == None) ? "NO DESTINATION" : ""$CurrentDestination; if ( IsIdle() ) { DestString = ((CurrentDestination != None) && CurrentDestination.ReachedByAgent(self, Location, true)) ? "Idle at "$DestString : "Idle en route to "$DestString; } else { DestString = "Moving to "$DestString; } return DestString; } /** * Get debug string about agent behavior */ function string GetBehaviorString() { local string BehaviorString; if ( CurrentBehavior != None ) { BehaviorString = CurrentBehavior.GetBehaviorString(); } else { BehaviorString = "Moving between Destinations"; } return BehaviorString; } simulated function InitDebugColor() { // DebugAgentColor.R = 30 + Rand(220); DebugAgentColor.G = 50 + Rand(205); // DebugAgentColor.B = 30 + Rand(220); } defaultproperties { NavigationHandleClass=class'NavigationHandle' Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment bEnabled=FALSE InvisibleUpdateTime=5.0 MinTimeBetweenFullUpdates=2.0 TickGroup=TG_DuringAsyncWork bCastShadows=true End Object Components.Add(MyLightEnvironment) LightEnvironment=MyLightEnvironment MeshMinScale3D=(X=1.0,Y=1.0,Z=1.0) MeshMaxScale3D=(X=1.0,Y=1.0,Z=1.0) bUniformScale=TRUE Health=100 ConformTraceDist=35.0 ConformTraceInterval=10 CurrentConformTraceInterval=10 AvoidOtherRadius=32.0 AwareRadius=256.0 // AwareRadius=1024.0 AvoidOtherSampleList.Add((RotOffset=0,NumMagSamples=10)) // 0 degrees AvoidOtherSampleList.Add((RotOffset=2048,NumMagSamples=8)) // 11 degrees AvoidOtherSampleList.Add((RotOffset=-2048,NumMagSamples=8)) AvoidOtherSampleList.Add((RotOffset=4096,NumMagSamples=6)) // 22 degrees AvoidOtherSampleList.Add((RotOffset=-4096,NumMagSamples=6)) AvoidOtherSampleList.Add((RotOffset=6144,NumMagSamples=4)) // 33 degrees AvoidOtherSampleList.Add((RotOffset=-6144,NumMagSamples=4)) AvoidOtherSampleList.Add((RotOffset=8192,NumMagSamples=4)) // 45 degrees AvoidOtherSampleList.Add((RotOffset=-8192,NumMagSamples=4)) AvoidOtherSampleList.Add((RotOffset=12288,NumMagSamples=2)) // 67 degrees AvoidOtherSampleList.Add((RotOffset=-12288,NumMagSamples=2)) AvoidOtherSampleList.Add((RotOffset=16384,NumMagSamples=1,bFallbackOnly=TRUE)) // 90 degrees AvoidOtherSampleList.Add((RotOffset=-16384,NumMagSamples=1,bFallbackOnly=TRUE)) AvoidOtherSampleList.Add((RotOffset=24576,NumMagSamples=1,bFallbackOnly=TRUE)) // 135 degrees AvoidOtherSampleList.Add((RotOffset=-24576,NumMagSamples=1,bFallbackOnly=TRUE)) AvoidOtherSampleList.Add((RotOffset=32768,NumMagSamples=1,bFallbackOnly=TRUE)) // 180 degrees ExtraPathCost=50 MaxPathLaneValue=10.f PENALTY_COEFF_ANGLETOGOAL=2.5f PENALTY_COEFF_ANGLETOVEL=1.f PENALTY_COEFF_MAG=1.f MIN_PENALTY_THRESHOLD=0.05f EyeZOffset=40.0 RotateToTargetSpeed=30000.0 MaxYawRate=40000.0 TickGroup=TG_DuringAsyncWork Physics=PHYS_Interpolating bStatic=FALSE bCollideActors=TRUE bBlockActors=FALSE bWorldGeometry=FALSE bCollideWorld=FALSE bProjTarget=TRUE bUpdateSimulatedPosition=FALSE bNoEncroachCheck=TRUE bPreferVisibleDestinationOnSpawn=TRUE RemoteRole=ROLE_None bNoDelete=false ProximityLODDist=2000.0 VisibleProximityLODDist=5000.0 ConformType=CFM_NavMesh GroundOffset=86.0 bCheckForObstacles=FALSE bUseNavMeshPathing=TRUE SearchExtent=(X=32.0,Y=32.0,Z=86.0) WalkableFloorZ=0.7 NotVisibleLifeSpan=10.f MaxLOSLifeDistanceSq=400000000.0 MaxWalkingSpeed=100.0 MaxRunningSpeed=300.0 SupportedEvents.Add(class'SeqEvent_TakeDamage') SupportedEvents.Add(class'SeqEvent_Death') BeaconMaxDist=1500.0 BeaconOffset=(x=0.0,y=0.0,z=140.0) BeaconTexture=Texture2D'EngineResources.WhiteSquareTexture' BeaconColor=(R=0.5f, G=0.5f, B=0.5f, A=0.5f) DeadBodyDuration=10.f SeePlayerInterval=0.0 RandomBehaviorInterval=30.0 ReachThreshold=1.0 DesiredGroupRadius=200.0 }