S&box Wiki

Revision Difference

Prediction#530567

<cat>Code.Player</cat> <title>Prediction</title> [Prediction](https://developer.valvesoftware.com/wiki/Prediction) refers to one of the systems Source (and Source 2) uses to smooth out the multiplayer experience, this system 'predicts' what effects a player's input will have on the world (by running the same calculations the server does multiple times and comparing them) and displays the result without having to wait for the server to both receive the player's input and send the result back. Without prediction the game would be effectively unplayable for anyone with more than 50ms of ping, only getting worse as it increases further. Incorrectly setting up prediction can lead to all sorts of issues in multiplayer such as: * Sluggish/slow responses to player input * Stuttering/hitching * Duplicated effects/animations * Broken hit registration <note>Prediction only exists on the client, the server isn't aware of it's existence nor does it take it into account for anything. If the client's predictions don't line up with the server the server's values will take precedence 100% of the time.</note> #Where to use prediction Prediction should be used for anything that is **directly** affected by a player's inputs, this includes: * The player itself * Any controllers or animators attached to the player * The player's ActiveChild (Weapons and other `IPlayerControllable` entities like vehicles) Technically every entity with a player as it's owner is predictable, but those are the main ones you should be worried about. <warning>The physics engine is [nondeterministic](https://en.wikipedia.org/wiki/Nondeterministic_algorithm) and cannot be predicted, this extends to any entity that uses it for movement. If you've ever wondered why the Half-Life 2 vehicles don't handle well in multiplayer, this is why.</warning> #Predicted methods Prediction isn't something you can start yourself, it's a context that is set by the engine for certain methods (and any code that branches off from those methods) These methods Prediction isn't something you can start yourself, it's a context that is set by the engine for certain methods (and any code that branches off from those methods) These methods are: <warning>Async tasks or any other 'delayed' code will break prediction, use `TimeSince` and check for completion in subsequent calls instead if you need to delay something</warning> * `Player.Tick()` which calls `ActiveChild.OnPlayerControlTick( Player player )` by default * `PlayerController.Tick()` * `PlayerAnimator.Tick()` #How to implement prediction Adding prediction to an entity is fairly straightforward if you know what you're doing. * Keep your code the same between client and server as much as possible * Mark any networked properties that change during predicted hooks to `[NetPredicted]` or `[NetLocalPredicted]` * Wrap clientside code that isn't supposed to be predicted (think notifications) in `using (Prediction.Off())` to keep them from running multiple times If you've done all of this the end result might look something like this example based on [dm98](https://github.com/Facepunch/dm98): ``` public partial class ExampleWeapon : BaseWeapon { [NetPredicted] public int Ammo { get; set; } public override float PrimaryRate => 5.0f; public bool TakeAmmo( int amount ) { if ( Ammo < amount ) return false; Ammo -= amount; // Predicted context still exists here since we're calling it from AttackPrimary() return true; } // AttackPrimary is our predicted 'hook' public override void AttackPrimary() { TimeSincePrimaryAttack = 0; // Defined as [NetPredicted] in BaseWeapon if ( !TakeAmmo( 1 ) ) return; Owner.SetAnimParam( "b_attack", true ); ShootBullet( 0.1f, 150.0f, Rand.Float( 6, 10 ), 3.0f ); } protected void ShootBullet( float spread, float force, float damage, float size ) { Vector3 forward = Owner.EyeRot.Forward + (Vector3.Random + Vector3.Random + Vector3.Random + Vector3.Random) * spread * 0.25f; foreach ( var tr in TraceBullet( Owner.EyePos, Owner.EyePos + forward.Normal * 5000, size ) ) { tr.Surface.DoBulletImpact( tr ); // TODO FOR EXAMPLE: Isn't the Prediction.Off down there unnecessary since clients peace out up here? if ( IsClient || !tr.Entity.IsValid() ) continue; using ( Prediction.Off() ) { DamageInfo info = DamageInfo.FromBullet( tr.EndPos, forward.Normal * force, damage ) .UsingTraceResult( tr ) .WithAttacker( Owner ) .WithWeapon( this ); tr.Entity.TakeDamage( info ); } } } } ```