//============================================================================= // KFEmit_Path //============================================================================= // Path emitter //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC // - Christian "schneidzekk" Schneider //============================================================================ class KFEmit_Path extends Emitter config(Game) abstract; const MAX_WAYPOINTS = 24; /** custom height offset for waypoints */ var const int WAYPOINT_HEIGHT; /** custom height offset for waypoints on nav mesh */ var const int WAYPOINT_HEIGHT_NAV_MESH; var const float MinDistanceBetweenWayPointsOnNavMesh; /** The Template to use for this emitter */ var ParticleSystem EmitterTemplate; /** path points to travel to */ var array WayPoints; /** total number of valid points in WayPoints list */ var int NumPoints; /** current position in WayPoints list */ var int Position; /********************************************************************************************* * Acceleration physics (KF1 style trails) ********************************************************************************************* */ /** Increases velocity correction to tigthen curve */ var const float CURVE_TURNRATE; /** Acceleration magnitude */ var const float ACCEL_RATE; /** If set, use KF1 style acceleration movement physics */ var bool bUseAccelerationPhysics; /** heading direction used for accel based movement */ var bool bHeadedRight; var const bool bShowEmitPathDebugArtifacts; simulated function PostBeginPlay() { local int i, Start; local KFPlayerController P; local Actor HitActor; local Vector HitLocation,HitNormal; Super.PostBeginPlay(); P = KFPlayerController(Owner); if ( P == None || P.Pawn == None ) { Destroy(); } else { SetTemplate(EmitterTemplate,true); SetLocation(P.Pawn.Location); WayPoints[0] = Location + (WAYPOINT_HEIGHT * vect(0,0,1)) + (200.0 * vector(P.Rotation)); HitActor = Trace(HitLocation, HitNormal, WayPoints[0], Location, false); if ( HitActor != None ) { WayPoints[0] = HitLocation; } NumPoints++; if( class'KFAIController'.default.bUseNavMesh ) { //BuildWaypointsFromNavMeshPathCache(); //if( class'KFAIController'.default.bShowDoorNavigationDebugArtifacts ) //{ // class'KFAIController'.static.ShowAdvancedRouteEdgeDebugInfo( P.MyKFNavigationHandle, true ); //} } else { if ( P.RouteCache[0] != None && P.RouteCache.length > 1 && P.ActorReachable(P.RouteCache[1]) ) { Start = 1; } for (i = Start; NumPoints < MAX_WAYPOINTS && i < P.RouteCache.length && P.RouteCache[i] != None; i++) { WayPoints[NumPoints++] = P.RouteCache[i].Location + (WAYPOINT_HEIGHT * vect(0,0,1)); } } StartNextPath(); } } /** Override the final waypoint (optional) */ simulated function SetDestination(vector Destination) { if ( NumPoints > 1 ) { WayPoints[NumPoints-1] = Destination; } } simulated event SetInitialState() { bScriptInitialized = true; if ( PlayerController(Owner).IsLocalPlayerController() ) { StartNextPath(); } else { //@warning: can't set bHidden because that would get replicated ParticleSystemComponent.DeactivateSystem(); SetPhysics(PHYS_None); } } simulated event SetTemplate(ParticleSystem NewTemplate, optional bool bDestroyOnFinish) { Super(Emitter).SetTemplate(NewTemplate, bDestroyOnFinish); } simulated function StartNextPath() { local Pawn OwnerPawn; local KFPlayerController KFPC; KFPC = KFPlayerController(Owner); if ( ShouldStopPathing() ) { Destroy(); return; } ++Position; if ( Position >= NumPoints || Position >= WayPoints.Length ) { LifeSpan = 5.0; Velocity = vect(0,0,0); ParticleSystemComponent.DeactivateSystem(); SetPhysics(PHYS_None); GotoState(''); } else if ( bUseAccelerationPhysics ) { OwnerPawn = (KFPC != None) ? KFPC.Pawn : None; if ( Position == 0 && OwnerPawn != None ) { SetLocation(OwnerPawn.Location + (WAYPOINT_HEIGHT * vect(0,0,1))); // initial forward velocity to make the path loop around Velocity = 4000 * CURVE_TURNRATE * Normal(WayPoints[0] - Location) + OwnerPawn.Velocity; } bHeadedRight = false; Acceleration = ACCEL_RATE * Normal(WayPoints[Position] - Location); Velocity *= 0.5; Velocity.Z = 0.5 * (Velocity.Z + Acceleration.Z); SetRotation(rotator(WayPoints[Position] - Location)); GotoState('Pathing'); } else { OwnerPawn = (KFPC != None) ? KFPC.Pawn : None; if ( Position == 0 && OwnerPawn != None ) { SetLocation(OwnerPawn.Location + (WAYPOINT_HEIGHT * vect(0,0,1)) + vector(OwnerPawn.Rotation) * 100.0); } SetRotation(rotator(WayPoints[Position] - Location)); GotoState('Pathing'); } } simulated function bool ShouldStopPathing() { local KFPlayerController KFPC; KFPC = KFPlayerController(Owner); if( KFPC != none && KFPC.Pawn != none ) { return KFPC.Pawn.IsA('KFPawn_Customization'); } return false; } state Pathing { simulated function Tick(float DeltaTime) { local KFPlayerController KFPC; KFPC = KFPlayerController(Owner); if ( ShouldStopPathing() ) { Destroy(); return; } //DrawDebugSphere(Location, 12, 12, 255, 0, 0, false); if ( !bUseAccelerationPhysics ) { TickUDK(DeltaTime, KFPC.Pawn); } else { TickClassic(DeltaTime); } } /** Velocity based movement based on UDK */ simulated function TickUDK(float DeltaTime, Pawn OwnerPawn) { local float MaxSpeed; SetRotation (rotator(WayPoints[Position] - Location)); if ( OwnerPawn != None ) { if ( VSize(Location - OwnerPawn.Location) < OwnerPawn.GroundSpeed && vector(Rotation) dot vector(OwnerPawn.Rotation) > 0.0 ) { // go faster when near owner to get some separation MaxSpeed = OwnerPawn.GroundSpeed + 400.0; } else { MaxSpeed = VSize(OwnerPawn.Velocity) + 200.0; RotationRate = default.RotationRate; } } else { MaxSpeed = 200.0; } Velocity = vector(Rotation) * MaxSpeed; if (VSize(WayPoints[Position] - Location) < FMax(80.0, VSize(Velocity) * DeltaTime * 3.0)) { StartNextPath(); } } /** Acceleration based movement from KF1 */ simulated function TickClassic(float DeltaTime) { Acceleration = ACCEL_RATE * Normal(WayPoints[Position] - Location); Velocity = Velocity + CURVE_TURNRATE * DeltaTime * Acceleration; // force double acceleration // use heading change to detect if we curved around WayPoint. if ( !bHeadedRight ) bHeadedRight = ( (Velocity Dot Acceleration) > 0 ); else if ( Velocity Dot Acceleration < 0 ) StartNextPath(); if ( WayPoints.Length > Position && VSize(WayPoints[Position] - Location) < 80 ) StartNextPath(); } simulated function BeginState(name PreviousStateName) { SetPhysics(PHYS_Projectile); } } defaultproperties { WAYPOINT_HEIGHT_NAV_MESH=25 MinDistanceBetweenWayPointsOnNavMesh=250 }