docsresourcesQb Diving

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>