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-inventoryenabled (for adding fish items)- Shiva CLI installed (
npm i -g @shiva-fw/cli)
1. Scaffold the Module
bash
shiva make module my-fishingThis 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_logslua
-- 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 M6. 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 migrateRestart your server, connect, and run /fish.