Skip to Content
QBCore docs – powered by Nextra 4
ResourcesQB-Diving - Underwater Exploration System

QB-Diving - Underwater Exploration System

The qb-diving resource provides a comprehensive underwater exploration and treasure hunting system for QBCore servers, featuring diving equipment, underwater locations, treasure collection, and marine activities.

Overview

QB-Diving creates an immersive underwater experience with realistic diving mechanics, treasure hunting, marine life interaction, and underwater exploration. Players can discover hidden treasures, explore shipwrecks, and engage in various aquatic activities.

Key Features

  • Diving Equipment: Scuba gear, oxygen tanks, and underwater tools
  • Treasure Hunting: Hidden treasures and valuable artifacts
  • Underwater Locations: Shipwrecks, caves, and special diving spots
  • Oxygen Management: Realistic breathing and tank management
  • Marine Collection: Collecting shells, pearls, and sea creatures
  • Diving Certification: Training and skill progression
  • Equipment Rental: Diving gear rental services
  • Underwater Photography: Marine life documentation
  • Salvage Operations: Recovering valuable items from wrecks

Installation

Prerequisites

  • QBCore Framework
  • qb-target (for interaction system)
  • qb-menu (for diving menus)
  • qb-inventory (for diving equipment)

Installation Steps

  1. Download the Resource
cd resources/[qb] git clone https://github.com/qbcore-framework/qb-diving.git
  1. Database Setup
-- Diving tables CREATE TABLE IF NOT EXISTS `diving_spots` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `coords` varchar(255) DEFAULT NULL, `depth` int(11) DEFAULT 0, `difficulty` varchar(50) DEFAULT 'beginner', `treasures` longtext DEFAULT NULL, `discovered_by` varchar(50) DEFAULT NULL, `discovery_date` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `diving_treasures` ( `id` int(11) NOT NULL AUTO_INCREMENT, `spot_id` int(11) DEFAULT NULL, `treasure_type` varchar(100) DEFAULT NULL, `coords` varchar(255) DEFAULT NULL, `value` int(11) DEFAULT 0, `collected` tinyint(1) DEFAULT 0, `collected_by` varchar(50) DEFAULT NULL, `collected_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `diving_certifications` ( `citizenid` varchar(50) NOT NULL, `certification_level` varchar(50) DEFAULT 'Open Water', `max_depth` int(11) DEFAULT 18, `issued_date` timestamp DEFAULT current_timestamp(), `instructor` varchar(50) DEFAULT NULL, PRIMARY KEY (`citizenid`) ); CREATE TABLE IF NOT EXISTS `diving_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `citizenid` varchar(50) DEFAULT NULL, `spot_id` int(11) DEFAULT NULL, `depth_reached` int(11) DEFAULT 0, `duration` int(11) DEFAULT 0, `treasures_found` int(11) DEFAULT 0, `dive_date` timestamp DEFAULT current_timestamp(), PRIMARY KEY (`id`) );
  1. Add Items to qb-core/shared/items.lua
-- Diving Equipment ['scuba_gear'] = { ['name'] = 'scuba_gear', ['label'] = 'Scuba Gear', ['weight'] = 5000, ['type'] = 'item', ['image'] = 'scuba_gear.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Complete scuba diving equipment' }, ['oxygen_tank'] = { ['name'] = 'oxygen_tank', ['label'] = 'Oxygen Tank', ['weight'] = 3000, ['type'] = 'item', ['image'] = 'oxygen_tank.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Pressurized oxygen tank for diving' }, ['diving_knife'] = { ['name'] = 'diving_knife', ['label'] = 'Diving Knife', ['weight'] = 200, ['type'] = 'item', ['image'] = 'diving_knife.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Sharp knife for underwater use' }, ['underwater_camera'] = { ['name'] = 'underwater_camera', ['label'] = 'Underwater Camera', ['weight'] = 800, ['type'] = 'item', ['image'] = 'underwater_camera.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Waterproof camera for marine photography' }, -- Treasures and Collectibles ['pearl'] = { ['name'] = 'pearl', ['label'] = 'Pearl', ['weight'] = 10, ['type'] = 'item', ['image'] = 'pearl.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = false, ['combinable'] = nil, ['description'] = 'Valuable pearl from oyster' }, ['coral'] = { ['name'] = 'coral', ['label'] = 'Coral Sample', ['weight'] = 100, ['type'] = 'item', ['image'] = 'coral.png', ['unique'] = false, ['useable'] = false, ['shouldClose'] = false, ['combinable'] = nil, ['description'] = 'Beautiful coral specimen' }, ['treasure_chest'] = { ['name'] = 'treasure_chest', ['label'] = 'Treasure Chest', ['weight'] = 2000, ['type'] = 'item', ['image'] = 'treasure_chest.png', ['unique'] = true, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Ancient treasure chest from shipwreck' }
  1. Add to server.cfg
