# Configuration

Every setting of Koja Fishing lives in `shared/`, split into one file per system.
This page walks through each file and says what every option does.
The skill tree is configured separately in `data/skills.json`.

| File | What it controls |
|---|---|
| `config.main.lua` | Language, debug, levels, security, shop limits, dynamic prices, redeem codes, electric fishing. |
| `config.minigame.lua` | Cast and stop keys, the strike window and the reeling fight per rarity. |
| `config.tutorial.lua` | The fishing guide: guide ped, starter kit, reward, cutscene shots. |
| `config.admin.lua` | Who can open the in-game admin panel. |
| `config.fish.lua` | Every fish species, equipment tiers, level gates, protected species. |
| `config.items.lua` | Rods, reels, lines, baits, accessories, treasures, their stats, prices and required levels. |
| `config.spots.lua` | Fishing spots, their requirements, fish lists and boosted zones. |
| `config.peds.lua` | NPCs: guide, shops, quests, tournaments, boat rental, licence exam, diving, digging, hermits. |
| `config.hotbar.lua` | The equipment hotbar and its key. |
| `config.quests.lua` | Quest boards, objectives and rewards. |
| `config.tournaments.lua` | Scheduled and automatic tournaments, prizes. |
| `config.test.lua` | The fishing licence exam and its lessons. |
| `config.boatrent.lua` | Rentable boats and the anchor key. |
| `config.diving.lua` | Diving caches, scuba gear, the first-dive tutorial and diving contracts. |
| `config.digging.lua` | Digging, the digging tutorial, the bike, digging gear and contracts. |
| `config.poaching.lua` | Protected spots and species, police inspections and fines. |
| `config.party.lua` | Fishing parties, shared sales and group goals. |

