//============================================================================= // AICommand_ScriptedPawn_TraverseSpline //============================================================================= // AICommand to get a pawn to follow a spline //============================================================================= // Killing Floor 2 // Copyright (C) 2018 Tripwire Interactive LLC //============================================================================= class AICommand_ScriptedPawn_TraverseSpline extends AICommand within KFAIController; var transient int SegmentGranularity; var transient KFPawn_Scripted MyScriptedPawn; var transient array Route; var transient int SegIdx, SubSegIdx; var transient vector Direction, SubSegEnd; var transient SplineComponent SegSplineComp; var transient float SegSplineLen; static function bool TraverseSpline(KFAIController AI, SplineActor SplineStart, SplineActor SplineEnd, int Granularity) { local AICommand_ScriptedPawn_TraverseSpline Cmd; if( AI != none && SplineStart != none ) { Cmd = new(AI) class'AICommand_ScriptedPawn_TraverseSpline'; if( Cmd != none ) { SplineStart.FindSplinePathTo(SplineEnd, Cmd.Route); Cmd.SegmentGranularity = Granularity; AI.PushCommand( Cmd ); return true; } } return false; } function Pushed() { local SplineActor SplineEnd; Super.Pushed(); MyScriptedPawn = KFPawn_Scripted(Pawn); // The route represents the path between the pawn's spawn location and its goal, but it's allowed // to continue after its goal if there are more spline actors that the goal connects to, so add // those to the end of the route SplineEnd = Route[Route.Length - 1]; while (SplineEnd.Connections.Length > 0) { SplineEnd = SplineEnd.Connections[0].ConnectTo; Route.AddItem(SplineEnd); } GotoState('TraversingSpline'); } function Paused( GameAICommand NewCommand ) { Super.Paused( NewCommand ); } function Resumed( Name OldCommandName ) { Super.Resumed( OldCommandName ); } function Popped() { Super.Popped(); Route.Length = 0; MyScriptedPawn.EndedRoute(Status == 'Success'); } function bool PawnReachedDestination(vector Dest) { return VSize2D(MyScriptedPawn.Location - Dest) < 10; } state TraversingSpline { Begin: if (Route.Length == 0) { Status = 'Failure'; } else { // consider the pawn to have "reached" the node it spawns at MyScriptedPawn.ReachedRouteMarker(0, Route[0], 0, 0); SegIdx = 1; do // for each segment (space between two spline actors)... { SegSplineComp = Route[SegIdx-1].FindSplineComponentTo(Route[SegIdx]); SegSplineLen = SegSplineComp.GetSplineLength(); SubSegIdx = 1; do // for each sub-segment (space between segment "granules")... { SubSegEnd = SegSplineComp != none ? SegSplineComp.GetLocationAtDistanceAlongSpline((float(SubSegIdx)/float(SegmentGranularity+1))*SegSplineLen) : Route[SegIdx].Location; do // update velocity and rotation toward sub-segment { Direction = SubSegEnd - Pawn.Location; if (MyScriptedPawn.Physics == PHYS_Walking) { Direction.Z = 0.f; } Direction = Normal(Direction); MyScriptedPawn.Velocity = Direction * MyScriptedPawn.GroundSpeed; MyScriptedPawn.SetDesiredRotation(rotator(Direction),,,0.1f); Sleep(0.f); } until (PawnReachedDestination(SubSegEnd)); // until we've reached the next segment "granule" MyScriptedPawn.ReachedRouteMarker( SegIdx, SubSegIdx == (SegmentGranularity + 1) ? Route[SegIdx] : none, SubSegIdx, SegSplineLen / float(SegmentGranularity+1)); ++SubSegIdx; } until (PawnReachedDestination(Route[SegIdx].Location) && SubSegIdx == (SegmentGranularity + 2)); // until we've reached the next spline actor AND we've reached each "granule" ... // ... because we can get close enough to the next spline actor before we've made sure we've reached each "granule", and that can throw off logic ++SegIdx; } until (SegIdx == Route.Length); // until we've reached the last spline actor in the route MyScriptedPawn.Acceleration = vect(0, 0, 0); Status = 'Success'; } PopCommand(self); }