ensure qb-diving

Configuration

Basic Configuration

Config = {} -- General Settings Config.UseTarget = true Config.OxygenDepletionRate = 1 -- Oxygen per second underwater Config.MaxDivingDepth = 100 -- meters Config.TreasureRespawnTime = 3600000 -- 1 hour in milliseconds -- Diving Equipment Rental Config.RentalLocations = { ["del_perro"] = { label = "Del Perro Diving Center", coords = vector3(-1686.72, -1072.69, 13.15), blip = { sprite = 404, color = 3, scale = 0.8 }, rental_prices = { scuba_gear = 500, oxygen_tank = 100, diving_knife = 50, underwater_camera = 200 } }, ["paleto_bay"] = { label = "Paleto Bay Marina", coords = vector3(-1604.97, 5252.35, 2.07), rental_prices = { scuba_gear = 450, oxygen_tank = 90 } } } -- Diving Spots Configuration Config.DivingSpots = { ["humane_labs"] = { label = "Humane Labs Wreck", coords = vector3(3380.85, 5172.49, -10.5), depth = 25, difficulty = "intermediate", required_certification = "Advanced Open Water", treasures = { ["tech_salvage"] = {weight = 0.3, value = 800}, ["rare_metal"] = {weight = 0.1, value = 1500}, ["documents"] = {weight = 0.05, value = 2000} }, marine_life = {"fish", "shark", "ray"} }, ["aircraft_carrier"] = { label = "Sunken Aircraft Carrier", coords = vector3(3013.95, -4795.32, -15.2), depth = 35, difficulty = "advanced", required_certification = "Deep Water", treasures = { ["military_equipment"] = {weight = 0.2, value = 1200}, ["gold_bars"] = {weight = 0.08, value = 2500}, ["ancient_artifact"] = {weight = 0.02, value = 5000} }, hazards = {"strong_current", "sharp_metal"} }, ["cayo_perico_reef"] = { label = "Cayo Perico Coral Reef", coords = vector3(4840.25, -5174.32, -8.0), depth = 15, difficulty = "beginner", required_certification = "Open Water", treasures = { ["pearl"] = {weight = 0.4, value = 300}, ["coral"] = {weight = 0.6, value = 150}, ["seashell"] = {weight = 0.8, value = 50} }, marine_life = {"tropical_fish", "turtle", "dolphin"} } } -- Certification Levels Config.CertificationLevels = { ["Open Water"] = { max_depth = 18, cost = 2500, duration = 30, -- minutes training unlocks = {"basic_diving", "shallow_treasures"} }, ["Advanced Open Water"] = { max_depth = 30, cost = 4000, prerequisites = {"Open Water"}, duration = 45, unlocks = {"deep_diving", "wreck_exploration"} }, ["Deep Water"] = { max_depth = 50, cost = 6500, prerequisites = {"Advanced Open Water"}, duration = 60, unlocks = {"extreme_depths", "rare_treasures"} }, ["Technical Diver"] = { max_depth = 100, cost = 10000, prerequisites = {"Deep Water"}, duration = 90, unlocks = {"unlimited_depth", "treasure_hunting_master"} } }

Treasure System

