Skip to main content

What hooks are

atlasFishing is uploaded to keymaster with asset escrow — the gameplay, client, and server logic is encrypted and not editable. Two files are deliberately left open so you can extend the resource:
atlasFishing/editable/client.lua   -- Fishing.Hooks (client side)
atlasFishing/editable/server.lua   -- Fishing.Hooks (server side)
Both are listed under escrow_ignore in fxmanifest.lua, and each is loaded before the rest of its side:
fxmanifest.lua
client_scripts {
    'editable/client.lua', -- user-editable hooks (Fishing.Hooks) — load first
    'client/main.lua',
    -- ...
}

server_scripts {
    'editable/server.lua', -- user-editable hooks (Fishing.Hooks) — load first
    'server/main.lua',
    -- ...
}
Because they load first, the Fishing.Hooks table always exists by the time the protected code calls into it. You edit the function bodies in these two files; you never edit anything else. An empty body is a no-op — the script checks each hook exists before calling it, so deleting a hook you don’t use is also fine.
These hooks supersede and expand the short “Editable hooks” section on the Configuration page. The hook names and arguments here are the source of truth.

Client vs server hooks

Hooks are split by side, and each side can only do what that side can do.
Runs on the player’s machine. Use these for presentation: toggling a HUD, playing a sound, a screen effect, a local notification. They have no authority over the catch or the player’s inventory.
  • OnMinigameOpen(id)
  • OnMinigameClose(id, result)
  • OnCastStart()
  • OnCatch(data)
  • OnCancel(reason)
Hooks are observers, not gates. Every hook is fire-and-forget — the script ignores whatever you return. Returning false (or a table) from any hook does nothing: it will not cancel a cast, block a catch, or override a reward. See Gating and canceling for what to do instead.

Death is already handled

When a player dies mid-attempt, the bridge’s built-in Bridge.OnDeath fires and atlasFishing calls Fishing.Cancel('death'), which unwinds the cast / bite-wait / minigame cleanly (toggle with Config.death.enabled). You do not need a hook for this. If you want to react to that cancel — for example to make sure your HUD comes back — use the client OnCancel(reason) hook, where reason will be 'death'.

Hook reference

Client hooks — editable/client.lua

OnMinigameOpen(id)
function
Fires the instant a minigame’s NUI is about to open (right after NUI focus is granted).
id
string
Which minigame: 'tension', 'whirlpool', 'castmeter', or 'rhythm'.
Return value is ignored.
OnMinigameClose(id, result)
function
Fires after the minigame resolves and NUI focus has been released.
id
string
The minigame that just closed (same set as above).
result
string
The outcome reported by the minigame: 'success', 'fail', 'timeout', or 'cancel'. (A death/cancel resolves the minigame through its 'fail' path.)
Return value is ignored.
OnCastStart()
function
Fires once per attempt, right after the rod is equipped and just before the casting progress bar. No arguments. Return value is ignored.
OnCatch(data)
function
Fires on the catching player’s client just before the catch presentation (the fish spawning on the ground, the flop, the pickup, the hold-up). This is presentation only — the item has not been granted yet; the server grants it on claim. For reward logic, use the server OnFishCaught hook instead.
data
table
The presentation payload:
FieldTypeMeaning
labelstringDisplay name, e.g. "Golden Koi".
itemstringInventory item key, e.g. "fish_koi".
tierstringjunk · common · uncommon · rare · epic · legendary.
propstringProp model used in the presentation.
junkbooleantrue for junk pulls (shells/critters — no item granted).
Return value is ignored.
OnCancel(reason)
function
Fires when an in-progress attempt is canceled via Fishing.Cancel.
reason
string
Currently 'death'. Treat any other value as a generic cancel — the set may grow.
Return value is ignored.

Server hooks — editable/server.lua

OnFishCaught(src, fish)
function
Fires server-side after a real fish has been added to the player’s inventory (in the fishing:claim callback). Does not fire for junk pulls. This is the correct, authoritative place to award extra rewards or log catches.
src
number
The player’s server ID.
fish
table
The matching Config.fish entry — key, label, tier, price, prop, and any custom fields you added to that entry.
Return value is ignored.
OnSell(src, count, value)
function
Fires server-side after a successful sale, once the money has been paid into the configured account.
src
number
The player’s server ID.
count
number
Number of fish sold.
value
number
Total amount paid out.
Return value is ignored.

Worked examples

All examples are copy-paste-ready into the matching editable/ file. They use the global Bridge (provided by atlasBridge), which is loaded before atlasFishing, so it is always available inside your hooks.

Hide a custom HUD during a minigame

The minigame takes NUI focus and fills the screen, so a busy HUD underneath looks cluttered. Hide it on open and restore it on close. The pattern works with SendNUIMessage to your own HUD resource, or with an export your HUD exposes.
Fishing.Hooks = {
    OnMinigameOpen = function(id)
        exports['my-hud']:setVisible(false)
    end,

    OnMinigameClose = function(id, result)
        exports['my-hud']:setVisible(true)
    end,

    -- If the player dies during a minigame the close hook still runs, but add a
    -- belt-and-braces restore on cancel so the HUD can never get stuck hidden.
    OnCancel = function(reason)
        exports['my-hud']:setVisible(true)
    end,

    OnCastStart = function() end,
    OnCatch = function(data) end,
}
SendNUIMessage targets atlasFishing’s own NUI page, so it only works here if your HUD lives in the same resource. To talk to a separate HUD resource, use that resource’s export (or exports['my-hud']:...) as in the first tab.

