> For the complete documentation index, see [llms.txt](https://wiki.redmodding.org/scripting-cyberpunk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.redmodding.org/scripting-cyberpunk/scripting/game-systems/events-and-callbacks/custom-system-events.md).

# Custom System Events

How to have global callbacks

{% hint style="info" %}
This requires [Codeware](/scripting-cyberpunk/scripting-cyberpunk/codeware.md)
{% endhint %}

{% hint style="info" %}
For more documentation, look here: <https://github.com/psiberx/cp2077-codeware/wiki#custom-events>
{% endhint %}

With [Codeware](/scripting-cyberpunk/scripting-cyberpunk/codeware.md), we can create custom global events that aren't scoped to specific contexts (such as [`redEvent`](https://nativedb.red4ext.com/c/3352609018084022)). With this, we can define events that can be dispatched anywhere (within vanilla or custom classes and structures) that can be handled with any (live) structure in the game.

When registering listeners to handle your custom events, you can register multiple listeners for different classes at a time, there (may need to be fact checked) is no limit.

## Simple Example

As a simple example, we'll create an event that will be dispatched whenever the player jumps.

### Defining the Event

{% hint style="info" %}
Your custom event must be defined with Redscript.
{% endhint %}

```swift
module MyMod

class JumpedEvent extends CallbackSystemEvent {
  public static final func Create() -> ref<JumpedEvent> {
    return new JumpedEvent();
  }
}
```

### Dispatching the Event

{% tabs %}
{% tab title="REDScript" %}

```swift
module MyMod

@wrapMethod(PlayerPuppet)
protected cb func OnAction(action: ListenerAction, consumer: ListenerActionConsumer) -> Bool {
  wrappedMethod(action, consumer);

  if action.IsAction(n"Jump") && action.IsButtonJustReleased() {
    GameInstance.GetCallbackSystem().DispatchEvent(JumpedEvent.Create());
  }
}
```

{% endtab %}

{% tab title="Lua" %}

```lua
registerForEvent('onInit', function()
  ---@param this PlayerPuppet
  ---@param action ListenerAction
  ---@param consumer ListenerActionConsumer
  ObserveAfter("PlayerPuppet", "OnAction",function(this, action, consumer)
    if action:IsAction("Jump") and action:IsButtonJustReleased() then
      GameInstance.GetCallbackSystem():DispatchEvent(MyMod_JumpedEvent.Create())
    end
  end)
end)
```

{% endtab %}
{% endtabs %}

### Handling the Event

{% tabs %}
{% tab title="REDScript" %}

```swift
public class MySystem extends ScriptableSystem {
  public func OnAttach() -> Void {
    GameInstance.GetCallbackSystem().RegisterCallback(NameOf<JumpedEvent>(), this, n"OnJump", false);
  }

  public func OnDetach() -> Void {
    GameInstance.GetCallbackSystem().UnregisterCallback(NameOf<JumpedEvent>(), this, n"OnJump");
  }

  protected cb func OnJump(event: ref<JumpedEvent>) -> Void {
    FTLog(s"\(this.GetClassName())#OnJump");
  }
}
```

{% endtab %}

{% tab title="Lua" %}

```lua
local mod = {
  listener = nil
}

-- Define our function to callback
function OnReady(event)
  print("player just jumped")
end

registerForEvent('onInit', function()
  -- Create our proxy
  mod.listener = NewProxy({
    OnJump = {
      args = {"handle:MyMod.JumpedEvent"},
      callback = function(event) OnReady(event) end
    }
  })

  local callbackSystem = Game.GetCallbackSystem()
  local target = mod.listener:Target()
  local fn = mod.listener:Function("OnJump")

  callbackSystem:RegisterCallback("MyMod.JumpedEvent", target, fn)
end)

registerForEvent('onShutdown', function()
  -- Unregister our callback before our mod is "removed".
  local callbackSystem = Game.GetCallbackSystem()
  local target = mod.listener:Target()
  local fn = mod.listener:Function("OnJump")

  callbackSystem:UnregisterCallback("MyMod.JumpedEvent", target, fn)
end)
```

{% endtab %}
{% endtabs %}

## Silly Example

To demonstrate that these types of events are global and can be handled by any structure, we'll have an example that will randomize the minimap's border color whenever the player shoots a weapon.

### Defining the Event

Here's how we can define the custom event.

The event is bare-bones and simple, as we don't need to have extra information, all we need to know is when the player shoots a weapon. If you want to expand this, feel free to add additional properties such as references to the player or the weapon they used.

```swift
module MyMod

class ShootEvent extends CallbackSystemEvent {
  public static final func Create() -> ref<ShootEvent> {
    return new ShootEvent();
  }
}
```

### Dispatching the Event

To dispatch the event, we'll need to know where in the game's code represents the player shoots a weapon. For our use-case, we can use [`ShootEvents#OnEnter`](https://nativedb.red4ext.com/ShootEvents#OnEnter).

{% tabs %}
{% tab title="REDScript" %}

```swift
module MyMod

@wrapMethod(ShootEvents)
protected final func OnEnter(stateContext: ref<StateContext>, scriptInterface: ref<StateGameScriptInterface>) -> Void {
  wrappedMethod(stateContext, scriptInterface);

  GameInstance.GetCallbackSystem().DispatchEvent(ShootEvent.Create());
}
```

{% endtab %}

{% tab title="Lua" %}

```lua
registerForEvent('onInit', function()
  ---@param this ShootEvents
  ---@param stateContext StateContext
  ---@param scriptInterface StateGameScriptInterface
  ObserveAfter("ShootEvents", "OnEnter", function(this, stateContext, scriptInterface)
    Game.GetCallbackSystem():DispatchEvent(MyMod_ShootEvent.Create())
  end)
end)
```

{% endtab %}
{% endtabs %}

### Handling the Event

To handle the event, we'll need to [find the logic controller](/scripting-cyberpunk/scripting/game-systems/ui-scripting/ink-controllers.md#finding-an-ink-controller) for the mini-map and hook into the [life-cycle event methods](/scripting-cyberpunk/scripting/game-systems/ui-scripting/ink-controllers.md#lifecycle-events) for it.

For our use-case, this would be [`gameuiMinimapContainerController#OnInitialize`](https://nativedb.red4ext.com/gameuiMinimapContainerController#OnInitialize) and [`gameuiMinimapContainerController#OnUnitialize`](https://nativedb.red4ext.com/gameuiMinimapContainerController#OnUnitialize). In these methods, we'll need to register/unregister the controller to listen to our event.

```swift
module MyMod

@wrapMethod(MinimapContainerController)
protected cb func OnPlayerAttach(player: ref<GameObject>) -> Bool {
  wrappedMethod(player);
  GameInstance.GetCallbackSystem().RegisterCallback(NameOf<ShootEvent>(), this, n"OnShootWeapon", false);
}

@wrapMethod(MinimapContainerController)
protected cb func OnPlayerDetach(player: ref<GameObject>) -> Bool {
  wrappedMethod(player);
  GameInstance.GetCallbackSystem().UnregisterCallback(NameOf<ShootEvent>(), this, n"OnShootWeapon");
}
```

After registering the event, we'll also need to define the function to handle the event for the controller. In that method, we'll also traverse the UI tree to find the border for the mini-map and randomize the color, thus changing the border whenever the player shoots a weapon.

```swift
@addMethod(MinimapContainerController)
protected cb func OnShootWeapon(event: ref<ShootEvent>) -> Void {
  let root: ref<inkCompoundWidget> = this.GetRootCompoundWidget();
  let border: ref<inkImage> = root.GetWidgetByPathName(n"MiniMapContainer/zoneVignette/border") as inkImage;

  if IsDefined(border) {
    let r: Uint8 = Cast<Uint8>(RandRangeF(0.0, 255.0));
    let g: Uint8 = Cast<Uint8>(RandRangeF(0.0, 255.0));
    let b: Uint8 = Cast<Uint8>(RandRangeF(0.0, 255.0));

    border.SetTintColor(r, g, b, Cast<Uint8>(1.0));
  }
}
```
