Core RPG handbook · Development build / version notes

Documentation

Register a custom world action

Add a typed, server-validated interaction handler using the actual public extension contract.

On this page

Goal#

Register a diagnostic “Read notice” action without adding reward or persistent-state behavior.

Before you begin#

A working interaction target, matching installed API, a server/host and normal composition/identity setup. The snippet was signature-checked, not compiled or executed in Unity for this task.

Exact steps and complete example#

Existing public assembly GreenCloakGames.Interaction.Extensibility avoids NGO dependency for customer handler. File Scripts/Interaction/Extensibility/CustomWorldActionRegistration.cs defines all these contracts in namespace GreenCloakGames.TileTickRPGCore.Interaction:

  • CustomWorldActionContext: GameObject Requester, Target; string ActionId; bool IsAuthoritativeServer.
  • ICustomWorldObjectActionHandler: bool IsActionAvailable(string actionId); bool CanExecute(CustomWorldActionContext context, out string reason); bool TryExecute(CustomWorldActionContext context, out string message).
  • CustomWorldActionRegistration.Register<T>(string actionId) where T:MonoBehaviour,ICustomWorldObjectActionHandler; returns IDisposable registration; duplicate/built-in/whitespace/control IDs rejected. Call main thread at init on EVERY peer. Target must author same ID and exact registered type. Registration alone does not offer action.

This sign action runs on the server and writes its message to the Console. It has no rewards or saved state:

using GreenCloakGames.TileTickRPGCore.Interaction;
using UnityEngine;

public sealed class ReadNoticeAction : MonoBehaviour, ICustomWorldObjectActionHandler
{
    private const string Id = "mygame.read_notice";
    private static CustomWorldActionRegistration registration;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void RegisterAction()
    {
        registration?.Dispose();
        registration = CustomWorldActionRegistration.Register<ReadNoticeAction>(Id);
    }

    public bool IsActionAvailable(string actionId) => actionId == Id;

    public bool CanExecute(CustomWorldActionContext context, out string reason)
    {
        bool allowed = context.IsAuthoritativeServer && context.Requester != null
            && context.Target != null && context.ActionId == Id;
        reason = allowed ? "" : "The authoritative server must execute this action.";
        return allowed;
    }

    public bool TryExecute(CustomWorldActionContext context, out string message)
    {
        if (!CanExecute(context, out message)) return false;
        Debug.Log("Notice read by " + context.Requester.name, context.Target);
        message = "";
        return true;
    }
}

Steps: place component on authored world object; in its WorldObjectActionDefinition set Verb=Read notice, Action Handler Behaviour=this component, Network Action Id=mygame.read_notice. Choose Allow Left Click and Priority deliberately. Keep WorldObjectInteractable/provider/collider and existing network identity configuration from working demo. Start host/client; use normal context action pipeline. Expected diagnostic log appears on server, rejected pure client direct mutation. No reward logic promised. Queries must stay pure; customer code owns transaction safety in TryExecute. Exact serialized names in definition: verb, displayColor, allowLeftClick, priority, actionHandlerBehaviour, networkActionId. Default action id auto-derives core.open_bank for BankAccessHandler, otherwise core.use_object; a custom handler must explicitly author the custom ID.

Old interface IWorldObjectActionHandler still exists with CanExecute(RPGUnitBrain) and Execute(RPGUnitBrain). Do not conflate it with the public custom registered contract.

What you should see#

The normal action pipeline calls the registered component on the authoritative server and writes the diagnostic log. Registration alone does not add a visible action to an object.

If something goes wrong#

Register on every peer, author the exact Network Action Id, bind the correct component type and preserve the demo's provider/collider/identity setup. Do not put reward mutations in availability queries.

Next steps#

Read the interaction pipeline and authority and readiness before adding transactions.