-- Treasure Types and Spawn Rates Config.TreasureTypes = { ["common"] = { items = {"pearl", "coral", "seashell"}, spawn_rate = 0.7, value_range = {50, 300} }, ["uncommon"] = { items = {"rare_pearl", "gold_coin", "silver_ingot"}, spawn_rate = 0.2, value_range = {300, 800} }, ["rare"] = { items = {"treasure_chest", "ancient_artifact", "precious_stone"}, spawn_rate = 0.08, value_range = {800, 2500} }, ["legendary"] = { items = {"legendary_treasure", "pirate_gold", "royal_crown"}, spawn_rate = 0.02, value_range = {2500, 10000} } } -- Equipment Durability Config.EquipmentDurability = { ["scuba_gear"] = { max_uses = 20, repair_cost = 250 }, ["oxygen_tank"] = { capacity = 1800, -- seconds of oxygen refill_cost = 50 }, ["diving_knife"] = { max_uses = 50, repair_cost = 25 } }

API Reference

Client Exports

StartDiving

Begin diving with equipment check.

-- Start diving at current location exports['qb-diving']:StartDiving() -- Start diving with specific gear exports['qb-diving']:StartDiving("advanced") -- Parameters: -- gear_type (optional): "basic", "advanced", "professional"

GetOxygenLevel

Get current oxygen level.

local oxygenPercent = exports['qb-diving']:GetOxygenLevel() -- Returns: number (0-100) representing oxygen percentage

CollectTreasure

Collect treasure at current location.

local success = exports['qb-diving']:CollectTreasure(treasureId) -- Parameters: -- treasureId: Unique identifier for treasure -- Returns: boolean success status

GetDivingDepth

Get current diving depth.

local depth = exports['qb-diving']:GetDivingDepth() -- Returns: number representing depth in meters

Server Exports

CreateDivingSpot

Create a new diving location.

local spotId = exports['qb-diving']:CreateDivingSpot(data) -- Parameters: -- data: { -- name = string, -- coords = vector3, -- depth = number, -- difficulty = string, -- treasures = table -- }

GiveCertification

Award diving certification to player.

local success = exports['qb-diving']:GiveCertification(source, level, instructor) -- Parameters: -- source: Player server ID -- level: Certification level -- instructor: Instructor name/ID

SpawnTreasure

Spawn treasure at location.

local treasureId = exports['qb-diving']:SpawnTreasure(coords, treasureType, value) -- Parameters: -- coords: Vector3 coordinates -- treasureType: Type of treasure -- value: Treasure value

Events

Client Events

-- Diving started RegisterNetEvent('qb-diving:client:divingStarted', function(depth, oxygenTime) -- Handle diving start end) -- Treasure found RegisterNetEvent('qb-diving:client:treasureFound', function(treasureData) -- Handle treasure discovery end) -- Oxygen warning RegisterNetEvent('qb-diving:client:oxygenWarning', function(remainingTime) -- Handle low oxygen warning end) -- Certification earned RegisterNetEvent('qb-diving:client:certificationEarned', function(level) -- Handle new certification end)

Server Events

-- Treasure collected RegisterNetEvent('qb-diving:server:treasureCollected', function(treasureId, spotId) -- Handle treasure collection end) -- Diving log entry RegisterNetEvent('qb-diving:server:logDive', function(logData) -- Handle dive logging end) -- Equipment rented RegisterNetEvent('qb-diving:server:rentEquipment', function(equipment, location) -- Handle equipment rental end)

Usage Examples

Diving System Implementation

