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