Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
056215d0ae | ||
|
|
db7cef893d | ||
|
|
c2c5483c87 | ||
|
|
c60174a3b3 | ||
|
|
d4870444df | ||
|
|
96c57d56a0 | ||
|
|
bfe1d8ee2d | ||
|
|
daefe46cb8 | ||
|
|
accee02a05 | ||
|
|
e64a1bd3d0 | ||
|
|
930ff6381f |
No files matched your search
@@ -0,0 +1,50 @@
|
|||||||
|
--[[
|
||||||
|
d4rk_divegear - die Tauchkonsole beim HUD anmelden (28.09.2026).
|
||||||
|
|
||||||
|
d4rk_hud verwaltet Lage, Groesse und Sichtbarkeit fremder Bauteile im
|
||||||
|
Editor (/hudbearbeiten) und speichert sie je Charakter; die Konsole
|
||||||
|
zeichnet sich weiter selbst (d4rk_hud/client/anmeldung.lua, Vertrag dort).
|
||||||
|
Laeuft d4rk_hud nicht, bleibt die Konsole, wo nui/style.css sie hinsetzt.
|
||||||
|
|
||||||
|
breite x hoehe: die Konsole bei 1080p, GEMESSEN am 28.09.2026 mit
|
||||||
|
d4rk-rp-planung/scripts/nui-pruefen/divegear-konsole.mjs (offsetWidth/
|
||||||
|
offsetHeight von #hud, --hud-scale 1.000): 366 x 165. Das Beispiel in
|
||||||
|
d4rk_hud/client/anmeldung.lua nennt 364 x 162 - das war nicht gemessen.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local KEY = 'tauchkonsole'
|
||||||
|
local BREITE, HOEHE = 366, 165
|
||||||
|
|
||||||
|
local function anmelden()
|
||||||
|
if GetResourceState('d4rk_hud') ~= 'started' then return end
|
||||||
|
|
||||||
|
pcall(function()
|
||||||
|
exports.d4rk_hud:BauteilAnmelden(KEY, {
|
||||||
|
name = locale('hud.konsole'),
|
||||||
|
breite = BREITE,
|
||||||
|
hoehe = HOEHE,
|
||||||
|
vorgabe = { anker = 'unten-mitte', x = 0, y = 76, skala = 1.0, an = true },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
AddEventHandler('d4rk_hud:bauteilLage', function(key, lage)
|
||||||
|
if key ~= KEY then return end
|
||||||
|
SendNUIMessage({ action = 'lage', lage = lage })
|
||||||
|
end)
|
||||||
|
|
||||||
|
AddEventHandler('d4rk_hud:bauteileAnfragen', anmelden)
|
||||||
|
|
||||||
|
AddEventHandler('onClientResourceStart', function(resource)
|
||||||
|
if resource == 'd4rk_hud' then SetTimeout(1000, function() anmelden() end) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
AddEventHandler('onClientResourceStop', function(resource)
|
||||||
|
-- Ohne HUD wieder die eigene Position.
|
||||||
|
if resource == 'd4rk_hud' then SendNUIMessage({ action = 'lage', lage = false }) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(1000)
|
||||||
|
anmelden()
|
||||||
|
end)
|
||||||
+171
-169
@@ -1,12 +1,17 @@
|
|||||||
local config = require 'config.client'
|
local config = require 'config.client'
|
||||||
local shared = require 'config.shared'
|
local shared = require 'config.shared'
|
||||||
|
|
||||||
|
--- Tauchgeraet bereit: Flasche mit Patrone UND Maske angezogen (28.09.2026 -
|
||||||
|
--- vorher hingen hier zwei Props, mask und tank).
|
||||||
local currentGear = {
|
local currentGear = {
|
||||||
mask = 0,
|
|
||||||
tank = 0,
|
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- Laufnummer der Faeden (Verbrauch, HUD, Waechter). Wird beim Tauschen einer
|
||||||
|
--- Patrone sofort neu angelegt - ohne Nummer liefen die alten Faeden nach
|
||||||
|
--- ihrem Wait einfach weiter, und es gaebe sie doppelt.
|
||||||
|
local gearLauf = 0
|
||||||
|
|
||||||
--- Lampe an? Der eigentliche Lichtkegel wird in client/light.lua gezeichnet,
|
--- Lampe an? Der eigentliche Lichtkegel wird in client/light.lua gezeichnet,
|
||||||
--- gesteuert über den replizierten Statebag.
|
--- gesteuert über den replizierten Statebag.
|
||||||
local lampOn = false
|
local lampOn = false
|
||||||
@@ -32,10 +37,6 @@ for i = 1, #config.warnAtSeconds do
|
|||||||
if dangerThreshold == 0 or threshold < dangerThreshold then dangerThreshold = threshold end
|
if dangerThreshold == 0 or threshold < dangerThreshold then dangerThreshold = threshold end
|
||||||
end
|
end
|
||||||
|
|
||||||
local MASK_MODEL = `p_d_scuba_mask_s`
|
|
||||||
local TANK_MODEL = `p_s_scuba_tank_s`
|
|
||||||
local MASK_BONE = 12844
|
|
||||||
local TANK_BONE = 24818
|
|
||||||
|
|
||||||
--- @param seconds number
|
--- @param seconds number
|
||||||
--- @return string mm:ss
|
--- @return string mm:ss
|
||||||
@@ -127,50 +128,6 @@ local function disableScuba(outOfAir, ped)
|
|||||||
SetPedMaxTimeUnderwater(ped, outOfAir and config.maxTimeUnderwaterOutOfAir or config.maxTimeUnderwater)
|
SetPedMaxTimeUnderwater(ped, outOfAir and config.maxTimeUnderwaterOutOfAir or config.maxTimeUnderwater)
|
||||||
end
|
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(ped, 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, ped, GetPedBoneIndex(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
|
|
||||||
|
|
||||||
--- @param ped number? default cache.ped
|
|
||||||
local function attachGear(ped)
|
|
||||||
ped = ped or cache.ped
|
|
||||||
currentGear.tank = attachProp(ped, TANK_MODEL, TANK_BONE, vec3(-0.25, -0.25, 0.0), vec3(180.0, 90.0, 0.0))
|
|
||||||
currentGear.mask = attachProp(ped, MASK_MODEL, MASK_BONE, vec3(0.0, 0.0, 0.0), vec3(180.0, 90.0, 0.0))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param state boolean
|
--- @param state boolean
|
||||||
--- @param notifyIfUnavailable boolean? Rückmeldung, wenn das Outfit keine Lampe hat
|
--- @param notifyIfUnavailable boolean? Rückmeldung, wenn das Outfit keine Lampe hat
|
||||||
local function setLamp(state, notifyIfUnavailable)
|
local function setLamp(state, notifyIfUnavailable)
|
||||||
@@ -187,18 +144,10 @@ local function setLamp(state, notifyIfUnavailable)
|
|||||||
-- aber falls sie bei einem Ped-Model doch nicht anschlägt, ist eine Meldung
|
-- aber falls sie bei einem Ped-Model doch nicht anschlägt, ist eine Meldung
|
||||||
-- besser als ein scheinbar kaputter Keybind.
|
-- besser als ein scheinbar kaputter Keybind.
|
||||||
if state and notifyIfUnavailable and not IsScubaGearLightEnabled(cache.ped) then
|
if state and notifyIfUnavailable and not IsScubaGearLightEnabled(cache.ped) then
|
||||||
exports.qbx_core:Notify(locale('error.no_lamp_on_outfit'), 'error')
|
lib.notify({ description = locale('error.no_lamp_on_outfit'), type = 'error' })
|
||||||
end
|
end
|
||||||
end
|
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
|
|
||||||
|
|
||||||
--- Schreibt die Restluft in die Metadata der Flasche. Der Server clampt den Wert
|
--- Schreibt die Restluft in die Metadata der Flasche. Der Server clampt den Wert
|
||||||
--- und gibt den akzeptierten Stand zurück, damit Client und Inventar nicht auseinanderlaufen.
|
--- und gibt den akzeptierten Stand zurück, damit Client und Inventar nicht auseinanderlaufen.
|
||||||
--- @param immediate boolean? true = Fire-and-Forget (Tod, Resource-Stop)
|
--- @param immediate boolean? true = Fire-and-Forget (Tod, Resource-Stop)
|
||||||
@@ -220,8 +169,10 @@ end
|
|||||||
--- Gemeinsamer Teardown für Ablegen, leere Flasche und Tod.
|
--- Gemeinsamer Teardown für Ablegen, leere Flasche und Tod.
|
||||||
--- @param outOfAir boolean?
|
--- @param outOfAir boolean?
|
||||||
--- @param immediateSync boolean?
|
--- @param immediateSync boolean?
|
||||||
local function removeGear(outOfAir, immediateSync)
|
--- @param ohneSync boolean? nach einem Patronentausch: der Client-Stand gehoert
|
||||||
syncTank(immediateSync)
|
--- zur ALTEN Patrone und darf die neue nicht ueberschreiben
|
||||||
|
local function removeGear(outOfAir, immediateSync, ohneSync)
|
||||||
|
if not ohneSync then syncTank(immediateSync) end
|
||||||
|
|
||||||
currentGear.enabled = false
|
currentGear.enabled = false
|
||||||
setLamp(false)
|
setLamp(false)
|
||||||
@@ -232,7 +183,6 @@ local function removeGear(outOfAir, immediateSync)
|
|||||||
narcosisDamageBuffer = 0.0
|
narcosisDamageBuffer = 0.0
|
||||||
engineDepthWarned = false
|
engineDepthWarned = false
|
||||||
|
|
||||||
deleteGear()
|
|
||||||
disableScuba(outOfAir)
|
disableScuba(outOfAir)
|
||||||
-- Stop breathing suit audio
|
-- Stop breathing suit audio
|
||||||
end
|
end
|
||||||
@@ -272,7 +222,7 @@ end
|
|||||||
|
|
||||||
lib.callback.register('d4rk_divegear:client:fillTank', function(tanks)
|
lib.callback.register('d4rk_divegear:client:fillTank', function(tanks)
|
||||||
if IsPedSwimmingUnderWater(cache.ped) then
|
if IsPedSwimmingUnderWater(cache.ped) then
|
||||||
exports.qbx_core:Notify(locale('error.underwater'), 'error')
|
lib.notify({ description = locale('error.underwater'), type = 'error' })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -281,11 +231,11 @@ lib.callback.register('d4rk_divegear:client:fillTank', function(tanks)
|
|||||||
if not tank then return end
|
if not tank then return end
|
||||||
|
|
||||||
if tank.oxygen >= tank.capacity then
|
if tank.oxygen >= tank.capacity then
|
||||||
exports.qbx_core:Notify(locale('error.tank_already_full'), 'error')
|
lib.notify({ description = locale('error.tank_already_full'), type = 'error' })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not lib.progressBar({
|
if not exports.d4rk_lib:progress({
|
||||||
duration = config.refillTankTimeMs,
|
duration = config.refillTankTimeMs,
|
||||||
label = locale('info.filling_air'),
|
label = locale('info.filling_air'),
|
||||||
useWhileDead = false,
|
useWhileDead = false,
|
||||||
@@ -314,23 +264,6 @@ RegisterNetEvent('d4rk_divegear:client:tankRefilled', function(slot, capacity)
|
|||||||
enableScuba()
|
enableScuba()
|
||||||
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
|
|
||||||
|
|
||||||
--- Manometer-Anzeige. Bei konstantem Volumen ist der Druck proportional zur
|
--- Manometer-Anzeige. Bei konstantem Volumen ist der Druck proportional zur
|
||||||
--- verbliebenen Gasmenge, deshalb reicht der lineare Anteil an der Füllung.
|
--- verbliebenen Gasmenge, deshalb reicht der lineare Anteil an der Füllung.
|
||||||
--- @return number bar
|
--- @return number bar
|
||||||
@@ -394,6 +327,8 @@ end
|
|||||||
|
|
||||||
exports('setHudVisible', setHudVisible)
|
exports('setHudVisible', setHudVisible)
|
||||||
exports('isHudVisible', function() return not hudHidden end)
|
exports('isHudVisible', function() return not hudHidden end)
|
||||||
|
-- Fuer /tauchprobe beobachten (client/probe.lua): ist das Tauchgeraet aktiv?
|
||||||
|
exports('geraetAktiv', function() return currentGear.enabled == true end)
|
||||||
|
|
||||||
--- Pflegt die geglättete Tiefe und schiebt den Zustand ins NUI.
|
--- Pflegt die geglättete Tiefe und schiebt den Zustand ins NUI.
|
||||||
--- Läuft auch über Wasser weiter, weil getDecayFactor die Tiefe braucht - nur die
|
--- Läuft auch über Wasser weiter, weil getDecayFactor die Tiefe braucht - nur die
|
||||||
@@ -411,7 +346,9 @@ local function startHudThread()
|
|||||||
lamp = locale('hud.lamp')
|
lamp = locale('hud.lamp')
|
||||||
})
|
})
|
||||||
|
|
||||||
while currentGear.enabled do
|
local meinLauf = gearLauf
|
||||||
|
|
||||||
|
while currentGear.enabled and meinLauf == gearLauf do
|
||||||
currentDepth = currentDepth + (getDepth() - currentDepth) * 0.25
|
currentDepth = currentDepth + (getDepth() - currentDepth) * 0.25
|
||||||
|
|
||||||
local visible = config.hudEnabled
|
local visible = config.hudEnabled
|
||||||
@@ -432,6 +369,9 @@ local function startHudThread()
|
|||||||
Wait(config.hudUpdateIntervalMs)
|
Wait(config.hudUpdateIntervalMs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Abgeloest durch einen neuen Faden (Patronentausch): der zeigt weiter.
|
||||||
|
if meinLauf ~= gearLauf and currentGear.enabled then return end
|
||||||
|
|
||||||
currentDepth = 0.0
|
currentDepth = 0.0
|
||||||
sendHudState(false)
|
sendHudState(false)
|
||||||
end)
|
end)
|
||||||
@@ -451,7 +391,7 @@ local function updateNarcosis(elapsed)
|
|||||||
if currentDepth >= engineWarnAt then
|
if currentDepth >= engineWarnAt then
|
||||||
if not engineDepthWarned then
|
if not engineDepthWarned then
|
||||||
engineDepthWarned = true
|
engineDepthWarned = true
|
||||||
exports.qbx_core:Notify(locale('error.crush_depth', math.floor(config.narcosis.engineDepthLimit)), 'error')
|
lib.notify({ description = locale('error.crush_depth', math.floor(config.narcosis.engineDepthLimit)), type = 'error' })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
engineDepthWarned = false
|
engineDepthWarned = false
|
||||||
@@ -471,7 +411,7 @@ local function updateNarcosis(elapsed)
|
|||||||
if activeTank and over >= -config.narcosis.warnBeforeMeters then
|
if activeTank and over >= -config.narcosis.warnBeforeMeters then
|
||||||
if not narcosisWarned then
|
if not narcosisWarned then
|
||||||
narcosisWarned = true
|
narcosisWarned = true
|
||||||
exports.qbx_core:Notify(locale('info.depth_limit', math.floor(activeTank.maxDepth or 0)), 'warning')
|
lib.notify({ description = locale('info.depth_limit', math.floor(activeTank.maxDepth or 0)), type = 'warning' })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
narcosisWarned = false
|
narcosisWarned = false
|
||||||
@@ -502,8 +442,9 @@ local function startOxygenLevelDecrementerThread()
|
|||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
local lastTick = GetGameTimer()
|
local lastTick = GetGameTimer()
|
||||||
local sinceSync = 0.0
|
local sinceSync = 0.0
|
||||||
|
local meinLauf = gearLauf
|
||||||
|
|
||||||
while currentGear.enabled do
|
while currentGear.enabled and meinLauf == gearLauf do
|
||||||
Wait(250)
|
Wait(250)
|
||||||
|
|
||||||
local now = GetGameTimer()
|
local now = GetGameTimer()
|
||||||
@@ -522,13 +463,13 @@ local function startOxygenLevelDecrementerThread()
|
|||||||
|
|
||||||
if activeTank.oxygen <= threshold and not warned[threshold] then
|
if activeTank.oxygen <= threshold and not warned[threshold] then
|
||||||
warned[threshold] = true
|
warned[threshold] = true
|
||||||
exports.qbx_core:Notify(locale('info.oxygen_low', formatTime(activeTank.oxygen)), 'warning')
|
lib.notify({ description = locale('info.oxygen_low', formatTime(activeTank.oxygen)), type = 'warning' })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if activeTank.oxygen <= 0 then
|
if activeTank.oxygen <= 0 then
|
||||||
disableScuba(true)
|
disableScuba(true)
|
||||||
exports.qbx_core:Notify(locale('error.out_of_air'), 'error')
|
lib.notify({ description = locale('error.out_of_air'), type = 'error' })
|
||||||
syncTank()
|
syncTank()
|
||||||
sinceSync = 0.0
|
sinceSync = 0.0
|
||||||
-- Stop breathing suit audio
|
-- Stop breathing suit audio
|
||||||
@@ -554,17 +495,14 @@ end
|
|||||||
--- und Fahrzeugausstieg vom Spiel zurückgesetzt werden.
|
--- und Fahrzeugausstieg vom Spiel zurückgesetzt werden.
|
||||||
local function startGearWatchdogThread()
|
local function startGearWatchdogThread()
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
while currentGear.enabled do
|
local meinLauf = gearLauf
|
||||||
|
|
||||||
|
while currentGear.enabled and meinLauf == gearLauf do
|
||||||
if config.removeGearOnDeath and IsPedDeadOrDying(cache.ped, true) then
|
if config.removeGearOnDeath and IsPedDeadOrDying(cache.ped, true) then
|
||||||
removeGear(false, true)
|
removeGear(false, true)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
if not isGearIntact() then
|
|
||||||
deleteGear()
|
|
||||||
attachGear()
|
|
||||||
end
|
|
||||||
|
|
||||||
if activeTank and activeTank.oxygen > 0 then
|
if activeTank and activeTank.oxygen > 0 then
|
||||||
enableScuba()
|
enableScuba()
|
||||||
end
|
end
|
||||||
@@ -574,31 +512,103 @@ local function startGearWatchdogThread()
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function putOnSuit()
|
--[[
|
||||||
|
ANLEGEN - 28.09.2026. Kein Balken und keine Props mehr: angezogen wird im
|
||||||
|
Inventar (Flasche und Maske sind Kleidung). Hier wird nur das Tauchgeraet
|
||||||
|
scharf geschaltet, wenn der Server sagt, dass beides getragen wird und
|
||||||
|
Luft in der Patrone ist.
|
||||||
|
]]
|
||||||
|
--- @param tank table aus d4rk_divegear:server:getWorn
|
||||||
|
--- @return boolean
|
||||||
|
local function anlegen(tank)
|
||||||
|
if not tank or (tank.oxygen or 0) <= 0 then return false end
|
||||||
|
|
||||||
|
activeTank = tank
|
||||||
|
warned = {}
|
||||||
|
gearLauf = gearLauf + 1
|
||||||
|
|
||||||
|
enableScuba()
|
||||||
|
currentGear.enabled = true
|
||||||
|
startOxygenLevelDecrementerThread()
|
||||||
|
startHudThread()
|
||||||
|
startGearWatchdogThread()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
NACHSEHEN, WAS ER TRAEGT - bei jeder Inventaraenderung und nach jedem
|
||||||
|
Umziehen. Mehrere Anstoesse in kurzer Folge fuehren zu EINER Frage mehr,
|
||||||
|
nicht zu vielen parallelen.
|
||||||
|
]]
|
||||||
|
local pruefenLaeuft, pruefenNochmal = false, false
|
||||||
|
|
||||||
|
local function pruefen()
|
||||||
|
if pruefenLaeuft then
|
||||||
|
pruefenNochmal = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
pruefenLaeuft = true
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
repeat
|
||||||
|
pruefenNochmal = false
|
||||||
|
|
||||||
|
local erg = lib.callback.await('d4rk_divegear:server:getWorn', false)
|
||||||
|
|
||||||
|
if type(erg) == 'table' then
|
||||||
|
if erg.ok then
|
||||||
|
if not currentGear.enabled then
|
||||||
|
if anlegen(erg.tank) then
|
||||||
|
lib.notify({ description = locale('info.bereit', formatTime(erg.tank.oxygen)), type = 'info' })
|
||||||
|
end
|
||||||
|
elseif activeTank and activeTank.name ~= erg.tank.name then
|
||||||
|
-- Patrone getauscht: der Client-Stand gehoert zur alten.
|
||||||
|
removeGear(false, false, true)
|
||||||
|
anlegen(erg.tank)
|
||||||
|
end
|
||||||
|
elseif currentGear.enabled then
|
||||||
|
lib.notify({ description = locale('error.' .. (erg.grund or 'gear_gone')), type = 'error' })
|
||||||
|
removeGear(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until not pruefenNochmal
|
||||||
|
|
||||||
|
pruefenLaeuft = false
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
AddEventHandler('d4rk:kleidung:angezogen', pruefen)
|
||||||
|
AddEventHandler('ox_inventory:updateInventory', pruefen)
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(2000)
|
||||||
|
pruefen()
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
EINE PATRONE BENUTZEN = SIE EINSETZEN. Nur an Land, mit Balken - wie
|
||||||
|
vorher das Anlegen.
|
||||||
|
|
||||||
|
UEBER EINEN CLIENT-EXPORT AM ITEM, NICHT CreateUseableItem (28.09.2026).
|
||||||
|
d4rk_items setzt consume immer (daten.lua:224, `or 1`), und d4rk_inventory
|
||||||
|
erreicht den qbx-Rueckruf nur bei consume == nil (server.lua:623) - fuer
|
||||||
|
Items aus unserer Tabelle feuerte er also nie. Die Patronen tragen
|
||||||
|
client_export = 'd4rk_divegear.patroneBenutzen' und consume = 0 (angelegt
|
||||||
|
mit /tauchartikel, server/artikel.lua); das Inventar ruft
|
||||||
|
exports.d4rk_divegear:patroneBenutzen(daten, slot) mit slot = { name,
|
||||||
|
slot, metadata } - dasselbe wie d4rk_medical `benutzen`.
|
||||||
|
]]
|
||||||
|
local function einsetzen(slot)
|
||||||
if IsPedSwimming(cache.ped) or cache.vehicle then
|
if IsPedSwimming(cache.ped) or cache.vehicle then
|
||||||
exports.qbx_core:Notify(locale('error.not_standing_up'), 'error')
|
lib.notify({ description = locale('error.not_standing_up'), type = 'error' })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local tanks = lib.callback.await('d4rk_divegear:server:getTanks', false)
|
if not exports.d4rk_lib:progress({
|
||||||
|
|
||||||
if not tanks or #tanks == 0 then
|
|
||||||
exports.qbx_core:Notify(locale('error.need_otube'), 'error')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local tank = chooseTank(tanks)
|
|
||||||
|
|
||||||
if not tank then return end
|
|
||||||
|
|
||||||
if tank.oxygen <= 0 then
|
|
||||||
exports.qbx_core:Notify(locale('error.tank_empty'), 'error')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if lib.progressBar({
|
|
||||||
duration = config.putOnSuitTimeMs,
|
duration = config.putOnSuitTimeMs,
|
||||||
label = locale('info.put_suit'),
|
label = locale('info.patrone_einsetzen'),
|
||||||
useWhileDead = false,
|
useWhileDead = false,
|
||||||
canCancel = true,
|
canCancel = true,
|
||||||
anim = {
|
anim = {
|
||||||
@@ -607,26 +617,36 @@ local function putOnSuit()
|
|||||||
blendIn = 8.0
|
blendIn = 8.0
|
||||||
}
|
}
|
||||||
}) then
|
}) then
|
||||||
activeTank = tank
|
return
|
||||||
warned = {}
|
|
||||||
|
|
||||||
deleteGear()
|
|
||||||
attachGear()
|
|
||||||
enableScuba()
|
|
||||||
currentGear.enabled = true
|
|
||||||
-- Initiate breathing suit audio
|
|
||||||
startOxygenLevelDecrementerThread()
|
|
||||||
startHudThread()
|
|
||||||
startGearWatchdogThread()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
RegisterNetEvent('d4rk_divegear:client:useGear', function()
|
-- Die laufende Luft zuerst sichern - danach gehoert der Client-Stand zur
|
||||||
if currentGear.enabled then
|
-- alten Patrone und wird nicht mehr geschrieben (pruefen -> removeGear ohneSync).
|
||||||
takeOffSuit()
|
if currentGear.enabled then syncTank() end
|
||||||
else
|
|
||||||
putOnSuit()
|
local ok, grund = lib.callback.await('d4rk_divegear:server:patroneTauschen', false, slot)
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
lib.notify({ description = locale('error.' .. tostring(grund or 'fehler')), type = 'error' })
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
lib.notify({ description = locale('success.patrone_getauscht'), type = 'success' })
|
||||||
|
|
||||||
|
--[[
|
||||||
|
IMMER NEU EINLESEN - 29.09.2026. Betreiber: "beim austauschen
|
||||||
|
restettet sich das hud aber nicht". pruefen() las die Patrone nur neu,
|
||||||
|
wenn sich ihr NAME aenderte - eine 5-Minuten-Patrone gegen eine andere
|
||||||
|
5-Minuten-Patrone galt als dieselbe, und die alte Restluft blieb stehen.
|
||||||
|
Die laufende Luft ist oben schon gesichert (syncTank vor dem Tausch),
|
||||||
|
also abbauen OHNE zu schreiben und frisch vom Server anlegen.
|
||||||
|
]]
|
||||||
|
if currentGear.enabled then removeGear(false, false, true) end
|
||||||
|
pruefen()
|
||||||
|
end
|
||||||
|
|
||||||
|
exports('patroneBenutzen', function(_, slot)
|
||||||
|
einsetzen(type(slot) == 'table' and slot.slot or nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
lib.addKeybind({
|
lib.addKeybind({
|
||||||
@@ -659,60 +679,42 @@ lib.addKeybind({
|
|||||||
lib.onCache('ped', function(ped)
|
lib.onCache('ped', function(ped)
|
||||||
if not currentGear.enabled then return end
|
if not currentGear.enabled then return end
|
||||||
|
|
||||||
deleteGear()
|
|
||||||
attachGear(ped)
|
|
||||||
|
|
||||||
if activeTank and activeTank.oxygen > 0 then
|
if activeTank and activeTank.oxygen > 0 then
|
||||||
enableScuba(ped)
|
enableScuba(ped)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- Ausrüstung und Flasche können während des Tauchgangs das Inventar verlassen -
|
--[[
|
||||||
--- weggegeben, abgelegt, vom Admin entfernt. Ohne diese Prüfung hielt der Client
|
GTA NIMMT FLASCHE UND MASKE SELBST AB - gemessen am 29.09.2026 mit
|
||||||
--- nur eine Slot-Nummer, die niemand mehr gegenprüfte, und man konnte unbegrenzt
|
/tauchprobe beobachten (Betreiber): im Moment, in dem "schwimmt" auf nein
|
||||||
--- weitertauchen. ox_inventory feuert bei jeder Änderung, also einmal nachfragen.
|
springt (flaches Wasser, Boden unter den Fuessen), verschwanden Komponente 8
|
||||||
AddEventHandler('ox_inventory:updateInventory', function()
|
(global 123 = GTAs Taucherflasche) und Prop 1 gleichzeitig - bei
|
||||||
if not currentGear.enabled or not activeTank then return end
|
ausgeschaltetem Tauchgeraet, also nicht durch diesen Code. Die Items lagen
|
||||||
|
weiter in ihren Slots.
|
||||||
|
|
||||||
|
CPED_CONFIG_FLAG_DisableTakeOffScubaGear = 409 (NACHGESCHLAGEN in der
|
||||||
|
SetPedConfigFlag-Doku der Nativ-Datenbank) schaltet das ab. Bei uns zieht
|
||||||
|
nur das Inventar aus. Gesetzt am Spieler-Ped, nach jedem Ped-Wechsel neu
|
||||||
|
(onCache-Parameter, nicht cache.ped), beim Stoppen wieder ab - sonst
|
||||||
|
bliebe sie ohne diese Resource stehen.
|
||||||
|
]]
|
||||||
|
local ABNEHMEN_AUS = 409
|
||||||
|
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
local expected = activeTank
|
SetPedConfigFlag(PlayerPedId(), ABNEHMEN_AUS, true)
|
||||||
|
|
||||||
-- Der Thread startet erst im nächsten Tick, die Prüfung oben ist also
|
|
||||||
-- schon veraltet. Beim Ablegen passiert genau das: removeGear sichert die
|
|
||||||
-- Restluft, das ist selbst eine Inventar-Änderung, und bis hierher ist
|
|
||||||
-- activeTank längst nil.
|
|
||||||
if not expected or not currentGear.enabled then return end
|
|
||||||
|
|
||||||
local result = lib.callback.await('d4rk_divegear:server:verifyGear', false, expected.slot, expected.name)
|
|
||||||
|
|
||||||
-- Während des Callbacks kann die Ausrüstung schon abgelegt worden sein.
|
|
||||||
if not currentGear.enabled or activeTank ~= expected then return end
|
|
||||||
|
|
||||||
if not result then return end
|
|
||||||
|
|
||||||
if not result.gear then
|
|
||||||
exports.qbx_core:Notify(locale('error.gear_gone'), 'error')
|
|
||||||
removeGear(true)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not result.tank then
|
|
||||||
exports.qbx_core:Notify(locale('error.tank_gone'), 'error')
|
|
||||||
removeGear(true)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
activeTank.slot = result.tank
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
lib.onCache('ped', function(ped)
|
||||||
|
SetPedConfigFlag(ped, ABNEHMEN_AUS, true)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
AddEventHandler('onResourceStop', function(resource)
|
AddEventHandler('onResourceStop', function(resource)
|
||||||
if resource ~= GetCurrentResourceName() then return end
|
if resource ~= GetCurrentResourceName() then return end
|
||||||
|
SetPedConfigFlag(cache.ped, ABNEHMEN_AUS, false)
|
||||||
|
|
||||||
syncTank(true)
|
syncTank(true)
|
||||||
setLamp(false)
|
setLamp(false)
|
||||||
applyNarcosisStage(0)
|
applyNarcosisStage(0)
|
||||||
deleteGear()
|
|
||||||
SendNUIMessage({ action = 'state', visible = false })
|
SendNUIMessage({ action = 'state', visible = false })
|
||||||
SetEnableScuba(cache.ped, false)
|
SetEnableScuba(cache.ped, false)
|
||||||
SetPedMaxTimeUnderwater(cache.ped, config.maxTimeUnderwater)
|
SetPedMaxTimeUnderwater(cache.ped, config.maxTimeUnderwater)
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
--[[
|
||||||
|
d4rk_divegear - /tauchprobe: welche Kleidung ist die Tauchmaske?
|
||||||
|
(plan-divegear.md Schritt 0, 28.09.2026)
|
||||||
|
|
||||||
|
Flasche und Maske werden Kleidungsstuecke aus dem Kleiderladen
|
||||||
|
(d4rk_garment). Dafuer braucht es Komponente, Sammlung und lokalen Index -
|
||||||
|
je Modell (m/f). Der Katalog hat keine Namen, also wird gemessen:
|
||||||
|
|
||||||
|
SetPedScubaGearVariation (0x36C6984C3ED0C911, PED, client) setzt laut
|
||||||
|
Nativ-Datenbank bei Freemode-Modellen die Tauchmaske. Diese Probe liest
|
||||||
|
ALLE Komponenten (0-11) und Props (0-7) vorher und nachher und nennt,
|
||||||
|
was sich geaendert hat - samt Sammlung, lokalem Index und Zahl der
|
||||||
|
Texturen.
|
||||||
|
|
||||||
|
GEGENPROBE: aendert sich gar nichts, steht "UNGUELTIG" da - das Native hat
|
||||||
|
dann nichts getan, und "keine Maske" waere die falsche Lesart.
|
||||||
|
|
||||||
|
DIE FLASCHE setzt das Native bei Freemode NICHT (laut Doku nur bei
|
||||||
|
player_*-Modellen). Nachgeschlagen, nicht gemessen: maennlich Komponente 8,
|
||||||
|
Drawable 123. Deshalb liest die Probe mit "flasche [n]" Komponente 8 bei
|
||||||
|
Drawable n (Vorgabe 123) - ob es die Flasche IST, sieht nur das Auge.
|
||||||
|
|
||||||
|
Zurueck zur eigenen Kleidung: /d4rk_anziehen (d4rk_inventory).
|
||||||
|
]]
|
||||||
|
|
||||||
|
local function modell(ped)
|
||||||
|
local m = GetEntityModel(ped)
|
||||||
|
if m == `mp_m_freemode_01` then return 'm' end
|
||||||
|
if m == `mp_f_freemode_01` then return 'f' end
|
||||||
|
return '?'
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stand(ped)
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for c = 0, 11 do
|
||||||
|
t['c' .. c] = {
|
||||||
|
art = 'c', id = c,
|
||||||
|
d = GetPedDrawableVariation(ped, c), tex = GetPedTextureVariation(ped, c),
|
||||||
|
sammlung = GetPedDrawableVariationCollectionName(ped, c) or 'base',
|
||||||
|
lokal = GetPedDrawableVariationCollectionLocalIndex(ped, c),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
for p = 0, 7 do
|
||||||
|
t['p' .. p] = {
|
||||||
|
art = 'p', id = p,
|
||||||
|
d = GetPedPropIndex(ped, p), tex = GetPedPropTextureIndex(ped, p),
|
||||||
|
sammlung = GetPedPropCollectionName(ped, p) or 'base',
|
||||||
|
lokal = GetPedPropCollectionLocalIndex(ped, p),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function zeile(ped, e)
|
||||||
|
local texturen = e.art == 'c' and GetNumberOfPedTextureVariations(ped, e.id, e.d)
|
||||||
|
or (e.d >= 0 and GetNumberOfPedPropTextureVariations(ped, e.id, e.d) or 0)
|
||||||
|
|
||||||
|
return ('%s%-2d global %4d Textur %2d Sammlung %-14s lokal %4d Texturen %d'):format(
|
||||||
|
e.art, e.id, e.d, e.tex, tostring(e.sammlung ~= '' and e.sammlung or 'base'), e.lokal or -1, texturen or -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
/tauchprobe beobachten [sekunden] - 29.09.2026. Betreiber: "bei der
|
||||||
|
taucherfalsche haben wir das problem er zieht sie aus wenn er an land ist".
|
||||||
|
|
||||||
|
Schreibt JEDE Aenderung an Flasche (Komponente 8) und Maske (Prop 1) mit
|
||||||
|
Zeit, Wasserzustand und ob das Tauchgeraet aktiv ist - damit man sieht,
|
||||||
|
WANN sie verschwindet (Wasser verlassen? Tauchgeraet aus? Neuanziehen?),
|
||||||
|
statt es zu raten. Alle 10 s eine Lebenszeile, damit Stille nicht wie
|
||||||
|
"keine Aenderung" aussieht, wenn die Probe gar nicht laeuft.
|
||||||
|
]]
|
||||||
|
local function beobachten(sekunden)
|
||||||
|
local ende = GetGameTimer() + sekunden * 1000
|
||||||
|
local start = GetGameTimer()
|
||||||
|
local letzt, lebenszeichen = nil, 0
|
||||||
|
|
||||||
|
print(('^5=== Tauchprobe beobachten, %d s ===^7'):format(sekunden))
|
||||||
|
print(' t Flasche (c8) Maske (p1) Wasser Schwimmt Unten Geraet Flag409')
|
||||||
|
|
||||||
|
while GetGameTimer() < ende do
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
local c8 = ('%s:%d (g%d t%d)'):format(GetPedDrawableVariationCollectionName(ped, 8) or 'base',
|
||||||
|
GetPedDrawableVariationCollectionLocalIndex(ped, 8) or -1, GetPedDrawableVariation(ped, 8), GetPedTextureVariation(ped, 8))
|
||||||
|
local p1 = ('%s:%d (g%d t%d)'):format(GetPedPropCollectionName(ped, 1) or 'base',
|
||||||
|
GetPedPropCollectionLocalIndex(ped, 1) or -1, GetPedPropIndex(ped, 1), GetPedPropTextureIndex(ped, 1))
|
||||||
|
local function jn(x) return (x == true or x == 1) and 'j' or 'n' end
|
||||||
|
local okA, aktiv = pcall(function() return exports.d4rk_divegear:geraetAktiv() end)
|
||||||
|
local zeile = ('%-29s %-29s %s %s %s %s %s'):format(c8, p1, jn(IsEntityInWater(ped)),
|
||||||
|
jn(IsPedSwimming(ped)), jn(IsPedSwimmingUnderWater(ped)), okA and jn(aktiv) or '?',
|
||||||
|
jn(GetPedConfigFlag(ped, 409, true)))
|
||||||
|
local t = (GetGameTimer() - start) / 1000
|
||||||
|
|
||||||
|
if zeile ~= letzt then
|
||||||
|
print((' %5.1f %s'):format(t, zeile))
|
||||||
|
letzt = zeile
|
||||||
|
elseif t - lebenszeichen >= 10 then
|
||||||
|
print((' %5.1f (unveraendert)'):format(t))
|
||||||
|
lebenszeichen = t
|
||||||
|
end
|
||||||
|
|
||||||
|
Wait(250)
|
||||||
|
end
|
||||||
|
|
||||||
|
print('^5=== Tauchprobe beobachten: Ende ===^7')
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterCommand('tauchprobe', function(_, args)
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
|
||||||
|
if args[1] == 'beobachten' then
|
||||||
|
CreateThread(function() beobachten(tonumber(args[2]) or 120) end)
|
||||||
|
lib.notify({ title = 'Tauchprobe', description = 'Beobachte - Ausgabe in F8.', type = 'info' })
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print('')
|
||||||
|
print(('^5=== d4rk_divegear: Tauchprobe (Modell %s) ===^7'):format(modell(ped)))
|
||||||
|
|
||||||
|
if args[1] == 'flasche' then
|
||||||
|
local n = tonumber(args[2]) or 123
|
||||||
|
|
||||||
|
SetPedComponentVariation(ped, 8, n, 0, 0)
|
||||||
|
Wait(300)
|
||||||
|
|
||||||
|
local s = stand(ped).c8
|
||||||
|
print(' Komponente 8 gesetzt auf global ' .. n .. ':')
|
||||||
|
print(' ' .. zeile(ped, s))
|
||||||
|
print((' Ist das die Taucherflasche? Ansehen. Andere Nummer: /tauchprobe flasche <n>. '
|
||||||
|
.. 'Zurueck: /d4rk_anziehen'))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local vorher = stand(ped)
|
||||||
|
SetPedScubaGearVariation(ped)
|
||||||
|
Wait(500)
|
||||||
|
local nachher = stand(ped)
|
||||||
|
|
||||||
|
local anders = 0
|
||||||
|
|
||||||
|
for k, n in pairs(nachher) do
|
||||||
|
local v = vorher[k]
|
||||||
|
|
||||||
|
if v.d ~= n.d or v.tex ~= n.tex then
|
||||||
|
anders = anders + 1
|
||||||
|
print(' vorher ' .. zeile(ped, v))
|
||||||
|
print(' nachher ' .. zeile(ped, n))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if anders == 0 then
|
||||||
|
print('^1 UNGUELTIG: SetPedScubaGearVariation hat nichts geaendert - keine Aussage.^7')
|
||||||
|
else
|
||||||
|
print(('^2 %d Teil(e) geaendert.^7 Zurueck zur eigenen Kleidung: /d4rk_anziehen'):format(anders))
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.notify({ title = 'Tauchprobe', description = 'Ergebnis in F8.', type = 'info' })
|
||||||
|
end, false)
|
||||||
+8
-2
@@ -9,8 +9,12 @@ return {
|
|||||||
--- Intervall des Watchdogs, der prüft ob die Ausrüstung noch am Ped hängt (ms).
|
--- Intervall des Watchdogs, der prüft ob die Ausrüstung noch am Ped hängt (ms).
|
||||||
gearWatchdogIntervalMs = 1000,
|
gearWatchdogIntervalMs = 1000,
|
||||||
|
|
||||||
--- Sekunden, die der Spieler ohne Ausrüstung unter Wasser überlebt (Vanilla-Verhalten).
|
--- Sekunden Luft ohne Ausruestung - der NORMALWERT dieses Servers.
|
||||||
maxTimeUnderwater = 50.0,
|
--- Stand bis 28.09.2026 auf 50.0 mit "Vanilla-Verhalten". GEMESSEN auf
|
||||||
|
--- d4rk-rp mit /medwasserprobe (d4rk_medical): 10,00 s an der Oberflaeche.
|
||||||
|
--- Mit 50 haette jeder nach dem ersten Ablegen fuenfmal so viel Luft - und
|
||||||
|
--- das Ertrinken (d4rk_medical, Config.Drowning) kaeme viel zu spaet.
|
||||||
|
maxTimeUnderwater = 10.0,
|
||||||
|
|
||||||
--- Sekunden, sobald die Flasche leer ist. Bewusst kurz: leer tauchen soll wehtun.
|
--- Sekunden, sobald die Flasche leer ist. Bewusst kurz: leer tauchen soll wehtun.
|
||||||
maxTimeUnderwaterOutOfAir = 1.0,
|
maxTimeUnderwaterOutOfAir = 1.0,
|
||||||
@@ -134,6 +138,8 @@ return {
|
|||||||
|
|
||||||
--- Taste zum An- und Ausschalten. Der Spieler kann sie in den FiveM-
|
--- Taste zum An- und Ausschalten. Der Spieler kann sie in den FiveM-
|
||||||
--- Tastenbelegungen unter "FiveM" umlegen.
|
--- Tastenbelegungen unter "FiveM" umlegen.
|
||||||
|
--- L ist AUCH das Fahrzeugschloss (d4rk_vehicle, wirkt von aussen bis
|
||||||
|
--- 6 m) - beide feuern. Bewusst so gelassen, Betreiber 28.09.2026.
|
||||||
lampKey = 'l',
|
lampKey = 'l',
|
||||||
|
|
||||||
--- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit
|
--- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit
|
||||||
|
|||||||
+39
-3
@@ -26,9 +26,45 @@ local config = {
|
|||||||
{ name = 'diving_tank_trimix', capacity = 900, pressure = 300, maxDepth = 100.0, gas = 'Trimix' }, -- 15 Minuten, tieftauglich
|
{ name = 'diving_tank_trimix', capacity = 900, pressure = 300, maxDepth = 100.0, gas = 'Trimix' }, -- 15 Minuten, tieftauglich
|
||||||
},
|
},
|
||||||
|
|
||||||
--- Item, das die Ausrüstung an- und auszieht, und das Füll-Item. Stehen hier,
|
--- SEIT 28.09.2026 SIND FLASCHE UND MASKE KLEIDUNG (plan-divegear.md).
|
||||||
--- weil sowohl der Useable-Item-Handler als auch die Besitzprüfung sie brauchen.
|
--- Betreiber: "die Prop flasche zu einer richtigen taucherfalsche + maske
|
||||||
gearItem = 'diving_gear',
|
--- welche wir im kleider laden kaufen koennen", und "die flaschen groessen
|
||||||
|
--- soll man an der normalen taucherfalsche austauschen koennen".
|
||||||
|
---
|
||||||
|
--- rigItem die Taucherflasche aus dem Kleiderladen (d4rk_garment -> item).
|
||||||
|
--- Sie traegt die eingesetzte Patrone in der Metadata:
|
||||||
|
--- patrone = Name aus tanks (unten), oxygen = Restsekunden.
|
||||||
|
--- maskItem die Tauchmaske aus dem Kleiderladen.
|
||||||
|
--- rigSlot / maskSlot wo sie getragen werden - d4rk_inventory
|
||||||
|
--- shared/d4rk_slots.lua:71-96. GEMESSEN am 29.09.2026 mit
|
||||||
|
--- /d4rk_kleidungsstand (Betreiber, Modell m):
|
||||||
|
--- Flasche m:c:8:mp_m_christmas2017:1 -> 31 Unterhemd (Komponente 8)
|
||||||
|
--- Maske m:p:1:mp_m_christmas2017:0 -> 27 Brille (PROP 1, nicht
|
||||||
|
--- die Maskenkomponente 1 - angenommen war Slot 29)
|
||||||
|
---
|
||||||
|
--- Die fuenf `tanks` sind jetzt PATRONEN: Items im Inventar, die man an der
|
||||||
|
--- getragenen Flasche tauscht (Patrone benutzen). Die alten Props und das
|
||||||
|
--- Item diving_gear gibt es nicht mehr.
|
||||||
|
rigItem = 'diving_tank',
|
||||||
|
maskItem = 'diving_mask',
|
||||||
|
rigSlot = 31,
|
||||||
|
maskSlot = 27,
|
||||||
|
|
||||||
|
--- Die Kleidungsstuecke im Kleiderladen, die statt `clothing` das Item
|
||||||
|
--- ausgeben (d4rk_garment.item, angelegt mit /tauchartikel). drawable ist
|
||||||
|
--- der LOKALE Index in der Sammlung, texture -1 = alle Farben.
|
||||||
|
--- Weiblich (f) gemessen am 29.09.2026 (Betreiber): dieselben Sammlungen und
|
||||||
|
--- Indizes wie m - Flasche mp_f_christmas2017:1, Maske mp_f_christmas2017:0.
|
||||||
|
kleidung = {
|
||||||
|
{ pedset = 'm', komponente = 'c:8', collection = 'mp_m_christmas2017', drawable = 1, item = 'diving_tank' },
|
||||||
|
{ pedset = 'm', komponente = 'p:1', collection = 'mp_m_christmas2017', drawable = 0, item = 'diving_mask' },
|
||||||
|
{ pedset = 'f', komponente = 'c:8', collection = 'mp_f_christmas2017', drawable = 1, item = 'diving_tank' },
|
||||||
|
{ pedset = 'f', komponente = 'p:1', collection = 'mp_f_christmas2017', drawable = 0, item = 'diving_mask' },
|
||||||
|
},
|
||||||
|
|
||||||
|
--- Das Fuell-Item. NICHT ANGELEGT: Nachfuellen kommt spaeter ueber einen
|
||||||
|
--- Shop oder Crafting (Betreiber, 28.09.2026). Der Weg bleibt stehen, damit
|
||||||
|
--- er sich dort anhaengen laesst - ohne das Item ist er nie erreichbar.
|
||||||
fillItem = 'diving_fill',
|
fillItem = 'diving_fill',
|
||||||
|
|
||||||
--- Flaschen ohne Metadata als voll behandeln. Betrifft alles, was frisch aus
|
--- Flaschen ohne Metadata als voll behandeln. Betrifft alles, was frisch aus
|
||||||
|
|||||||
+13
-1
@@ -13,11 +13,23 @@ shared_scripts {
|
|||||||
'@qbx_core/modules/lib.lua',
|
'@qbx_core/modules/lib.lua',
|
||||||
}
|
}
|
||||||
|
|
||||||
server_script 'server/main.lua'
|
server_scripts {
|
||||||
|
'@d4rk_lib/server/diagnose_shared.lua',
|
||||||
|
'server/main.lua',
|
||||||
|
-- /tauchartikel: die Items in d4rk_item anlegen, nur auf Zuruf (28.09.2026).
|
||||||
|
'server/artikel.lua',
|
||||||
|
}
|
||||||
|
|
||||||
client_scripts {
|
client_scripts {
|
||||||
|
-- lib.notify im Hausdesign (d4rk_lib) - VOR allen, die melden.
|
||||||
|
'@d4rk_lib/client/meldung_shared.lua',
|
||||||
'client/main.lua',
|
'client/main.lua',
|
||||||
|
-- Die Konsole beim HUD anmelden (d4rk_hud, BauteilAnmelden).
|
||||||
|
'client/hud.lua',
|
||||||
'client/light.lua',
|
'client/light.lua',
|
||||||
|
-- Messung fuer die Umstellung auf Kleidung (plan-divegear.md Schritt 0).
|
||||||
|
-- Wird nach der Messung wieder entfernt.
|
||||||
|
'client/probe.lua',
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_page 'nui/index.html'
|
ui_page 'nui/index.html'
|
||||||
|
|||||||
+45
-4
@@ -10,11 +10,19 @@
|
|||||||
"no_lamp_on_outfit": "Deine Tauchausrüstung hat keine Lampe.",
|
"no_lamp_on_outfit": "Deine Tauchausrüstung hat keine Lampe.",
|
||||||
"crush_depth": "Lebensgefahr! Ab %d m zerdrückt dich der Wasserdruck.",
|
"crush_depth": "Lebensgefahr! Ab %d m zerdrückt dich der Wasserdruck.",
|
||||||
"gear_gone": "Deine Tauchausrüstung ist weg!",
|
"gear_gone": "Deine Tauchausrüstung ist weg!",
|
||||||
"tank_gone": "Deine Tauchflasche ist weg!"
|
"tank_gone": "Deine Tauchflasche ist weg!",
|
||||||
|
"keine_flasche": "Du trägst keine Taucherflasche.",
|
||||||
|
"keine_maske": "Ohne Tauchmaske kein Tauchgerät.",
|
||||||
|
"keine_patrone": "In deiner Taucherflasche steckt keine Patrone.",
|
||||||
|
"keine_patrone_im_slot": "Diese Patrone hast du nicht mehr.",
|
||||||
|
"kein_platz": "Kein Platz für die alte Patrone.",
|
||||||
|
"ungueltig": "Das geht nicht.",
|
||||||
|
"fehler": "Das hat nicht geklappt."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"took_out": "Du hast deine Ausrüstung abgelegt!",
|
"took_out": "Du hast deine Ausrüstung abgelegt!",
|
||||||
"tube_filled": "Du hast deine Tauchflasche erfolgreich aufgefüllt!"
|
"tube_filled": "Du hast deine Tauchflasche erfolgreich aufgefüllt!",
|
||||||
|
"patrone_getauscht": "Patrone eingesetzt."
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"put_suit": "Tauchanzug wird angezogen...",
|
"put_suit": "Tauchanzug wird angezogen...",
|
||||||
@@ -22,7 +30,9 @@
|
|||||||
"filling_air": "Sauerstoff wird aufgefüllt...",
|
"filling_air": "Sauerstoff wird aufgefüllt...",
|
||||||
"oxygen_low": "Nur noch %s Luft!",
|
"oxygen_low": "Nur noch %s Luft!",
|
||||||
"tank_remaining": "%s verbleibend (%d%%) · %s bis %d m",
|
"tank_remaining": "%s verbleibend (%d%%) · %s bis %d m",
|
||||||
"depth_limit": "Du näherst dich der Grenze deines Atemgases (%d m)!"
|
"depth_limit": "Du näherst dich der Grenze deines Atemgases (%d m)!",
|
||||||
|
"bereit": "Tauchgerät bereit - %s Luft.",
|
||||||
|
"patrone_einsetzen": "Patrone wird eingesetzt..."
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"choose_tank": "Tauchflasche wählen"
|
"choose_tank": "Tauchflasche wählen"
|
||||||
@@ -31,10 +41,41 @@
|
|||||||
"air": "Luft",
|
"air": "Luft",
|
||||||
"depth": "Tiefe",
|
"depth": "Tiefe",
|
||||||
"pressure": "Druck",
|
"pressure": "Druck",
|
||||||
"lamp": "Lampe"
|
"lamp": "Lampe",
|
||||||
|
"konsole": "Tauchkonsole"
|
||||||
},
|
},
|
||||||
"keybind": {
|
"keybind": {
|
||||||
"toggle_lamp": "Tauchlampe an/aus",
|
"toggle_lamp": "Tauchlampe an/aus",
|
||||||
"toggle_hud": "Tauch-HUD ein-/ausblenden"
|
"toggle_hud": "Tauch-HUD ein-/ausblenden"
|
||||||
|
},
|
||||||
|
"artikel": {
|
||||||
|
"diving_tank": {
|
||||||
|
"name": "Taucherflasche",
|
||||||
|
"text": "Tauchgerät zum Anziehen. Eine Patrone benutzen, um sie einzusetzen oder zu tauschen."
|
||||||
|
},
|
||||||
|
"diving_mask": {
|
||||||
|
"name": "Tauchmaske",
|
||||||
|
"text": "Ohne Maske kein Atmen unter Wasser."
|
||||||
|
},
|
||||||
|
"diving_tank_small": {
|
||||||
|
"name": "Flaschenpatrone (5 Min)",
|
||||||
|
"text": "Pressluft, 300 bar, bis 45 m. Reicht für etwa 5 Minuten."
|
||||||
|
},
|
||||||
|
"diving_tank_medium": {
|
||||||
|
"name": "Flaschenpatrone (10 Min)",
|
||||||
|
"text": "Pressluft, 300 bar, bis 45 m. Reicht für etwa 10 Minuten."
|
||||||
|
},
|
||||||
|
"diving_tank_large": {
|
||||||
|
"name": "Flaschenpatrone (15 Min)",
|
||||||
|
"text": "Pressluft, 300 bar, bis 45 m. Reicht für etwa 15 Minuten."
|
||||||
|
},
|
||||||
|
"diving_tank_xl": {
|
||||||
|
"name": "Flaschenpatrone (20 Min)",
|
||||||
|
"text": "Pressluft, 300 bar, bis 45 m. Reicht für etwa 20 Minuten."
|
||||||
|
},
|
||||||
|
"diving_tank_trimix": {
|
||||||
|
"name": "Trimix-Patrone (15 Min)",
|
||||||
|
"text": "Trimix, 300 bar, bis 100 m. Reicht für etwa 15 Minuten."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+45
-4
@@ -10,11 +10,19 @@
|
|||||||
"no_lamp_on_outfit": "Your diving suit doesn't have a lamp.",
|
"no_lamp_on_outfit": "Your diving suit doesn't have a lamp.",
|
||||||
"crush_depth": "Danger! Below %d m the water pressure will crush you.",
|
"crush_depth": "Danger! Below %d m the water pressure will crush you.",
|
||||||
"gear_gone": "Your diving gear is gone!",
|
"gear_gone": "Your diving gear is gone!",
|
||||||
"tank_gone": "Your air tank is gone!"
|
"tank_gone": "Your air tank is gone!",
|
||||||
|
"keine_flasche": "You are not wearing a scuba tank.",
|
||||||
|
"keine_maske": "No diving mask, no breathing apparatus.",
|
||||||
|
"keine_patrone": "There is no cartridge in your scuba tank.",
|
||||||
|
"keine_patrone_im_slot": "You no longer have that cartridge.",
|
||||||
|
"kein_platz": "No room for the old cartridge.",
|
||||||
|
"ungueltig": "That does not work.",
|
||||||
|
"fehler": "That did not work."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"took_out": "You've taken your gear off!",
|
"took_out": "You've taken your gear off!",
|
||||||
"tube_filled": "You've successfully refilled your air tank!"
|
"tube_filled": "You've successfully refilled your air tank!",
|
||||||
|
"patrone_getauscht": "Cartridge inserted."
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"put_suit": "Putting on your diving suit...",
|
"put_suit": "Putting on your diving suit...",
|
||||||
@@ -22,7 +30,9 @@
|
|||||||
"filling_air": "Filling air...",
|
"filling_air": "Filling air...",
|
||||||
"oxygen_low": "Only %s of air left!",
|
"oxygen_low": "Only %s of air left!",
|
||||||
"tank_remaining": "%s remaining (%d%%) · %s down to %d m",
|
"tank_remaining": "%s remaining (%d%%) · %s down to %d m",
|
||||||
"depth_limit": "You're approaching the limit of your breathing gas (%d m)!"
|
"depth_limit": "You're approaching the limit of your breathing gas (%d m)!",
|
||||||
|
"bereit": "Breathing apparatus ready - %s of air.",
|
||||||
|
"patrone_einsetzen": "Inserting cartridge..."
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"choose_tank": "Choose an air tank"
|
"choose_tank": "Choose an air tank"
|
||||||
@@ -31,10 +41,41 @@
|
|||||||
"air": "Air",
|
"air": "Air",
|
||||||
"depth": "Depth",
|
"depth": "Depth",
|
||||||
"pressure": "Pressure",
|
"pressure": "Pressure",
|
||||||
"lamp": "Lamp"
|
"lamp": "Lamp",
|
||||||
|
"konsole": "Dive console"
|
||||||
},
|
},
|
||||||
"keybind": {
|
"keybind": {
|
||||||
"toggle_lamp": "Toggle dive lamp",
|
"toggle_lamp": "Toggle dive lamp",
|
||||||
"toggle_hud": "Toggle dive HUD"
|
"toggle_hud": "Toggle dive HUD"
|
||||||
|
},
|
||||||
|
"artikel": {
|
||||||
|
"diving_tank": {
|
||||||
|
"name": "Scuba tank",
|
||||||
|
"text": "Breathing apparatus to wear. Use a cartridge to insert or swap it."
|
||||||
|
},
|
||||||
|
"diving_mask": {
|
||||||
|
"name": "Diving mask",
|
||||||
|
"text": "No mask, no breathing underwater."
|
||||||
|
},
|
||||||
|
"diving_tank_small": {
|
||||||
|
"name": "Tank cartridge (5 min)",
|
||||||
|
"text": "Compressed air, 300 bar, down to 45 m. Lasts about 5 minutes."
|
||||||
|
},
|
||||||
|
"diving_tank_medium": {
|
||||||
|
"name": "Tank cartridge (10 min)",
|
||||||
|
"text": "Compressed air, 300 bar, down to 45 m. Lasts about 10 minutes."
|
||||||
|
},
|
||||||
|
"diving_tank_large": {
|
||||||
|
"name": "Tank cartridge (15 min)",
|
||||||
|
"text": "Compressed air, 300 bar, down to 45 m. Lasts about 15 minutes."
|
||||||
|
},
|
||||||
|
"diving_tank_xl": {
|
||||||
|
"name": "Tank cartridge (20 min)",
|
||||||
|
"text": "Compressed air, 300 bar, down to 45 m. Lasts about 20 minutes."
|
||||||
|
},
|
||||||
|
"diving_tank_trimix": {
|
||||||
|
"name": "Trimix cartridge (15 min)",
|
||||||
|
"text": "Trimix, 300 bar, down to 100 m. Lasts about 15 minutes."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+34
-3
@@ -118,10 +118,35 @@ function formatTime(seconds) {
|
|||||||
return `${mm}:${ss}`;
|
return `${mm}:${ss}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(state) {
|
// Lage vom HUD (d4rk_hud, BauteilAnmelden - client/hud.lua). null = keine,
|
||||||
hud.classList.toggle('hidden', !state.visible);
|
// dann gilt die Position aus style.css. 28.09.2026.
|
||||||
|
let lage = null;
|
||||||
|
let letzterStand = null;
|
||||||
|
|
||||||
if (!state.visible) return;
|
function lageAnwenden() {
|
||||||
|
if (!lage) {
|
||||||
|
hud.classList.remove('gelagert');
|
||||||
|
hud.style.left = '';
|
||||||
|
hud.style.top = '';
|
||||||
|
hud.style.removeProperty('--lage-scale');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hud.classList.add('gelagert');
|
||||||
|
hud.style.left = `${lage.links}px`;
|
||||||
|
hud.style.top = `${lage.oben}px`;
|
||||||
|
hud.style.setProperty('--lage-scale', String(lage.skalierung ?? 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(state) {
|
||||||
|
letzterStand = state;
|
||||||
|
|
||||||
|
// Im HUD abgeschaltet, Fotomodus, Auswahl: das HUD sagt "nicht sichtbar".
|
||||||
|
const zeigen = state.visible && (!lage || lage.sichtbar !== false);
|
||||||
|
|
||||||
|
hud.classList.toggle('hidden', !zeigen);
|
||||||
|
|
||||||
|
if (!zeigen) return;
|
||||||
|
|
||||||
const capacity = state.capacity > 0 ? state.capacity : 1;
|
const capacity = state.capacity > 0 ? state.capacity : 1;
|
||||||
const ratio = Math.min(1, Math.max(0, state.oxygen / capacity));
|
const ratio = Math.min(1, Math.max(0, state.oxygen / capacity));
|
||||||
@@ -171,6 +196,12 @@ window.addEventListener('message', (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.action === 'state') render(data);
|
if (data.action === 'state') render(data);
|
||||||
|
|
||||||
|
if (data.action === 'lage') {
|
||||||
|
lage = data.lage || null;
|
||||||
|
lageAnwenden();
|
||||||
|
if (letzterStand) render(letzterStand);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Damit sich das HUD im Browser ohne FiveM anschauen laesst.
|
// Damit sich das HUD im Browser ohne FiveM anschauen laesst.
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ body {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lage vom HUD (28.09.2026): linke obere Ecke in Bildschirmpixeln, Skalierung
|
||||||
|
fuer den bei 1080p gebauten Inhalt - rechnet d4rk_hud, hier nur anwenden. */
|
||||||
|
#hud.gelagert {
|
||||||
|
bottom: auto;
|
||||||
|
transform: scale(var(--lage-scale, 1));
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hud.gelagert.hidden {
|
||||||
|
transform: scale(var(--lage-scale, 1)) translateY(14px);
|
||||||
|
}
|
||||||
|
|
||||||
.caption {
|
.caption {
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
--[[
|
||||||
|
d4rk_divegear - /tauchartikel: die Items in d4rk_item anlegen (28.09.2026).
|
||||||
|
|
||||||
|
DIE ITEMS GEHOEREN d4rk_items. Angelegt wird ueber dieselben Exporte wie
|
||||||
|
der Editor im Panel (adminItemList / adminItemCreate) - dasselbe Muster
|
||||||
|
wie d4rk_medical `medartikel` (server/artikel.lua dort).
|
||||||
|
|
||||||
|
NUR AUF ZURUF, NIE BEIM START: eine Saat bei jedem Start holte zurueck, was
|
||||||
|
der Betreiber geloescht hat (Gedaechtnis "Wiederholte Saat holt
|
||||||
|
Geloeschtes zurueck"). Vorhandene Items werden NICHT angefasst - was im
|
||||||
|
Panel gepflegt ist, gewinnt.
|
||||||
|
|
||||||
|
SERVERKONSOLE ODER ADMIN IM SPIEL (Diagnosebefehl, restricted group.admin,
|
||||||
|
seit 07.10.2026 - vorher nur source 0; Ausgabe in die F8-Konsole, #81).
|
||||||
|
|
||||||
|
Taucherflasche Kleidung (Kleiderladen, d4rk_garment -> item). Traegt
|
||||||
|
die Patrone in der Metadata. Gewicht GESETZT: Geraet
|
||||||
|
mit Atemregler ohne Patrone.
|
||||||
|
Tauchmaske Kleidung.
|
||||||
|
Patronen die fuenf aus config/shared.lua. Gewichte aus
|
||||||
|
items_for_ox_inventory.lua (dem Original).
|
||||||
|
client_export + consume = 0: siehe client/main.lua
|
||||||
|
(einsetzen) - CreateUseableItem feuert hier nie.
|
||||||
|
|
||||||
|
consume = 0 bei ALLEN - wie das Kleidungsitem `clothing` in
|
||||||
|
d4rk_inventory/data/items.lua. Mit dem Vorgabewert 1 (d4rk_items
|
||||||
|
daten.lua:224) koennte "Benutzen" im Inventar die Flasche verbrauchen.
|
||||||
|
Am 28.09.2026 zuerst ohne angelegt (Flasche, Maske: consume 1) - deshalb
|
||||||
|
gibt es `tauchartikel nachziehen`: stellt bei VORHANDENEN Items nur
|
||||||
|
consume und den Export richtig, alles andere bleibt, wie es im Panel steht.
|
||||||
|
|
||||||
|
stack = false bei allen: die Luft steht in der Metadata jedes Stuecks.
|
||||||
|
KEIN decay und keine durability-Schalter: metadata.durability zeigt den
|
||||||
|
Fuellstand und darf bei 0 nicht zum Loeschen fuehren (ENTSCHEIDUNGEN.md).
|
||||||
|
]]
|
||||||
|
|
||||||
|
local shared = require 'config.shared'
|
||||||
|
|
||||||
|
local ITEMS = 'd4rk_items'
|
||||||
|
local EXPORT = 'd4rk_divegear.patroneBenutzen'
|
||||||
|
|
||||||
|
-- Die Schalter einer Zeile als Boolean: adminItemUpdate liest sie mit
|
||||||
|
-- `w.x and 1 or 0`, eine 0 als Zahl waere in Lua wahr (wie d4rk_medical
|
||||||
|
-- server/artikel.lua, SCHALTER).
|
||||||
|
local SCHALTER = {
|
||||||
|
'stack', 'close_ui', 'nur_ausgabe', 'aktiv', 'cancel',
|
||||||
|
'disable_move', 'disable_car', 'disable_combat', 'allow_armed', 'durability',
|
||||||
|
}
|
||||||
|
|
||||||
|
local function wahr(x) return x == true or x == 1 end
|
||||||
|
|
||||||
|
local GEWICHT = {
|
||||||
|
[shared.rigItem] = 3000,
|
||||||
|
[shared.maskItem] = 400,
|
||||||
|
diving_tank_small = 4000,
|
||||||
|
diving_tank_medium = 7000,
|
||||||
|
diving_tank_large = 10000,
|
||||||
|
diving_tank_xl = 13000,
|
||||||
|
diving_tank_trimix = 11000,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function liste()
|
||||||
|
local l = {
|
||||||
|
{ name = shared.rigItem },
|
||||||
|
{ name = shared.maskItem },
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #shared.tankNames do
|
||||||
|
l[#l + 1] = { name = shared.tankNames[i], patrone = true }
|
||||||
|
end
|
||||||
|
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
DIE KLEIDUNGSEINTRAEGE (d4rk_garment) - 29.09.2026. Ohne sie gibt der
|
||||||
|
Kleiderladen fuer Flasche und Maske `clothing` aus statt der Items
|
||||||
|
(d4rk_inventory d4rkStueckGeben -> d4rk_items GarmentInfo). Wie die Items:
|
||||||
|
nur auf Zuruf, vorhandene Zeilen werden nicht angefasst.
|
||||||
|
]]
|
||||||
|
local function kleidungAnlegen()
|
||||||
|
local ok, zeilen = pcall(function() return exports[ITEMS]:adminGarmentList() end)
|
||||||
|
|
||||||
|
if not ok or type(zeilen) ~= 'table' then
|
||||||
|
sag('^1 Kleidung: adminGarmentList antwortet nicht - UNGUELTIG, nichts angelegt.^7')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, k in ipairs(shared.kleidung or {}) do
|
||||||
|
local vorhanden
|
||||||
|
|
||||||
|
for _, z in ipairs(zeilen) do
|
||||||
|
if z.pedset == k.pedset and (z.collection or '') == k.collection
|
||||||
|
and tonumber(z.drawable) == k.drawable and tonumber(z.texture) == -1 then
|
||||||
|
vorhanden = z
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = ('%s %s %s:%d'):format(k.pedset, k.komponente, k.collection, k.drawable)
|
||||||
|
|
||||||
|
if vorhanden then
|
||||||
|
sag((' Kleidung %-34s vorhanden (Item %s)%s'):format(name, tostring(vorhanden.item),
|
||||||
|
vorhanden.item == k.item and '' or ' ^3- soll ' .. k.item .. ', im Panel pruefen^7'))
|
||||||
|
else
|
||||||
|
local okC, erfolg, grund = pcall(function()
|
||||||
|
return exports[ITEMS]:adminGarmentCreate(0, {
|
||||||
|
pedset = k.pedset, komponente = k.komponente, collection = k.collection,
|
||||||
|
drawable = k.drawable, texture = -1,
|
||||||
|
label = locale('artikel.' .. k.item .. '.name'), item = k.item, aktiv = true,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
sag((' Kleidung %-34s %s'):format(name,
|
||||||
|
(okC and erfolg == true) and '^2angelegt -> ' .. k.item .. '^7'
|
||||||
|
or ('^1FEHLER: %s^7'):format(okC and tostring(grund) or tostring(erfolg))))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Diagnosebefehl('tauchartikel', { restricted = 'group.admin' }, function(source, args)
|
||||||
|
|
||||||
|
local nachziehen = args[1] == 'nachziehen'
|
||||||
|
|
||||||
|
if GetResourceState(ITEMS) ~= 'started' then
|
||||||
|
sag('^1[d4rk_divegear] tauchartikel: d4rk_items laeuft nicht - nichts angelegt.^7')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, zeilen = pcall(function() return exports[ITEMS]:adminItemList() end)
|
||||||
|
|
||||||
|
if not ok or type(zeilen) ~= 'table' then
|
||||||
|
sag('^1[d4rk_divegear] tauchartikel: adminItemList antwortet nicht - UNGUELTIG, nichts angelegt.^7')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local da = {}
|
||||||
|
for _, z in ipairs(zeilen) do if z.name then da[z.name] = z end end
|
||||||
|
|
||||||
|
-- GEGENPROBE: eine leere Liste hiesse "d4rk_items kennt kein einziges
|
||||||
|
-- Item" - das ist nie wahr, sondern eine kaputte Antwort.
|
||||||
|
if next(da) == nil then
|
||||||
|
sag('^1[d4rk_divegear] tauchartikel: adminItemList lieferte 0 Items - UNGUELTIG, nichts angelegt.^7')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
sag(('[d4rk_divegear] tauchartikel: %d Items in d4rk_item bekannt.'):format(#zeilen))
|
||||||
|
|
||||||
|
for _, it in ipairs(liste()) do
|
||||||
|
local sollExport = it.patrone and EXPORT or nil
|
||||||
|
local z = da[it.name]
|
||||||
|
|
||||||
|
if z then
|
||||||
|
local istExport = (z.client_export and z.client_export ~= '') and z.client_export or nil
|
||||||
|
local passt = tonumber(z.consume) == 0 and istExport == sollExport
|
||||||
|
|
||||||
|
if passt then
|
||||||
|
sag((' %-20s vorhanden, passt'):format(it.name))
|
||||||
|
elseif not nachziehen then
|
||||||
|
sag((' %-20s ^3vorhanden, consume %s / Export %s - soll 0 / %s: tauchartikel nachziehen^7')
|
||||||
|
:format(it.name, tostring(z.consume), tostring(istExport), tostring(sollExport)))
|
||||||
|
else
|
||||||
|
local w = {}
|
||||||
|
for k, v in pairs(z) do w[k] = v end
|
||||||
|
for _, k in ipairs(SCHALTER) do w[k] = wahr(z[k]) end
|
||||||
|
w.consume = 0
|
||||||
|
w.client_export = sollExport or ''
|
||||||
|
|
||||||
|
local okU, erfolg, grund = pcall(function() return exports[ITEMS]:adminItemUpdate(0, z.id, w) end)
|
||||||
|
|
||||||
|
sag((' %-20s %s'):format(it.name,
|
||||||
|
(okU and erfolg == true) and '^2nachgezogen (consume 0)^7'
|
||||||
|
or ('^1FEHLER: %s^7'):format(okU and tostring(grund) or tostring(erfolg))))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local werte = {
|
||||||
|
name = it.name,
|
||||||
|
label = locale('artikel.' .. it.name .. '.name'),
|
||||||
|
description = locale('artikel.' .. it.name .. '.text'),
|
||||||
|
weight = GEWICHT[it.name] or 1000,
|
||||||
|
stack = false,
|
||||||
|
close_ui = true,
|
||||||
|
aktiv = true,
|
||||||
|
consume = 0,
|
||||||
|
client_export = sollExport,
|
||||||
|
}
|
||||||
|
|
||||||
|
local okC, erfolg, grund = pcall(function() return exports[ITEMS]:adminItemCreate(0, werte) end)
|
||||||
|
|
||||||
|
sag((' %-20s %s'):format(it.name,
|
||||||
|
(okC and erfolg == true) and '^2angelegt^7'
|
||||||
|
or ('^1FEHLER: %s^7'):format(okC and tostring(grund) or tostring(erfolg))))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
kleidungAnlegen()
|
||||||
|
end)
|
||||||
|
--[[
|
||||||
|
/tauchstand <id> - WAS LIEGT IN DEN SLOTS? (29.09.2026; seit 07.10.2026 auch Admins im Spiel, F8)
|
||||||
|
|
||||||
|
Betreiber: "nachdem er durchgelaufen ist sagt er du traegst keine
|
||||||
|
taucherflasche". getWorn (server/main.lua) liest rigSlot und maskSlot -
|
||||||
|
dieser Befehl zeigt, was dort wirklich liegt: Itemname, Kleidungskennung,
|
||||||
|
Patrone. Ein `clothing` mit der richtigen Kennung heisst: vor dem
|
||||||
|
/tauchartikel-Eintrag gekauft, die Optik stimmt, das Item nicht.
|
||||||
|
]]
|
||||||
|
Diagnosebefehl('tauchstand', { restricted = 'group.admin' }, function(source, args)
|
||||||
|
|
||||||
|
local ziel = tonumber(args[1])
|
||||||
|
|
||||||
|
if not ziel or not GetPlayerName(ziel) then
|
||||||
|
sag('[d4rk_divegear] tauchstand <server-id> - Spieler nicht gefunden.')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
sag(('[d4rk_divegear] tauchstand %d:'):format(ziel))
|
||||||
|
|
||||||
|
for _, s in ipairs({ { 'Flasche', shared.rigSlot, shared.rigItem }, { 'Maske', shared.maskSlot, shared.maskItem } }) do
|
||||||
|
local slot = exports.d4rk_inventory:GetSlot(ziel, s[2])
|
||||||
|
local m = slot and slot.metadata or {}
|
||||||
|
|
||||||
|
sag((' %-8s Slot %d: %s (soll %s) garment %s patrone %s oxygen %s'):format(s[1], s[2],
|
||||||
|
slot and slot.name or '<leer>', s[3], tostring(m.garment), tostring(m.patrone), tostring(m.oxygen)))
|
||||||
|
end
|
||||||
|
end)
|
||||||
+119
-27
@@ -26,7 +26,7 @@ end
|
|||||||
local function getTankSlot(source, slotId)
|
local function getTankSlot(source, slotId)
|
||||||
if type(slotId) ~= 'number' then return end
|
if type(slotId) ~= 'number' then return end
|
||||||
|
|
||||||
local slot = exports.ox_inventory:GetSlot(source, slotId)
|
local slot = exports.d4rk_inventory:GetSlot(source, slotId)
|
||||||
|
|
||||||
if not slot then return end
|
if not slot then return end
|
||||||
|
|
||||||
@@ -37,6 +37,19 @@ local function getTankSlot(source, slotId)
|
|||||||
return slot, tankConfig
|
return slot, tankConfig
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Die getragene Taucherflasche (Kleidung, shared.rigSlot) und die Patrone darin.
|
||||||
|
--- @return table? slot nil = keine Flasche angezogen
|
||||||
|
--- @return table? tankConfig nil = keine (gueltige) Patrone eingesetzt
|
||||||
|
local function getRig(source)
|
||||||
|
local slot = exports.d4rk_inventory:GetSlot(source, shared.rigSlot)
|
||||||
|
|
||||||
|
if not slot or slot.name ~= shared.rigItem then return nil end
|
||||||
|
|
||||||
|
local patrone = slot.metadata and slot.metadata.patrone
|
||||||
|
|
||||||
|
return slot, patrone and shared.tankByName[patrone] or nil
|
||||||
|
end
|
||||||
|
|
||||||
--- Schreibt die Restluft in die Metadata.
|
--- Schreibt die Restluft in die Metadata.
|
||||||
--- SetMetadata *ersetzt* die Metadata-Tabelle komplett (modules/inventory/server.lua),
|
--- SetMetadata *ersetzt* die Metadata-Tabelle komplett (modules/inventory/server.lua),
|
||||||
--- deshalb wird der bestehende Inhalt kopiert statt nur oxygen gesetzt.
|
--- deshalb wird der bestehende Inhalt kopiert statt nur oxygen gesetzt.
|
||||||
@@ -55,7 +68,7 @@ local function writeTank(source, slot, tankConfig, oxygen)
|
|||||||
-- Items, sonst würde ox_inventory die leere Flasche löschen (items/server.lua).
|
-- Items, sonst würde ox_inventory die leere Flasche löschen (items/server.lua).
|
||||||
metadata.durability = math.floor(oxygen / tankConfig.capacity * 100)
|
metadata.durability = math.floor(oxygen / tankConfig.capacity * 100)
|
||||||
|
|
||||||
exports.ox_inventory:SetMetadata(source, slot.slot, metadata)
|
exports.d4rk_inventory:SetMetadata(source, slot.slot, metadata)
|
||||||
|
|
||||||
return oxygen
|
return oxygen
|
||||||
end
|
end
|
||||||
@@ -64,7 +77,7 @@ end
|
|||||||
--- @param source number
|
--- @param source number
|
||||||
--- @return table[]
|
--- @return table[]
|
||||||
local function getTankList(source)
|
local function getTankList(source)
|
||||||
local found = exports.ox_inventory:Search(source, 'slots', shared.tankNames)
|
local found = exports.d4rk_inventory:Search(source, 'slots', shared.tankNames)
|
||||||
local tanks = {}
|
local tanks = {}
|
||||||
|
|
||||||
for i = 1, #shared.tankNames do
|
for i = 1, #shared.tankNames do
|
||||||
@@ -79,7 +92,7 @@ local function getTankList(source)
|
|||||||
tanks[#tanks + 1] = {
|
tanks[#tanks + 1] = {
|
||||||
slot = slot.slot,
|
slot = slot.slot,
|
||||||
name = name,
|
name = name,
|
||||||
label = (exports.ox_inventory:Items(name) or {}).label or name,
|
label = (exports.d4rk_inventory:Items(name) or {}).label or name,
|
||||||
oxygen = readOxygen(slot, tankConfig),
|
oxygen = readOxygen(slot, tankConfig),
|
||||||
capacity = tankConfig.capacity,
|
capacity = tankConfig.capacity,
|
||||||
pressure = tankConfig.pressure,
|
pressure = tankConfig.pressure,
|
||||||
@@ -99,9 +112,11 @@ local function syncTank(source, slotId, oxygen)
|
|||||||
|
|
||||||
if not oxygen then return end
|
if not oxygen then return end
|
||||||
|
|
||||||
local slot, tankConfig = getTankSlot(source, slotId)
|
-- Seit 28.09.2026 steckt die Luft in der GETRAGENEN Flasche (Kleidung);
|
||||||
|
-- slotId bleibt im Aufruf, zaehlt aber nicht mehr.
|
||||||
|
local slot, tankConfig = getRig(source)
|
||||||
|
|
||||||
if not slot then return end
|
if not slot or not tankConfig then return end
|
||||||
|
|
||||||
local current = readOxygen(slot, tankConfig)
|
local current = readOxygen(slot, tankConfig)
|
||||||
local now = os.time()
|
local now = os.time()
|
||||||
@@ -118,28 +133,102 @@ end
|
|||||||
|
|
||||||
lib.callback.register('d4rk_divegear:server:getTanks', getTankList)
|
lib.callback.register('d4rk_divegear:server:getTanks', getTankList)
|
||||||
|
|
||||||
--- Prüft, ob der Spieler Ausrüstung und Flasche überhaupt noch besitzt.
|
--[[
|
||||||
--- Ohne das konnte man beides im Tauchgang weggeben und trotzdem weiteratmen -
|
WAS TRAEGT ER? - 28.09.2026, ersetzt verifyGear.
|
||||||
--- der Client hielt nur eine Slot-Nummer, die niemand mehr gegenprüfte.
|
|
||||||
--- @return { gear: boolean, tank: number? }
|
|
||||||
lib.callback.register('d4rk_divegear:server:verifyGear', function(source, slotId, name)
|
|
||||||
local gearCount = exports.ox_inventory:Search(source, 'count', shared.gearItem)
|
|
||||||
local slot = getTankSlot(source, slotId)
|
|
||||||
|
|
||||||
if slot and slot.name == name then
|
Flasche (rigSlot) UND Maske (maskSlot) angezogen, und in der Flasche eine
|
||||||
return { gear = (gearCount or 0) > 0, tank = slotId }
|
Patrone. Die Antwort nennt den Grund, wenn etwas fehlt - der Client fragt
|
||||||
end
|
bei jeder Inventaraenderung, und "keine Maske" ist etwas anderes als "keine
|
||||||
|
Patrone".
|
||||||
|
]]
|
||||||
|
lib.callback.register('d4rk_divegear:server:getWorn', function(source)
|
||||||
|
local rig, tankConfig = getRig(source)
|
||||||
|
|
||||||
-- Nur umsortiert? Dann dieselbe Flaschensorte woanders im Inventar suchen,
|
if not rig then return { ok = false, grund = 'keine_flasche' } end
|
||||||
-- sonst kostet jedes Verschieben im Inventar die Ausrüstung.
|
|
||||||
local found = shared.tankByName[name] and exports.ox_inventory:Search(source, 'slots', name)
|
local maske = exports.d4rk_inventory:GetSlot(source, shared.maskSlot)
|
||||||
|
|
||||||
|
if not maske or maske.name ~= shared.maskItem then return { ok = false, grund = 'keine_maske' } end
|
||||||
|
if not tankConfig then return { ok = false, grund = 'keine_patrone' } end
|
||||||
|
|
||||||
|
local patrone = rig.metadata.patrone
|
||||||
|
|
||||||
return {
|
return {
|
||||||
gear = (gearCount or 0) > 0,
|
ok = true,
|
||||||
tank = found and found[1] and found[1].slot or nil
|
tank = {
|
||||||
|
slot = rig.slot,
|
||||||
|
name = patrone,
|
||||||
|
label = (exports.d4rk_inventory:Items(patrone) or {}).label or patrone,
|
||||||
|
oxygen = readOxygen(rig, tankConfig),
|
||||||
|
capacity = tankConfig.capacity,
|
||||||
|
pressure = tankConfig.pressure,
|
||||||
|
maxDepth = tankConfig.maxDepth,
|
||||||
|
gas = tankConfig.gas,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
PATRONE TAUSCHEN - 28.09.2026. Die benutzte Patrone wandert in die
|
||||||
|
getragene Flasche, die alte kommt mit ihrer Restluft als Item zurueck.
|
||||||
|
|
||||||
|
REIHENFOLGE: erst pruefen, ob die alte Patrone ueberhaupt Platz hat, dann
|
||||||
|
die neue nehmen, dann die alte geben - scheitert das Geben, bekommt der
|
||||||
|
Spieler die neue zurueck. Zuletzt die Flasche beschreiben (Metadata
|
||||||
|
KOPIEREN, SetMetadata ersetzt die ganze Tabelle - ENTSCHEIDUNGEN.md).
|
||||||
|
]]
|
||||||
|
lib.callback.register('d4rk_divegear:server:patroneTauschen', function(source, cartSlot)
|
||||||
|
cartSlot = math.tointeger(tonumber(cartSlot))
|
||||||
|
|
||||||
|
if not cartSlot or cartSlot == shared.rigSlot then return false, 'ungueltig' end
|
||||||
|
|
||||||
|
local cart, cartConfig = getTankSlot(source, cartSlot)
|
||||||
|
|
||||||
|
if not cart then return false, 'keine_patrone_im_slot' end
|
||||||
|
|
||||||
|
local rig, altConfig = getRig(source)
|
||||||
|
|
||||||
|
if not rig then return false, 'keine_flasche' end
|
||||||
|
|
||||||
|
local altName = rig.metadata and rig.metadata.patrone
|
||||||
|
local altLuft = altConfig and readOxygen(rig, altConfig) or nil
|
||||||
|
|
||||||
|
if altConfig and not exports.d4rk_inventory:CanCarryItem(source, altName, 1) then
|
||||||
|
return false, 'kein_platz'
|
||||||
|
end
|
||||||
|
|
||||||
|
local neuLuft = readOxygen(cart, cartConfig)
|
||||||
|
|
||||||
|
if not exports.d4rk_inventory:RemoveItem(source, cart.name, 1, nil, cartSlot) then
|
||||||
|
return false, 'fehler'
|
||||||
|
end
|
||||||
|
|
||||||
|
if altConfig then
|
||||||
|
local meta = {
|
||||||
|
oxygen = math.floor(altLuft),
|
||||||
|
durability = math.floor(altLuft / altConfig.capacity * 100),
|
||||||
|
}
|
||||||
|
|
||||||
|
if not exports.d4rk_inventory:AddItem(source, altName, 1, meta) then
|
||||||
|
exports.d4rk_inventory:AddItem(source, cart.name, 1, cart.metadata)
|
||||||
|
return false, 'kein_platz'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local meta = {}
|
||||||
|
|
||||||
|
for key, value in pairs(rig.metadata or {}) do meta[key] = value end
|
||||||
|
|
||||||
|
meta.patrone = cart.name
|
||||||
|
meta.oxygen = math.floor(neuLuft)
|
||||||
|
meta.durability = math.floor(neuLuft / cartConfig.capacity * 100)
|
||||||
|
|
||||||
|
exports.d4rk_inventory:SetMetadata(source, rig.slot, meta)
|
||||||
|
lastSync[source] = os.time()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
lib.callback.register('d4rk_divegear:server:syncTank', function(source, slotId, oxygen)
|
lib.callback.register('d4rk_divegear:server:syncTank', function(source, slotId, oxygen)
|
||||||
return syncTank(source, slotId, oxygen)
|
return syncTank(source, slotId, oxygen)
|
||||||
end)
|
end)
|
||||||
@@ -154,15 +243,18 @@ AddEventHandler('playerDropped', function()
|
|||||||
lastSync[source] = nil
|
lastSync[source] = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
exports.qbx_core:CreateUseableItem(shared.gearItem, function(source)
|
-- Eine Patrone benutzen = sie einsetzen: ueber den Client-Export am Item
|
||||||
TriggerClientEvent('d4rk_divegear:client:useGear', source)
|
-- (client/main.lua, patroneBenutzen). CreateUseableItem feuert fuer Items aus
|
||||||
end)
|
-- d4rk_items nie - consume ist dort immer gesetzt (28.09.2026).
|
||||||
|
|
||||||
|
-- STILLGELEGT (Nachfuellen kommt spaeter ueber Shop/Crafting). Und ACHTUNG fuer
|
||||||
|
-- dann: CreateUseableItem feuert fuer Items aus d4rk_items nie (consume ist
|
||||||
|
-- dort immer gesetzt) - der Weg braucht einen Client-Export wie die Patronen.
|
||||||
exports.qbx_core:CreateUseableItem(shared.fillItem, function(source)
|
exports.qbx_core:CreateUseableItem(shared.fillItem, function(source)
|
||||||
local tanks = getTankList(source)
|
local tanks = getTankList(source)
|
||||||
|
|
||||||
if #tanks == 0 then
|
if #tanks == 0 then
|
||||||
exports.qbx_core:Notify(source, locale('error.no_tank'), 'error')
|
exports.d4rk_bridge:Notify(source, locale('error.no_tank'), 'error')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -175,12 +267,12 @@ exports.qbx_core:CreateUseableItem(shared.fillItem, function(source)
|
|||||||
if not slot then return end
|
if not slot then return end
|
||||||
|
|
||||||
-- Nach dem Progressbar nochmal prüfen: das Item kann in der Zwischenzeit weg sein.
|
-- Nach dem Progressbar nochmal prüfen: das Item kann in der Zwischenzeit weg sein.
|
||||||
if not exports.ox_inventory:RemoveItem(source, shared.fillItem, 1) then return end
|
if not exports.d4rk_inventory:RemoveItem(source, shared.fillItem, 1) then return end
|
||||||
|
|
||||||
writeTank(source, slot, tankConfig, tankConfig.capacity)
|
writeTank(source, slot, tankConfig, tankConfig.capacity)
|
||||||
lastSync[source] = os.time()
|
lastSync[source] = os.time()
|
||||||
|
|
||||||
-- Erst melden wenn das Item tatsächlich weg und die Flasche voll ist.
|
-- Erst melden wenn das Item tatsächlich weg und die Flasche voll ist.
|
||||||
exports.qbx_core:Notify(source, locale('success.tube_filled'), 'success')
|
exports.d4rk_bridge:Notify(source, locale('success.tube_filled'), 'success')
|
||||||
TriggerClientEvent('d4rk_divegear:client:tankRefilled', source, slot.slot, tankConfig.capacity)
|
TriggerClientEvent('d4rk_divegear:client:tankRefilled', source, slot.slot, tankConfig.capacity)
|
||||||
end)
|
end)
|
||||||
Reference in new issue
Block a user