-- Client-side diving mechanics local isDiving = false local oxygenLevel = 100 local currentDepth = 0 local divingStartTime = 0 RegisterNetEvent('qb-diving:client:startDiving', function() local ped = PlayerPedId() local coords = GetEntityCoords(ped) -- Check if near water if not IsEntityInWater(ped) then QBCore.Functions.Notify("You need to be in water to start diving", "error") return end -- Check for diving equipment QBCore.Functions.TriggerCallback('qb-diving:server:hasEquipment', function(hasEquipment, oxygenTime) if hasEquipment then isDiving = true oxygenLevel = 100 divingStartTime = GetGameTimer() -- Set underwater effects SetPedMaxTimeUnderwater(ped, oxygenTime) QBCore.Functions.Notify("Diving started! Oxygen: " .. oxygenTime .. " seconds", "success") -- Start oxygen monitoring CreateThread(function() while isDiving do Wait(1000) if IsEntityInWater(ped) then currentDepth = GetEntityHeightAboveGround(ped) * -1 oxygenLevel = oxygenLevel - Config.OxygenDepletionRate -- Depth pressure effects if currentDepth > 30 then -- Nitrogen narcosis simulation SetPedMotionBlur(ped, true) end -- Oxygen warnings if oxygenLevel <= 20 and oxygenLevel > 0 then QBCore.Functions.Notify("Low oxygen! Surface soon!", "warning") elseif oxygenLevel <= 0 then -- Emergency surface isDiving = false\n TriggerEvent('qb-diving:client:emergencySurface')\n end\n else\n -- Player surfaced\n isDiving = false\n SetPedMotionBlur(ped, false)\n QBCore.Functions.Notify(\"Diving ended\", \"info\")\n end\n end\n end)\n else\n QBCore.Functions.Notify(\"You need diving equipment to dive\", \"error\")\n end\n end)\nend)\n```\n\n### Treasure Hunting System\n\n```lua\n-- Treasure detection and collection\nRegisterNetEvent('qb-diving:client:searchForTreasure', function()\n if not isDiving then\n QBCore.Functions.Notify(\"You must be diving to search for treasure\", \"error\")\n return\n end\n \n local ped = PlayerPedId()\n local coords = GetEntityCoords(ped)\n \n QBCore.Functions.Progressbar(\"searching_treasure\", \"Searching for treasure...\", 10000, false, true, {\n disableMovement = true,\n disableCarMovement = true,\n disableMouse = false,\n disableCombat = true,\n }, {\n animDict = \"amb@world_human_gardener_plant@male@base\",\n anim = \"base\",\n }, {}, {}, function()\n -- Search completed\n TriggerServerEvent('qb-diving:server:searchTreasure', coords, currentDepth)\n end, function()\n QBCore.Functions.Notify(\"Search cancelled\", \"error\")\n end)\nend)\n\n-- Server-side treasure spawning and detection\nRegisterNetEvent('qb-diving:server:searchTreasure', function(coords, depth)\n local src = source\n local player = QBCore.Functions.GetPlayer(src)\n \n if not player then return end\n \n -- Find nearest diving spot\n local nearestSpot = nil\n local nearestDistance = math.huge\n \n for spotId, spotData in pairs(Config.DivingSpots) do\n local distance = #(coords - spotData.coords)\n if distance < nearestDistance and distance <= 50.0 then\n nearestSpot = spotData\n nearestDistance = distance\n end\n end\n \n if nearestSpot then\n -- Check certification requirements\n local hasValidCert = CheckCertification(player.PlayerData.citizenid, nearestSpot.required_certification)\n \n if not hasValidCert then\n TriggerClientEvent('QBCore:Notify', src, 'Insufficient certification for this depth', 'error')\n return\n end\n \n -- Roll for treasure based on spot configuration\n local treasureChance = math.random()\n local foundTreasure = nil\n \n for treasureType, treasureData in pairs(nearestSpot.treasures) do\n if treasureChance <= treasureData.weight then\n foundTreasure = {\n type = treasureType,\n value = treasureData.value,\n coords = coords\n }\n break\n end\n end\n \n if foundTreasure then\n -- Give treasure to player\n local treasureItem = GetTreasureItem(foundTreasure.type)\n player.Functions.AddItem(treasureItem, 1, false, {\n value = foundTreasure.value,\n found_at = nearestSpot.label,\n depth = depth\n })\n \n TriggerClientEvent('QBCore:Notify', src, 'You found: ' .. foundTreasure.type .. '!', 'success')\n \n -- Log the find\n MySQL.insert('INSERT INTO diving_treasures (spot_id, treasure_type, coords, value, collected, collected_by, collected_at) VALUES (?, ?, ?, ?, ?, ?, NOW())', {\n GetSpotId(nearestSpot),\n foundTreasure.type,\n json.encode(coords),\n foundTreasure.value,\n 1,\n player.PlayerData.citizenid\n })\n else\n TriggerClientEvent('QBCore:Notify', src, 'No treasure found in this area', 'error')\n end\n else\n TriggerClientEvent('QBCore:Notify', src, 'No diving spots nearby', 'error')\n end\nend)\n```\n\n### Certification Training System\n\n```lua\n-- Diving certification training\nRegisterNetEvent('qb-diving:client:startTraining', function(certLevel)\n local player = QBCore.Functions.GetPlayerData()\n local certConfig = Config.CertificationLevels[certLevel]\n \n if not certConfig then\n QBCore.Functions.Notify(\"Invalid certification level\", \"error\")\n return\n end\n \n -- Check prerequisites\n QBCore.Functions.TriggerCallback('qb-diving:server:checkPrerequisites', function(hasPrereqs, canAfford)\n if not hasPrereqs then\n QBCore.Functions.Notify(\"You don't meet the prerequisites\", \"error\")\n return\n end\n \n if not canAfford then\n QBCore.Functions.Notify(\"Insufficient funds: $\" .. certConfig.cost, \"error\")\n return\n end\n \n -- Start training sequence\n QBCore.Functions.Notify(\"Starting \" .. certLevel .. \" certification training\", \"info\")\n \n -- Theory training\n QBCore.Functions.Progressbar(\"diving_theory\", \"Studying diving theory...\", certConfig.duration * 1000 / 3, false, true, {\n disableMovement = true,\n disableCarMovement = true,\n disableMouse = false,\n disableCombat = true,\n }, {\n animDict = \"amb@world_human_clipboard@male@base\",\n anim = \"base\",\n }, {}, {}, function()\n -- Practical training\n QBCore.Functions.Progressbar(\"diving_practical\", \"Practical diving training...\", certConfig.duration * 1000 / 3, false, true, {\n disableMovement = true,\n disableCarMovement = true,\n disableMouse = false,\n disableCombat = true,\n }, {\n animDict = \"amb@world_human_muscle_free_weights@male@barbell@base\",\n anim = \"base\",\n }, {}, {}, function()\n -- Final test\n QBCore.Functions.Progressbar(\"diving_test\", \"Taking certification test...\", certConfig.duration * 1000 / 3, false, true, {\n disableMovement = true,\n disableCarMovement = true,\n disableMouse = false,\n disableCombat = true,\n }, {\n animDict = \"amb@world_human_clipboard@male@base\",\n anim = \"base\",\n }, {}, {}, function()\n -- Award certification\n TriggerServerEvent('qb-diving:server:awardCertification', certLevel)\n end)\n end)\n end)\n \n end, certLevel)\nend)\n```\n\n## Troubleshooting\n\n### Common Issues\n\n#### Oxygen System Not Working\n```lua\n-- Check diving state\nif not isDiving then\n print(\"Player not in diving state\")\n return\nend\n\n-- Verify oxygen calculation\nlocal timeUnderwater = (GetGameTimer() - divingStartTime) / 1000\nlocal expectedOxygen = 100 - (timeUnderwater * Config.OxygenDepletionRate)\nprint(\"Expected oxygen:\", expectedOxygen, \"Actual:\", oxygenLevel)\n```\n\n#### Treasure Spawning Issues\n- Verify diving spot coordinates are underwater\n- Check treasure spawn rates and weights\n- Ensure database tables are properly created\n\n#### Certification Problems\n```lua\n-- Debug certification checking\nRegisterCommand('checkdivecert', function()\n QBCore.Functions.TriggerCallback('qb-diving:server:getCertification', function(cert)\n if cert then\n print(\"Certification:\", cert.certification_level, \"Max Depth:\", cert.max_depth)\n else\n print(\"No diving certification found\")\n end\n end)\nend)\n```\n\n### Debug Commands\n\n```lua\n-- Give diving equipment\n/givedivegear [player_id]\n\n-- Award certification\n/givecert [player_id] [level]\n\n-- Spawn treasure\n/spawntreasure [type] [value]\n\n-- Check oxygen level\n/checkoxygen\n```\n\n### Performance Optimization\n\n1. **Treasure Cleanup**: Remove old treasure spawns regularly\n2. **Oxygen Monitoring**: Optimize oxygen calculation frequency\n3. **Water Detection**: Cache water detection results\n\n<Callout type=\"warning\">\n Test underwater mechanics thoroughly as they directly affect player safety and experience.\n</Callout>
Last updated on