Award a custom item or bonus money on a catch

Use the server hook OnFishCaught — it runs after the fish is already in the player’s inventory, with full server authority. Here we drop occasional bonus cash and a guaranteed extra item on legendary catches.
editable/server.lua
Fishing.Hooks = {
    OnFishCaught = function(src, fish)
        -- 10% chance of a small cash tip on any catch
        if math.random() < 0.10 then
            Bridge.AddMoney(src, 'cash', 50, 'fishing-bonus')
            Bridge.Notify(src, { title = 'Lucky!', description = 'You found $50 tucked in the catch.', type = 'success' })
        end

        -- Every legendary catch also grants a collectible item
        if fish.tier == 'legendary' then
            if Bridge.Inventory.CanCarry(src, 'fishing_trophy', 1) then
                Bridge.Inventory.AddItem(src, 'fishing_trophy', 1)
            end
        end
    end,

    OnSell = function(src, count, value) end,
}
Only grant rewards from a server hook. OnCatch runs on the client and has no authority — anything you “give” there is cosmetic and trivially spoofable. fishing_trophy (and any item you add) must exist in your inventory definitions, exactly like the fish keys on the Items & Icons page.

Notify on rare catches

A simple flourish: announce only the good pulls. Use the client OnCatch hook so the message lands the moment the catch is presented. data.tier tells you the rarity; data.junk lets you skip junk.
editable/client.lua
local RARE_TIERS = { rare = true, epic = true, legendary = true }

Fishing.Hooks = {
    OnCatch = function(data)
        if data.junk then return end
        if RARE_TIERS[data.tier] then
            Bridge.Notify({
                title = 'Rare Catch!',
                description = ('You reeled in a %s.'):format(data.label),
                type = 'success',
            })
        end
    end,

    OnMinigameOpen = function(id) end,
    OnMinigameClose = function(id, result) end,
    OnCastStart = function() end,
    OnCancel = function(reason) end,
}
Server-side notifications take the player id first: Bridge.Notify(src, { ... }). Client-side they take just the payload: Bridge.Notify({ ... }). Mind which side your hook is on.

Reward sellers (server OnSell)

OnSell is handy for loyalty bonuses, taxes, or logging revenue. Here, a flat 5% bonus on top of any sale of 10+ fish:
editable/server.lua
Fishing.Hooks = {
    OnFishCaught = function(src, fish) end,

    OnSell = function(src, count, value)
        if count >= 10 then
            local bonus = math.floor(value * 0.05)
            Bridge.AddMoney(src, 'bank', bonus, 'fishing-bulk-bonus')
            Bridge.Notify(src, { title = 'Bulk Bonus', description = ('+%d for selling %d fish.'):format(bonus, count), type = 'success' })
        end
    end,
}

Gating and canceling — what hooks cannot do

You may want to require something before a player can fish — an item, a job, a zone — and block the attempt otherwise. Hooks cannot do this. They are observers: OnCastStart fires after the rod is already equipped and the cast has begun, and its return value is ignored, so there is no hook that can stop a cast, reject a catch, or veto a sale. Use the supported, server-authoritative levers instead:
1

Require an item — use bait as the gate

Fishing already requires the rod item and consumes a bait per attempt (rolled and removed server-side in fishing:begin). To require a permit or licence, make it a bait the player must own, or sell the rod only to players who qualify. This is the intended progression lever — see Baits and the supplies shop.
2

Restrict zones — use multipliers, not blocks

Config.zones boost or reduce odds per tier; by design they never gate fishing — players can fish anywhere. Set a tier’s multiplier to 0.0 to make it impossible in open water but possible only inside a zone, which is the closest supported “you must be here for X” behavior. See Hotspot zones.
3

Gate by job/permission — do it where you control the rod

The cleanest job/permission gate is to control who can obtain the rod (e.g. only sell it to a fisherman job), since possessing the rod is what unlocks fishing. The bridge exposes Bridge.GetJob(src) and Bridge.IsAdmin(src) server-side if you build that check into your own shop or item-granting flow.
4

Soft client feedback — fine, but never trust it

You can use the client OnCastStart to show a message (“You need a fishing licence”) — but it runs after casting begins and can’t stop anything, so treat it as cosmetic only. Any real restriction must be enforced server-side as above.
If you genuinely need a hard server-side cast gate (a hook that can return false to cancel fishing:begin), that is a feature request, not something the current hooks support — don’t try to fake it from a client hook.

Testing your hooks

1

Enable debug logging

Turn on the bridge’s debug output so you can see hook-adjacent activity in the server/client consoles:
server.cfg
setr atlas:debug "true"
(atlasFishing also has its own Config.debug flag in config.lua for extra fishing-specific output.)
2

Reload after every edit

The editable/ files are plain scripts — changes only take effect on a resource restart:
restart atlasFishing
A refresh is not needed for edits to existing files, but restart the resource so the new hook bodies load.
3

Drive the hooks quickly

Use the built-in admin command to hand yourself fish and bait without grinding:
/givefish fish_koi 1
Then fish (or sell) to fire OnCatch / OnFishCaught / OnSell, and watch your print output or HUD react.
4

Sanity-check with prints first

Before wiring a real HUD or reward, drop a print in each hook to confirm it fires with the data you expect:
editable/server.lua
OnFishCaught = function(src, fish)
    print(('[fishing] %s caught %s (%s)'):format(src, fish.key, fish.tier))
end,