├── README.md ├── addons └── groups │ └── server │ └── main.lua ├── client ├── bucket.lua └── main.lua ├── fxmanifest.lua ├── ox_inv ├── data │ ├── items.lua │ └── shops.lua └── modules │ └── items │ └── client.lua ├── server └── main.lua └── shared └── config.lua /README.md: -------------------------------------------------------------------------------- 1 | # Simple Fishing 2 | 3 | ## Dependencies 4 | 5 | - [ox_lib](https://github.com/overextended/ox_lib) 6 | - [ox_target](https://github.com/overextended/ox_target) 7 | - [ox_inventory](https://github.com/overextended/ox_inventory) 8 | - OneSync Infinity 9 | 10 | ## Optional Dependencies 11 | 12 | - [renewed phone](https://github.com/Renewed-Scripts/qb-phone) - for group fishing 13 | 14 | ## Installation 15 | 16 | - Drop resource into your server directory and add `ensure qw_fishing` to your `server.cfg` (This needs to start after all of the above dependencies) 17 | - add everything from the `ox_inv` directory to their respective places in [ox_inventory](https://github.com/overextended/ox_inventory) 18 | - setup your config file for fishing 19 | - Test it out and have fun. If you need support please re-read this document one more time. If you still need support then join my [discord](https://dsc.gg/qw-scripts) 20 | 21 | ## Preview 22 | 23 | [https://www.youtube.com/watch?v=Az6Lqb5X90E&list=PLZMrb2RbC2T8Kt3Za3q9pVCNCFp8NM45j&index=8](https://www.youtube.com/watch?v=Az6Lqb5X90E&list=PLZMrb2RbC2T8Kt3Za3q9pVCNCFp8NM45j&index=8) 24 | 25 | This preview doesn't show the selling part in it, but I was to lazy to remake the preview. 26 | -------------------------------------------------------------------------------- /addons/groups/server/main.lua: -------------------------------------------------------------------------------- 1 | local FishingGroups = {} 2 | 3 | RegisterNetEvent('qw_fishing:groups:startGroupFishing', function() 4 | local src = source 5 | local group = exports['qb-phone']:GetGroupByMembers(src) 6 | if not group then 7 | TriggerClientEvent('ox_lib:notify', src, 8 | { type = 'error', description = 'you are not in a group!' }) 9 | return 10 | end 11 | 12 | if FishingGroups[group] then -- TODO: re-write this into a different event and just add/remove target option based on whether or not they are in a group 13 | exports['qb-phone']:resetJobStatus(group) 14 | FishingGroups[group] = nil 15 | exports['qb-phone']:pNotifyGroup(group, 'Fishing', 16 | "All done. Hope you enjoyed the fishing!", 17 | 'fa fa-fish', 18 | '#03adfc', 5000) 19 | return 20 | end 21 | 22 | FishingGroups[group] = { 23 | totalCaught = 0, 24 | } 25 | 26 | local FishTable = { 27 | { name = "Catch Fish (0/" .. Config.TotalFishToCatchForGroup .. ")", isDone = false, id = 1 }, 28 | } 29 | 30 | exports['qb-phone']:setJobStatus(group, "Fishing", FishTable) 31 | exports['qb-phone']:pNotifyGroup(group, 'Fishing', 'Make your way to a legal fishing zone.', 'fa fa-fish', 32 | '#03adfc', 15000) 33 | end) 34 | 35 | RegisterNetEvent('qw_fishing:groups:bumpFishingTask', function(group) 36 | if not group then 37 | print('no group', group) 38 | return 39 | end 40 | 41 | if not FishingGroups[group] then 42 | print('no active fishing group') 43 | return 44 | end 45 | 46 | FishingGroups[group].totalCaught = FishingGroups[group].totalCaught + 1 47 | 48 | if FishingGroups[group].totalCaught >= Config.TotalFishToCatchForGroup then 49 | exports['qb-phone']:resetJobStatus(group) 50 | FishingGroups[group] = nil 51 | exports['qb-phone']:pNotifyGroup(group, 'Fishing', 52 | "All done. Hope you enjoyed the fishing!", 53 | 'fa fa-fish', 54 | '#03adfc', 5000) 55 | return 56 | end 57 | 58 | 59 | local FishTable = { 60 | { name = "Catch Fish (" .. FishingGroups[group].totalCaught .. "/" .. Config.TotalFishToCatchForGroup .. ")", isDone = false, id = 1 }, 61 | } 62 | 63 | exports['qb-phone']:setJobStatus(group, "Fishing", FishTable) 64 | exports['qb-phone']:pNotifyGroup(group, 'Fishing', 65 | "(" .. FishingGroups[group].totalCaught .. "/" .. Config.TotalFishToCatchForGroup .. ") fish caught.", 66 | 'fa fa-fish', 67 | '#03adfc', 15000) 68 | end) 69 | 70 | AddEventHandler('qb-phone:server:GroupDeleted', function(group) 71 | if FishingGroups[group] then 72 | FishingGroups[group] = nil 73 | end 74 | end) 75 | -------------------------------------------------------------------------------- /client/bucket.lua: -------------------------------------------------------------------------------- 1 | IsPlacingPreview = false 2 | local previewedObject = nil 3 | 4 | RegisterNetEvent('qw_fishing:client:pickupBucket', function(data) 5 | exports.ox_target:removeEntity(data.netId, { ("baitbucket:%s"):format(data.netId) }) 6 | exports.ox_target:removeEntity(data.netId, { ("getbait:%s"):format(data.netId) }) 7 | if lib.progressBar({ duration = 2000, label = 'Picking Up', useWhileDead = false, canCancel = true, 8 | disable = { car = true } }) then 9 | TriggerServerEvent('qw_fishing:server:pickupBaitBucket', { netId = data.netId }) 10 | end 11 | end) 12 | 13 | RegisterNetEvent('qw_fishing:client:grabBait', function(data) 14 | if lib.progressBar({ duration = 2000, label = 'Grabbing Bait', useWhileDead = false, canCancel = true, 15 | disable = { car = true } }) then 16 | TriggerServerEvent('qw_fishing:server:grabBaitFromBucket', { netId = data.netId }) 17 | end 18 | end) 19 | 20 | function CancelPlacement() 21 | IsPlacingPreview = false 22 | DeleteObject(previewedObject) 23 | previewedObject = nil 24 | lib.hideTextUI() 25 | end 26 | 27 | function PlaceSpawnedObject(coords, object, slot, bait) 28 | LocalPlayer.state.invHotkeys = false 29 | lib.hideTextUI() 30 | if lib.progressCircle({ duration = 2000, position = 'bottom', useWhileDead = false, canCancel = true, 31 | disable = { car = true } }) then 32 | FreezeEntityPosition(previewedObject, true) 33 | IsPlacingPreview = false 34 | 35 | TriggerServerEvent('qw_fishing:server:spawnNewBucket', coords, object, slot, bait) 36 | 37 | DeleteObject(previewedObject) 38 | 39 | previewedObject = nil 40 | HasAlreadyPlacedTable = true 41 | LocalPlayer.state.invHotkeys = true 42 | else 43 | CancelPlacement() 44 | LocalPlayer.state.invHotkeys = true 45 | end 46 | end 47 | 48 | function CreatePreviewedObject(model, hasDistanceCheck, slot, bait) 49 | IsPlacingPreview = true 50 | lib.requestModel(model) 51 | 52 | previewedObject = CreateObject(model, GetEntityCoords(cache.ped), true, true, false) 53 | 54 | SetEntityAlpha(previewedObject, 150, false) 55 | SetEntityCollision(previewedObject, false, false) 56 | FreezeEntityPosition(previewedObject, true) 57 | 58 | lib.showTextUI('[E] - Place Bucket \n [Q] - Cancel') 59 | while IsPlacingPreview do 60 | local hit, _, coords, _, _ = lib.raycast.cam(1, 4) 61 | 62 | if hit then 63 | SetEntityCoords(previewedObject, coords.x, coords.y, coords.z) 64 | PlaceObjectOnGroundProperly(previewedObject) 65 | local distanceCheck = #(coords - GetEntityCoords(cache.ped)) 66 | 67 | if IsControlJustPressed(0, 44) then CancelPlacement() end 68 | 69 | if IsControlJustPressed(0, 38) then 70 | if not hasDistanceCheck then 71 | PlaceSpawnedObject(coords, model, slot, bait) 72 | return 73 | end 74 | 75 | if distanceCheck < 2.5 then 76 | PlaceSpawnedObject(coords, model, slot, bait) 77 | else 78 | lib.notify({ 79 | title = 'Placement', 80 | description = 'you may not place this object here...', 81 | style = { 82 | backgroundColor = '#141517', 83 | color = '#909296' 84 | }, 85 | icon = 'xmark', 86 | iconColor = 'red', 87 | }) 88 | end 89 | end 90 | end 91 | 92 | Wait(1) 93 | end 94 | end 95 | 96 | AddEventHandler('onResourceStop', function(resourceName) 97 | if GetCurrentResourceName() == resourceName then 98 | CancelPlacement() 99 | end 100 | end) 101 | -------------------------------------------------------------------------------- /client/main.lua: -------------------------------------------------------------------------------- 1 | local FullyLoaded = LocalPlayer.state.isLoggedIn 2 | 3 | local pedSpawned = false 4 | local spawnedPed = nil 5 | 6 | local sellPedSpawned = false 7 | local spawnedSellPed = nil 8 | 9 | local inZone = false 10 | local PZones = {} 11 | 12 | local isFishing = false 13 | 14 | local fishingRod = nil 15 | local rodModel = "prop_fishing_rod_01" 16 | local rodHash = `prop_fishing_rod_01` 17 | 18 | local function reelInFish(slot, durability) 19 | local fish = Config.FishingLoot[math.random(1, #Config.FishingLoot)] 20 | local chanceToCatch = math.random(1, 100) 21 | 22 | if fish.chance > chanceToCatch then 23 | lib.notify({ 24 | description = 'got a little nibble...', 25 | type = 'success' 26 | }) 27 | Wait(1000) 28 | local success = lib.skillCheck(fish['skillcheck']) 29 | if success then 30 | lib.notify({ 31 | title = 'Fishing', 32 | description = 'You caught a ' .. fish.name .. '!', 33 | type = 'success' 34 | }) 35 | TriggerServerEvent('qw_fishing:server:giveFish', fish, slot, durability) 36 | if Config.ScullyEmotes then 37 | exports.scully_emotemenu:CancelAnimation() 38 | else 39 | ClearPedTasks(cache.ped) 40 | DeleteObject(fishingRod) 41 | end 42 | 43 | isFishing = false 44 | else 45 | lib.notify({ 46 | title = 'Fishing', 47 | description = 'You lost the fish!', 48 | type = 'error' 49 | }) 50 | end 51 | end 52 | end 53 | 54 | local function startFishing(slot, durability) 55 | if isFishing then return end 56 | local baitAmount = exports.ox_inventory:Search('count', 'fishbait') 57 | 58 | if baitAmount < 1 then 59 | lib.notify({ 60 | title = 'Fishing', 61 | description = 'You don\'t have any bait!', 62 | type = 'error' 63 | }) 64 | return 65 | end 66 | if Config.ScullyEmotes then 67 | exports.scully_emotemenu:PlayByCommand('fishing2') 68 | else 69 | local ped = cache.ped 70 | 71 | local playerPos = GetEntityCoords(ped) 72 | local bone = GetPedBoneIndex(ped, 60309) 73 | 74 | lib.requestModel(rodModel) 75 | lib.requestAnimDict('amb@world_human_stand_fishing@idle_a') 76 | 77 | if fishingRod then 78 | DeleteObject(fishingRod) 79 | fishingRod = nil 80 | end 81 | 82 | fishingRod = CreateObject(rodHash, 1.0, 1.0, 1.0, 1, 1, 0) 83 | ClearPedTasksImmediately(ped) 84 | 85 | AttachEntityToEntity(fishingRod, ped, bone, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 1) 86 | Wait(0) 87 | TaskPlayAnim(ped, "amb@world_human_stand_fishing@idle_a", "idle_c", 20.0, -8, -1, 17, 0, 0, 0, 0) 88 | 89 | SetModelAsNoLongerNeeded('prop_fishing_rod_01') 90 | end 91 | isFishing = true 92 | 93 | local seconds = 0 94 | CreateThread(function() 95 | while isFishing do 96 | Wait(5000) 97 | seconds = seconds + 5 98 | local chance = math.random() * 100 99 | if chance + seconds > 100 then 100 | reelInFish(slot, durability) 101 | end 102 | end 103 | end) 104 | end 105 | 106 | RegisterNetEvent('qw_fishing:client:Fish', function(slot, durability) 107 | if not inZone then 108 | lib.notify({ 109 | title = 'Fishing', 110 | description = 'Looks like you can\'t fish here...', 111 | type = 'error' 112 | }) 113 | return 114 | end 115 | 116 | startFishing(slot, durability) 117 | end) 118 | 119 | RegisterNetEvent('qw_fishing:client:placeBaitBucket', function(slot, bait) 120 | if IsPlacingPreview then return end 121 | CreatePreviewedObject(Config.BaitBucketProp, true, slot, bait) 122 | end) 123 | 124 | RegisterNetEvent('qw_fishing:client:sellFish', function() 125 | local fishCount = 0 126 | for i = 1, #Config.FishingLoot do 127 | local fish = Config.FishingLoot[i] 128 | local fishAmount = exports.ox_inventory:Search('count', fish.name) 129 | fishCount = fishCount + fishAmount 130 | end 131 | 132 | if fishCount > 0 then 133 | TriggerServerEvent('qw_fishing:server:sellFish') 134 | else 135 | lib.notify({ 136 | title = 'Fishing', 137 | description = 'You don\'t have any fish to sell!', 138 | type = 'error' 139 | }) 140 | end 141 | end) 142 | 143 | RegisterNetEvent('qw_fishing:client:fillBaitBucket', function() 144 | local bucketAmount = exports.ox_inventory:Search('count', 'bucket') 145 | if bucketAmount < 1 then 146 | lib.notify({ 147 | title = 'Fishing', 148 | description = 'You don\'t have any buckets!', 149 | type = 'error' 150 | }) 151 | end 152 | 153 | local askToFill = lib.alertDialog({ 154 | header = 'Fishing', 155 | content = 'Are you sure you want to fill your buckets with bait? \n This will cost $' .. 156 | Config.BaitPrice * bucketAmount .. '!', 157 | centered = true, 158 | cancel = true, 159 | labels = { 160 | cancel = 'No', 161 | confirm = 'Purchase' 162 | } 163 | }) 164 | 165 | if askToFill == 'confirm' then 166 | TriggerServerEvent('qw_fishing:server:fillBaitBucket', Config.BaitPrice * bucketAmount) 167 | end 168 | end) 169 | 170 | local function createFishingZones() 171 | for i = 1, #Config.FishingPoints do 172 | local zone = Config.FishingPoints[i] 173 | 174 | PZones[#PZones + 1] = lib.zones.poly({ 175 | points = zone, 176 | thickness = 2, 177 | debug = Config.Debug, 178 | inside = function() 179 | inZone = true 180 | end, 181 | onEnter = function() 182 | inZone = true 183 | end, 184 | onExit = function() 185 | inZone = false 186 | if Config.ScullyEmotes then 187 | exports.scully_emotemenu:CancelAnimation() 188 | else 189 | ClearPedTasks(cache.ped) 190 | DeleteObject(fishingRod) 191 | end 192 | end, 193 | }) 194 | end 195 | end 196 | 197 | local function SpawnShopPed() 198 | if pedSpawned then return end 199 | local model = joaat(Config.ShopPed.model) 200 | 201 | lib.requestModel(model) 202 | 203 | local coords = Config.ShopPed.coords 204 | local ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1, coords.w, false, false) 205 | 206 | spawnedPed = ped 207 | 208 | TaskStartScenarioInPlace(ped, 'PROP_HUMAN_STAND_IMPATIENT', 0, true) 209 | FreezeEntityPosition(ped, true) 210 | SetEntityInvincible(ped, true) 211 | SetBlockingOfNonTemporaryEvents(ped, true) 212 | 213 | pedSpawned = true 214 | end 215 | 216 | local function SpawnSellPed() 217 | if sellPedSpawned then return end 218 | 219 | local model = joaat(Config.SellPed.model) 220 | 221 | lib.requestModel(model) 222 | 223 | local coords = Config.SellPed.coords 224 | local ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1, coords.w, false, false) 225 | 226 | spawnedSellPed = ped 227 | 228 | TaskStartScenarioInPlace(ped, 'PROP_HUMAN_STAND_IMPATIENT', 0, true) 229 | FreezeEntityPosition(ped, true) 230 | SetEntityInvincible(ped, true) 231 | SetBlockingOfNonTemporaryEvents(ped, true) 232 | 233 | local options = {} 234 | 235 | if Config.UseGroups then 236 | options = { 237 | { 238 | name = 'fishing:sellfish', 239 | label = 'Sell Fish', 240 | icon = 'fa-solid fa-fish', 241 | event = 'qw_fishing:client:sellFish', 242 | canInteract = function(_, distance) 243 | return distance < 2.0 244 | end 245 | }, 246 | { 247 | name = 'fishing:fillbaitbucket', 248 | label = 'Fill Bait Bucket', 249 | icon = 'fa-solid fa-fish', 250 | event = 'qw_fishing:client:fillBaitBucket', 251 | canInteract = function(_, distance) 252 | return distance < 2.0 253 | end 254 | }, 255 | { 256 | name = 'fishing:startGroupFish', 257 | label = 'Start/Stop Group Fish', 258 | icon = 'fa-solid fa-user-group', 259 | serverEvent = 'qw_fishing:groups:startGroupFishing', 260 | canInteract = function(_, distance) 261 | return distance < 2.0 262 | end 263 | } 264 | } 265 | else 266 | options = { 267 | { 268 | name = 'fishing:sellfish', 269 | label = 'Sell Fish', 270 | icon = 'fa-solid fa-fish', 271 | event = 'qw_fishing:client:sellFish', 272 | canInteract = function(_, distance) 273 | return distance < 2.0 274 | end 275 | }, 276 | { 277 | name = 'fishing:fillbaitbucket', 278 | label = 'Fill Bait Bucket', 279 | icon = 'fa-solid fa-fish', 280 | event = 'qw_fishing:client:fillBaitBucket', 281 | canInteract = function(_, distance) 282 | return distance < 2.0 283 | end 284 | }, 285 | } 286 | end 287 | 288 | exports.ox_target:addLocalEntity(spawnedSellPed, options) 289 | 290 | sellPedSpawned = true 291 | end 292 | 293 | local function deletePeds() 294 | if not pedSpawned and not sellPedSpawned then return end 295 | DeleteEntity(spawnedPed) 296 | DeleteEntity(spawnedSellPed) 297 | spawnedPed = nil 298 | pedSpawned = false 299 | spawnedSellPed = nil 300 | sellPedSpawned = false 301 | end 302 | 303 | local function removePolyZone() 304 | if PZones == nil then return end 305 | 306 | for i = 1, #PZones do 307 | PZones[i]:remove() 308 | end 309 | end 310 | 311 | AddStateBagChangeHandler('isBaitBucket', nil, function(bagName, _, value) 312 | local ent = GetEntityFromStateBagName(bagName) 313 | if ent == 0 or not value then return end 314 | 315 | local entNetId = ObjToNet(ent) 316 | 317 | local options = { 318 | { 319 | name = ("baitbucket:%s"):format(entNetId), 320 | event = 'qw_fishing:client:pickupBucket', 321 | icon = 'fa-solid fa-hand', 322 | netId = entNetId, 323 | label = 'Pickup Bait Bucket', 324 | canInteract = function(_, distance) 325 | return distance < 2.0 326 | end 327 | }, 328 | { 329 | name = ("getbait:%s"):format(entNetId), 330 | event = 'qw_fishing:client:grabBait', 331 | icon = 'fa-solid fa-hand', 332 | netId = entNetId, 333 | label = 'Grab Some Bait', 334 | canInteract = function(_, distance) 335 | return distance < 2.0 336 | end 337 | } 338 | } 339 | 340 | exports.ox_target:addEntity(entNetId, options) 341 | end) 342 | 343 | exports.ox_inventory:displayMetadata('bait', 'Bait') 344 | exports.ox_inventory:displayMetadata('fish_quality', 'Quality') 345 | 346 | AddStateBagChangeHandler('isLoggedIn', nil, function(_, _, value) 347 | FullyLoaded = value 348 | end) 349 | 350 | RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() 351 | Wait(100) 352 | SpawnShopPed() 353 | SpawnSellPed() 354 | createFishingZones() 355 | exports.ox_inventory:displayMetadata('bait', 'Bait') 356 | exports.ox_inventory:displayMetadata('fish_quality', 'Quality') 357 | end) 358 | 359 | RegisterNetEvent('QBCore:Client:OnPlayerUnload', function() 360 | deletePeds() 361 | removePolyZone() 362 | end) 363 | 364 | 365 | AddEventHandler('onResourceStart', function(resource) 366 | if resource ~= GetCurrentResourceName() then return end 367 | if not FullyLoaded then return end 368 | Wait(100) 369 | SpawnShopPed() 370 | SpawnSellPed() 371 | createFishingZones() 372 | exports.ox_inventory:displayMetadata('bait', 'Bait') 373 | exports.ox_inventory:displayMetadata('fish_quality', 'Quality') 374 | end) 375 | 376 | AddEventHandler('onResourceStop', function(resource) 377 | if resource ~= GetCurrentResourceName() then return end 378 | deletePeds() 379 | removePolyZone() 380 | end) 381 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | description 'Simple Fishing System for FiveM' 5 | author 'qw-scripts' 6 | version '0.1.0' 7 | 8 | client_scripts { 9 | 'client/**/*', 10 | } 11 | 12 | server_scripts { 13 | 'server/**/*', 14 | 'addons/groups/server/**/*' -- COMMENT THIS OUT IF YOU ARE NOT USING GROUPS 15 | } 16 | 17 | shared_scripts { 18 | 'shared/**/*', 19 | '@ox_lib/init.lua' 20 | } 21 | 22 | lua54 'yes' 23 | -------------------------------------------------------------------------------- /ox_inv/data/items.lua: -------------------------------------------------------------------------------- 1 | ItemsToAdd = { 2 | ['fishingrod'] = { 3 | label = 'Fishing Rod', 4 | weight = 1000, 5 | stack = false, 6 | close = true 7 | }, 8 | ['fishbait'] = { 9 | label = 'Fishing Bait', 10 | weight = 1000, 11 | stack = true, 12 | close = false 13 | }, 14 | ['bucket'] = { 15 | label = 'Bait Bucket', 16 | weight = 5000, 17 | stack = false, 18 | close = true 19 | }, 20 | ['catfish'] = { 21 | label = 'Catfish', 22 | weight = 2000, 23 | stack = true, 24 | }, 25 | ['largemouthbass'] = { 26 | label = 'Large Mouth Bass', 27 | weight = 4000, 28 | stack = true, 29 | }, 30 | ['redfish'] = { 31 | label = 'Red Fish', 32 | weight = 3000, 33 | stack = true, 34 | }, 35 | ['salmon'] = { 36 | label = 'Salmon', 37 | weight = 3000, 38 | stack = true, 39 | }, 40 | ['stingray'] = { 41 | label = 'Sting Ray', 42 | weight = 3000, 43 | stack = true, 44 | }, 45 | ['stripedbass'] = { 46 | label = 'Striped Bass', 47 | weight = 3000, 48 | stack = true, 49 | }, 50 | ['whale'] = { 51 | label = 'Whale', 52 | weight = 10000, 53 | stack = true, 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /ox_inv/data/shops.lua: -------------------------------------------------------------------------------- 1 | -- MAKE SURE TO SET THE LOCATION OF THE SHOP TO THE SAME LOCATION YOU HAVE THE PED SPAWNING AT 2 | Fishing = { 3 | name = 'Fishing', 4 | blip = { 5 | id = 210, colour = 69, scale = 0.8 6 | }, inventory = { 7 | { name = 'bucket', price = 150, metadata = { bait = 10 } }, 8 | { name = 'fishingrod', price = 100, metadata = { durability = 100 } } 9 | }, locations = { 10 | vec3(-1694.65, -1057.43, 13.02 - 1) 11 | }, targets = { 12 | { loc = vec3(-1694.65, -1057.43, 13.02 - 1), length = 0.6, width = 3.0, heading = 51.19, minZ = 12.0, maxZ = 13.8, 13 | distance = 3.0 } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ox_inv/modules/items/client.lua: -------------------------------------------------------------------------------- 1 | Item('fishingrod', function(data, slot) 2 | ox_inventory:useItem(data, function(data) 3 | if data then 4 | if data.metadata.durability <= 0 then 5 | lib.notify({ 6 | description = 'Your fishing rod is broken', 7 | type = 'error' 8 | }) 9 | return 10 | end 11 | TriggerEvent('qw_fishing:client:Fish', data.slot, data.metadata.durability) 12 | end 13 | end) 14 | end) 15 | 16 | Item('bucket', function(data, slot) 17 | ox_inventory:useItem(data, function(data) 18 | if data then 19 | TriggerEvent('qw_fishing:client:placeBaitBucket', data.slot, data.metadata.bait) 20 | end 21 | end) 22 | end) 23 | -------------------------------------------------------------------------------- /server/main.lua: -------------------------------------------------------------------------------- 1 | local spawnedInBuckets = {} 2 | 3 | RegisterNetEvent('qw_fishing:server:pickupBaitBucket', function(data) 4 | local src = source 5 | local ped = GetPlayerPed(src) 6 | 7 | local netId = data.netId 8 | local ent = NetworkGetEntityFromNetworkId(netId) 9 | 10 | 11 | if ent == 0 then return end 12 | 13 | local distanceFromBucket = #(GetEntityCoords(ped) - GetEntityCoords(ent)) 14 | 15 | if distanceFromBucket > 2.0 then return end 16 | 17 | exports.ox_inventory:AddItem(src, 'bucket', 1, { ['bait'] = Entity(ent).state['bait'] }) 18 | Wait(200) 19 | DeleteEntity(ent) 20 | 21 | local tempTable = {} 22 | for i = 1, #spawnedInBuckets do 23 | local bucket = spawnedInBuckets[i] 24 | if bucket ~= netId then 25 | tempTable[#tempTable + 1] = bucket 26 | end 27 | end 28 | 29 | spawnedInBuckets = tempTable 30 | end) 31 | 32 | RegisterNetEvent('qw_fishing:server:grabBaitFromBucket', function(data) 33 | local src = source 34 | local ped = GetPlayerPed(src) 35 | 36 | local netId = data.netId 37 | local ent = NetworkGetEntityFromNetworkId(netId) 38 | 39 | if ent == 0 then return end 40 | 41 | local bait = Entity(ent).state['bait'] 42 | 43 | if bait <= 0 then return end 44 | 45 | local distanceFromBucket = #(GetEntityCoords(ped) - GetEntityCoords(ent)) 46 | 47 | if distanceFromBucket > 2.0 then return end 48 | 49 | Entity(ent).state['bait'] = bait - 1 50 | 51 | exports.ox_inventory:AddItem(src, 'fishbait', 1) 52 | end) 53 | 54 | RegisterNetEvent('qw_fishing:server:spawnNewBucket', function(coords, object, slot, bait) 55 | local src = source 56 | local ped = GetPlayerPed(src) 57 | 58 | local distanceFromBucket = #(GetEntityCoords(ped) - coords) 59 | 60 | if distanceFromBucket > 2.0 then return end 61 | 62 | local obj = CreateObjectNoOffset(object, coords.x, coords.y, coords.z, true, false, false) 63 | 64 | local bucketBaitAmount = bait 65 | 66 | FreezeEntityPosition(obj, true) 67 | 68 | Entity(obj).state['bait'] = bucketBaitAmount 69 | Entity(obj).state['isBaitBucket'] = true 70 | 71 | local netId = NetworkGetNetworkIdFromEntity(obj) 72 | 73 | spawnedInBuckets[#spawnedInBuckets + 1] = netId 74 | exports.ox_inventory:RemoveItem(src, 'bucket', 1, _, slot) 75 | end) 76 | 77 | RegisterNetEvent('qw_fishing:server:giveFish', function(fish, slot, durability) 78 | local src = source 79 | local quality = math.ceil(math.random() * 100) 80 | local qualIndentifier = nil 81 | 82 | for i = 1, #Config.FishQualityIdentifiers do 83 | if quality >= Config.FishQualityIdentifiers[i].min and quality <= Config.FishQualityIdentifiers[i].max then 84 | qualIndentifier = Config.FishQualityIdentifiers[i].label 85 | end 86 | end 87 | 88 | if exports.ox_inventory:CanCarryItem(src, fish.name, 1) then 89 | exports.ox_inventory:AddItem(src, fish.name, 1, { ['fish_quality'] = qualIndentifier }) 90 | exports.ox_inventory:RemoveItem(src, 'fishbait', 1) 91 | exports.ox_inventory:SetDurability(src, slot, durability - Config.RodDegen) 92 | end 93 | 94 | if Config.UseGroups then 95 | TriggerEvent('qw_fishing:groups:bumpFishingTask', exports['qb-phone']:GetGroupByMembers(src)) 96 | end 97 | end) 98 | 99 | RegisterNetEvent('qw_fishing:server:sellFish', function() -- TODO: re-write this terrible event at some point 100 | local src = source 101 | local paymentTotal = 0 102 | local itemsToRemove = {} 103 | 104 | for i = 1, #Config.FishingLoot do 105 | local fish = Config.FishingLoot[i] 106 | for j, k in pairs(fish['quality_payment']) do 107 | local item = exports.ox_inventory:Search(src, 'count', fish.name, { ['fish_quality'] = j }) 108 | if item ~= 0 then 109 | local payment = k * item 110 | paymentTotal = paymentTotal + payment 111 | 112 | itemsToRemove[#itemsToRemove + 1] = { ['name'] = fish.name,['count'] = item } 113 | end 114 | end 115 | end 116 | 117 | for i = 1, #itemsToRemove do 118 | local item = itemsToRemove[i] 119 | exports.ox_inventory:RemoveItem(src, item.name, item.count) 120 | end 121 | 122 | if paymentTotal > 0 and exports.ox_inventory:AddItem(src, 'money', paymentTotal) then 123 | TriggerClientEvent('ox_lib:notify', src, 124 | { type = 'success', description = 'You have made $' .. paymentTotal .. ' in total!' }) 125 | end 126 | end) 127 | 128 | RegisterNetEvent('qw_fishing:server:fillBaitBucket', function(moneyToCharge) 129 | local src = source 130 | 131 | local items = exports.ox_inventory:Search(src, 'slots', 'bucket', { ['bait'] = 0 }) 132 | 133 | if #items == 0 then 134 | TriggerClientEvent('ox_lib:notify', src, { type = 'error', description = 'You still have some bait left...' }) 135 | return 136 | end 137 | 138 | if exports.ox_inventory:RemoveItem(src, 'money', moneyToCharge) then 139 | for i = 1, #items do 140 | local slot = items[i].slot 141 | exports.ox_inventory:SetMetadata(src, slot, { ['bait'] = Config.BaitBucketStartingAmount }) 142 | end 143 | end 144 | end) 145 | 146 | AddEventHandler('onResourceStop', function(resourceName) 147 | if GetCurrentResourceName() == resourceName then 148 | for i = 1, #spawnedInBuckets do 149 | local netId = spawnedInBuckets[i] 150 | local ent = NetworkGetEntityFromNetworkId(netId) 151 | DeleteEntity(ent) 152 | end 153 | end 154 | end) 155 | -------------------------------------------------------------------------------- /shared/config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | Config.Debug = false 4 | Config.UseGroups = true 5 | 6 | Config.ShopPed = { 7 | model = 's_m_m_ammucountry', 8 | coords = vec4( -1694.91, -1057.22, 13.02, 227.1) 9 | } 10 | 11 | 12 | Config.SellPed = { 13 | model = 's_m_m_ammucountry', 14 | coords = vec4( -1702.2, -1090.26, 13.15, 35.04) 15 | } 16 | 17 | Config.BaitBucketStartingAmount = 10 18 | 19 | Config.ScullyEmotes = false 20 | 21 | Config.BaitBucketProp = 'prop_bucket_01a' 22 | 23 | Config.TotalFishToCatchForGroup = 10 24 | 25 | Config.RodDegen = math.random(1, 10) 26 | 27 | Config.BaitPrice = 30 28 | 29 | Config.FishingPoints = { 30 | [1] = { 31 | vec( -1742.37, -1103.44, 13.02), 32 | vec( -1739.87, -1104.72, 13.02), 33 | vec( -1765.49, -1136.07, 13.02), 34 | vec( -1767.49, -1133.5, 13.02), 35 | }, 36 | [2] = { 37 | vec( -1735.55, -1122.92, 13.02), 38 | vec( -1737.49, -1121.08, 13.14), 39 | vec( -1794.03, -1188.39, 13.02), 40 | vec( -1792.21, -1190.24, 13.02) 41 | } 42 | } 43 | 44 | Config.FishingLoot = { 45 | [1] = { 46 | ['name'] = 'catfish', 47 | ['chance'] = 60, 48 | ['skillcheck'] = { 49 | 'easy', 'easy', 'easy' 50 | }, 51 | ['quality_payment'] = { 52 | ['Trash'] = 10, 53 | ['Poor'] = 20, 54 | ['Good'] = 25, 55 | ['Great'] = 40, 56 | } 57 | }, 58 | [2] = { 59 | ['name'] = 'largemouthbass', 60 | ['chance'] = 60, 61 | ['skillcheck'] = { 62 | 'easy', 'easy', 'easy' 63 | }, 64 | ['quality_payment'] = { 65 | ['Trash'] = 10, 66 | ['Poor'] = 20, 67 | ['Good'] = 25, 68 | ['Great'] = 40, 69 | } 70 | }, 71 | [3] = { 72 | ['name'] = 'redfish', 73 | ['chance'] = 65, 74 | ['skillcheck'] = { 75 | 'easy', 'easy', 'easy' 76 | }, 77 | ['quality_payment'] = { 78 | ['Trash'] = 10, 79 | ['Poor'] = 20, 80 | ['Good'] = 25, 81 | ['Great'] = 40, 82 | } 83 | }, 84 | [4] = { 85 | ['name'] = 'salmon', 86 | ['chance'] = 65, 87 | ['skillcheck'] = { 88 | 'easy', 'easy', 'easy' 89 | }, 90 | ['quality_payment'] = { 91 | ['Trash'] = 15, 92 | ['Poor'] = 25, 93 | ['Good'] = 35, 94 | ['Great'] = 80, 95 | } 96 | }, 97 | [5] = { 98 | ['name'] = 'stingray', 99 | ['chance'] = 35, 100 | ['skillcheck'] = { 101 | 'easy', 'easy', 'easy' 102 | }, 103 | ['quality_payment'] = { 104 | ['Trash'] = 25, 105 | ['Poor'] = 45, 106 | ['Good'] = 65, 107 | ['Great'] = 125, 108 | } 109 | }, 110 | [6] = { 111 | ['name'] = 'stripedbass', 112 | ['chance'] = 45, 113 | ['skillcheck'] = { 114 | 'easy', 'easy', 'easy' 115 | }, 116 | ['quality_payment'] = { 117 | ['Trash'] = 15, 118 | ['Poor'] = 25, 119 | ['Good'] = 30, 120 | ['Great'] = 45, 121 | } 122 | }, 123 | [7] = { 124 | ['name'] = 'whale', 125 | ['chance'] = 20, 126 | ['skillcheck'] = { 127 | 'easy', 'easy', 'easy' 128 | }, 129 | ['quality_payment'] = { 130 | ['Trash'] = 90, 131 | ['Poor'] = 110, 132 | ['Good'] = 135, 133 | ['Great'] = 155, 134 | } 135 | }, 136 | } 137 | 138 | 139 | Config.FishQualityIdentifiers = { 140 | [1] = { 141 | min = 0, 142 | max = 25, 143 | label = 'Trash', 144 | }, 145 | [2] = { 146 | min = 26, 147 | max = 50, 148 | label = 'Poor', 149 | }, 150 | [3] = { 151 | min = 51, 152 | max = 75, 153 | label = 'Good', 154 | }, 155 | [4] = { 156 | min = 76, 157 | max = 100, 158 | label = 'Great', 159 | }, 160 | } 161 | --------------------------------------------------------------------------------