> ## Documentation Index
> Fetch the complete documentation index at: https://docs.atlasscripts.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> The authoritative reference for every atlasFishing config block, the editable hooks, and death-cancel behavior.

Almost all `atlasFishing` behavior lives in one file:

```text theme={null}
atlasFishing/config.lua
```

It is a shared script loaded before the rest of the resource, so it is pure data — **keep secrets out of it**. Locations, prices, item keys, minigame difficulty, zones, and shop contents all belong here. This page documents every block in load order.

<Note>
  `Config.enabled` is force-set to `false` if the name guard fails (`AtlasFishing.ResourceOk == false`). Keep the folder named `atlasFishing`.
</Note>

## Global switches

```lua theme={null}
Config.enabled = true
Config.debug = false
```

<ParamField path="Config.enabled" type="boolean" default="true">
  Master on/off. Automatically forced to `false` if the resource folder is renamed (the name guard sets `ResourceOk = false`).
</ParamField>

<ParamField path="Config.debug" type="boolean" default="false">
  Extra debug output while testing. Turn it off before release.
</ParamField>

## Fisherman ped — `Config.ped`

One ped with a two-option interaction: **Supplies** (open the shop) and **Sell** (sell your catch).

```lua theme={null}
Config.ped = {
    model = 's_m_m_cntrybar_01',
    coords = vec4(-1819.42, -1193.62, 14.31, 134.0), -- x, y, z, heading
    scenario = 'WORLD_HUMAN_STAND_FISHING',
    interact = { method = 'interact', distance = 2.5, expandDistance = 1.4, key = 'E', marker = 'fish' },
    blip = { enabled = true, sprite = 68, color = 3, scale = 0.8, label = 'Fishing' },
}
```

<ParamField path="model" type="string" default="s_m_m_cntrybar_01">
  Ped model.
</ParamField>

<ParamField path="coords" type="vec4" default="vec4(-1819.42, -1193.62, 14.31, 134.0)">
  Position and heading (the fourth value is heading). The default is a placeholder — move it.
</ParamField>

<ParamField path="scenario" type="string" default="WORLD_HUMAN_STAND_FISHING">
  Ambient pose for the ped. Optional.
</ParamField>

