Files
d4rk_divegear/client/main.lua
T
D4rkst3randClaude Opus 5 e0f758161d
Lint / Lint Resource (push) Has been cancelled
docs: Attach-Parameter gegen die Native-Doku korrigiert
Der 15. Parameter heisst syncRot, nicht fixedRot. useSoftPinning=false ist
laut Doku korrekt fuer eine Attachment, die sich nicht loesen soll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 23:27:59 +02:00

249 lines
7.2 KiB
Lua

local config = require 'config.client'
local currentGear = {
mask = 0,
tank = 0,
enabled = false
}
local oxygenLevel = 0
local MASK_MODEL = `p_d_scuba_mask_s`
local TANK_MODEL = `p_s_scuba_tank_s`
local MASK_BONE = 12844
local TANK_BONE = 24818
local function enableScuba()
SetEnableScuba(cache.ped, true)
SetPedMaxTimeUnderwater(cache.ped, 2000.00)
end
--- @param outOfAir boolean? true wenn die Flasche leer ist statt die Ausrüstung abgelegt wurde
local function disableScuba(outOfAir)
SetEnableScuba(cache.ped, false)
SetPedMaxTimeUnderwater(cache.ped, outOfAir and config.maxTimeUnderwaterOutOfAir or config.maxTimeUnderwater)
end
local function deleteGear()
if currentGear.mask ~= 0 then
DetachEntity(currentGear.mask, false, true)
DeleteEntity(currentGear.mask)
currentGear.mask = 0
end
if currentGear.tank ~= 0 then
DetachEntity(currentGear.tank, false, true)
DeleteEntity(currentGear.tank)
currentGear.tank = 0
end
end
--- Hängt ein Prop hart an einen Ped-Bone.
--- useSoftPinning bleibt bewusst false: mit true darf die Physik-Engine die
--- Attachment bei Ragdoll und harten Anim-Übergängen lösen, dann fliegt das Prop weg.
local function attachProp(model, bone, offset, rotation)
lib.requestModel(model)
local prop = CreateObject(model, 1.0, 1.0, 1.0, true, true, false)
SetEntityCollision(prop, false, false) --- Disable collision so it doesn't block bullets
AttachEntityToEntity(prop, cache.ped, GetPedBoneIndex(cache.ped, bone),
offset.x, offset.y, offset.z,
rotation.x, rotation.y, rotation.z,
false, -- p9
false, -- useSoftPinning ("if set to false attached entity will not detach when fixed")
false, -- collision
false, -- isPed
2, -- rotationOrder
true) -- syncRot
SetModelAsNoLongerNeeded(model)
return prop
end
local function attachGear()
currentGear.tank = attachProp(TANK_MODEL, TANK_BONE, vec3(-0.25, -0.25, 0.0), vec3(180.0, 90.0, 0.0))
currentGear.mask = attachProp(MASK_MODEL, MASK_BONE, vec3(0.0, 0.0, 0.0), vec3(180.0, 90.0, 0.0))
end
local function isPropIntact(prop)
return prop ~= 0 and DoesEntityExist(prop) and GetEntityAttachedTo(prop) == cache.ped
end
local function isGearIntact()
return isPropIntact(currentGear.mask) and isPropIntact(currentGear.tank)
end
--- Gemeinsamer Teardown für Ablegen, leere Flasche und Tod.
--- @param outOfAir boolean?
local function removeGear(outOfAir)
currentGear.enabled = false
deleteGear()
disableScuba(outOfAir)
-- Stop breathing suit audio
end
lib.callback.register('qbx_divegear:client:fillTank', function()
if IsPedSwimmingUnderWater(cache.ped) then
exports.qbx_core:Notify(locale('error.underwater', {oxygenlevel = oxygenLevel}), 'error')
return false
end
if lib.progressBar({
duration = config.refillTankTimeMs,
label = locale('info.filling_air'),
useWhileDead = false,
canCancel = true,
anim = {
dict = 'clothingshirt',
clip = 'try_shirt_positive_d',
blendIn = 8.0
}
}) then
oxygenLevel = config.startingOxygenLevel
exports.qbx_core:Notify(locale('success.tube_filled'), 'success')
if currentGear.enabled then
enableScuba()
end
return true
end
end)
local function takeOffSuit()
if lib.progressBar({
duration = config.takeOffSuitTimeMs,
label = locale('info.pullout_suit'),
useWhileDead = false,
canCancel = true,
anim = {
dict = 'clothingshirt',
clip = 'try_shirt_positive_d',
blendIn = 8.0
}
}) then
removeGear()
exports.qbx_core:Notify(locale('success.took_out'))
end
end
local function startOxygenLevelDrawTextThread()
CreateThread(function()
while currentGear.enabled do
if IsPedSwimmingUnderWater(cache.ped) then
qbx.drawText2d({
text = oxygenLevel..'⏱',
coords = vec2(1.0, 1.42),
scale = 0.45
})
end
Wait(0)
end
end)
end
local function startOxygenLevelDecrementerThread()
CreateThread(function()
while currentGear.enabled do
if IsPedSwimmingUnderWater(cache.ped) and oxygenLevel > 0 then
oxygenLevel -= config.decayRate
if oxygenLevel % 10 == 0 and oxygenLevel ~= config.startingOxygenLevel then
-- Initiate breathing suit audio
end
if oxygenLevel == 0 then
disableScuba(true)
-- Stop breathing suit audio
end
end
Wait(1000)
end
end)
end
--- Hängt die Ausrüstung nach, wenn sie verloren gegangen ist, und stellt die
--- Scuba-Flags wieder her. Nötig weil Fremd-Scripts (Emotes, Handcuffs, Death-Handler)
--- attached Props aufräumen und SetEnableScuba/SetPedMaxTimeUnderwater bei Respawn
--- und Fahrzeugausstieg vom Spiel zurückgesetzt werden.
local function startGearWatchdogThread()
CreateThread(function()
while currentGear.enabled do
if config.removeGearOnDeath and IsPedDeadOrDying(cache.ped, true) then
removeGear()
break
end
if not isGearIntact() then
deleteGear()
attachGear()
end
if oxygenLevel > 0 then
enableScuba()
end
Wait(config.gearWatchdogIntervalMs)
end
end)
end
local function putOnSuit()
if oxygenLevel <= 0 then
exports.qbx_core:Notify(locale('error.need_otube'), 'error')
return
end
if IsPedSwimming(cache.ped) or cache.vehicle then
exports.qbx_core:Notify(locale('error.not_standing_up'), 'error')
return
end
if lib.progressBar({
duration = config.putOnSuitTimeMs,
label = locale('info.put_suit'),
useWhileDead = false,
canCancel = true,
anim = {
dict = 'clothingshirt',
clip = 'try_shirt_positive_d',
blendIn = 8.0
}
}) then
deleteGear()
attachGear()
enableScuba()
currentGear.enabled = true
-- Initiate breathing suit audio
startOxygenLevelDecrementerThread()
startOxygenLevelDrawTextThread()
startGearWatchdogThread()
end
end
RegisterNetEvent('qbx_divegear:client:useGear', function()
if currentGear.enabled then
takeOffSuit()
else
putOnSuit()
end
end)
--- Beim Model-Wechsel (Kleiderladen, Skin-Change) ist der Ped ein anderer -
--- alle attached Props sind weg und die Scuba-Flags gelten für den alten Ped.
lib.onCache('ped', function()
if not currentGear.enabled then return end
deleteGear()
attachGear()
if oxygenLevel > 0 then
enableScuba()
end
end)
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
deleteGear()
SetEnableScuba(cache.ped, false)
SetPedMaxTimeUnderwater(cache.ped, config.maxTimeUnderwater)
end)