6 minutes to read
Created by
Calytic
Updated by
Wulf

Hook events

Using hook states to manage complex integration scenarios

This guide is for uMod, not Oxide.

Introduction

A hook event is a state container that is passed through the hook execution workflow and may be used to communicate generic state information to any other subscribed hook methods invoked before or after.

Consider the following call-stack and the corresponding hook implementations..

Interface.CallHook("OnChat", player, message)
  └───PluginOne.cs
      └───void OnChat(IPlayer player, string message)
  └───PluginTwo.cs
      └───bool OnChat(IPlayer player, string message)
  └───PluginThree.cs
      └───bool OnChat(IPlayer player, string message)

Notice the return types vary and assuming these three plugins are not integrated, the hooks will always be called in the order they are subscribed.

Any given OnChat hook method above ought to have the same level of responsibility as any other. This sharing of responsibility can make cross-plugin integration difficult, especially considering that hooks have return behavior which is designed to modify game behavior and as such extert authority on the outcome of the hook call.

The hook event splits hook behavior into six distincts workflow states (Before, After, Completed, Canceled, Failed, and normal execution). After implementing hook events, the resulting call-stack may resemble the following...

Interface.CallHook("OnChat", player, message)
  └───PluginOne.cs
      └───[Before] void OnChat(IPlayer player, string message, HookEvent hookEvent)
  └───PluginTwo.cs
      └───bool OnChat(IPlayer player, string message, HookEvent hookEvent)
  └───PluginThree.cs
      └───[Completed] bool OnChat(IPlayer player, string message, HookEvent hookEvent)

The hook methods above will be invoked in a particular order and allow greater flexibility when integrating plugins.

Hook wrappers

The examples below wrap the OnPlayerChat but hook wrappers may be applied to any hook. Like the hook attribute, the Before, After, Completed, Canceled, and Failed attributes accept a name parameter to specify the hook name if the method name does not match the hook name.

Before

Annotate a method with the Before attribute to use it before the normal hook execution workflow.

[Before]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    /* ... */
}

After

Annotate a method with the After attribute to use it when the normal hook execution workflow ends, even if it ends in a canceled or failed state.

[After]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    /* ... */
}

Completed

Annotate a method with the Completed attribute to use it after the normal hook execution workflow is completed. The event state will be completed at the end of the hook execution workflow if the hook was not cancelled and did not fail.

[Completed]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    /* ... */
}

Canceled

Annotate a method with the Canceled attribute to use it when the normal hook execution workflow is canceled. Any hook may indicate that further execution should be canceled and ought to include a reason for the cancellation.

[Canceled]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    /* ... */
}

Canceling a hook will not prevent further hook execution but it will allow other hooks to observe cancellation.

Failed

Annotate a method with the Failed attribute to use it when the normal hook execution workflow encounters an error.

[Failed]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    /* ... */
}

Failure will not prevent further hook execution but it will allow other hooks to observe the failure.

Hook state

The HookEvent object contains the current hook state and may be used (in addition to the hook wrappers above) to schedule callbacks to be invoked when a particular hook state is triggered.

Completed

Add a callback to listen to the hook event completion.

void OnPlayerChat(IPlayer player, string message, HookEvent e)
{      
    // Invoked when the hook execution workflow is completed, similar to the Completed hook wrapper
    e.Context.Completed(delegate(Plugin plugin, HookEvent completedEvent)
    {
        player.Reply(completedEvent.StateReason);
    });
}

Canceled

Check for previous cancellation and add a callback to listen for future hook cancellation.

void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    // Check if a previously executed hook set the hook state to Canceled
    if (e.Canceled)
    {
        player.Reply(e.StateReason);
    }
       
    // In case another hook post-execution attempts to cancel the hook event
    e.Context.Canceled(delegate(Plugin plugin, HookEvent canceledEvent)
    {
        player.Reply(canceledEvent.StateReason);
    });
}

The Cancel method should provide preceeding and proceeding hooks an explanation of why the hook was canceled but it will not prevent further hook execution.

bool IsMuted(IPlayer player)
{
    return player.BelongsToGroup("muted");
}

bool OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    if (IsMuted(player))
    {
        e.Cancel("Player is muted");
        return true;
    }
}

Failed

A Failed state is the result of an exception in any hook in the hook execution stack. Check for previous failure and add a callback to listen for future hook failure.

void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
    // Check if a previously executed hook failed
    if (e.Failed)
    {
        player.Reply(e.StateReason);
    }
       
    // In case another hook post-execution fails
    e.Context.Failed(delegate(Plugin plugin, HookEvent failedEvent)
    {
        player.Reply(failedEvent.StateReason);
    });
}