> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/3d-interactive-minigames-bundle/systems/hooks.md).

# Hooks

`onSuccess` and `onFail` let a hack point fire something on the client, on the server, or run an inline function when the minigame ends. Every hack point supports them, and every hack type in `cfg.hacks.types` supports defaults that apply when a point does not override.

***

### Where hooks live

Three layers, most-specific first. Only one wins per side, hooks do not merge:

1. **Per-point override**, on the entry in `data/saved_minigames.lua`.
2. **Per-type default**, on the hack type in `cfg.hacks.types`.
3. **Global default**, `cfg.hacks.onSuccess` / `cfg.hacks.onFail`.

The first one that exists wins. If a point sets `onSuccess`, the type and global defaults are ignored for success, even if the point does not set `onFail`, the fail hook still falls through to the type or global.

***

### Shape

```lua
onSuccess = {
    fn          = function(ctx) print('done') end,
    clientEvent = 'mypack:client:openDoor',
    serverEvent = 'mypack:server:openVault',
    args        = { 'vault_1', 42 },
}
```

Every field is optional. Any subset works. All that are present fire.

| Field         | Runs where | Notes                                         |
| ------------- | ---------- | --------------------------------------------- |
| `fn`          | client     | Called with a context table. Not serialisable |
| `clientEvent` | client     | `TriggerEvent(clientEvent, args...)`          |
| `serverEvent` | server     | `TriggerServerEvent(serverEvent, args...)`    |
| `args`        | both       | Passed as varargs to whichever event fires    |

***

### Context table (`fn`)

The inline function receives a single table:

```lua
onSuccess = {
    fn = function(ctx)
        print(ctx.hackType)   -- 'fingerprint'
        print(ctx.minigame)   -- 'untangle'
        print(ctx.coords)     -- vec4
        print(ctx.entity)     -- entity handle (nil for local types)
        print(ctx.playerPed)  -- PlayerPedId()
    end,
}
```

Great for one-off hacks. Not durable, see the auto-save warning below.

***

### Persistence

`data/saved_minigames.lua` is rewritten on every `/create_minigame` and `/remove_minigame`. It serialises Lua values, but functions are not serialisable, so a hook with `fn = function() ... end` on a saved point gets dropped on the next auto-save and a warning prints:

```
[rm_3dminigames] WARNING: entry #3 (bank vault) has an fn hook that will
be dropped by auto-save. Use clientEvent/serverEvent for persistence, or
set cfg.hacks.autoSave = false.
```

Two ways out:

1. **Use `clientEvent` or `serverEvent`** and put the actual code in a handler somewhere else. That is the recommended path.
2. **Turn off auto-save.** `cfg.hacks.autoSave = false` and the file is never rewritten. The commands still work, they just do not persist.

***

### Examples

#### Global default that logs to Discord

```lua
cfg.hacks.onSuccess = {
    serverEvent = 'mypack:log',
    args        = { 'hack:success' },
}
cfg.hacks.onFail = {
    serverEvent = 'mypack:log',
    args        = { 'hack:fail' },
}
```

Every hack point in the world reports its outcome. Per-point overrides still work on top.

#### Per-type default: laptop always opens a menu

```lua
cfg.hacks.types.laptop_open.onSuccess = {
    clientEvent = 'mypack:openLaptopMenu',
}
```

Applies to every `laptop_open` point that does not set its own `onSuccess`.

#### Per-point override in `data/saved_minigames.lua`

```lua
SavedMinigames = {
    {
        label      = 'Bank vault laptop',
        hackType   = 'laptop_open',
        coords     = vec4(150.20, -1040.10, 29.35, 340.00),
        minigame   = 'vaultCombination',
        difficulty = 'hard',
        onSuccess  = {
            serverEvent = 'bank:server:openVault',
            args        = { 'main_vault' },
        },
        onFail = {
            clientEvent = 'bank:client:triggerAlarm',
        },
    },
}
```

#### Callsite hook (library mode)

Exports do not support hooks directly, because the export already returns the boolean. Handle the outcome inline:

```lua
CreateThread(function()
    if exports['rm_3dminigames']:untangle({ difficulty = 'hard' }) then
        TriggerServerEvent('mypack:server:openVault')
    else
        TriggerServerEvent('mypack:server:triggerAlarm')
    end
end)
```

***

### Ordering

When a hack point ends, the client resolves hooks in this order:

1. `fn` fires immediately, on the client.
2. `clientEvent` fires next, on the same client.
3. `serverEvent` fires last, over the network.

The scenes and cam movement finish first, so a `fn` that opens a follow-up minigame will not fight the sync-scene for control.

***

### What not to do

* Do not put secrets in `args`. The event fires from client to server, so the client owns the payload.
* Do not use `fn` for anything you need to survive a `/create_minigame` reshuffle. Auto-save will drop it. See above.
* Do not fire a hack point's own event from inside its own hook. That will re-enter the pipeline and softlock.
