Skip to Content
QBCore docs – powered by Nextra 4
ResourcesQB-Fishing - Angling and Marine Harvesting System

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

  1. Download the Resource
cd resources/[qb] git clone https://github.com/qbcore-framework/qb-fishing.git
  1. 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`) );
  1. 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' }
  1. 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>
Last updated on