Deathmatch Party - Lag Compensation Component
Lag Compensation Component — Overview
Key data structures
-
FBoxInformation: Location, rotation, extent for a single hitbox.USTRUCT(BlueprintType) struct FBoxInformation { GENERATED_BODY() UPROPERTY() FVector Location; UPROPERTY() FRotator Rotation; UPROPERTY() FVector BoxExtent; }; -
FFramePackage: Timestamp, map of hitboxFName→FBoxInformation, andAPartyCharacter*.USTRUCT(BlueprintType) struct FFramePackage { GENERATED_BODY() UPROPERTY() float Time; UPROPERTY() TMap<FName, FBoxInformation> HitBoxInfo; UPROPERTY() APartyCharacter* PartyCharacter; }; -
FrameHistory: DoubleLinkedList inside theULagCompensationComponentclss storing recent frames; trimmed toMaxRecordTime.TDoubleLinkedList<FFramePackage> FrameHistory; UPROPERTY(EditAnywhere) float MaxRecordTime = 0.5f;
How it records frames
- Server-only recording:
TickComponentcallsSaveFramePackage()when the owning character has server authority. - SaveFramePackage(FFramePackage&) captures current transform + extent of each collision box in the
HitCollisionBoxesof the party character into aFFramePackageand pushes it intoFrameHistory. - Frames older than MaxRecordTime are removed.
How it answers rewind queries
- RPC entry points (all UFUNCTION(Server, Reliable)):
- ServerScoreRequest — hit-scan (single hit).
- ProjectileServerScoreRequest — projectile hit validation.
- ShotgunServerScoreRequest — multi-pellet / shotgun validation.
Request flow (common pattern):
- Client detects hit locally or the projectile hits locally and gathers relevant info (trace start, hit location(s), projectile init velocity, and HitTime computed as server time - round-trip estimate).
- Client calls the appropriate server RPC on its character’s ULagCompensationComponent.
- Server: GetFrameToCheck(HitCharacter, HitTime) locates two recorded frames around HitTime, interpolates between them with FrameInterpolation(…) to produce an accurate pose for that time.
- Server temporarily MoveBoxes(…) on the hit character to those historic transforms and disables the character mesh collisions (EnableCharacterMeshCollision(…, NoCollision)) to avoid double collisions.
- Depending on weapon type:
- Hit-scan: ConfirmHit(…) performs LineTraceSingleByChannel against the moved box components to check for head/body hit.
- Projectile: ProjectileConfirmHit(…) runs PredictProjectilePath using the supplied InitialVelocity and TraceStart, checks HitResult.
- Shotgun: ShotgunConfirmHit(…) moves the target’s boxes for each candidate character and performs multiple traces (one per pellet target), counting head/body hits.
- Server ResetBoxes(…) to the cached frame and re-enables mesh collision.
- If a hit is confirmed, the server applies damage via UGameplayStatics::ApplyDamage(…) using controller/weapon info (implemented inside the _Implementation methods).
Files and call sites in this project
- Component creation / wiring:
- APartyCharacter:
- Creates component in constructor: LagCompensationComponent = CreateDefaultSubobject
(...) - Sets PartyCharacter and PartyController on PostInitializeComponents.
- Hit boxes are created and stored in HitCollisionBoxes via SetupCollisionBoxes().
- Creates component in constructor: LagCompensationComponent = CreateDefaultSubobject
- APartyCharacter:
- Weapon/attack flows that invoke rewind:
- ProjectileBullet.cpp
- OnHit — when projectile hits locally and bUseServerSideRewind is true and owner is locally controlled:
- Calls OwnerCharacter->GetLagCompensationComponent()->ProjectileServerScoreRequest(…) with TraceStart, InitialVelocity, and OwnerController->GetServerTime() - OwnerController->SingleTripTime.
- OnHit — when projectile hits locally and bUseServerSideRewind is true and owner is locally controlled:
- ProjectileBullet.cpp
- Shotgun.cpp
- AShotgun::FireShotgun — when client fires with bUseServerSideRewind true and not server-authority:
- Gathers pellet HitTargets and calls ShotgunServerScoreRequest(…).
- AShotgun::FireShotgun — when client fires with bUseServerSideRewind true and not server-authority:
- Weapon.h
- Weapons expose flag bUseServerSideRewind to opt into this behavior.
- Hit-scan usage:
- ULagCompensationComponent::ServerSideRewind / ConfirmHit — supports hit-scan validation if you call ServerScoreRequest. (Search your project for where hit-scan weapons call this server RPC; shotgun/projectile examples above are explicit.)
Important implementation notes / behavior details (quick)
- MaxRecordTime defaults to 0.5s; frames older than that are discarded from FrameHistory.
- Rewind works by moving UBoxComponent hitboxes to historical transforms, temporarily disabling `/ altering mesh collision to avoid mesh-level hits interfering with the test.
- Projectile paths validated using UGameplayStatics::PredictProjectilePath with the supplied InitialVelocity.
- Shotgun logic counts headshots vs body shots per target (maps returned in FShotgunServerSideRewindResult).
- RPCs are server-reliable — the authoritative server performs final validation and applies damage.
How to enable / use in your weapons
- Set AWeapon::bUseServerSideRewind = true for weapons that should use server-side rewind.
- When firing:
- For projectile weapons: ensure the projectile owner is locally controlled, then on local projectile hit call ProjectileServerScoreRequest(…) with:
- TraceStart — starting point of projectile traces (usually muzzle socket location).
- InitialVelocity — the projectile initial velocity (serialized).
- HitTime — use controller server time minus estimated network one-way delay (GetServerTime() - SingleTripTime).
- For shotgun / multi-hit weapons: build the pellet HitTargets array client-side and call ShotgunServerScoreRequest(…).
- For hitscan: call ServerScoreRequest(…) with TraceStart, HitLocation, and computed HitTime.
- For projectile weapons: ensure the projectile owner is locally controlled, then on local projectile hit call ProjectileServerScoreRequest(…) with:
Where to look if behavior is wrong / debugging tips
- Check recorded frames in FrameHistory and timestamps inserted by SaveFramePackage.
- GetFrameToCheck finds older/younger frames and calls FrameInterpolation. Verify FrameInterpolation is producing expected transforms (interpolation fraction calculation matters).
- Ensure HitCollisionBoxes are properly initialized and registered (SetupCollisionBoxes in APartyCharacter).
- Confirm clients compute HitTime using the same clock base as server RPC arguments (the project uses APartyPlayerController::GetServerTime and SingleTripTime).