Skip to content

Your First Module

Build a complete fishing module from scratch. By the end of this tutorial you'll have:

  • A working minigame that lets players fish at defined locations
  • Items added to inventory on success
  • Commands and NUI feedback
  • Config, locales, and a database migration

Prerequisites

  • A running Shiva server
  • shiva-inventory enabled (for adding fish items)
  • Shiva CLI installed (npm i -g @shiva-fw/cli)

1. Scaffold the Module

bash
shiva make module my-fishing

This creates:

resources/[shiva]/my-fishing/
├── manifest.lua
├── fxmanifest.lua
├── config.lua
├── server/
│   └── init.lua
├── client/
│   └── init.lua
├── shared/
│   └── locales/
│       └── en.lua
└── migrations/

2. Edit manifest.lua

lua
return {
    name    = 'my-fishing',
    version = '1.0.0',
    provides = {},
    requires = {
        'IPlayer',
        'IInventory',
    },
}

3. Add Config

lua
-- config.lua
Config = {}

Config.fishingSpots = {
    { label = 'Alamo Sea',   coords = { x = 1204.0, y = 4461.0, z = 30.0 } },
    { label = 'Lago Zancudo', coords = { x = -1692.0, y = 4276.0, z = 32.0 } },
}

Config.catchChance = 0.6    -- 60% chance per cast
Config.castTime   = 10      -- seconds to wait per cast

Config.fish = {
    { item = 'fish_bass',   label = 'Bass',   weight = 0.6 },
    { item = 'fish_trout',  label = 'Trout',  weight = 0.3 },
    { item = 'fish_salmon', label = 'Salmon', weight = 0.1 },
}

4. Add a Migration

bash
shiva make migration my-fishing create_fishing_logs
lua
-- migrations/001_create_fishing_logs.lua
return {
    up = function(db)
        db:execute([[
            CREATE TABLE fishing_logs (
                id         INT AUTO_INCREMENT PRIMARY KEY,
                player_id  INT NOT NULL,
                fish       VARCHAR(64) NOT NULL,
                caught_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ]])
    end,
    down = function(db)
        db:execute('DROP TABLE IF EXISTS fishing_logs')
    end,
}

5. Server Logic

lua
-- server/init.lua
local M = {}

function M.start(container)
    local inventory = container:make('IInventory')
    local DB = require('shiva-fw.database')

    -- Command: /fish
    RegisterCommand('fish', function(source)
        -- In a real module, validate proximity to a fishing spot
        local roll = math.random()
        if roll <= Config.catchChance then
            local fish = pickFish()
            inventory:addItem(source, fish.item, 1)
            TriggerClientEvent('my-fishing:caught', source, fish)
            DB:execute('INSERT INTO fishing_logs (player_id, fish) VALUES (?, ?)', source, fish.item)
        else
            TriggerClientEvent('my-fishing:missed', source)
        end
    end, false)
end

function pickFish()
    local roll = math.random()
    local cumulative = 0
    for _, fish in ipairs(Config.fish) do
        cumulative = cumulative + fish.weight
        if roll <= cumulative then
            return fish
        end
    end
    return Config.fish[1]
end

return M

6. Client Feedback

lua
-- client/init.lua
AddEventHandler('my-fishing:caught', function(fish)
    -- Show a notification (using the notifications contract)
    exports['shiva-notifications']:notify('success', ('You caught a %s!'):format(fish.label))
end)

AddEventHandler('my-fishing:missed', function()
    exports['shiva-notifications']:notify('info', 'The fish got away...')
end)

7. Run Migrations and Test

bash
shiva migrate

Restart your server, connect, and run /fish.

Next Steps

Released under the MIT License.