Interactions are an intent pipeline: the client chooses a stable action ID and target, the server owns approach/range/staleness validation, and a registered handler executes only after the request remains valid.
Two layers you will see
| Layer | Key types | Purpose |
|---|---|---|
| Local/content context | IContextActionProvider, ContextAction, WorldObjectInteractable | Build the actions a player can see/select. |
| Server execution | InteractionActionId, ServerInteractionPipeline, IServerInteractionHandler | Validate network intent, approach target, revalidate, execute authoritatively. |
Stable action IDs
InteractionActionId is string-backed on purpose. Built-ins include core.attack, core.talk_to, core.open_shop, core.open_bank, core.pick_up, core.use_object, door actions, and skill-specific IDs. Custom IDs should be lowercase, namespaced, and stable once shipped.
using GreenCloakGames.TileTickRPGCore.Multiplayer.Contracts.Interaction;
public static class MyInteractionIds
{
public static readonly InteractionActionId HarvestCrystal =
new InteractionActionId("mygame.harvest_crystal");
}
Custom server handler shape
using GreenCloakGames.TileTickRPGCore.Multiplayer.Contracts.Interaction;
using GreenCloakGames.TileTickRPGCore.Multiplayer.NGO;
using GreenCloakGames.TileTickRPGCore.Multiplayer.NGO.Interaction;
public sealed class HarvestCrystalHandler : IServerInteractionHandler
{
public InteractionActionId ActionId => MyInteractionIds.HarvestCrystal;
public bool CanBegin(NetworkPlayer requester, INetworkInteractable target,
out InteractionRejectionReason reason)
{
// Only action-specific checks belong here.
// Ownership, target existence, interactability and approach are pipeline concerns.
reason = InteractionRejectionReason.None;
return true;
}
public InteractionResult Execute(NetworkPlayer requester, INetworkInteractable target)
{
// Mutate authoritative gameplay state here or call a shared service.
return new InteractionResult
{
Status = InteractionResultStatus.Completed,
Reason = InteractionRejectionReason.None,
ActionIdValue = ActionId.Value
};
}
}
The pipeline deliberately owns shared concerns so handlers stay small. Do not reimplement generic ownership, approach, cancellation, or stale-request logic inside every action.
Local world object handlers
IWorldObjectActionHandler remains a useful content/runtime seam with CanExecute(RPGUnitBrain) and Execute(RPGUnitBrain). In networked gameplay, make sure the actual mutation is reached through the authoritative server path rather than directly from a client context menu.