From 930ff6381f690caabc9011711dca1d61f172a687 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Mon, 28 Sep 2026 23:40:24 +0200 Subject: [PATCH 1/7] chore: Luft ohne Flasche 10 s (gemessen), /tauchprobe fuer die Kleidung maxTimeUnderwater stand auf 50 mit "Vanilla-Verhalten"; auf d4rk-rp sind es gemessen 10,00 s (/medwasserprobe). /tauchprobe liest, was SetPedScubaGearVariation an der Kleidung aendert (Maske) und zeigt Komponente 8 bei einem Drawable (Flasche) - plan-divegear.md Schritt 0. Co-Authored-By: Claude Opus 5.5 (1M context) --- client/probe.lua | 109 ++++++++++++++++++++++++++++++++++++++++++++++ config/client.lua | 10 ++++- fxmanifest.lua | 3 ++ 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 client/probe.lua diff --git a/client/probe.lua b/client/probe.lua new file mode 100644 index 0000000..769d1b7 --- /dev/null +++ b/client/probe.lua @@ -0,0 +1,109 @@ +--[[ + 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 + +RegisterCommand('tauchprobe', function(_, args) + local ped = PlayerPedId() + + 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 . ' + .. '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) diff --git a/config/client.lua b/config/client.lua index b4f23cf..674c5ae 100644 --- a/config/client.lua +++ b/config/client.lua @@ -9,8 +9,12 @@ return { --- Intervall des Watchdogs, der prüft ob die Ausrüstung noch am Ped hängt (ms). gearWatchdogIntervalMs = 1000, - --- Sekunden, die der Spieler ohne Ausrüstung unter Wasser überlebt (Vanilla-Verhalten). - maxTimeUnderwater = 50.0, + --- Sekunden Luft ohne Ausruestung - der NORMALWERT dieses Servers. + --- 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. maxTimeUnderwaterOutOfAir = 1.0, @@ -134,6 +138,8 @@ return { --- Taste zum An- und Ausschalten. Der Spieler kann sie in den FiveM- --- 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', --- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit diff --git a/fxmanifest.lua b/fxmanifest.lua index 58798d0..67aa82c 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -18,6 +18,9 @@ server_script 'server/main.lua' client_scripts { 'client/main.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' From e64a1bd3d08c1411f72bb5a9c0dad767504a2ce7 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Mon, 28 Sep 2026 23:48:26 +0200 Subject: [PATCH 2/7] feat: Flasche und Maske als Kleidung, Patronen tauschen, HUD-Anmeldung - Tauchgeraet bereit = Taucherflasche (rigItem, Slot 31) mit Patrone UND Maske (maskItem, Slot 29) angezogen; Server getWorn statt verifyGear, geprueft bei jeder Inventaraenderung und nach jedem Umziehen. Props, diving_gear und der Anzieh-Balken entfallen. - Die fuenf Flaschen sind Patronen: benutzen = in die getragene Flasche einsetzen, die alte kommt mit Restluft zurueck (patroneTauschen). - Faeden mit Laufnummer, damit ein Tausch sie nicht verdoppelt. - exports.ox_inventory -> exports.d4rk_inventory, Meldungen ueber lib.notify (meldung_shared) und d4rk_bridge:Notify. - Konsole beim HUD angemeldet (client/hud.lua), 366 x 165 bei 1080p gemessen; ohne HUD bleibt die CSS-Position. - Nachfuellen (diving_fill) stillgelegt: kommt spaeter ueber Shop/Crafting. Co-Authored-By: Claude Opus 5.5 (1M context) --- client/hud.lua | 50 ++++++++ client/main.lua | 298 +++++++++++++++++++--------------------------- config/shared.lua | 28 ++++- fxmanifest.lua | 4 + locales/de.json | 19 ++- locales/en.json | 19 ++- nui/script.js | 37 +++++- nui/style.css | 12 ++ server/main.lua | 148 ++++++++++++++++++----- 9 files changed, 401 insertions(+), 214 deletions(-) create mode 100644 client/hud.lua diff --git a/client/hud.lua b/client/hud.lua new file mode 100644 index 0000000..213c33b --- /dev/null +++ b/client/hud.lua @@ -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) diff --git a/client/main.lua b/client/main.lua index 0cfbb79..10c1830 100644 --- a/client/main.lua +++ b/client/main.lua @@ -1,12 +1,17 @@ local config = require 'config.client' 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 = { - mask = 0, - tank = 0, 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, --- gesteuert über den replizierten Statebag. local lampOn = false @@ -32,10 +37,6 @@ for i = 1, #config.warnAtSeconds do if dangerThreshold == 0 or threshold < dangerThreshold then dangerThreshold = threshold 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 --- @return string mm:ss @@ -127,50 +128,6 @@ local function disableScuba(outOfAir, ped) SetPedMaxTimeUnderwater(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(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 notifyIfUnavailable boolean? Rückmeldung, wenn das Outfit keine Lampe hat 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 -- besser als ein scheinbar kaputter Keybind. 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 -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 --- und gibt den akzeptierten Stand zurück, damit Client und Inventar nicht auseinanderlaufen. --- @param immediate boolean? true = Fire-and-Forget (Tod, Resource-Stop) @@ -220,8 +169,10 @@ end --- Gemeinsamer Teardown für Ablegen, leere Flasche und Tod. --- @param outOfAir boolean? --- @param immediateSync boolean? -local function removeGear(outOfAir, immediateSync) - syncTank(immediateSync) +--- @param ohneSync boolean? nach einem Patronentausch: der Client-Stand gehoert +--- 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 setLamp(false) @@ -232,7 +183,6 @@ local function removeGear(outOfAir, immediateSync) narcosisDamageBuffer = 0.0 engineDepthWarned = false - deleteGear() disableScuba(outOfAir) -- Stop breathing suit audio end @@ -272,7 +222,7 @@ end lib.callback.register('d4rk_divegear:client:fillTank', function(tanks) if IsPedSwimmingUnderWater(cache.ped) then - exports.qbx_core:Notify(locale('error.underwater'), 'error') + lib.notify({ description = locale('error.underwater'), type = 'error' }) return end @@ -281,7 +231,7 @@ lib.callback.register('d4rk_divegear:client:fillTank', function(tanks) if not tank then return end 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 end @@ -314,23 +264,6 @@ RegisterNetEvent('d4rk_divegear:client:tankRefilled', function(slot, capacity) enableScuba() 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 --- verbliebenen Gasmenge, deshalb reicht der lineare Anteil an der Füllung. --- @return number bar @@ -411,7 +344,9 @@ local function startHudThread() lamp = locale('hud.lamp') }) - while currentGear.enabled do + local meinLauf = gearLauf + + while currentGear.enabled and meinLauf == gearLauf do currentDepth = currentDepth + (getDepth() - currentDepth) * 0.25 local visible = config.hudEnabled @@ -432,6 +367,9 @@ local function startHudThread() Wait(config.hudUpdateIntervalMs) end + -- Abgeloest durch einen neuen Faden (Patronentausch): der zeigt weiter. + if meinLauf ~= gearLauf and currentGear.enabled then return end + currentDepth = 0.0 sendHudState(false) end) @@ -451,7 +389,7 @@ local function updateNarcosis(elapsed) if currentDepth >= engineWarnAt then if not engineDepthWarned then 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 else engineDepthWarned = false @@ -471,7 +409,7 @@ local function updateNarcosis(elapsed) if activeTank and over >= -config.narcosis.warnBeforeMeters then if not narcosisWarned then 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 else narcosisWarned = false @@ -502,8 +440,9 @@ local function startOxygenLevelDecrementerThread() CreateThread(function() local lastTick = GetGameTimer() local sinceSync = 0.0 + local meinLauf = gearLauf - while currentGear.enabled do + while currentGear.enabled and meinLauf == gearLauf do Wait(250) local now = GetGameTimer() @@ -522,13 +461,13 @@ local function startOxygenLevelDecrementerThread() if activeTank.oxygen <= threshold and not warned[threshold] then 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 if activeTank.oxygen <= 0 then disableScuba(true) - exports.qbx_core:Notify(locale('error.out_of_air'), 'error') + lib.notify({ description = locale('error.out_of_air'), type = 'error' }) syncTank() sinceSync = 0.0 -- Stop breathing suit audio @@ -554,17 +493,14 @@ end --- und Fahrzeugausstieg vom Spiel zurückgesetzt werden. local function startGearWatchdogThread() 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 removeGear(false, true) break end - if not isGearIntact() then - deleteGear() - attachGear() - end - if activeTank and activeTank.oxygen > 0 then enableScuba() end @@ -574,31 +510,94 @@ local function startGearWatchdogThread() 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 (Server: CreateUseableItem je + Patrone). Nur an Land, mit Balken - wie vorher das Anlegen. +]] +RegisterNetEvent('d4rk_divegear:client:patroneEinsetzen', function(slot) 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 end - local tanks = lib.callback.await('d4rk_divegear:server:getTanks', false) - - 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({ + if not lib.progressBar({ duration = config.putOnSuitTimeMs, - label = locale('info.put_suit'), + label = locale('info.patrone_einsetzen'), useWhileDead = false, canCancel = true, anim = { @@ -607,26 +606,22 @@ local function putOnSuit() blendIn = 8.0 } }) then - activeTank = tank - warned = {} - - deleteGear() - attachGear() - enableScuba() - currentGear.enabled = true - -- Initiate breathing suit audio - startOxygenLevelDecrementerThread() - startHudThread() - startGearWatchdogThread() + return end -end -RegisterNetEvent('d4rk_divegear:client:useGear', function() - if currentGear.enabled then - takeOffSuit() - else - putOnSuit() + -- Die laufende Luft zuerst sichern - danach gehoert der Client-Stand zur + -- alten Patrone und wird nicht mehr geschrieben (pruefen -> removeGear ohneSync). + if currentGear.enabled then syncTank() end + + 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 + + lib.notify({ description = locale('success.patrone_getauscht'), type = 'success' }) + pruefen() end) lib.addKeybind({ @@ -659,60 +654,17 @@ lib.addKeybind({ lib.onCache('ped', function(ped) if not currentGear.enabled then return end - deleteGear() - attachGear(ped) - if activeTank and activeTank.oxygen > 0 then enableScuba(ped) 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 ---- nur eine Slot-Nummer, die niemand mehr gegenprüfte, und man konnte unbegrenzt ---- weitertauchen. ox_inventory feuert bei jeder Änderung, also einmal nachfragen. -AddEventHandler('ox_inventory:updateInventory', function() - if not currentGear.enabled or not activeTank then return end - - CreateThread(function() - local expected = activeTank - - -- 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) - AddEventHandler('onResourceStop', function(resource) if resource ~= GetCurrentResourceName() then return end syncTank(true) setLamp(false) applyNarcosisStage(0) - deleteGear() SendNUIMessage({ action = 'state', visible = false }) SetEnableScuba(cache.ped, false) SetPedMaxTimeUnderwater(cache.ped, config.maxTimeUnderwater) diff --git a/config/shared.lua b/config/shared.lua index 00e682c..917f2ed 100644 --- a/config/shared.lua +++ b/config/shared.lua @@ -26,9 +26,31 @@ local config = { { 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, - --- weil sowohl der Useable-Item-Handler als auch die Besitzprüfung sie brauchen. - gearItem = 'diving_gear', + --- SEIT 28.09.2026 SIND FLASCHE UND MASKE KLEIDUNG (plan-divegear.md). + --- Betreiber: "die Prop flasche zu einer richtigen taucherfalsche + maske + --- 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: 31 Unterhemd (Komponente 8), + --- 29 Maske (Komponente 1). Welche Komponente die GTA-Taucherflasche + --- wirklich ist, steht erst nach der Messung (Schritt 0) fest. + --- + --- 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 = 29, + + --- 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', --- Flaschen ohne Metadata als voll behandeln. Betrifft alles, was frisch aus diff --git a/fxmanifest.lua b/fxmanifest.lua index 67aa82c..d1172fa 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -16,7 +16,11 @@ shared_scripts { server_script 'server/main.lua' client_scripts { + -- lib.notify im Hausdesign (d4rk_lib) - VOR allen, die melden. + '@d4rk_lib/client/meldung_shared.lua', 'client/main.lua', + -- Die Konsole beim HUD anmelden (d4rk_hud, BauteilAnmelden). + 'client/hud.lua', 'client/light.lua', -- Messung fuer die Umstellung auf Kleidung (plan-divegear.md Schritt 0). -- Wird nach der Messung wieder entfernt. diff --git a/locales/de.json b/locales/de.json index ce2fa74..1855377 100644 --- a/locales/de.json +++ b/locales/de.json @@ -10,11 +10,19 @@ "no_lamp_on_outfit": "Deine Tauchausrüstung hat keine Lampe.", "crush_depth": "Lebensgefahr! Ab %d m zerdrückt dich der Wasserdruck.", "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": { "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": { "put_suit": "Tauchanzug wird angezogen...", @@ -22,7 +30,9 @@ "filling_air": "Sauerstoff wird aufgefüllt...", "oxygen_low": "Nur noch %s Luft!", "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": { "choose_tank": "Tauchflasche wählen" @@ -31,7 +41,8 @@ "air": "Luft", "depth": "Tiefe", "pressure": "Druck", - "lamp": "Lampe" + "lamp": "Lampe", + "konsole": "Tauchkonsole" }, "keybind": { "toggle_lamp": "Tauchlampe an/aus", diff --git a/locales/en.json b/locales/en.json index 9e9460d..45e8216 100644 --- a/locales/en.json +++ b/locales/en.json @@ -10,11 +10,19 @@ "no_lamp_on_outfit": "Your diving suit doesn't have a lamp.", "crush_depth": "Danger! Below %d m the water pressure will crush you.", "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": { "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": { "put_suit": "Putting on your diving suit...", @@ -22,7 +30,9 @@ "filling_air": "Filling air...", "oxygen_low": "Only %s of air left!", "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": { "choose_tank": "Choose an air tank" @@ -31,7 +41,8 @@ "air": "Air", "depth": "Depth", "pressure": "Pressure", - "lamp": "Lamp" + "lamp": "Lamp", + "konsole": "Dive console" }, "keybind": { "toggle_lamp": "Toggle dive lamp", diff --git a/nui/script.js b/nui/script.js index 0a45cfb..1bbadaf 100644 --- a/nui/script.js +++ b/nui/script.js @@ -118,10 +118,35 @@ function formatTime(seconds) { return `${mm}:${ss}`; } -function render(state) { - hud.classList.toggle('hidden', !state.visible); +// Lage vom HUD (d4rk_hud, BauteilAnmelden - client/hud.lua). null = keine, +// 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 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 === 'lage') { + lage = data.lage || null; + lageAnwenden(); + if (letzterStand) render(letzterStand); + } }); // Damit sich das HUD im Browser ohne FiveM anschauen laesst. diff --git a/nui/style.css b/nui/style.css index 1a1b4ba..3ef7378 100644 --- a/nui/style.css +++ b/nui/style.css @@ -60,6 +60,18 @@ body { 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 { font-size: 9px; font-weight: 600; diff --git a/server/main.lua b/server/main.lua index 167dfd4..c425b02 100644 --- a/server/main.lua +++ b/server/main.lua @@ -26,7 +26,7 @@ end local function getTankSlot(source, slotId) 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 @@ -37,6 +37,19 @@ local function getTankSlot(source, slotId) return slot, tankConfig 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. --- SetMetadata *ersetzt* die Metadata-Tabelle komplett (modules/inventory/server.lua), --- 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). 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 end @@ -64,7 +77,7 @@ end --- @param source number --- @return table[] 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 = {} for i = 1, #shared.tankNames do @@ -79,7 +92,7 @@ local function getTankList(source) tanks[#tanks + 1] = { slot = slot.slot, 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), capacity = tankConfig.capacity, pressure = tankConfig.pressure, @@ -99,9 +112,11 @@ local function syncTank(source, slotId, oxygen) 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 now = os.time() @@ -118,28 +133,102 @@ end 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 - ---- 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) +--[[ + WAS TRAEGT ER? - 28.09.2026, ersetzt verifyGear. - if slot and slot.name == name then - return { gear = (gearCount or 0) > 0, tank = slotId } - end + Flasche (rigSlot) UND Maske (maskSlot) angezogen, und in der Flasche eine + Patrone. Die Antwort nennt den Grund, wenn etwas fehlt - der Client fragt + 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, - -- sonst kostet jedes Verschieben im Inventar die Ausrüstung. - local found = shared.tankByName[name] and exports.ox_inventory:Search(source, 'slots', name) + if not rig then return { ok = false, grund = 'keine_flasche' } end + + 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 { - gear = (gearCount or 0) > 0, - tank = found and found[1] and found[1].slot or nil + ok = true, + 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) +--[[ + 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) return syncTank(source, slotId, oxygen) end) @@ -154,15 +243,20 @@ AddEventHandler('playerDropped', function() lastSync[source] = nil end) -exports.qbx_core:CreateUseableItem(shared.gearItem, function(source) - TriggerClientEvent('d4rk_divegear:client:useGear', source) -end) +-- Eine Patrone benutzen = sie in die getragene Flasche einsetzen (28.09.2026). +-- d4rk_inventory ruft den Rueckruf mit den Itemdaten samt Slot +-- (modules/bridge/qbx/server.lua: cb(source, data)). +for i = 1, #shared.tankNames do + exports.qbx_core:CreateUseableItem(shared.tankNames[i], function(source, data) + TriggerClientEvent('d4rk_divegear:client:patroneEinsetzen', source, data and data.slot) + end) +end exports.qbx_core:CreateUseableItem(shared.fillItem, function(source) local tanks = getTankList(source) 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 end @@ -175,12 +269,12 @@ exports.qbx_core:CreateUseableItem(shared.fillItem, function(source) if not slot then return end -- 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) lastSync[source] = os.time() -- 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) end) From accee02a0597bb5dec33af6a9faef9209690985e Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Mon, 28 Sep 2026 23:52:27 +0200 Subject: [PATCH 3/7] feat: Patronen per Client-Export, /tauchartikel legt die Items an CreateUseableItem feuert fuer Items aus d4rk_items nie: consume ist dort immer gesetzt (daten.lua:224), das Inventar erreicht den qbx-Rueckruf nur bei consume == nil (server.lua:623). Die Patronen tragen deshalb client_export = d4rk_divegear.patroneBenutzen und consume 0, wie d4rk_medical. /tauchartikel legt die sieben Items ueber adminItemCreate an, nur auf Zuruf; "nachziehen" stellt bei vorhandenen nur consume und Export richtig. Flasche und Maske consume 0 wie das Kleidungsitem clothing. Co-Authored-By: Claude Opus 5.5 (1M context) --- client/main.lua | 19 +++++- fxmanifest.lua | 6 +- locales/de.json | 30 +++++++++ locales/en.json | 30 +++++++++ server/artikel.lua | 149 +++++++++++++++++++++++++++++++++++++++++++++ server/main.lua | 14 ++--- 6 files changed, 236 insertions(+), 12 deletions(-) create mode 100644 server/artikel.lua diff --git a/client/main.lua b/client/main.lua index 10c1830..1bdcbe2 100644 --- a/client/main.lua +++ b/client/main.lua @@ -586,10 +586,19 @@ CreateThread(function() end) --[[ - EINE PATRONE BENUTZEN = SIE EINSETZEN (Server: CreateUseableItem je - Patrone). Nur an Land, mit Balken - wie vorher das Anlegen. + 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`. ]] -RegisterNetEvent('d4rk_divegear:client:patroneEinsetzen', function(slot) +local function einsetzen(slot) if IsPedSwimming(cache.ped) or cache.vehicle then lib.notify({ description = locale('error.not_standing_up'), type = 'error' }) return @@ -622,6 +631,10 @@ RegisterNetEvent('d4rk_divegear:client:patroneEinsetzen', function(slot) lib.notify({ description = locale('success.patrone_getauscht'), type = 'success' }) pruefen() +end + +exports('patroneBenutzen', function(_, slot) + einsetzen(type(slot) == 'table' and slot.slot or nil) end) lib.addKeybind({ diff --git a/fxmanifest.lua b/fxmanifest.lua index d1172fa..8f89270 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -13,7 +13,11 @@ shared_scripts { '@qbx_core/modules/lib.lua', } -server_script 'server/main.lua' +server_scripts { + 'server/main.lua', + -- /tauchartikel: die Items in d4rk_item anlegen, nur auf Zuruf (28.09.2026). + 'server/artikel.lua', +} client_scripts { -- lib.notify im Hausdesign (d4rk_lib) - VOR allen, die melden. diff --git a/locales/de.json b/locales/de.json index 1855377..b9eee54 100644 --- a/locales/de.json +++ b/locales/de.json @@ -47,5 +47,35 @@ "keybind": { "toggle_lamp": "Tauchlampe an/aus", "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." + } } } diff --git a/locales/en.json b/locales/en.json index 45e8216..8f85334 100644 --- a/locales/en.json +++ b/locales/en.json @@ -47,5 +47,35 @@ "keybind": { "toggle_lamp": "Toggle dive lamp", "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." + } } } diff --git a/server/artikel.lua b/server/artikel.lua new file mode 100644 index 0000000..a50e961 --- /dev/null +++ b/server/artikel.lua @@ -0,0 +1,149 @@ +--[[ + 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. + + NUR AN DER SERVERKONSOLE (restricted, source 0). + + 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 + +RegisterCommand('tauchartikel', function(source, args) + if source ~= 0 then return end + + local nachziehen = args[1] == 'nachziehen' + + if GetResourceState(ITEMS) ~= 'started' then + print('^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 + print('^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 + print('^1[d4rk_divegear] tauchartikel: adminItemList lieferte 0 Items - UNGUELTIG, nichts angelegt.^7') + return + end + + print(('[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 + print((' %-20s vorhanden, passt'):format(it.name)) + elseif not nachziehen then + print((' %-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) + + print((' %-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) + + print((' %-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 +end, true) diff --git a/server/main.lua b/server/main.lua index c425b02..bad7222 100644 --- a/server/main.lua +++ b/server/main.lua @@ -243,15 +243,13 @@ AddEventHandler('playerDropped', function() lastSync[source] = nil end) --- Eine Patrone benutzen = sie in die getragene Flasche einsetzen (28.09.2026). --- d4rk_inventory ruft den Rueckruf mit den Itemdaten samt Slot --- (modules/bridge/qbx/server.lua: cb(source, data)). -for i = 1, #shared.tankNames do - exports.qbx_core:CreateUseableItem(shared.tankNames[i], function(source, data) - TriggerClientEvent('d4rk_divegear:client:patroneEinsetzen', source, data and data.slot) - end) -end +-- Eine Patrone benutzen = sie einsetzen: ueber den Client-Export am Item +-- (client/main.lua, patroneBenutzen). CreateUseableItem feuert fuer Items aus +-- 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) local tanks = getTankList(source) From daefe46cb860b3ba2a32ef9cdb5e08a4868818ba Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 29 Sep 2026 00:10:11 +0200 Subject: [PATCH 4/7] feat: Taucherflasche und Maske im Kleiderladen (Modell m) Gemessen am 29.09.2026 (/d4rk_kleidungsstand, Betreiber): Flasche m:c:8:mp_m_christmas2017:1 (Unterhemd, Slot 31), Maske m:p:1:mp_m_christmas2017:0 - ein PROP im Brillenslot 27, nicht die Maskenkomponente (angenommen war 29). texture -1: alle 26 Farben. /tauchartikel legt die d4rk_garment-Zeilen auf Zuruf an. Modell f fehlt. Co-Authored-By: Claude Opus 5.5 (1M context) --- config/shared.lua | 20 +++++++++++++++---- server/artikel.lua | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/config/shared.lua b/config/shared.lua index 917f2ed..d169bb5 100644 --- a/config/shared.lua +++ b/config/shared.lua @@ -36,9 +36,11 @@ local config = { --- 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: 31 Unterhemd (Komponente 8), - --- 29 Maske (Komponente 1). Welche Komponente die GTA-Taucherflasche - --- wirklich ist, steht erst nach der Messung (Schritt 0) fest. + --- 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 @@ -46,7 +48,17 @@ local config = { rigItem = 'diving_tank', maskItem = 'diving_mask', rigSlot = 31, - maskSlot = 29, + 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): NOCH NICHT GEMESSEN - dort gibt es die Items erst, wenn + --- die Kennungen hier stehen. + 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' }, + }, --- Das Fuell-Item. NICHT ANGELEGT: Nachfuellen kommt spaeter ueber einen --- Shop oder Crafting (Betreiber, 28.09.2026). Der Weg bleibt stehen, damit diff --git a/server/artikel.lua b/server/artikel.lua index a50e961..f73f5c7 100644 --- a/server/artikel.lua +++ b/server/artikel.lua @@ -71,6 +71,52 @@ local function liste() 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 + print('^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 + print((' 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) + + print((' 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 + RegisterCommand('tauchartikel', function(source, args) if source ~= 0 then return end @@ -146,4 +192,6 @@ RegisterCommand('tauchartikel', function(source, args) or ('^1FEHLER: %s^7'):format(okC and tostring(grund) or tostring(erfolg)))) end end + + kleidungAnlegen() end, true) From bfe1d8ee2da3dfc06596bd967ef667ca21755627 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 29 Sep 2026 00:16:48 +0200 Subject: [PATCH 5/7] fix: GTA nimmt Flasche und Maske nicht mehr selbst ab (Flag 409) Gemessen mit /tauchprobe beobachten: beim Wechsel "schwimmt" -> nein verschwanden Komponente 8 (global 123) und Prop 1 gleichzeitig, bei ausgeschaltetem Tauchgeraet; die Items blieben in den Slots. CPED_CONFIG_FLAG_DisableTakeOffScubaGear = 409 (nachgeschlagen) am Spieler-Ped, nach jedem Ped-Wechsel neu, beim Stoppen ab. /tauchprobe beobachten zeigt die Flagge mit an. Co-Authored-By: Claude Opus 5.5 (1M context) --- client/main.lua | 27 +++++++++++++++++++++++++ client/probe.lua | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/client/main.lua b/client/main.lua index 1bdcbe2..3b7c17b 100644 --- a/client/main.lua +++ b/client/main.lua @@ -327,6 +327,8 @@ end exports('setHudVisible', setHudVisible) 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. --- Läuft auch über Wasser weiter, weil getDecayFactor die Tiefe braucht - nur die @@ -672,8 +674,33 @@ lib.onCache('ped', function(ped) end end) +--[[ + GTA NIMMT FLASCHE UND MASKE SELBST AB - gemessen am 29.09.2026 mit + /tauchprobe beobachten (Betreiber): im Moment, in dem "schwimmt" auf nein + springt (flaches Wasser, Boden unter den Fuessen), verschwanden Komponente 8 + (global 123 = GTAs Taucherflasche) und Prop 1 gleichzeitig - bei + 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() + SetPedConfigFlag(PlayerPedId(), ABNEHMEN_AUS, true) +end) + +lib.onCache('ped', function(ped) + SetPedConfigFlag(ped, ABNEHMEN_AUS, true) +end) + AddEventHandler('onResourceStop', function(resource) if resource ~= GetCurrentResourceName() then return end + SetPedConfigFlag(cache.ped, ABNEHMEN_AUS, false) syncTank(true) setLamp(false) diff --git a/client/probe.lua b/client/probe.lua index 769d1b7..82cfee3 100644 --- a/client/probe.lua +++ b/client/probe.lua @@ -62,9 +62,60 @@ local function zeile(ped, e) 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))) From 96c57d56a0fd1356d5214976f05c61077b1d62fd Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 29 Sep 2026 00:21:14 +0200 Subject: [PATCH 6/7] fix: Balken ueber d4rk_lib, /tauchstand zeigt die Slots - lib.progressBar -> exports.d4rk_lib:progress (Betreiber: "beim benutzen kommt noch der alte balken von ox_lib"). - /tauchstand (Serverkonsole): was liegt in rigSlot/maskSlot. Erster Lauf: beide Slots "clothing" mit der richtigen Kennung - vor dem Kleidungseintrag gekauft; getWorn antwortete also richtig. Co-Authored-By: Claude Opus 5.5 (1M context) --- client/main.lua | 4 ++-- server/artikel.lua | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/client/main.lua b/client/main.lua index 3b7c17b..0fce645 100644 --- a/client/main.lua +++ b/client/main.lua @@ -235,7 +235,7 @@ lib.callback.register('d4rk_divegear:client:fillTank', function(tanks) return end - if not lib.progressBar({ + if not exports.d4rk_lib:progress({ duration = config.refillTankTimeMs, label = locale('info.filling_air'), useWhileDead = false, @@ -606,7 +606,7 @@ local function einsetzen(slot) return end - if not lib.progressBar({ + if not exports.d4rk_lib:progress({ duration = config.putOnSuitTimeMs, label = locale('info.patrone_einsetzen'), useWhileDead = false, diff --git a/server/artikel.lua b/server/artikel.lua index f73f5c7..6915d68 100644 --- a/server/artikel.lua +++ b/server/artikel.lua @@ -195,3 +195,32 @@ RegisterCommand('tauchartikel', function(source, args) kleidungAnlegen() end, true) +--[[ + /tauchstand - WAS LIEGT IN DEN SLOTS? (29.09.2026, nur Serverkonsole) + + 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. +]] +RegisterCommand('tauchstand', function(source, args) + if source ~= 0 then return end + + local ziel = tonumber(args[1]) + + if not ziel or not GetPlayerName(ziel) then + print('[d4rk_divegear] tauchstand - Spieler nicht gefunden.') + return + end + + print(('[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 {} + + print((' %-8s Slot %d: %s (soll %s) garment %s patrone %s oxygen %s'):format(s[1], s[2], + slot and slot.name or '', s[3], tostring(m.garment), tostring(m.patrone), tostring(m.oxygen))) + end +end, true) From d4870444dff01a6cd7f902f0e31374367464d091 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 29 Sep 2026 00:28:05 +0200 Subject: [PATCH 7/7] fix: nach dem Patronentausch immer neu einlesen Gleiche Groesse gegen gleiche Groesse galt als dieselbe Patrone - die Konsole zeigte weiter die alte Restluft (Betreiber, 29.09.2026). Co-Authored-By: Claude Opus 5.5 (1M context) --- client/main.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/main.lua b/client/main.lua index 0fce645..13e569f 100644 --- a/client/main.lua +++ b/client/main.lua @@ -632,6 +632,16 @@ local function einsetzen(slot) 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