//============================================================================= // SavedMove is used during network play to buffer recent client moves, // for use when the server modifies the clients actual position, etc. // Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. //============================================================================= class SavedMove extends Object native; // also stores info in Acceleration attribute var SavedMove NextMove; // Next move in linked list. var float TimeStamp; // Time of this move. var float Delta; // amount of time for this move var bool bRun; var bool bDuck; var bool bPressedJump; var bool bDoubleJump; var bool bPreciseDestination; var bool bForceRMVelocity; // client-side only (for replaying moves) - not replicated var bool bForceMaxAccel; var EDoubleClickDir DoubleClickMove; // Double click info. var EPhysics SavedPhysics; var vector StartLocation, StartRelativeLocation, StartVelocity, StartFloor, SavedLocation, SavedVelocity, SavedRelativeLocation, RMVelocity, Acceleration; var rotator Rotation; var Actor StartBase, EndBase; var float CustomTimeDilation; var float AccelDotThreshold; // threshold for deciding this is an "important" move based on DP with last acked acceleration // Root motion correction variables var bool bRootMotionFromInterpCurve; var float RootMotionInterpCurrentTime; var Vector RootMotionInterpCurveLastValue; var ERootMotionMode RootMotionMode; `if(`__TW_) // Put here to avoid casting // Recoil system var rotator WeaponBufferRotation; `endif function Clear() { TimeStamp = 0; Delta = 0; DoubleClickMove = DCLICK_None; Acceleration = vect(0,0,0); StartVelocity = vect(0,0,0); bRun = false; bDuck = false; bPressedJump = false; bDoubleJump = false; bPreciseDestination = false; bForceRMVelocity = false; CustomTimeDilation = 1.0; } function PostUpdate(PlayerController P) { bDoubleJump = P.bDoubleJump || bDoubleJump; if ( P.Pawn != None ) { RMVelocity = P.Pawn.RMVelocity; SavedLocation = P.Pawn.Location; SavedVelocity = P.Pawn.Velocity; EndBase = P.Pawn.Base; if ( (EndBase != None) && !EndBase.bWorldGeometry ) SavedRelativeLocation = P.Pawn.Location - EndBase.Location; } Rotation = P.Rotation; `if(`__TW_) // Save the WeaponBufferRotation WeaponBufferRotation = P.WeaponBufferRotation; `endif } function bool IsImportantMove(vector CompareAccel) { local vector AccelNorm; // check if any important movement flags set if ( bPressedJump || bDoubleJump || ((DoubleClickMove != DCLICK_None) && (DoubleClickMove != DCLICK_Active) && (DoubleClickMove != DCLICK_Done)) ) { return true; } if( bRootMotionFromInterpCurve ) { return TRUE; } // check if acceleration has changed significantly AccelNorm = Normal(Acceleration); return ( (CompareAccel != AccelNorm) && ((CompareAccel Dot AccelNorm) < AccelDotThreshold) ); } function vector GetStartLocation() { if( (StartBase != None) && !StartBase.bWorldGeometry ) { return StartBase.Location + StartRelativeLocation; } return StartLocation; } function SetInitialPosition(Pawn P) { SavedPhysics = P.Physics; StartLocation = P.Location; StartVelocity = P.Velocity; StartBase = P.Base; StartFloor = P.Floor; CustomTimeDilation = P.CustomTimeDilation; if( (StartBase != None) && !StartBase.bWorldGeometry ) { StartRelativeLocation = P.Location - StartBase.Location; } // Store root motion information bRootMotionFromInterpCurve = P.bRootMotionFromInterpCurve; if( bRootMotionFromInterpCurve ) { RootMotionInterpCurrentTime = P.RootMotionInterpCurrentTime; RootMotionInterpCurveLastValue = P.RootMotionInterpCurveLastValue; RootMotionMode = P.Mesh.RootMotionMode; } } function bool CanCombineWith(SavedMove NewMove, Pawn InPawn, float MaxDelta) { if( InPawn == None ) { return FALSE; } if( bRootMotionFromInterpCurve ) { return FALSE; } if ( NewMove.Acceleration == vect(0,0,0) ) { return ( (Acceleration == vect(0,0,0)) && (StartVelocity == vect(0,0,0)) && (NewMove.StartVelocity == vect(0,0,0)) && (SavedPhysics == InPawn.Physics) && !bPressedJump && !NewMove.bPressedJump && (bRun == NewMove.bRun) && (bDuck == NewMove.bDuck) && (bPreciseDestination == NewMove.bPreciseDestination) && (bDoubleJump == NewMove.bDoubleJump) && ((DoubleClickMove == DCLICK_None) || (DoubleClickMove == DCLICK_Active)) && (NewMove.DoubleClickMove == DoubleClickMove) && !bForceRMVelocity && !NewMove.bForceRMVelocity) && (CustomTimeDilation == NewMove.CustomTimeDilation); } else { return ( (InPawn != None) && (NewMove.Delta + Delta < MaxDelta) && (SavedPhysics == InPawn.Physics) && !bPressedJump && !NewMove.bPressedJump && (bRun == NewMove.bRun) && (bDuck == NewMove.bDuck) && (bDoubleJump == NewMove.bDoubleJump) && (bPreciseDestination == NewMove.bPreciseDestination) && ((DoubleClickMove == DCLICK_None) || (DoubleClickMove == DCLICK_Active)) && (NewMove.DoubleClickMove == DoubleClickMove) && ((Normal(Acceleration) Dot Normal(NewMove.Acceleration)) > 0.99) && !bForceRMVelocity && !NewMove.bForceRMVelocity) && (CustomTimeDilation == NewMove.CustomTimeDilation); } } function SetMoveFor(PlayerController P, float DeltaTime, vector NewAccel, EDoubleClickDir InDoubleClick) { Delta = DeltaTime; // NOTE: max replicated vector component magnitude is 2^18, and acceleration is multiplied by 10 before replication // quick check to make sure we never cross this limit of 2^18/10 if( VSize(NewAccel) > 26214 ) { NewAccel = 26214 * Normal(NewAccel); } if( P.Pawn != None ) { SetInitialPosition(P.Pawn); } Acceleration = NewAccel; DoubleClickMove = InDoubleClick; bRun = (P.bRun > 0); bDuck = (P.bDuck > 0); bPressedJump = P.bPressedJump; bDoubleJump = P.bDoubleJump; bPreciseDestination = P.bPreciseDestination; bForceRMVelocity = P.bPreciseDestination || (P.Pawn != None && P.Pawn.Mesh != None && !P.Pawn.bRootMotionFromInterpCurve && (P.Pawn.Mesh.RootMotionMode == RMM_Accel || P.Pawn.Mesh.RootMotionMode == RMM_Velocity)); bForceMaxAccel = P.Pawn != None && P.Pawn.bForceMaxAccel; TimeStamp = P.WorldInfo.TimeSeconds; } /** * Called before PlayerController.ClientUpdatePosition uses this SavedMove to make a predictive correction */ function PrepMoveFor( Pawn P ) { if( P != None ) { P.bForceRMVelocity = bForceRMVelocity; P.bForceMaxAccel = bForceMaxAccel; // Reset root motion information P.bRootMotionFromInterpCurve = bRootMotionFromInterpCurve; if( P.bRootMotionFromInterpCurve ) { P.Mesh.RootMotionMode = RootMotionMode; P.RootMotionInterpCurveLastValue = RootMotionInterpCurveLastValue; P.SetRootMotionInterpCurrentTime( RootMotionInterpCurrentTime, Delta, TRUE ); } } } /** * Called after PlayerController.ClientUpdatePosition used this SavedMove to make a predictive correction */ function ResetMoveFor( Pawn P ) { if( P != None ) { SavedLocation = P.Location; SavedVelocity = P.Velocity; EndBase = P.Base; if( EndBase != None && !EndBase.bWorldGeometry ) { SavedRelativeLocation = P.Location - EndBase.Location; } P.bForceRMVelocity = false; } } /* CompressedFlags() returns a byte containing encoded special movement information (jumping, crouching, etc.) SetFlags() and UnCompressFlags() should be overridden to allow game specific special movement information */ function byte CompressedFlags() { local byte Result; Result = DoubleClickMove; if ( bRun ) Result += 8; if ( bDuck ) Result += 16; if ( bPressedJump ) Result += 32; if ( bDoubleJump ) Result += 64; if ( bPreciseDestination ) Result += 128; return Result; } /* SetFlags() Set the movement parameters for PC based on the passed in Flags */ static function EDoubleClickDir SetFlags(byte Flags, PlayerController PC) { if ( (Flags & 8) != 0 ) PC.bRun = 1; else PC.bRun = 0; if ( (Flags & 16) != 0 ) PC.bDuck = 1; else PC.bDuck = 0; PC.bPreciseDestination = ( (Flags & 128) != 0 ); PC.bDoubleJump = ( (Flags & 64) != 0 ); PC.bPressedJump = ( (Flags & 32) != 0 ); switch (Flags & 7) { case 0: return DCLICK_None; break; case 1: return DCLICK_Left; break; case 2: return DCLICK_Right; break; case 3: return DCLICK_Forward; break; case 4: return DCLICK_Back; break; } return DCLICK_None; } function String GetDebugString() { local String Str; Str = self@`showvar(Delta)@`showvar(SavedPhysics)@`showvar(StartLocation)@`showvar(StartVelocity)@`showvar(SavedLocation)@`showvar(SavedVelocity)@`showvar(RMVelocity)@`showvar(Acceleration)@`showvar(bRootMotionFromInterpCurve)@`showvar(RootMotionInterpCurrentTime); return Str; } defaultproperties { AccelDotThreshold=+0.9 }