<ParamField path="interact" type="table">
  How players reach the menu. See [Interaction method](#interaction-method) below.
</ParamField>

<ParamField path="blip" type="table">
  Map blip — `enabled`, `sprite`, `color`, `scale`, `label`.
</ParamField>

### Interaction method

`interact.method` decides how players open the ped menu. The **same options apply to the boat-rental ped**.

| Method     | Behavior                                                                                |
| ---------- | --------------------------------------------------------------------------------------- |
| `interact` | Bridge interaction prompt with the configured `key` (default).                          |
| `target`   | `ox_target` / `qb-target` eye option, when a target provider is detected by the bridge. |
| `textui`   | Proximity text prompt that opens the menu on `key` press.                               |

<ParamField path="interact.method" type="string" default="interact">
  `interact` · `target` · `textui`. Use `target` only if `ox_target` or `qb-target` is installed and detected; otherwise keep `interact` or `textui`.
</ParamField>

<ParamField path="interact.distance" type="number" default="2.5">
  Distance at which the interaction becomes available.
</ParamField>

<ParamField path="interact.expandDistance" type="number" default="1.4">
  Extra comfort range for the marker / prompt.
</ParamField>

<ParamField path="interact.key" type="string" default="E">
  Key used by `interact` and `textui` methods.
</ParamField>

<ParamField path="interact.marker" type="string" default="fish">
  Bridge marker style shown over the ped.
</ParamField>

## Rod — `Config.rod`

A single rod — progression is bait-only. The rod is registered usable automatically (`Bridge.Inventory.RegisterUsableItem(Config.rod.item)`).

```lua theme={null}
Config.rod = {
    item = 'fishing_rod',
    prop = { model = 'fish_rod', bone = 18905, pos = vec3(0.15, 0.09, 0.01), rot = vec3(58.0, 100.0, -180.0) },
    anim = { dict = 'amb@world_human_stand_fishing@idle_a', clip = 'idle_b', flag = 49 },
}
```

<ParamField path="item" type="string" default="fishing_rod">
  Inventory item the player uses to fish. If you rename it, update your inventory definition **and** `Config.shop.items`.
</ParamField>

<ParamField path="prop" type="table">
  Held rod prop — `model`, `bone`, `pos` (offset), `rot` (rotation).
</ParamField>

<ParamField path="anim" type="table">
  Fishing idle animation — `dict`, `clip`, `flag`.
</ParamField>

## Casting — `Config.cast`

```lua theme={null}
Config.cast = {
    requireWater = true,
    allowBoats = true,
    castMs = 3000,
    biteWaitMin = 4000,
    biteWaitMax = 12000,
    cancelKey = 'X',
}
```

<ParamField path="requireWater" type="boolean" default="true">
  Require the player to face water before casting.
</ParamField>

<ParamField path="allowBoats" type="boolean" default="true">
  Allow fishing while in a boat.
</ParamField>

<ParamField path="castMs" type="number" default="3000">
  Cast progress-bar duration, in milliseconds.
</ParamField>

<ParamField path="biteWaitMin" type="number" default="4000">
  Minimum random idle wait before the bite/minigame, in milliseconds.
</ParamField>

<ParamField path="biteWaitMax" type="number" default="12000">
  Maximum random idle wait before the bite/minigame, in milliseconds.
</ParamField>

<ParamField path="cancelKey" type="string" default="X">
  Key to stop fishing mid-attempt.
</ParamField>

## Rarity tiers — `Config.tiers`

Base weights — the probability of each tier with neutral bait and no zone bonus. Higher number = more likely.

```lua theme={null}
Config.tiers = {
    junk      = 10,
    common    = 45,
    uncommon  = 24,
    rare      = 9,
    epic      = 3,
    legendary = 1,
}
```

These are starting weights for the catch roll. Bait and zones multiply them per tier — see [Economy Balancing](/atlas-fishing/economy-balancing) for the full formula.

## Baits — `Config.baits`

An ordered list. Each bait has a price, a fail chance (catch nothing), and per-tier weight multipliers.

```lua theme={null}
Config.baits = {
    { key = 'bait_worms',  label = 'Worms',       price = 15,  fail = 0.28, mult = { junk = 1.4, common = 1.2, uncommon = 0.6, rare = 0.2, epic = 0.0, legendary = 0.0 } },
    -- ...
    { key = 'bait_lure',   label = 'Glowing Lure', price = 600, fail = 0.05, mult = { junk = 0.0, common = 0.3, uncommon = 0.6, rare = 1.2, epic = 2.0, legendary = 2.5 } },
}
```

<ParamField path="key" type="string" required>
  Inventory item key for the bait.
</ParamField>

<ParamField path="label" type="string" required>
  Display name in the supplies shop and bait radial.
</ParamField>

<ParamField path="price" type="number" required>
  Shop price. Should match the matching `Config.shop.items` entry.
</ParamField>

<ParamField path="fail" type="number" required>
  Chance (0–1) to catch nothing on a successful minigame. Better bait should fail less.
</ParamField>

<ParamField path="mult" type="table" required>
  Per-tier weight multipliers — `junk`, `common`, `uncommon`, `rare`, `epic`, `legendary`. A `0.0` makes that tier impossible with this bait.
</ParamField>

<ParamField path="image" type="string">
  Optional per-bait image URL for the bait radial that overrides [`Config.Images`](#artwork-config-images). Without it the radial uses the `images/` art (then the bridge's `fish` icon). Separate from the inventory PNG.
</ParamField>

The catch weighting combines all four factors:

```text theme={null}
effectiveWeight(fish) = tiers[fish.tier] * (fish.weight or 1) * bait.mult[fish.tier] * zone.mult[fish.tier]
```

## Fish & junk — `Config.fish`

Every catch entry: item key, label, rarity tier, sell price, held prop, and an optional intra-tier weight.

```lua theme={null}
{ key = 'fish_snapper', label = 'Snapper', tier = 'rare', price = 1100, prop = 'fish_snapper' }
{ key = 'fish_koi',     label = 'Golden Koi', tier = 'legendary', price = 25000, prop = 'fish_koi', weight = 0.3 }
```

<ParamField path="key" type="string" required>
  Inventory item key. Must exist in your inventory for the player to receive the catch (junk entries omit a granted item — see below).
</ParamField>

<ParamField path="label" type="string" required>
  Display name.
</ParamField>

<ParamField path="tier" type="string" required>
  One of `junk`, `common`, `uncommon`, `rare`, `epic`, `legendary`.
</ParamField>

<ParamField path="price" type="number">
  Sell price. Junk entries omit `price` (they are not sellable items).
</ParamField>

<ParamField path="prop" type="string" required>
  Prop model used for the ground spawn / held-fish presentation.
</ParamField>

<ParamField path="weight" type="number" default="1">
  Optional bias **within** a tier — lower makes a fish rarer among its tier-mates (e.g. the Golden Koi at `0.3`).
</ParamField>

### Junk — `Config.junk`

```lua theme={null}
Config.junk = { enabled = true }
```

Junk catches (conch, scallop, clam, oyster, starfish, sea urchin) have **no `item` key** — they are not granted to the inventory, only shown as a "you pulled up junk" result with a real shell/critter prop. Set `enabled = false` to remove junk from the catch pool entirely.

## Hotspot zones — `Config.zones`

Players can fish anywhere; standing inside a zone applies its per-tier multipliers on top of bait. Zones only ever boost (or reduce) odds — they never gate fishing.

```lua theme={null}
Config.zones = {
    { id = 'deep_sea',  label = 'Deep Sea',  coords = vec3(-2100.0, -1400.0, 0.0), radius = 400.0, mult = { rare = 1.4, epic = 2.0, legendary = 2.5 } },
    { id = 'alamo_sea', label = 'Alamo Sea', coords = vec3(1200.0, 4200.0, 30.0),  radius = 600.0, mult = { rare = 1.2, epic = 1.4, legendary = 1.4 } },
}
```

<ParamField path="id" type="string" required>
  Unique zone ID.
</ParamField>

<ParamField path="label" type="string" required>
  Display / debug name.
</ParamField>

<ParamField path="coords" type="vec3" required>
  Zone center.
</ParamField>

<ParamField path="radius" type="number" required>
  Zone radius in game units.
</ParamField>

<ParamField path="mult" type="table" required>
  Per-tier multipliers applied while inside the zone. Tiers you omit are unchanged (multiplier of 1).
</ParamField>

## Catch sequence — `Config.catchSeq`

The ground spawn, pickup, hold animation, and the flopping-fish effect after a successful catch.

```lua theme={null}
Config.catchSeq = {
    spawnRadius = 1.4,
    pickupAnim = { dict = 'pickup_object', clip = 'pickup_low', flag = 48, durationMs = 1100 },
    holdAnim   = { dict = 'anim@heists@box_carry@', clip = 'idle', flag = 49 },
    holdMs     = 2600,
    holdProp   = { bone = 24816, pos = vec3(0.0, 0.43, -0.05), rot = vec3(164.0, -95.0, 0.0) },
    flop = {
        enabled = true, durationMs = 3000, sideRoll = 90.0, pitch = 0.0,
        speed = 7.5, rockDeg = 24.0, yawDeg = 20.0, hop = 0.07,
        groundOffset = 0.06, decay = 0.45,
    },
}
```

<ParamField path="spawnRadius" type="number" default="1.4">
  Raycast circle radius around the ped used to find a ground spot for the catch.
</ParamField>

<ParamField path="pickupAnim" type="table">
  Pickup animation — `dict`, `clip`, `flag`, `durationMs`.
</ParamField>

<ParamField path="holdAnim" type="table">
  Carry/hold animation — `dict`, `clip`, `flag`.
</ParamField>

<ParamField path="holdMs" type="number" default="2600">
  How long the player holds up the catch, in milliseconds.
</ParamField>

<ParamField path="holdProp" type="table">
  Held-prop attachment (left hand) — `bone`, `pos`, `rot`.
</ParamField>

<ParamField path="flop" type="table">
  The whole-entity flop the fish does on the ground before pickup. Driven by entity motion (not bones), so it works on every prop.
</ParamField>

<Accordion title="Flop tuning keys">
  * `enabled` — turn the flop on/off.
  * `durationMs` — flop time before the player reaches for it.
  * `sideRoll` — base roll that lays the fish on its side. If a model lies the wrong way, set `sideRoll = 0` and `pitch = 90` instead.
  * `pitch` — alternate axis for laying the fish flat.
  * `speed` — flop tempo.
  * `rockDeg` — how far it rocks past the side-roll each slap.
  * `yawDeg` — side-to-side body swing.
  * `hop` — vertical hop height (m) per slap.
  * `groundOffset` — rest height above the ground (≈ the fish's half-thickness).
  * `decay` — `0..1`, how much the flopping tires out over the duration.
</Accordion>

## Minigames — `Config.minigame`

Selects and tunes the four NUI minigames. Each game's difficulty block is sent to its NUI.

```lua theme={null}
Config.minigame = {
    mode = 'random',
    static = 'tension',
    minMs = 2500,
    pool = { 'tension', 'whirlpool', 'castmeter', 'rhythm' },
    sounds = true,
    -- per-game difficulty blocks (tension / whirlpool / castmeter / rhythm) ...
}
```

<ParamField path="mode" type="string" default="random">
  `random` picks from `pool` each catch; `static` always uses `static`.
</ParamField>

<ParamField path="static" type="string" default="tension">
  The minigame used when `mode = 'static'`.
</ParamField>

<ParamField path="minMs" type="number" default="2500">
  Anti-cheat floor: a `success` reported faster than this is **rejected server-side**. Keep it near default unless you also shorten the games.
</ParamField>

<ParamField path="pool" type="string[]" default="{ 'tension', 'whirlpool', 'castmeter', 'rhythm' }">
  The minigames eligible in `random` mode.
</ParamField>

<ParamField path="sounds" type="boolean" default="true">
  Procedural UI sounds in the minigames. Set `false` to mute.
</ParamField>

The per-game difficulty blocks (`tension`, `whirlpool`, `castmeter`, `rhythm`) are documented in full on the [Minigames](/atlas-fishing/minigames) page.

## Death cancellation — `Config.death`

```lua theme={null}
Config.death = {
    enabled = true,
}
```

<ParamField path="enabled" type="boolean" default="true">
  When on, an in-progress fishing attempt (cast, bite wait, or minigame) is cancelled if the player dies — so they are never left stuck in a fishing state.
</ParamField>

Death detection is handled by `atlasBridge`. If your server uses a custom death system and fishing does not cancel on death, route your framework's death event through the bridge with a convar in `server.cfg`:

```cfg theme={null}
setr atlas:deathEvent "baseevents:onPlayerDied"
```

## Selling — `Config.sell`

```lua theme={null}
Config.sell = {
    mode = 'select',
    selectUi = 'radial',
    account = 'bank',
}
```

<ParamField path="mode" type="string" default="select">
  `all` sells everything at once; `select` lets the player choose what to sell.
</ParamField>

<ParamField path="selectUi" type="string" default="radial">
  When `mode = 'select'`: `radial` (radial menu) or `input` (number input).
</ParamField>

<ParamField path="account" type="string" default="bank">
  Bridge money account paid into — typically `cash` or `bank`.
</ParamField>

## Supplies shop — `Config.shop`

The custom NUI shop that sells the rod and baits. Every `key` here must exist in your inventory.

```lua theme={null}
Config.shop = {
    title = 'Fishing Supplies',
    payments = { 'cash', 'bank' },
    items = {
        { key = 'fishing_rod', label = 'Fishing Rod', price = 250 },
        { key = 'bait_worms',  label = 'Worms',        price = 15 },
        -- ...
    },
}
```

<ParamField path="title" type="string" default="Fishing Supplies">
  Shop window title.
</ParamField>

<ParamField path="payments" type="string[]" default="{ 'cash', 'bank' }">
  Accounts the player may pay with. The first entry is the default.
</ParamField>

<ParamField path="items" type="table[]" required>
  Catalog entries — `key`, `label`, `price`. Keep prices in sync with `Config.baits[].price`.
</ParamField>

## Boat rental — `Config.boatrental`

Optional. A second ped opens a boat shop NUI; the server spawns the chosen boat. One rental per player; a 1s client tracker despawns the boat if the renter strays past `cleanupDistance`, and the bridge auto-cleans it on disconnect.

```lua theme={null}
Config.boatrental = {
    enabled = true,
    account = 'cash',
    seatOnRent = true,
    cleanupDistance = 180.0,
    trackIntervalMs = 1000,
    ped = { model = 's_m_y_baywatch_01', coords = vec4(-1817.5, -1196.7, 14.3, 130.0),
            scenario = 'WORLD_HUMAN_CLIPBOARD',
            interact = { method = 'interact', distance = 2.5, expandDistance = 1.4, key = 'E', marker = 'boat' },
            blip = { enabled = true, sprite = 410, color = 3, scale = 0.8, label = 'Boat Rental' } },
    spawn = vec4(-1850.6, -1235.9, 0.2, 200.0),
    preview = vec4(-2100.0, -1020.0, 12.0, 90.0),
    imageUrl = nil,
    boats = {
        { model = 'seashark', label = 'Seashark', price = 300, desc = 'Personal watercraft — fast and nimble.' },
        -- ...
    },
}
```

<ParamField path="enabled" type="boolean" default="true">
  Enables the rental ped and boat shop. Set `false` to remove the system.
</ParamField>

<ParamField path="account" type="string" default="cash">
  Account rentals are charged from — `cash` or `bank`. Flat fee, no refund.
</ParamField>

<ParamField path="seatOnRent" type="boolean" default="true">
  Drop the player into the boat on spawn.
</ParamField>

<ParamField path="cleanupDistance" type="number" default="180.0">
  Despawn the rental if the renter gets this far (m) from it.
</ParamField>

<ParamField path="trackIntervalMs" type="number" default="1000">
  Distance-tracker tick. Only runs while a rental is active.
</ParamField>

<ParamField path="ped" type="table">
  Rental ped — `model`, `coords`, `scenario`, `interact` (same options as the fisherman), `blip`.
</ParamField>

<ParamField path="spawn" type="vec4" default="vec4(-1850.6, -1235.9, 0.2, 200.0)">
  Water spawn point for the rented boat. Placeholder — move it to valid water.
</ParamField>

<ParamField path="preview" type="vec4" default="vec4(-2100.0, -1020.0, 12.0, 90.0)">
  Offscreen showroom point for the boat preview camera.
</ParamField>

<ParamField path="imageUrl" type="string" default="nil">
  Optional explicit image template where `{model}` is replaced with the boat model (a local or CDN URL). When set it wins; when `nil`, boat art comes from [`Config.Images`](#artwork-config-images) (then the inline SVG fallback).
</ParamField>

<ParamField path="boats" type="table[]">
  Boat catalog — `model`, `label`, `price` (flat session cost), `desc`. Defaults are stock GTA V boats from `seashark` up to `tug`.
</ParamField>

## Artwork — `Config.Images`

**Artwork loads from the resource's `images/` folder by default — drop PNGs named by item/boat key, no CDN required.** The 31 item images (rod, baits, fish) ship pre-filled; the boat folder is empty for you to add your own renders. Files are served straight to the UI over CEF's `nui://` protocol, so there's nothing to host.

```
atlasFishing/images/
├── items/    ← rod, baits, fish — named by item key (ships filled)
│   ├── fishing_rod.png
│   ├── bait_worms.png
│   └── fish_tuna.png …
└── boats/    ← boat showroom — named by boat model (drop your own)
    ├── seashark.png
    └── dinghy.png …
```

For each surface the script builds the URL from the key as `base .. key .. '.' .. extension`, e.g. `nui://atlasFishing/images/items/ + fish_tuna + .png`. The default config:

```lua theme={null}
Config.Images = {
    extension  = 'png',
    base       = 'nui://atlasFishing/images/items/',   -- global fallback (shop, radial)
    Shops      = nil,                                   -- nil → uses base
    BoatRental = 'nui://atlasFishing/images/boats/',
    Radial     = nil,                                   -- nil → uses base
}
```

<Note>
  **Missing files fall back gracefully.** If a file isn't present, that one item or boat shows its built-in placeholder (a glyph icon, or the inline SVG for boats) — never a broken image. So you can drop art in piece by piece; each item lights up as soon as its PNG exists.
</Note>

### Use a CDN instead

Point any `base` at an `https://` URL and the script builds CDN URLs the same way. Override per-surface (`Shops`, `BoatRental`, `Radial`) when that art lives in a different folder; a per-surface base falls back to `base`.

```lua theme={null}
Config.Images = {
    extension  = 'png',
    base       = 'https://cdn.example.com/items/',   -- shop + radials
    BoatRental = 'https://cdn.example.com/boats/',
}
```

<ParamField path="extension" type="string" default="png">
  File extension appended to every key (without the dot).
</ParamField>

<ParamField path="base" type="string" default="nui://atlasFishing/images/items/">
  Global base, used by any surface that doesn't set its own. A trailing `/` is optional. A local `nui://` path by default; set an `https://` URL for a CDN. Empty/`nil` → placeholder.
</ParamField>

<ParamField path="Shops" type="string" default="nil">
  Base for the supplies shop (rod + baits). Keyed by item key. `nil` → falls back to `base`.
</ParamField>

<ParamField path="BoatRental" type="string" default="nui://atlasFishing/images/boats/">
  Base for the boat-rental showroom. Keyed by boat model. Overridden by `Config.boatrental.imageUrl` if that template is set.
</ParamField>

<ParamField path="Radial" type="string" default="nil">
  Base for the bait and sell radials. Keyed by item key. `nil` → falls back to `base`.
</ParamField>

<Tip>
  The key is the item key (`bait_worms`, `fish_tuna`, `fishing_rod`, …) for the shop and radials, and the boat model (`seashark`, `dinghy`, …) for the boat rental — name your files to match.
</Tip>

## Player messages — `Config.notify`

Short, customer-facing strings. Keep them brief so they fit a notification.

```lua theme={null}
Config.notify = {
    noBait    = 'You need bait before you can fish.',
    noWater   = 'Face the water before casting.',
    cancelled = 'You stopped fishing.',
    gotAway   = 'The catch got away.',
    nothing   = 'Nothing bit this time.',
    junk      = 'You pulled up junk.',
    noFish    = 'You have no fish to sell.',
}
```

## Editable hooks

`editable/client.lua` and `editable/server.lua` expose `Fishing.Hooks` — callbacks that fire at key moments. They are **safe to edit**, load before the rest of each side, and run as no-ops when left empty. Use them for logging, custom rewards, or HUD toggles.

<CodeGroup>
  ```lua editable/server.lua theme={null}
  Fishing.Hooks = {
      -- A player caught + claimed a fish. `fish` = the Config.fish entry (key, label, tier, price...).
      OnFishCaught = function(src, fish) end,

      -- A player sold fish. `count` = number sold, `value` = total paid.
      OnSell = function(src, count, value) end,
  }
  ```

  ```lua editable/client.lua theme={null}
  Fishing.Hooks = {
      -- A minigame is about to open. `id` = 'tension' | 'whirlpool' | 'castmeter' | 'rhythm'.
      OnMinigameOpen = function(id) end,

      -- A minigame closed. `result` = 'success' | 'fail' | 'timeout' | 'cancel'.
      OnMinigameClose = function(id, result) end,

      -- The player just cast the line.
      OnCastStart = function() end,

      -- A catch is about to be presented. `data` = { label, item, tier, prop, junk }.
      OnCatch = function(data) end,

      -- Fishing was cancelled mid-attempt. `reason` = 'death' (extend as needed).
      OnCancel = function(reason) end,
  }
  ```
</CodeGroup>

<Tip>
  A common use: hide your HUD while a minigame is open with `OnMinigameOpen`/`OnMinigameClose`, and log every catch with the server-side `OnFishCaught`.
</Tip>

<Tip>
  All default ped, boat-rental, and zone coordinates are placeholders for testing. Move the fisherman, the rental ped, the boat spawn, and the zones before launching.
</Tip>
