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 false, -- collision false, -- isPed 2, -- rotationOrder true) -- fixedRot 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)