Class EcaAuthority
The single answer to "may this ECA step change authoritative state, here, on this peer?".
public static class EcaAuthority
- Inheritance
-
EcaAuthority
- Inherited Members
Remarks
Why a helper and not a field test. Every ECA action used to decide this for itself, and
the two things they reached for were both wrong. #if UNITY_SERVER is a build-target
define, so it is undefined in the editor the scene server is developed in. Gating on
TickEventData.IsReplicateTick is worse, because the server's own ability spawn
and self-target dispatches carry replicate ticks — so a guard meant to suppress a client
suppressed the server as well and the effect never happened anywhere. Both mistakes are
invisible in a build and only show up as "the ability does nothing".
What it asks. The identities the event carries, in the order they are most likely to be
networked: the explicit initiator, the event's initiator, then the event's target character.
The first one with a live FishNet.Object.NetworkObject decides, by way of
FishNet.Object.NetworkObject.IsServerInitialized — the same question
BaseAction.IsServer asks, so a client-hosted process answers for the peer the
character actually belongs to rather than for the process.
What it does when nothing is networked. It allows. A trigger fired by a scene object,
an edit-mode test, or a character that has not spawned yet has no peer to be wrong about;
refusing there would silently disable every scene-authored trigger in the project. On a real
client the characters involved always carry a NetworkObject, and that object reports
IsServerInitialized == false, so the case this gate exists for is always decided by
evidence rather than by the fallback.
Methods
Allows(PeerEvidence)
The gate's decision, as a pure function of the evidence. Split out so the rule can be tested without a NetworkManager, and so every caller shares one interpretation of "undecidable".
public static bool Allows(EcaAuthority.PeerEvidence evidence)
Parameters
evidenceEcaAuthority.PeerEvidenceWhat the event proved about the executing peer.
Returns
- bool
True when authoritative mutation is permitted.
Evidence(ICharacter, EventData)
Resolves what the event can prove about the peer it is running on.
public static EcaAuthority.PeerEvidence Evidence(ICharacter initiator, EventData eventData)
Parameters
initiatorICharacterThe action's explicit initiator, or null.
eventDataEventDataThe event being executed, or null.
Returns
- EcaAuthority.PeerEvidence
The strongest evidence available.
IsServer(EventData)
True when this peer may mutate authoritative state for the supplied event.
public static bool IsServer(EventData eventData)
Parameters
eventDataEventDataThe event being executed, or null.
Returns
IsServer(ICharacter, EventData)
True when this peer may mutate authoritative state for the supplied event.
public static bool IsServer(ICharacter initiator, EventData eventData)
Parameters
initiatorICharacterThe action's explicit initiator, or null.
eventDataEventDataThe event being executed, or null.
Returns
MayPredict(EventData)
True when this peer may apply an effect for feedback. See MayPredict(ICharacter, EventData).
public static bool MayPredict(EventData eventData)
Parameters
eventDataEventDataThe event being executed, or null.
Returns
MayPredict(ICharacter, EventData)
True when this peer may apply an effect for FEEDBACK, ahead of the server agreeing.
public static bool MayPredict(ICharacter initiator, EventData eventData)
Parameters
initiatorICharacterThe action's explicit initiator, or null.
eventDataEventDataThe event being executed, or null.
Returns
Remarks
Why this is separate from IsServer(ICharacter, EventData). That gate asks "may I change authoritative state", and its answer has to stay no on a client. But using it for everything meant a player's own hit produced nothing locally until the server's report arrived — hit, pause, then the world reacts. In a predicted client that is not acceptable, and it is not what the gate was protecting against: the risk was a client inventing effects for characters it has no business simulating, not a caster showing the result of its own cast.
Only the initiator's owner predicts. The server always may. A client may only when it owns the character that CAUSED the effect — it has that character's input, it predicted the cast, and it is the only peer with anything to predict from. A mere observer answers false: it has no input stream for the caster, holds every peer interpolated, and the server is going to tell it what happened anyway.
What makes this safe. The authoritative consequences of a predicted effect are
already gated one level down, inside the controllers: CharacterDamageController.Kill,
QueueCombatEvent and RecordCombatContribution each return early unless
IsServerStarted. So a predicted hit moves a bar and raises local events; it cannot
kill anybody, award loot rights, or emit a combat report. Death in particular stays
server-only and unpredicted by deliberate decision — a corpse that stands back up is far
worse than one that arrives an RTT late.
An action whose effect is not player-visible — threat, taunt, revive — has nothing to gain here and must keep using IsServer(ICharacter, EventData). Predicting those buys no feel and adds a way to be wrong.