We are looking to have certain events be client authoritative, such as when the player takes contact damage.
We want this to improve the general feel of the game.
My immediate thought is to use ICommandData and ICommandDataSerializer, but would like your feedback on this plus any other recommendations on how to go about this.
Any thoughts on this from @NikiWalker or @CMarastoni ?
Client authority can be supported manually in Netcode for Entities (via commands or RPCs) but it’s tricky, and for contact damage (presumably falling? and being hit by vehicles or similar?) client prediction broadly is advised, which is generally as simple as including the contact damage systems in the prediction loop and client world. Fall damage in particular tends to be extremely predictable in the common case.
But yeah, if you truly want client authority, add “client authoritative additional damage” fields to the input struct, and increment them every time you take client authoritative damage. Prefer incrementing a persistent value to firing one off client authority damages via input, as it will account for bouts of packet loss. You can therefore also client predict these value changes too (as that data is available to you in the prediction loop).
If you’re instead preferring to have bespoke UI and FX logic, using RPCs to reliably send additional damage events would also work, but you’d have to predict them yourself in this UI/FX layer.
I’ve never tried this, but my hunch is vaguely something like this:
- Your client has 100 health, and falls off a ledge for 10 damage, so plays weak damage V&SFX and updates the healthbar to use a delta of
currentHp:100-10
.
- The client sends off their next tick input with a persistent client authoritative 10 damage (and crucially; keeps sending 10).
- A few ticks later (before any server ack), the unlucky player then gets run over by a train for 2000 damage, and predicts the damage would have killed the player entity, thus shows aggressive damage V&SFX, and updates their HP damage to
currentHp::100-2010
.
- Thus, sends inputs with the new 2010 value (combining 2000 and 10).
- The server eventually replies with a snapshot containing 2 updated values:
[GhostField] currentHp:90
, and [GhostField] clientAuthoritativeDamageAccountedFor:2010
, implying the server accepted the 10, but disagreed with the 2000 train damage (maybe the server knew a teammate threw a shield on you just after you landed). Regardless, the server has incorporated both client authoritative damage changes in its own authoritative logic, so your actual healthbar value just needs to calculate currentHp:90-(clientAuthoritativeDamage-clientAuthoritativeDamageAccountedFor)
.
This is technically mixed authority, but it also shows a simple way to “ack” client authoritative damage events from the server via a GhostField counter. Typically damage in games is more complex than this, but it’s a start
Thanks for the answer, the idea of having combined authority is good.
However in our case the scenario is actually a bit different, which I should have explained in more detail.
Contact damage in our scenario is damage done to the player by collision with enemy entities.
These enemies are simulated on the client and only position synced once per x frames (currently 1000), which leads to some discrepancy between server and client, meaning that when server authoritative, a player can experience taking damage from a collision that does not seem to happen on the client side.
For this reason, we want to have full client authoritative behaviour.
We are also looking at doing it for damage dealt by the player to enemies, for the same reason. This is a bit more involved since it involves projectiles and multiple enemies at once, but conceptually it is similar.
So with that in mind, is using input as a way of communication, the best approach?
My understanding is that RPCs including input have guaranteed delivery, is that wrong, i.e. would there be a possible issue with packet losses?
Oh cool, it’s that use-case, nice! IIRC these were movements that couldn’t be inferred exclusively by the client, right? Otherwise you could make the movement 100% deterministically tied to the ServerTick
.
When it’s just your own owned entity, input is fine, but yes, if you also want to support damaging other entities, RPCs are a better choice.
RPCs are 100% reliable and sequenced, but therefore have the problem of stalling during packet loss until resent.
Inputs are technically unreliable, but with the following caveats:
- We send the latest input plus the 3 previous inputs in every unreliable input RPC packet sent from the client to the server.
- This
latest+previous3
will soon be configurable…
- …and tied to
TargetCommandSlack:2+NumAdditionalCommandsToSend:2
, as there is no point sending (for example) 16 inputs if they expect to arrive on the server with only 2 TargetCommandSlack
ticks of buffering, as ~12 of them will statistically almost always be too out of date to be usable by the server.
- The
InputEvent
struct is 100% reliable (by virtue of the incrementing counter and some code-generated boilerplate). You can test this using the Predicted Spawn NetcodeSample (where we spawn grenades using an InputEvent
), combining it with the Lag Spike Simulator built into the PlayMode Tools window. Set the lag spike to a high value (e.g. 10s), trigger it, and spawn many grenades. You’ll notice all the predicted spawn get destroyed, but the real grenade spawns (clamped to 5) all occur at once after the lag spike has ended.
Thanks again.
We are using a 3rd party navigation package and it is not netcode aware, so we don’t actually control the movement directly (apart from the smoothed corrections done each sync).
I’ll check out the InputEvent and otherwise give the RPC approach a go
1 Like