:::hint{type="info" title="Most values can be edited in game"}
The admin panel (`/fishingadmin`) edits almost every value below while the server runs — see [config.admin.lua](#configadminlua).
A value saved in the panel is stored in the `koja-fishing-config` table and overrides the file on every start.
To go back to the file value, use **reset to default** in the panel.
:::

## config.main.lua

```lua title="shared/config.main.lua"
Config.Locale = 'en'
Config.Debug = false
Config.ChanceToRodDamage = 15
Config.CreateAutoTable = true
Config.StartingValues = { level = 1, exp = 0 }
```

| Key | What it does |
|---|---|
| `Config.Locale` | Language of notifications, the whole UI and the translated content: `pl`, `en`, `de`, `es`, `fr`, `it`, `pt`, `cs`, `ru` or `tr` (`locales/<locale>.json`). Missing keys fall back to Polish. |
| `Config.Debug` | Console logs **and** debug commands (`/battery`, `/divingtest`, `/rewardtest`, `/digprop`, `kf_levelup_preview`). Turn it off on a live server. |
| `Config.ChanceToRodDamage` | Chance in % that a lost fight (snapped line or escaped fish) also destroys the rod. The **Rod care** skill lowers it. |
| `Config.CreateAutoTable` | Creates and migrates database tables on start. |
| `Config.StartingValues` | Level and XP of a new angler. |

### Levels

```lua
Config.MaxLevel = 30
Config.ExpBase = 100
Config.ExpGrowth = 20
```

The XP needed to go from level `n` to `n + 1` is `ExpBase + (n - 1) * ExpGrowth`.
Each level gained grants skill points (`sp_per_level` in `data/skills.json`).
XP stops at the last level.

### Security

```lua
Config.Security = {
    ValidatePedDistance = true,
    PedInteractDistance = 10.0,
    OnSuspicious = function(source, reason)
    end,
}
```

| Key | What it does |
|---|---|
| `ValidatePedDistance` | The server rejects shop, tournament and licence exam requests from players who are not next to the matching ped. Boat rental, the guide, diving and digging use their own fixed radius. |
| `PedInteractDistance` | Maximum distance in metres to the ped. |
| `OnSuspicious` | Called on the server for every rejected cheat attempt. Plug in your anticheat, ban system or a webhook here. |

See [Security](/jobs/fishing/security) for what is validated.

### Shop and dynamic prices

```lua
Config.Shop = { MaxQuantityPerItem = 100 }

Config.PriceChange = {
    enabled = true,
    changeInterval = 7200,
    maxItemsToChange = 8,
}
```

| Key | What it does |
|---|---|
| `Shop.MaxQuantityPerItem` | Largest quantity of one item in a single buy or sell. |
| `PriceChange.enabled` | Puts random items on sale. |
| `PriceChange.changeInterval` | Seconds between price rolls. |
| `PriceChange.maxItemsToChange` | How many items are discounted at a time. |

A discounted item costs between `price - maxPriceChange` and `price` (see [items](#configitemslua)).
Shop peds can change prices with their own multipliers (see [peds](#configpedslua)).

### Redeem codes

```lua
Config.RedeemCode = {
    enabled = true,
    createCommand = 'createfishingcode',
}
```

Codes give a temporary luck bonus and are entered in the shop.
Each player can use a code once.
Create codes from the server console — see [Commands](/jobs/fishing/commands).

### Electric fishing

`Config.BatteryElectric` tunes the illegal battery method.

| Key | What it does |
|---|---|
| `placeDurationMs`, `connectDurationMs`, `activateDurationMs` | Progress bar length for placing the battery, connecting the cable, starting it. |
| `activateSkillCheck`, `activateSkillCheckDifficulty`, `activateSkillCheckKeys` | Built-in skill check when starting the battery: on or off, the difficulty of each round and the keys used. |
| `cycleDurationMs` | How long one battery cycle lasts. |
| `firstEmergeDelayMs`, `emergeIntervalMs` | Delay before the first fish and between fish. |
| `fishCountMin`, `fishCountMax`, `maxFishPerCycle` | Fish per cycle (skill `klusownik_battery_yield` adds on top). |
| `catchChanceMin`, `catchChanceMax` | Chance range in % to grab a stunned fish. A failed grab loses that fish. |
| `collectMinigame`, `minigameDurationMs` | Short minigame when grabbing a fish. |
| `interactionMode` | `ox_target`, or `drawtext` (3D text, key `drawTextControl`, range `drawTextInteractDistance`). |

Every grabbed fish counts as poaching evidence — see [config.poaching.lua](#configpoachinglua).

## config.minigame.lua

Keys, the strike window and the reeling fight.

```lua title="shared/config.minigame.lua"
Config.Minigame = {
    CastKey = 'E',
    CancelKey = 'X',
    DuiHeight = 1.35,
    DuiSize = 0.85,
    HookWindowMs = 1800,
    DirectionChange = { min = 1600, max = 3400 },
    DirectionWarningMs = 650,
    TensionGraceSeconds = 0.4,
    StartDistance = { min = 11.0, max = 16.0 },
    MaxDistance = 24.0,
}
```

| Key | What it does |
|---|---|
| `CastKey` / `CancelKey` | Default keys of `kf_cast` and `kf_cancel` (FiveM key names). Players can rebind them in **Settings → Key Bindings → FiveM**. |
| `DuiHeight` / `DuiSize` | Height above the player and size, in metres, of the in-world fishing indicator. |
| `HookWindowMs` | Time to click after the bite before the fish escapes. |
| `DirectionChange` | Random time in ms between changes of the side the fish pulls to. |
| `DirectionWarningMs` | How long before a side change the warning shows. |
| `TensionGraceSeconds` | Seconds the line can stay at 100% tension before it snaps. |
| `StartDistance` | Distance in metres of the fish when the fight starts. |
| `MaxDistance` | The fish escapes when it gets this far away. |
| `RodDamage` | Damage roll per rarity sent to the server when a fight is lost; the server then breaks the rod with the `Config.ChanceToRodDamage` chance. |
| `Rarity` | Fight balance per rarity, see below. |

Every value in `Rarity.<rarity>` is a rate per second:

| Key | What it does |
|---|---|
| `reelSpeed` | Metres reeled in while holding left mouse. |
| `fishPull` | Metres the fish swims away while you are not reeling. |
| `reelTension` / `relaxTension` | Tension added while reeling / removed while not reeling. |
| `wrongTension` | Extra tension while holding the wrong side key, or both. |
| `counterRelief` | Tension removed while holding the correct side key. |
| `driftSpeed` | Side drift when not countering. The line snaps when the drift reaches the edge. |
| `burstChance` / `burstTension` | Chance of a sudden run, and extra tension while reeling during a run. |

## config.tutorial.lua

The fishing guide for new anglers.

```lua title="shared/config.tutorial.lua"
Config.Tutorial = {
    Enabled = true,
    GuidePed = 'kuba',
    InviteNewPlayers = true,
    InviteDistance = 60.0,
    StarterKit = {
        { name = 'fishing_rod', count = 1 },
        { name = 'basic_reel', count = 1 },
        { name = 'basic_line', count = 2 },
        { name = 'worm_bait', count = 15 },
    },
    SkillPed = 'angelo',
    RewardXp = 50,
    BookItem = 'fishpedia',
    FishingPoint = vector3(-1847.0, -1250.8, 13.0),
}
```

| Key | What it does |
|---|---|
| `Enabled` | Turns the guide on. |
| `GuidePed` | Key of the guide ped in `Config.Peds` (`pedtype = 'guide'`). |
| `InviteNewPlayers` / `InviteDistance` | Anglers who have not finished the guide see a marker above the guide and get a hint within this distance. |
| `StarterKit` | Items given once per character when the guide starts. |
| `SkillPed` | Ped the "unlock your first skill" step points to. |
| `RewardXp` | XP given once when the guide is finished. |
| `BookItem` | Usable item given at the end (and again by the guide when the player lost it). Using it opens the Fishpedia. `false` disables it. |
| `FishingPoint` | Waypoint of the "go to the water" step. Any fishing spot completes the step. |
| `Cutscene` | Shots of the intro tour, see below. |

Each `Cutscene` entry is one shot:

| Field | What it does |
|---|---|
| `ped` | Ped key. The camera is placed in front of the ped's first zone: `distance` metres in front, `side` metres to its right, `height` above its feet, aiming at `lookHeight`. |
| `coords` / `offset` | Instead of `ped`: a world position and the camera offset from it. |
| `fov` | Camera field of view. |
| `duration` | Shot length in ms. |
| `title` / `text` | Locale keys of the caption. |

## config.admin.lua

The in-game admin panel edits config values live and stores them in the `koja-fishing-config` table.

```lua title="shared/config.admin.lua"
Config.Admin = {
    Enabled = true,
    Command = 'fishingadmin',
    Ace = 'koja-fishing.admin',
    Groups = { 'admin', 'superadmin' },
}
```

| Key | What it does |
|---|---|
| `Enabled` | Turns the panel and its command on. |
| `Command` | Chat command that opens the panel. |
| `Ace` | ACE permission that allows the panel (`add_ace group.admin koja-fishing.admin allow`). |
| `Groups` | Framework groups that can use the panel without the ACE permission. |

The panel groups its sections into **Gameplay**, **Content**, **World** and **System**:

| Section | Edits |
|---|---|
| General | `Locale`, `ChanceToRodDamage`, `MaxLevel`, `ExpBase`, `ExpGrowth`, `StartingValues`, `Shop`, `PriceChange`, `RedeemCode`, `Security`, `Hotbar`. |
| Minigame, Tutorial, Battery | `Config.Minigame`, `Config.Tutorial` (with cutscene shots), `Config.BatteryElectric`. |
| Fish, Items | `Config.Fish` with `RarityMinLevel`, `Config.Items` with `EquipmentTiers`. |
| Spots, Bubble zones | `Config.Spots`, `BoostedZonesSettings`, and the boosted zones of every spot. You can also activate a chosen zone right away. |
| Peds, Boat rental, Digging, Diving | `Config.Peds`, `Config.Rent`, `Config.Digging`, `Config.Diving`. |
| Quests, Tournaments, Licence exam | `Config.Quests`, `Config.Tournaments`, `Config.Test` (with questions). |
| Players | Online players and offline lookup by identifier: grant or revoke the licence, reset exam attempts, add XP (online only), add skill points, set the level. |

Items and fish use a list and detail editor: you can add an entry from a template or as a copy, delete it and restore a deleted file entry.
Entries added in the panel live in the database; their key must be 2–40 characters of `a-z`, `0-9` and `_`.
Spots, peds, quest boards, exam questions, boats, diving caches and cutscene shots use the same editor without adding or deleting.

`Config.Admin`, `Config.Debug`, `Config.CreateAutoTable`, `Config.Poaching`, `Config.Party`, the generated level table and functions such as `OnSuspicious` stay file-only.

:::hint{type="warning" title="New items still need the inventory"}
An item or fish added in the panel must also be registered in your inventory, otherwise it cannot be given.
:::

## config.fish.lua

Each fish is an entry in `Config.Fish`, keyed by its item name:

```lua title="shared/config.fish.lua"
['swamp_catfish'] = {
    name = 'Swamp Catfish',
    description = 'A huge catfish from the Zancudo marshes. Protected species.',
    rarity = 'uncommon',
    protected = true,
    weight = { min = 6.0, max = 18.0 },
    sellPrice = 260,
    xp = 30,
    length = { min = 70, max = 130 },
    image = 'swamp_catfish.webp',
    sellable = true,
    requirements = {
        minRodTier = 2,
        minLineTier = 2,
        minBaitTier = 1,
    },
},
```

| Field | What it does |
|---|---|
| `rarity` | `common`, `uncommon`, `rare` or `legendary`. Drives quality, level gate, fight difficulty and boosted zone odds. |
| `weight` | Weight range in kg. The line must hold this weight. |
| `length` | Length range in cm, shown on the catch screen. |
| `sellPrice` | Price for an average-weight fish. Heavier fish are displayed as worth more; the shop pays `sellPrice` per fish plus trader skill bonuses. |
| `xp` | XP for releasing the fish. |
| `requirements` | Minimum tier of rod, line and bait (`Config.EquipmentTiers`). |
| `minLevel` | Optional. Overrides the rarity level gate. |
| `mustRelease` | Optional. The fish is never rolled. |
| `sellable = false` | The shop refuses to buy it. |
| `protected` | Optional. Protected species: carrying it is evidence for the police, see [config.poaching.lua](#configpoachinglua). |

`Config.RarityMinLevel` sets the level an angler needs for each rarity (`common = 1`, `uncommon = 5`, `rare = 15`, `legendary = 24`).
`Config.EquipmentTiers` maps every rod, line, bait and reel to a tier from 1 to 5.

Names and descriptions are translated from the locales (`game.content.fish.<key>.name` / `.description`); the config text is the fallback.

## config.items.lua

Equipment, accessories and treasures, keyed by item name:

```lua title="shared/config.items.lua"
['advanced_rod'] = {
    name = 'Advanced Rod',
    requiredLevel = 3,
    price = 500,
    maxPriceChange = 100,
    sellPrice = 375,
    stats = { castDistance = 50.0, catchBonus = 10, durability = 150 },
    category = 'equipment',
    quality = 'uncommon',
},
```

| Field | What it does |
|---|---|
| `requiredLevel` | Fishing level from which the shop sells the item. The level-up screen lists gear unlocked at the new level. |
| `price` | Shop price. Items without a price (or `price = 0`) cannot be bought. |
| `maxPriceChange` | Largest discount during a price roll. |
| `sellPrice` | What the shop pays back. |
| `stats` | Rods: `catchBonus`, `durability`. Reels: `biteChance`, `waitTimeReduction` (seconds off the bite wait). Lines: `maxFishWeight`, `tensionResistance`. Baits: `catchBonus`, `rareFishBonus`. Necklaces: `luckBonus`. |
| `category` | Shop category: `equipment`, `baits`, `accessories`, `treasure` or `other`. |
| `quality` | Quality shown in the shop and used by its quality filter. |

Treasures (`pearl`, `ancient_coin`, `sunken_compass`, `ship_bell`, `pirate_doubloon`, `rusty_key`, `fossil_shell`, `amber_chunk`) have `price = 0`: they come from diving and digging and can only be sold.

:::hint{type="danger" title="Keep sellPrice below the cheapest buy price"}
Keep `sellPrice` lower than `price - maxPriceChange`, otherwise players can buy during a sale and sell back at a profit.
The server enforces this anyway: for shop items it pays at most the cheapest buy price at that ped minus 1, but the UI shows the configured value.
Trader skill bonuses only apply to fish, never to equipment.
:::

Accessories work from the inventory: the best `luckBonus` necklace a player carries is applied automatically.

## config.spots.lua

A player can only fish inside a spot.

```lua title="shared/config.spots.lua"
{
    id = 'cassidy_creek',
    name = 'Cassidy Creek',
    description = 'A clear forest river full of trout. Licence and an Advanced Rod required.',
    hidden = { enable = false },
    requiredLevel = 3,
    requireLicense = true,
    minRodTier = 2,
    locationType = 'radius',
    radius = { coords = vector3(-1651.7494, 4451.0513, 4.7721), range = 85.0 },
    fish = {
        { name = 'trout', chance = 40 },
        { name = 'rainbow_trout', chance = 25 },
    },
    waitTimeModifier = 0.95,
    blip = { enabled = true, sprite = 68, color = 25, label = 'Cassidy Creek', minLevel = 3 },
},
```

| Field | What it does |
|---|---|
| `id` | Unique id. Quest objectives filter on it with `spot`. |
| `name` / `description` | Shown when the player enters the spot. |
| `requiredLevel` | Angler level needed to fish here. |
| `requireLicense` | Optional. Only anglers with a fishing licence can fish here. |
| `minRodTier` | Optional. Minimum rod tier from `Config.EquipmentTiers.rods` (2 = Advanced Rod). |
| `unlockFlag` | Optional. Fishing needs this unlock, for example `hermit_cave` from Tom's quest (see [Exports](/jobs/fishing/exports)). |
| `protected` | Optional. Fishing here is poaching: catches are recorded as evidence and can alert the police. |
| `radius` | Centre and radius of the spot. Only `locationType = 'radius'` is supported. |
| `fish` | Weighted fish list. Without a list the spot rolls by rarity from all fish, using bait, reel and luck bonuses. |
| `waitTimeModifier` | Multiplies the bite wait. |
| `boostedZones` | Candidate bubble zones. |
| `boostedFish` | Extra multiplier for specific fish inside an active boosted zone. |
| `hidden` | Hidden spot: no blip, a networked prop marks it. |
| `blip` | Map blip for the spot. `minLevel` shows the blip only from that fishing level. |

All requirements are checked by the server when the cast starts.

`Config.BoostedZonesSettings` controls the bubble event: one zone on the map is active for `activeDurationSeconds`, then the system waits `cooldownSeconds` and picks another.
Inside it, `rarityMultipliers` change the odds per rarity.

## config.peds.lua

```lua title="shared/config.peds.lua"
['hank'] = {
    name = 'Hank',
    model = 'a_m_m_hillbilly_01',
    pedtype = 'shop',
    icon = 'fa-solid fa-store',
    licenserequired = false,
    scenario = 'WORLD_HUMAN_LEANING',
    shop = {
        buyMultiplier = 0.85,
        sellMultiplier = 1.15,
    },
    zones = {
        { coords = vector4(1302.5378, 4226.6919, 33.9087, 75.7342) },
    },
    blip = { enabled = true, sprite = 52, color = 3 },
},
```

| Field | What it does |
|---|---|
| `pedtype` | `guide`, `shop`, `quest`, `tournament`, `boat_rental`, `test` (licence exam), `diving`, `digging` or `hermit`. |
| `zones` | One or more positions. Each zone spawns the ped. A zone can set its own `waypoint` for info peds, and the digging ped's zone can set `bikeSpawn`. |
| `licenserequired` | The ped only talks to players with a fishing licence. |
| `scenario` | GTA scenario the ped plays. Instead you can use `anim = { dict, name }` and `prop = { model, bone, offset, rotation }`. |
| `icon` | ox_target icon. |
| `blip` | Map blip. The label is translated from `game.peds.<pedtype>`; set `blip.label` to override it. |
| `shop` | Shop peds only. `buyMultiplier` and `sellMultiplier` change this ped's prices. |
| `info.waypoint` | Diving and digging peds. Point marked by the **Mark on map** button at the first zone, also used by the digging tutorial. Diving falls back to the middle of `Config.Diving.Props`. |
| `hermit` | Hermit peds. `storyParagraphs` and a `quest` with `id`, `requirements` (`item`, `count`) and `reward` (`xp`, `money`, `items`, `unlock`). Texts come from `game.content.hermits.<key>.*`. |

Most default peds have a second zone on Chumash Pier.
Hank runs a village shop at the Alamo Sea; the hermits Tom and Gustaw have no blip.

The server measures distance to these zones, so a ped you remove here can no longer be used from anywhere.

## config.hotbar.lua

The hotbar is where the angler equips gear before casting.

| Key | What it does |
|---|---|
| `OpenCommand` / `OpenKey` | Command (`kf_hotbar_open`) and default key (`Q`) that open the hotbar. Players can rebind it in GTA key settings. |
| `CommandDescription` | Label of the key binding in the GTA settings. |
| `Slots` | The four slots — `rod`, `reel`, `line`, `bait` — and which items fit each. All four must be filled to fish. |

## config.quests.lua

Quests are grouped in boards.
With `AutoUnlockNextBoard = true` each board unlocks when the previous one is complete, and quests in a board are done in order.

```lua title="shared/config.quests.lua"
{
    id = 4,
    name = 'Angler’s Path',
    description = 'Prove yourself on the river and earn a master’s rod.',
    image = './images/quests/quests_legal.webp',
    type = 'main',
    independent = true,
    quests = {
        { id = 31, title = 'Catch 5 trout at Cassidy Creek', objective = {
            label = 'Caught', target = 5,
            type = 'catch', item = 'trout', spot = 'cassidy_creek',
        }, reward = { xp = 150, money = 500 } },
        { id = 33, title = 'Sell 15 fish', objective = {
            label = 'Sold', target = 15, type = 'sell',
        }, reward = { xp = 250, items = { { name = 'master_rod', count = 1 } } } },
    },
},
```

| Board field | What it does |
|---|---|
| `type` | `main` or `side` — the page of the quest window the board is shown on. |
| `independent` | The board is available from the start, in parallel with other boards, and does not block the chain. |
| `name`, `description` | Board texts, translated from `game.content.quests.boards.b<id>.*`. |
| `lore` | Story text shown on the board. Comes from `game.content.quests.boards.b<id>.lore`. |

| Objective field | What it does |
|---|---|
| `type` | `catch` (fish hooked and landed), `sell` (fish sold), `dig` (successful dig), `dive` (cache searched or diving contract crate collected). |
| `target` | Amount needed. |
| `item` | Optional. Fish or item name, or a list. For `dive` it is the cache id, or `contract_<template>` for a diving contract crate. |
| `rarity` | Optional. Rarity or a list. |
| `spot` | Optional. Spot id or a list. |
| `hours` | Optional. `{ from = 0, to = 5 }` in server time; wraps over midnight. |

| Reward field | What it does |
|---|---|
| `xp` | XP given when the quest completes. |
| `money` | Cash given when the quest completes. |
| `items` | List of `{ name, count }` given when the quest completes. |

Other resources can add progress too — see [Exports and events](/jobs/fishing/exports).

## config.tournaments.lua

| Key | What it does |
|---|---|
| `Enabled` | Turns tournaments on. |
| `MaxVisible` | Active tournaments shown at once. |
| `LeaderboardLimit` | Rows in the leaderboard. |
| `RequireSingleActiveParticipation` | A player can be in one running tournament at a time. |
| `EntryMoneyType` | `cash` or `bank` for the entry fee. |
| `Auto` | Repeating tournaments, see below. |
| `List` | Fixed tournaments with dates. |

A fixed tournament scores points per species with `fishPoints`, between `startsAt` and `endsAt` (`YYYY-MM-DD HH:MM:SS`, server time).

`Auto` creates a new tournament every `IntervalHours`, lasting `DurationHours`, from `Template`.
`ScoringMode` is `weight_max` (heaviest single fish, in grams) or `weight_sum` (total weight).

### Prizes

Prizes are paid automatically when a tournament ends:

```lua
prize = {
    name = 'Biggest Fish Trophy',
    image = 'items/big_catch.png',
    rewards = {
        { rank = 1, money = 2500, xp = 300, items = { { name = 'legendary_bait', count = 5 } } },
        { rankFrom = 2, rankTo = 3, money = 1000 },
    },
},
```

Only participants with more than 0 points are ranked.
Online winners get the reward immediately; offline winners get it a few seconds after they next join.
Each tournament is settled once — the result is stored in the database and survives restarts.

## config.test.lua

The fishing licence exam is taken at the `test` ped.

| Key | What it does |
|---|---|
| `Enabled` | Turns the exam on. |
| `RequireLicenseToFish` | The server refuses to start fishing anywhere without a licence. Use `requireLicense` on a spot to lock only that spot. |
| `PassPercent` | Score needed to pass. |
| `MaxAttempts` / `CooldownSeconds` | Failed attempts before a cooldown, and its length. |
| `TimeLimit` | Seconds to finish. Enforced by the server. |
| `Lessons` | Lessons on the **Study** screen: `protected`, `conduct`, `control`, `gear`, `boats`, `safety`. Texts come from `game.test.lessons.<id>.*`; remove an id to hide that lesson. |
| `IntroStory` | Text shown before the exam. |
| `Questions` | Questions with `answers`; mark correct ones with `correct = true`, set `multi = true` for multiple choice. |

The intro, questions and answers are translated from `game.test.content.*`; the config texts are the fallback.
A text edited in the admin panel is shown as written.
Correct answers never leave the server.

## config.boatrent.lua

```lua title="shared/config.boatrent.lua"
Config.Rent = {
    SpotClearRadius = 7.0,
    AnchorKey = 'G',
    Boats = {
        {
            model = 'dinghy',
            label = 'Dinghy',
            deposit = 500,
            minLevel = 1,
            parkingSpots = {
                vector4(-1793.6165, -1229.7045, -0.4824, 230.8976),
                vector4(-3438.0, 946.0, 0.0, 326.5),
            },
        },
    },
}
```

A boat spawns at the free parking spot nearest to the renting player, so one list can cover several marinas (Del Perro and Chumash Pier by default).
`SpotClearRadius` is the free space a parking spot needs.
The deposit is taken in cash and refunded when the boat is brought back within 20 m of the spot it was rented from.
`minLevel` is checked on the server.
`AnchorKey` is the default key of `kf_anchor`, which drops and raises the anchor of the boat you are on; the anchor is also in the ox_target menu.

## config.diving.lua

### Caches

| Key | What it does |
|---|---|
| `Enabled` | Turns diving on. |
| `CollectDurationMs` | Search time for a cache. |
| `RespawnTimeMs` | Default respawn time; each prop can set `respawnTimeMs`. |
| `CollectDistance` | Maximum distance checked by the server. |
| `RequireLicense` | Searching a cache requires a fishing licence. |
| `OutlineDistance`, `OutlineColor` | Outline on nearby caches. |
| `Target` | ox_target `Icon` and `Label` of the search option. The label is translated from `game.content.diving.target`. |
| `Props` | Caches: `id`, `model`, `coords` (vector4). |
| `OnCollected` | Server function that gives the loot. Edit the `possibleRewards` table inside it. |

### Scuba gear

Gear is bought and refilled at the diving ped (Marek), paid in cash.

| Key | What it does |
|---|---|
| `Gear.StarterFree` | The first-dive tutorial gives level-1 gear for free. |
| `Gear.RefillPricePerMinute` | Price of a tank refill per minute of missing air. |
| `Gear.Tank.levels` | `capacity` in seconds of air and `price` per level. |
| `Gear.Fins.levels` | `swim` speed multiplier (max 1.49) and `price`. |
| `Gear.Light.levels` | Head lamp `range` in metres (0 = none) and `price`. |
| `Gear.Scanner.levels` | Crate marker `range`, `reveal` (show the loot tier before opening) and `price`. |
| `Tutorial.RewardXp` | XP for finishing the first-dive tutorial, once. |
| `Tutorial.MinSeconds` | The server rejects a tutorial finished faster than this. |

### Diving contracts

```lua
Contracts = {
    OffersCount = 3,
    RefreshMinutes = 30,
    MaxActive = 1,
    Areas = {
        { id = 'del_perro_wreck', center = vector3(-2885.0, -270.0, -20.0), radius = 70.0, minTier = 1 },
    },
    Templates = {
        { id = 'salvage', count = { 3, 5 }, tier = 1, reward = { money = { 600, 1100 }, xp = { 60, 110 } } },
    },
},
```

| Key | What it does |
|---|---|
| `OffersCount` | Contracts offered at once. |
| `RefreshMinutes` | Offers regenerate per player after this many minutes. |
| `MaxActive` | Active contracts at a time. |
| `Areas` | Reefs and wrecks: `id`, `center`, `radius`, `minTier` (lowest contract tier sent there). |
| `Templates` | `id` (`salvage`, `recovery`, `treasure` by default), `count` range of crates, `tier`, and `reward` with `money` and `xp` ranges and `items` (`name`, `count` range, optional `chance` in %). |

A tier N contract needs tank and scanner level N or higher.
Crate positions are generated per contract inside the area.

## config.digging.lua

### Digging

| Key | What it does |
|---|---|
| `Enabled` | Turns digging on. |
| `ShovelItem` / `RequireShovel` | Item that starts digging, and whether it is required. |
| `ProgressDurationMs` | Dig time. The server rejects finishes that come too early. |
| `CooldownMs` | Pause between digs. |
| `MaxMoveDistance` | How far the player may move while digging. |
| `MaxGroundDelta` | How far above the ground the player may stand to dig. |
| `Drops` | Weighted loot: `item`, `min`, `max`, `chance`. Includes the treasures `rusty_key`, `fossil_shell`, `amber_chunk` and `pirate_doubloon`. |
| `EnableTestCommand` / `TestCommandBypassShovel` | Enables `/dig`; the shovel bypass also needs `Config.Debug = true`. |
| `Animation`, `PropModel`, `PropBone`, `PropOffset`, `PropRotation` | Animation and the shovel in hand. |

### Tutorial and bike

| Key | What it does |
|---|---|
| `Intro.Enabled` | The first talk with the digging ped (Stefan) offers a short tutorial. |
| `Intro.GiveShovel` | One shovel when the tutorial starts, once per character. |
| `Intro.RewardXp` | XP when the tutorial is finished, once. |
| `Intro.BeachRadius` | The "go to the beach" step completes inside this radius of the ped's `info.waypoint`. |
| `Bike.Enabled` | The digging ped lends a bike at any time. |
| `Bike.Model` / `Bike.Plate` | Bike model (`scorcher` by default) and plate text. |
| `Bike.Spawn` | Where the bike appears at the ped's first zone. Other zones use a point next to the ped; any zone can set its own `bikeSpawn`. |
| `Bike.DespawnMinutes` | The bike is removed after this long with nobody on it, when the player is not near it. |

### Digging gear and contracts

| Key | What it does |
|---|---|
| `Gear.Shovel.levels` | `speed` multiplies `ProgressDurationMs`, `loot` is a % bonus for rarer `Drops`, `price`. |
| `Gear.Detector.levels` | `range` for buried caches (0 = none), `price`. |
| `Gear.Bag.levels` | `cooldown` multiplies `CooldownMs`, `price`. |
| `Contracts.OffersCount`, `RefreshMinutes`, `MaxActive` | Same as for diving. |
| `Contracts.Blip` | `sprite`, `color`, `alpha` and `scale` of the active contract area on the map. |
| `Contracts.Areas` | Beaches: `id`, `center`, `radius`, `minTier`. |
| `Contracts.Templates` | `id` (`bait_run`, `relic`, `pirate` by default), `count`, `tier`, `reward` with `money`, `xp` and `items` (`chance` is rolled when the offer is made). |

Tier 1 contracts need no gear, tier 2 needs detector level 2, tier 3 needs detector and shovel level 3.
Upgrades are paid in cash at the digging ped.

## config.poaching.lua

```lua title="shared/config.poaching.lua"
Config.Poaching = {
    Enabled = true,
    PoliceJobs = { 'police', 'sheriff' },
    MinPoliceGrade = 0,
    RequireOnDuty = false,
    EvidenceMinutes = 30,
    InspectDistance = 4.0,
    TargetCooldownSeconds = 60,
    Billing = 'money',
    Society = 'society_police',
    ConfiscateProtectedFish = true,
    Alert = { Enabled = true, Chance = 35, CooldownSeconds = 120, BlipSeconds = 90 },
    Offences = {
        { id = 'protected_zone', amount = 2500 },
        { id = 'protected_species', amount = 800, perUnit = true },
        { id = 'no_license', amount = 1000 },
        { id = 'electric_fishing', amount = 5000 },
        { id = 'littering', amount = 300 },
    },
    MaxFine = 25000,
}
```

| Key | What it does |
|---|---|
| `Enabled` | Turns evidence, alerts and fines on. |
| `PoliceJobs`, `MinPoliceGrade`, `RequireOnDuty` | Who gets the "Inspect catch" ox_target option on players and the alerts. |
| `EvidenceMinutes` | How long a catch in a protected spot or an electric-fishing catch counts as evidence. |
| `InspectDistance` | Maximum distance between officer and angler. |
| `TargetCooldownSeconds` | Seconds before the same angler can be fined again. |
| `Billing` | How the fine is paid, see below. |
| `Society` | Society account that receives the money (`money` and `esx_billing`). |
| `ConfiscateProtectedFish` | Fining for protected species removes those fish from the angler. |
| `Alert` | Police alert when someone catches a fish in a protected spot: `Chance` in % per catch, `CooldownSeconds` per angler, `BlipSeconds` on the officers' map. |
| `Offences` | Offences the officer can pick: `id`, `amount`, `perUnit` (multiplied by the number of protected fish found). Labels come from `game.poaching.offences.<id>`. |
| `MaxFine` | Upper limit for a single fine. |

| `Billing` | Behaviour |
|---|---|
| `money` | Taken from the angler (bank first, then cash) and paid into `Society` through `esx_addonaccount` when it is started. |
| `esx_billing` | A bill is inserted into the `billing` table for `Society`. |
| `okokBilling` | Nothing is charged; create the invoice from the `koja-fishing:server:fineIssued` event. |
| `custom` | Nothing is charged; only `koja-fishing:server:fineIssued` fires. |

Protected spots use `protected = true` in `config.spots.lua`, protected species `protected = true` in `config.fish.lua`.
See [Exports and events](/jobs/fishing/exports) for the poaching events.

## config.party.lua

```lua title="shared/config.party.lua"
Config.Party = {
    Enabled = true,
    MaxMembers = 4,
    InviteDistance = 30.0,
    InviteTimeoutSeconds = 60,
    MembersCanInvite = false,
    OpenCommand = 'kf_party',
    OpenKey = 'F7',
    AcceptKey = 'Y',
    DeclineKey = 'N',
    ShareRadius = 150.0,
    ShareModeDefault = false,
    XpBonusPercent = 10,
}
```

| Key | What it does |
|---|---|
| `Enabled` | Turns parties on. |
| `MaxMembers` | Party size, leader included. |
| `InviteDistance` | Nearby players shown in the invite list and the maximum distance to invite. |
| `InviteTimeoutSeconds` | How long an invite stays open. |
| `MembersCanInvite` | `false`: only the leader invites. `true`: every member can. |
| `OpenCommand` / `OpenKey` | Command and default key of the party window. |
| `AcceptKey` / `DeclineKey` | Default keys to answer an invite. |
| `ShareRadius` | Members within this distance share a sale and count as fishing together. |
| `ShareModeDefault` | Whether the shared wallet starts on. The leader can toggle it. |
| `XpBonusPercent` | Bonus XP for released fish when another member is within `ShareRadius`. |
| `Goals` | Group goals offered to the leader, see below. |

| `Goals` key | What it does |
|---|---|
| `OffersCount` | Goals offered at once. |
| `RefreshMinutes` | Offers regenerate after this many minutes. |
| `DurationMinutes` | Time to finish a picked goal. |
| `Templates` | `id`, `type`, `count` range, `reward` with `money` and `xp` ranges, optional `rarity` for `catch` (that rarity or better). |

Goal types: `catch` (fish landed), `sell_value` (total $ of fish sold), `dive` (caches and contract crates), `dig_cache` (contract caches dug up).

## Translations

The locale is loaded on the server too (`shared/i18n.lua`), so content in the configs is translated at start:

| Locale key | Translates |
|---|---|
| `game.content.items.<key>.*`, `game.content.fish.<key>.*` | Item and fish `name` and `description`. |
| `game.content.spots.<id>.*` | Spot `name`, `description` and blip label. |
| `game.content.quests.boards.b<id>.*`, `game.content.quests.q<id>.*` | Board `name`, `description`, `lore`; quest `title` and `objective` label. |
| `game.content.tournaments.*` | Tournament names, descriptions and prize names. |
| `game.content.hermits.<key>.*` | Hermit role, story, quest and unlock texts. |
| `game.test.content.*`, `game.test.lessons.<id>.*` | Exam intro, questions, answers and lessons. |
| `game.peds.<pedtype>` | Ped role labels and blips. |

The config text is used when a key is missing.
The admin panel labels and section descriptions come from the locales as well.

## data/skills.json

The skill tree has three branches — `wedkarz` (angler), `klusownik` (poacher), `handlarz` (trader) — listed in `meta.trees` with the level that unlocks them.
Each node has `requirements.skills`, `requirements.level`, `effect_per_level` and an `upgrades` list whose `price` is the skill point cost of each level.

| Skill id | Effect per level |
|---|---|
| `wedkarz_fast_bite` | % shorter bite wait |
| `wedkarz_luck` | luck bonus |
| `wedkarz_xp` | % more XP for released fish |
| `wedkarz_rod_care` | % lower rod break chance |
| `klusownik_battery_yield` | extra fish per battery cycle |
| `klusownik_battery_catch` | % better grab chance |
| `klusownik_diving_speed` | % faster cache search |
| `handlarz_sell_bonus`, `handlarz_negotiator` | % more money for fish |

Keep the ids — the code reads them.
The UI shows the catalog the server sends from `data/skills.json`; `web/src/data/skills.json` is only a fallback bundled into the UI.
