Table of Contents

Class BaseAction

Namespace
FishMMO.Shared.Core
Assembly
FishMMO.Shared.dll

Abstract base class for all ECA actions. Serialized inline via [SerializeReference] on Trigger assets. Derive from this class and add [Serializable] to create concrete actions.

[Serializable]
public abstract class BaseAction : IAction
Inheritance
BaseAction
Implements
Derived
Inherited Members

Fields

StopChainOnFailure

When true and this action implements IAbortableAction, returning false from TryExecute(ICharacter, EventData) aborts the remainder of the current action list (e.g. stop applying damage after a resource consume fails). Has no effect on actions that do not implement IAbortableAction.

[Tooltip("If this action implements IAbortableAction, abort the rest of the action chain when TryExecute returns false.")]
public bool StopChainOnFailure

Field Value

bool

TargetSelector

Optional selector that picks one or more targets for this action. When set, the action runs once per selected target. When unset, the action runs once against the current event data (reading TargetCharacter or falling back to the initiator).

[Tooltip("Optional selector for this action. When unset the action runs once against the current event target.")]
[SerializeReference]
public TargetSelector TargetSelector

Field Value

TargetSelector

Methods

Execute(ICharacter, EventData)

Executes the action. Must be implemented by derived classes.

public abstract void Execute(ICharacter initiator, EventData eventData)

Parameters

initiator ICharacter

The character initiating the action.

eventData EventData

Event data for the action.

GetTooltipContribution()

Returns a short, designer-facing tooltip line describing this action's effect, or null when the action has nothing to contribute. Override on actions that produce a player-visible outcome (damage, heal, buff apply, knockback, etc.) so ability tooltips can list effects without designer authoring text twice.

public virtual string GetTooltipContribution()

Returns

string

Remarks

The base implementation returns null. Aggregators (BaseAbilityTemplate) skip null/whitespace contributions, so most actions need not override.

IsClientPeer(ICharacter, EventData)

True when this peer has a screen — i.e. it is a client rather than the dedicated server.

protected static bool IsClientPeer(ICharacter initiator, EventData eventData)

Parameters

initiator ICharacter

The character the action is running for.

eventData EventData

The event being executed, or null.

Returns

bool

True when the local peer is a client.

Remarks

For actions whose entire effect is presentational: particles, sound, floating text, camera shake. Those must run on every client that can see the event and on NONE of the server, which has nothing to render and would spend the allocation for no viewer.

The mirror of IsServer(ICharacter), and not a substitute for it. This answers "should I draw something", never "may I change state". An action that mutates anything must still gate on EcaAuthority; gating a state change on this instead would let every client change state and the server change none.

There is no host mode in this project, so a peer is a client or a server and never both. A character with no networked identity — a scene-authored trigger, an edit-mode test — answers true, matching the "allow when undecidable" stance the authority gate takes.

IsReplayTick(EventData)

True when this execution is a prediction REPLAY rather than a first run.

protected static bool IsReplayTick(EventData eventData)

Parameters

eventData EventData

The event being executed, or null.

Returns

bool

True when the event carries a replayed tick.

Remarks

A reconcile replays every tick between the corrected one and now, so anything an action does that is visible or one-shot — a floating number, a VFX instance, a sound — fires once per replayed tick unless it is suppressed here. That is the visual spam PlayFXAction already guards against; this is the same test with one definition instead of an open-coded copy per action.

Never use this as an authority gate. The server's own ability dispatches carry replicate ticks too, so gating state changes on it suppresses the server and the effect happens nowhere — the exact mistake EcaAuthority exists to prevent. This answers "have I already done this once", not "am I allowed to do this".

IsServer(ICharacter)

True when this action is running on a peer that is actually hosting a server.

protected static bool IsServer(ICharacter initiator)

Parameters

initiator ICharacter

The character the action is running for.

Returns

bool

True when the local peer's server is initialized.

Remarks

The runtime replacement for #if UNITY_SERVER, which every server-only action body used to be wrapped in. UNITY_SERVER is a build-target define, so it is undefined in the editor — and the scene server runs from the editor. Every one of those bodies therefore compiled to nothing there, which meant a bindstone bound nobody, a merchant opened no shop, and a teleporter moved no one, in the configuration the project is developed in. It is the same compile-time gate already found and removed from NPC.OnStartServer and from scene object registration.

Asks the initiator's own FishNet.Object.NetworkObject rather than a global singleton, so a client-hosted process answers for the peer the character belongs to.

SendToOwner<T>(ICharacter, T)

Sends a broadcast to the one player an action is acting for.

protected static void SendToOwner<T>(ICharacter character, T message) where T : struct, IBroadcast

Parameters

character ICharacter

The character whose owning connection should receive it.

message T

The broadcast to send.

Type Parameters

T

The broadcast type.

Remarks

Not initiator.NetworkObject.Broadcast(...), which is what these actions used to call. That sends to the observers of the initiator's NetworkObject — every client that can see the player, not the player. One person opening a merchant therefore opened the shop on every screen within observer range, because the client handlers have no reason to filter a message they were sent directly.

Anything that opens a window, offers a quest, or reports a personal result belongs here. Genuine world state — a switch throwing, a door opening — should go to the observers of the object that changed, which is a different set again.

TryResolveTarget(EventData, out ICharacter)

Strict target resolution: returns the explicit TargetCharacter only. Does not fall back to the initiator — outward-effecting actions (damage, dispel, interrupt, knockback) should use this so a misconfigured selector can't silently make a caster attack itself.

protected static bool TryResolveTarget(EventData eventData, out ICharacter target)

Parameters

eventData EventData

The action's event data (may be null).

target ICharacter

Resolved target character, or null when none is present.

Returns

bool

True when an explicit target was resolved.

TryResolveTargetOrInitiator(ICharacter, EventData, out ICharacter)

Forgiving target resolution: prefers TargetCharacter, then falls back to the initiator. Use for self-effecting actions (resource costs, self-buffs, cooldown starts) where "no target" naturally means "act on self". Outward-effecting actions should use TryResolveTarget(EventData, out ICharacter) instead so a missing target produces a no-op rather than a self-hit.

protected static bool TryResolveTargetOrInitiator(ICharacter initiator, EventData eventData, out ICharacter target)

Parameters

initiator ICharacter

The action's initiator.

eventData EventData

The action's event data (may be null).

target ICharacter

Resolved character, or null when neither is available.

Returns

bool

True when a non-null target was resolved.