QB-Fishing - Angling and Marine Harvesting System
The qb-fishing resource provides a comprehensive fishing system for QBCore servers, featuring various fishing methods, bait systems, fish species, and equipment management.
Overview
QB-Fishing creates a realistic angling experience with multiple fishing techniques, seasonal fish behavior, equipment requirements, and fish processing. Players can fish in various water bodies, from ocean fishing to lake angling.
Key Features
- Multiple Fishing Methods: Rod fishing, net fishing, and spear fishing
- Bait System: Different baits for different fish species
- Fish Species: Variety of fish with different rarity and value
- Equipment Management: Rods, reels, nets, and fishing gear
- Seasonal Fishing: Time and weather-based fish behavior
- Fish Processing: Cleaning and selling fish products
- Fishing Licenses: Legal requirements for fishing
- Boat Fishing: Deep sea and offshore fishing
- Competition System: Fishing tournaments and competitions
Installation
Prerequisites
- QBCore Framework
- qb-target (for interaction system)
- qb-menu (for fishing menus)
- qb-inventory (for fishing equipment)
- qb-weather (for weather effects)
Installation Steps
- Download the Resource
cd resources/[qb]
git clone https://github.com/qbcore-framework/qb-fishing.git
- Database Setup
-- Fishing tables
CREATE TABLE IF NOT EXISTS `fishing_licenses` (
`citizenid` varchar(50) NOT NULL,
`license_type` varchar(50) DEFAULT 'recreational',
`issued_date` timestamp DEFAULT current_timestamp(),
`expires_date` timestamp DEFAULT NULL,
`status` varchar(50) DEFAULT 'active',
PRIMARY KEY (`citizenid`)
);
CREATE TABLE IF NOT EXISTS `fishing_catches` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`citizenid` varchar(50) DEFAULT NULL,
`fish_type` varchar(100) DEFAULT NULL,
`weight` float DEFAULT 0,
`length` float DEFAULT 0,
`location` varchar(255) DEFAULT NULL,
`bait_used` varchar(100) DEFAULT NULL,
`weather` varchar(50) DEFAULT NULL,
`time_caught` timestamp DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `fishing_competitions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`start_time` timestamp DEFAULT NULL,
`end_time` timestamp DEFAULT NULL,
`fish_type` varchar(100) DEFAULT NULL,
`prize_pool` int(11) DEFAULT 0,
`status` varchar(50) DEFAULT 'upcoming',
PRIMARY KEY (`id`)
);
- Add Items to qb-core/shared/items.lua
-- Fishing Equipment
['fishing_rod'] = {
['name'] = 'fishing_rod',
['label'] = 'Fishing Rod',
['weight'] = 1500,
['type'] = 'item',
['image'] = 'fishing_rod.png',
['unique'] = false,
['useable'] = true,
['shouldClose'] = true,
['combinable'] = nil,
['description'] = 'Basic fishing rod for angling'
},
['fishing_net'] = {
['name'] = 'fishing_net',
['label'] = 'Fishing Net',
['weight'] = 2000,
['type'] = 'item',
['image'] = 'fishing_net.png',
['unique'] = false,
['useable'] = true,
['shouldClose'] = true,
['combinable'] = nil,
['description'] = 'Net for catching multiple fish'
},
['worm_bait'] = {
['name'] = 'worm_bait',
['label'] = 'Worm Bait',
['weight'] = 50,
['type'] = 'item',
['image'] = 'worm_bait.png',
['unique'] = false,
['useable'] = false,
['shouldClose'] = false,
['combinable'] = nil,
['description'] = 'Live worms for fishing bait'
},
-- Fish
['bass'] = {
['name'] = 'bass',
['label'] = 'Bass',
['weight'] = 300,
['type'] = 'item',
['image'] = 'bass.png',
['unique'] = false,
['useable'] = false,
['shouldClose'] = false,
['combinable'] = nil,
['description'] = 'Fresh bass fish'
},
['salmon'] = {
['name'] = 'salmon',
['label'] = 'Salmon',
['weight'] = 500,
['type'] = 'item',
['image'] = 'salmon.png',
['unique'] = false,
['useable'] = false,
['shouldClose'] = false,
['combinable'] = nil,
['description'] = 'Premium salmon fish'
}
- Add to server.cfg
ensure qb-fishing
Configuration
Basic Configuration
Config = {}
-- General Settings
Config.UseTarget = true
Config.LicenseRequired = true
Config.FishRespawnTime = 120000 -- 2 minutes
-- Fishing Locations
Config.FishingSpots = {
["pier"] = {
label = "Del Perro Pier",
coords = vector3(-1850.02, -1248.19, 8.61),
radius = 50,
fish_types = {
["mackerel"] = {weight = 0.4, min_weight = 0.5, max_weight = 2.0},
["sea_bass"] = {weight = 0.3, min_weight = 1.0, max_weight = 4.0},
["tuna"] = {weight = 0.2, min_weight = 5.0, max_weight = 15.0},
["shark"] = {weight = 0.1, min_weight = 20.0, max_weight = 50.0}
},
required_license = "recreational",
best_times = {"dawn", "dusk"}
},
["lake"] = {
label = "Alamo Sea",
coords = vector3(740.0, 4169.0, 40.0),
radius = 200,
fish_types = {
["bass"] = {weight = 0.5, min_weight = 1.0, max_weight = 3.0},
["catfish"] = {weight = 0.3, min_weight = 2.0, max_weight = 8.0},
["pike"] = {weight = 0.2, min_weight = 3.0, max_weight = 10.0}
},
required_license = "recreational"
},
["deep_sea"] = {
label = "Pacific Ocean Deep",
coords = vector3(-3500.0, 3500.0, 0.0),
radius = 1000,
fish_types = {
["marlin"] = {weight = 0.15, min_weight = 50.0, max_weight = 200.0},
["swordfish"] = {weight = 0.2, min_weight = 30.0, max_weight = 100.0},
["mahi_mahi"] = {weight = 0.35, min_weight = 5.0, max_weight = 20.0},
["yellowfin_tuna"] = {weight = 0.3, min_weight = 10.0, max_weight = 80.0}
},
required_license = "commercial",
boat_required = true
}
}
-- Fish Configuration
Config.Fish = {
["bass"] = {
label = "Largemouth Bass",
value_per_kg = 150,
preferred_bait = {"worm_bait", "lure_bait"},
preferred_weather = {"clear", "overcast"},
rarity = "common"
},
["salmon"] = {
label = "Atlantic Salmon",
value_per_kg = 300,
preferred_bait = {"fly_bait", "lure_bait"},
preferred_weather = {"clear", "rain"},
rarity = "uncommon"
},
["tuna"] = {
label = "Bluefin Tuna",
value_per_kg = 500,
preferred_bait = {"live_bait", "lure_bait"},
preferred_weather = {"clear"},
rarity = "rare"
},
["marlin"] = {
label = "Blue Marlin",
value_per_kg = 1000,
preferred_bait = {"live_bait"},
preferred_weather = {"clear"},
rarity = "legendary",
trophy_fish = true
}
}
-- Bait Configuration
Config.Bait = {
["worm_bait"] = {
label = "Earthworms",
effectiveness = {
bass = 0.8,
catfish = 0.9,
pike = 0.6
},
cost = 5
},
["lure_bait"] = {
label = "Artificial Lure",
effectiveness = {
bass = 0.7,
salmon = 0.8,
tuna = 0.6
},
cost = 15
},
["live_bait"] = {
label = "Live Minnows",
effectiveness = {
tuna = 0.9,
marlin = 0.8,
swordfish = 0.7
},
cost = 25
}
}
Equipment System
-- Fishing Equipment
Config.Equipment = {
["fishing_rod"] = {
label = "Basic Fishing Rod",
durability = 100,
repair_cost = 50,
catch_bonus = 1.0,
fish_types = {"bass", "mackerel", "catfish"}
},
["pro_fishing_rod"] = {
label = "Professional Fishing Rod",
durability = 200,
repair_cost = 150,
catch_bonus = 1.5,
fish_types = {"bass", "salmon", "tuna", "pike"}
},
["deep_sea_rod"] = {
label = "Deep Sea Fishing Rod",
durability = 150,
repair_cost = 200,
catch_bonus = 2.0,
fish_types = {"tuna", "marlin", "swordfish", "shark"}
},
["fishing_net"] = {
label = "Fishing Net",
durability = 80,
repair_cost = 75,
multiple_catch = true,
max_catches = 5
}
}
-- License Types
Config.LicenseTypes = {
["recreational"] = {
label = "Recreational Fishing License",
cost = 500,
duration = 30, -- days
daily_limit = 10, -- fish per day
areas_allowed = {"pier", "lake"}
},
["commercial"] = {
label = "Commercial Fishing License",
cost = 2500,
duration = 30,
daily_limit = 50,
areas_allowed = {"pier", "lake", "deep_sea"},
boat_fishing = true
},
["tournament"] = {
label = "Tournament License",
cost = 1000,
duration = 7,
special_events = true,
areas_allowed = {"pier", "lake", "deep_sea"}
}
}
API Reference
Client Exports
StartFishing
Begin fishing at current location.
-- Start basic fishing
exports['qb-fishing']:StartFishing()
-- Start fishing with specific equipment
exports['qb-fishing']:StartFishing("pro_fishing_rod", "worm_bait")
-- Parameters:
-- equipment (optional): Fishing equipment to use
-- bait (optional): Bait type to use
CastLine
Cast fishing line into water.
local success = exports['qb-fishing']:CastLine(targetCoords, power)
-- Parameters:
-- targetCoords: Vector3 coordinates to cast to
-- power: Casting power (0.1 to 1.0)
-- Returns: boolean success status
ReelIn
Reel in fishing line.
local fishCaught = exports['qb-fishing']:ReelIn(timing, strength)
-- Parameters:
-- timing: Player reaction timing
-- strength: Reeling strength
-- Returns: fish data or nil
Server Exports
CheckFishingLicense
Verify player’s fishing license.
local isValid = exports['qb-fishing']:CheckFishingLicense(source, licenseType)
-- Parameters:
-- source: Player server ID
-- licenseType: Required license type
-- Returns: boolean license validity
SpawnFish
Spawn fish in specific area.
local fishId = exports['qb-fishing']:SpawnFish(fishType, coords, area)
-- Parameters:
-- fishType: Type of fish to spawn
-- coords: Vector3 spawn coordinates
-- area: Fishing area name
ProcessCatch
Process caught fish and give rewards.
exports['qb-fishing']:ProcessCatch(source, fishData, equipment)
-- Parameters:
-- source: Player server ID
-- fishData: Fish information
-- equipment: Equipment used
Events
Client Events
-- Fish hooked
RegisterNetEvent('qb-fishing:client:fishHooked', function(fishType, difficulty)
-- Handle fish hooking
end)
-- Catch successful
RegisterNetEvent('qb-fishing:client:catchSuccessful', function(fishData)
-- Handle successful catch
end)
-- Line broken
RegisterNetEvent('qb-fishing:client:lineBroken', function()
-- Handle line breaking
end)
-- Competition started
RegisterNetEvent('qb-fishing:client:competitionStarted', function(compData)
-- Handle competition start
end)
Server Events
-- Player started fishing
RegisterNetEvent('qb-fishing:server:startFishing', function(location, equipment)
-- Handle fishing start
end)
-- Fish caught
RegisterNetEvent('qb-fishing:server:fishCaught', function(fishData, location)
-- Handle fish catch
end)
-- Competition entry
RegisterNetEvent('qb-fishing:server:joinCompetition', function(compId)
-- Handle competition entry
end)
Usage Examples
Basic Fishing System
-- Client-side fishing mechanics
local isFishing = false
local fishingData = {}
local currentBait = nil
RegisterNetEvent('qb-fishing:client:startFishing', function(baitType)
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
-- Check if near water
if not IsNearWater(coords) then
QBCore.Functions.Notify("You need to be near water to fish", "error")
return
end
-- Check for fishing equipment
QBCore.Functions.TriggerCallback('qb-fishing:server:hasEquipment', function(hasRod, hasBait)
if not hasRod then
QBCore.Functions.Notify("You need a fishing rod", "error")
return
end
if not hasBait then
QBCore.Functions.Notify("You need bait to fish", "error")
return
end
-- Start fishing
isFishing = true
currentBait = baitType
-- Play fishing animation
local animDict = "amb@world_human_stand_fishing@idle_a"
RequestAnimDict(animDict)
while not HasAnimDictLoaded(animDict) do
Wait(1)
end
TaskPlayAnim(ped, animDict, "idle_c", 8.0, -8.0, -1, 1, 0, false, false, false)
QBCore.Functions.Notify("Fishing started! Wait for a bite...", "success")
-- Start fishing logic
CreateThread(function()
while isFishing do
Wait(math.random(5000, 15000)) -- Random wait for fish
if isFishing then
-- Fish bite chance
local biteChance = math.random()
if biteChance < 0.3 then -- 30% chance
TriggerEvent('qb-fishing:client:fishBite')
break
end
end
end
end)
end, baitType)
end)
RegisterNetEvent('qb-fishing:client:fishBite', function()
QBCore.Functions.Notify("Fish on the line! Press [E] to reel in!", "primary")
-- Start reeling minigame
local reelSuccess = false
local reelTime = GetGameTimer() + 5000 -- 5 seconds to reel
CreateThread(function()
while GetGameTimer() < reelTime and isFishing do
Wait(1)
if IsControlJustPressed(0, 38) then -- E key
-- Simple timing-based minigame
local timing = (reelTime - GetGameTimer()) / 5000
if timing > 0.3 and timing < 0.8 then
reelSuccess = true
break
end
end
end
if reelSuccess then
TriggerServerEvent('qb-fishing:server:catchFish', currentBait)
else
QBCore.Functions.Notify("The fish got away!", "error")
end
-- Stop fishing
isFishing = false
ClearPedTasks(PlayerPedId())
end)
end)
Fish Processing System
-- Server-side fish catch processing
RegisterNetEvent('qb-fishing:server:catchFish', function(baitUsed)
local src = source
local player = QBCore.Functions.GetPlayer(src)
if not player then return end
-- Check fishing license
local hasLicense = exports['qb-fishing']:CheckFishingLicense(src, 'recreational')
if not hasLicense then
TriggerClientEvent('QBCore:Notify', src, 'You need a valid fishing license', 'error')
return
end
-- Determine fish type based on location and bait
local playerCoords = GetEntityCoords(GetPlayerPed(src))
local fishingSpot = GetNearestFishingSpot(playerCoords)
if not fishingSpot then
TriggerClientEvent('QBCore:Notify', src, 'No fish in this area', 'error')
return
end
-- Roll for fish type
local caughtFish = RollForFish(fishingSpot, baitUsed)
if caughtFish then
-- Calculate fish weight and value
local fishConfig = Config.Fish[caughtFish.type]\n local weight = math.random(fishConfig.min_weight * 100, fishConfig.max_weight * 100) / 100\n local value = math.floor(weight * fishConfig.value_per_kg)\n \n -- Give fish to player\n player.Functions.AddItem(caughtFish.type, 1, false, {\n weight = weight,\n value = value,\n caught_at = fishingSpot.label,\n bait_used = baitUsed,\n timestamp = os.date(\"%Y-%m-%d %H:%M:%S\")\n })\n \n -- Remove bait\n player.Functions.RemoveItem(baitUsed, 1)\n \n -- Log the catch\n MySQL.insert('INSERT INTO fishing_catches (citizenid, fish_type, weight, location, bait_used, weather) VALUES (?, ?, ?, ?, ?, ?)', {\n player.PlayerData.citizenid,\n caughtFish.type,\n weight,\n fishingSpot.label,\n baitUsed,\n GetCurrentWeather()\n })\n \n TriggerClientEvent('QBCore:Notify', src, 'Caught a ' .. weight .. 'kg ' .. fishConfig.label .. '!', 'success')\n TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[caughtFish.type], \"add\")\n else\n TriggerClientEvent('QBCore:Notify', src, 'No fish caught this time', 'error')\n \n -- Still remove bait (consumed)\n player.Functions.RemoveItem(baitUsed, 1)\n end\nend)\n\nfunction RollForFish(fishingSpot, baitUsed)\n local fishRoll = math.random()\n local currentWeight = 0\n \n for fishType, fishData in pairs(fishingSpot.fish_types) do\n currentWeight = currentWeight + fishData.weight\n \n if fishRoll <= currentWeight then\n -- Check bait effectiveness\n local baitEffectiveness = Config.Bait[baitUsed].effectiveness[fishType] or 0.5\n local baitRoll = math.random()\n \n if baitRoll <= baitEffectiveness then\n return {\n type = fishType,\n min_weight = fishData.min_weight,\n max_weight = fishData.max_weight\n }\n end\n end\n end\n \n return nil\nend\n```\n\n### Fishing Competition System\n\n```lua\n-- Fishing tournament management\nlocal activeCompetitions = {}\n\nRegisterNetEvent('qb-fishing:server:createCompetition', function(compData)\n local src = source\n local player = QBCore.Functions.GetPlayer(src)\n \n -- Only allow authorized players to create competitions\n if player.PlayerData.job.name ~= \"fishing_guide\" then\n TriggerClientEvent('QBCore:Notify', src, 'Only fishing guides can create competitions', 'error')\n return\n end\n \n local competitionId = math.random(10000, 99999)\n \n activeCompetitions[competitionId] = {\n id = competitionId,\n name = compData.name,\n start_time = os.time(),\n end_time = os.time() + (compData.duration * 60), -- Duration in minutes\n fish_type = compData.fish_type,\n prize_pool = compData.prize_pool,\n participants = {},\n leaderboard = {},\n status = 'active'\n }\n \n -- Announce competition to all players\n TriggerClientEvent('QBCore:Notify', -1, 'Fishing Competition Started: ' .. compData.name, 'primary')\n TriggerClientEvent('qb-fishing:client:competitionAnnouncement', -1, activeCompetitions[competitionId])\nend)\n\n-- Handle competition fish catches\nRegisterNetEvent('qb-fishing:server:competitionCatch', function(compId, fishData)\n local src = source\n local player = QBCore.Functions.GetPlayer(src)\n local competition = activeCompetitions[compId]\n \n if not competition or competition.status ~= 'active' then\n return\n end\n \n -- Check if fish type matches competition\n if fishData.type == competition.fish_type then\n local citizenId = player.PlayerData.citizenid\n \n -- Update leaderboard\n if not competition.leaderboard[citizenId] or \n competition.leaderboard[citizenId].weight < fishData.weight then\n \n competition.leaderboard[citizenId] = {\n name = player.PlayerData.charinfo.firstname .. \" \" .. player.PlayerData.charinfo.lastname,\n weight = fishData.weight,\n fish_type = fishData.type\n }\n \n -- Notify participants of new leader\n for participantId, _ in pairs(competition.participants) do\n TriggerClientEvent('QBCore:Notify', participantId, 'New leader in competition!', 'primary')\n end\n end\n end\nend)\n\n-- End competition and award prizes\nfunction EndCompetition(compId)\n local competition = activeCompetitions[compId]\n if not competition then return end\n \n competition.status = 'completed'\n \n -- Sort leaderboard by weight\n local sortedResults = {}\n for citizenId, data in pairs(competition.leaderboard) do\n table.insert(sortedResults, {citizenId = citizenId, data = data})\n end\n \n table.sort(sortedResults, function(a, b)\n return a.data.weight > b.data.weight\n end)\n \n -- Award prizes to top 3\n local prizes = {competition.prize_pool * 0.5, competition.prize_pool * 0.3, competition.prize_pool * 0.2}\n \n for i = 1, math.min(3, #sortedResults) do\n local winner = sortedResults[i]\n local player = QBCore.Functions.GetPlayerByCitizenId(winner.citizenId)\n \n if player then\n player.Functions.AddMoney('bank', prizes[i], 'fishing-competition-prize')\n TriggerClientEvent('QBCore:Notify', player.PlayerData.source, 'You won place #' .. i .. ' in the fishing competition! Prize: $' .. prizes[i], 'success')\n end\n end\n \n -- Announce results\n TriggerClientEvent('qb-fishing:client:competitionResults', -1, sortedResults, competition)\n \n -- Clean up\n activeCompetitions[compId] = nil\nend\n```\n\n## Troubleshooting\n\n### Common Issues\n\n#### Fish Not Spawning\n```lua\n-- Debug fish spawning\nRegisterCommand('debugfish', function()\n local coords = GetEntityCoords(PlayerPedId())\n local spot = GetNearestFishingSpot(coords)\n if spot then\n print(\"Fishing spot:\", spot.label)\n print(\"Fish types:\", json.encode(spot.fish_types))\n else\n print(\"No fishing spot nearby\")\n end\nend)\n```\n\n#### License Verification Issues\n- Check database table structure for fishing_licenses\n- Verify license expiration calculations\n- Ensure proper license type checking\n\n#### Bait Effectiveness Problems\n```lua\n-- Debug bait effectiveness\nRegisterNetEvent('qb-fishing:debug:baitCheck', function(baitType, fishType)\n local effectiveness = Config.Bait[baitType].effectiveness[fishType]\n print(\"Bait:\", baitType, \"Fish:\", fishType, \"Effectiveness:\", effectiveness)\nend)\n```\n\n### Debug Commands\n\n```lua\n-- Give fishing equipment\n/givefishgear [player_id]\n\n-- Start fishing competition\n/startfishcomp [name] [duration] [fish_type] [prize]\n\n-- Check fishing license\n/checkfishlicense [player_id]\n\n-- Spawn fish for testing\n/spawnfish [type] [x] [y] [z]\n```\n\n<Callout type=\"warning\">\n Test fishing mechanics thoroughly, especially the timing-based minigames, to ensure they work well with server latency.\n</Callout>