From e5f47610a14e66f5b2872598bf3052cd8b184247 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 11 Aug 2026 09:11:12 +0200 Subject: [PATCH] feat: Tiefengrenze ueber das Atemgas Macht ein Tiefenlimit mechanisch statt zur Absprache - und oeffnet die tiefen Spots fuer den, der die richtige Flasche dabei hat. Der Grund fuer die realen 40-45 m ist nicht der Druck, sondern der Stickstoff in der Pressluft: tiefer wirkt er narkotisch, ab ~56 m wird auch der Sauerstoff giftig. Wer tiefer will, atmet Trimix, wo Helium einen Teil des Stickstoffs ersetzt. Genau so umgesetzt: - maxDepth und gas pro Flasche in config/shared.lua; Pressluft 45 m, neues diving_tank_trimix 100 m - Darunter Tiefenrausch in drei Stufen: Bildschirmeffekt, Kamerawackeln, hoeherer Luftverbrauch, in der letzten Stufe Schaden. Schwellen, Effekte, Staerken und Schaden komplett in config.narcosis - Vorwarnung 5 m vor der Grenze, Tiefenanzeige im HUD wird rot und pulsiert - Effekte werden beim Auftauchen, Ablegen, Tod und Resource-Stop aufgeraeumt - Auswahlmenue zeigt Gas und Tiefengrenze pro Flasche - Icon fuer die Trimix-Flasche, violett abgesetzt Keine Dekompression - auf Wunsch bewusst weggelassen. Co-Authored-By: Claude Opus 5 --- README.md | 57 ++++++++++++---- TESTING.md | 36 ++++++++--- client/main.lua | 108 ++++++++++++++++++++++++++++++- config/client.lua | 51 +++++++++++++++ config/shared.lua | 17 +++-- icons/diving_fill.png | Bin 7884 -> 7884 bytes icons/diving_gear.png | Bin 6915 -> 6915 bytes icons/diving_tank_large.png | Bin 6240 -> 6240 bytes icons/diving_tank_medium.png | Bin 5997 -> 5997 bytes icons/diving_tank_small.png | Bin 5519 -> 5519 bytes icons/diving_tank_trimix.png | Bin 0 -> 6831 bytes icons/diving_tank_xl.png | Bin 6328 -> 6328 bytes icons/gen-icons.mjs | 17 ++++- icons/src/diving_tank_trimix.svg | 34 ++++++++++ items_for_ox_inventory.lua | 10 ++- locales/de.json | 3 +- locales/en.json | 3 +- nui/script.js | 1 + nui/style.css | 6 ++ server/main.lua | 4 +- 20 files changed, 315 insertions(+), 32 deletions(-) create mode 100644 icons/diving_tank_trimix.png create mode 100644 icons/src/diving_tank_trimix.svg diff --git a/README.md b/README.md index 0c2c4b3..511abf1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Wer es nur unter Wasser will: `hudOnlyUnderwater = true`. sehen den Lichtkegel, und zwar in die Richtung, in die tatsächlich geschaut wird. - **Tiefenanzeige** in Metern. Optional steigt der Luftverbrauch mit der Tiefe (`depthDecayEnabled`, per Default aus). +- **Tiefengrenze übers Atemgas** — mit Pressluft ab 45 m Tiefenrausch, mit Trimix + erst ab 100 m. Macht das Tiefenlimit mechanisch statt zur Absprache. - **Ausrüstung bleibt** — bei Emotes, Ragdoll, Kleiderwechsel und aufräumenden Fremd-Scripts. - **HUD ausblendbar** für Unterwasser-Screenshots. @@ -60,9 +62,9 @@ Wer es nur unter Wasser will: `hudOnlyUnderwater = true`. 2. [`items_for_ox_inventory.lua`](items_for_ox_inventory.lua) komplett markieren und in `ox_inventory/data/items.lua` innerhalb der `return { … }`-Klammer - einfügen. Sechs Items, nichts weiter anzupassen. + einfügen. Sieben Items, nichts weiter anzupassen. -3. Die sechs PNGs aus [`icons/`](icons) nach `cdn.d4rkst3r.de/items/` hochladen. +3. Die sieben PNGs aus [`icons/`](icons) nach `cdn.d4rkst3r.de/items/` hochladen. Sie heißen genau wie die Items, deshalb braucht es kein `client.image`. Es gibt bewusst **keine** fertige `items.lua` zum Ersetzen — die würde die @@ -70,14 +72,17 @@ restlichen Items des Servers überschreiben. ## Items -| Icon | Item | Label | Laufzeit | Fülldruck | Gewicht | -|---|---|---|---|---|---| -| | `diving_gear` | Tauchausrüstung | — | — | 5000 | -| | `diving_fill` | Pressluft-Kartusche | — | — | 1000 | -| | `diving_tank_small` | Tauchflasche (5 Min) | 300 s | 300 bar | 4000 | -| | `diving_tank_medium` | Tauchflasche (10 Min) | 600 s | 300 bar | 7000 | -| | `diving_tank_large` | Tauchflasche (15 Min) | 900 s | 300 bar | 10000 | -| | `diving_tank_xl` | Tauchflasche (20 Min) | 1200 s | 300 bar | 13000 | +| Icon | Item | Label | Laufzeit | Gas | max. Tiefe | Gewicht | +|---|---|---|---|---|---|---| +| | `diving_gear` | Tauchausrüstung | — | — | — | 5000 | +| | `diving_fill` | Pressluft-Kartusche | — | — | — | 1000 | +| | `diving_tank_small` | Tauchflasche (5 Min) | 300 s | Pressluft | 45 m | 4000 | +| | `diving_tank_medium` | Tauchflasche (10 Min) | 600 s | Pressluft | 45 m | 7000 | +| | `diving_tank_large` | Tauchflasche (15 Min) | 900 s | Pressluft | 45 m | 10000 | +| | `diving_tank_xl` | Tauchflasche (20 Min) | 1200 s | Pressluft | 45 m | 13000 | +| | `diving_tank_trimix` | Trimix-Flasche (15 Min) | 900 s | Trimix | 100 m | 11000 | + +Alle Flaschen starten bei 300 bar. `diving_gear` und `diving_fill` stammen aus dem Original (dort hießen sie *Diving Gear* und *Diving Tube*). Neu sind nur die vier Flaschen: im Original war @@ -130,7 +135,7 @@ FiveM* umlegbar. | Option | Default | Bedeutung | |---|---|---| -| `tanks` | 4 Flaschen | `name`, `capacity` (Sekunden), `pressure` (bar) | +| `tanks` | 5 Flaschen | `name`, `capacity` (Sekunden), `pressure` (bar), `maxDepth` (Meter), `gas` (Label fürs Menü) | | `newTanksFull` | `true` | Flaschen ohne Metadata (Shop, `AddItem`, Admin-Befehl) gelten als voll. Auf `false` muss jede neue Flasche erst mit einer Kartusche befüllt werden | | `syncIntervalSeconds` | `15` | Wie oft die Restluft an den Server gemeldet wird. Bei einem Crash gehen maximal so viele Sekunden verloren | | `maxDrainPerSecond` | `5.0` | Obergrenze der serverseitigen Prüfung | @@ -158,6 +163,14 @@ FiveM* umlegbar. | `maxDecayFactor` | `4.0` | Deckel für den Faktor | | `deepWarningDepth` | `25.0` | Ab hier wird die Tiefenanzeige eingefärbt | +**Tiefenrausch** (`narcosis`) + +| Option | Default | Bedeutung | +|---|---|---| +| `enabled` | `true` | Komplett abschaltbar | +| `warnBeforeMeters` | `5.0` | Vorwarnung so viele Meter vor der Grenze | +| `stages` | 3 Stufen | Pro Stufe: `over` (Meter unter der Grenze), `timecycle` + `strength`, `shake` + `shakeIntensity`, `drainFactor`, `damagePerTick` | + **HUD** | Option | Default | Bedeutung | @@ -200,6 +213,28 @@ ausmessen. > derselben Polizei-Taschenlampe: `prop_cs_police_torch`, `prop_police_torch`, > `prop_scn_police_torch`. Für die Beleuchtung braucht es das Prop ohnehin nicht. +## Tiefengrenze und Tiefenrausch + +Warum 40–45 m real das Limit sind, liegt nicht am Druck, sondern am **Stickstoff +in der Pressluft**: tiefer wirkt er narkotisch, und ab etwa 56 m wird auch der +Sauerstoff giftig. Wer tiefer will, atmet **Trimix** — dort ersetzt Helium einen +Teil des Stickstoffs. + +Genau so ist es hier umgesetzt: jede Flasche hat in `config/shared.lua` eine +`maxDepth`. Darunter setzt Tiefenrausch ein, in drei Stufen, je tiefer desto +heftiger — Bildschirmeffekt, Kamerawackeln, höherer Luftverbrauch, in der letzten +Stufe Schaden. Fünf Meter vorher kommt eine Warnung, und die Tiefenanzeige im HUD +wird rot und pulsiert, sobald die Grenze überschritten ist. + +Damit ist ein 45-m-Limit keine Absprache mehr, sondern spürbar — und die tiefen +Spots bleiben erreichbar, kosten aber die richtige Flasche. + +Alles daran ist in `config/client.lua` unter `narcosis` einstellbar: Schwellen, +Effekte, Stärke, Verbrauchsaufschlag, Schaden. `enabled = false` schaltet es ganz aus. + +> Die `timecycle`-Namen sind Spiel-Effekte, keine Natives. Existiert einer nicht, +> passiert einfach nichts — dann einen anderen eintragen. + ## Exports ```lua diff --git a/TESTING.md b/TESTING.md index 0504f6a..1dd1f2e 100644 --- a/TESTING.md +++ b/TESTING.md @@ -32,7 +32,7 @@ tatsächlich im Spiel lief — nicht, was „sollte gehen". - [ ] 🟡 Flasche im Inventar ansehen → `durability`-Balken zeigt den Füllstand - [ ] 🟡 Flasche einem anderen Spieler geben → Restluft geht mit - [ ] 🟡 Flasche in eine Kiste legen und wieder rausnehmen → Restluft bleibt -- [ ] 🟢 Alle vier Größen einmal durch → Laufzeiten passen ungefähr (5/10/15/20 min) +- [ ] 🟢 Alle Größen einmal durch → Laufzeiten passen (5/10/15/20 min, Trimix 15) ## 3 · Auffüllen @@ -50,13 +50,33 @@ tatsächlich im Spiel lief — nicht, was „sollte gehen". - [ ] 🔴 Untertauchen → Luft läuft, Anzeige zählt runter - [ ] 🟡 Warnungen bei 60 / 30 / 10 Sekunden → je genau **einmal** - [ ] 🔴 Flasche leer unter Wasser → Meldung, Scuba aus, Ertrinken setzt ein -- [ ] 🟡 Tief tauchen → Verbrauch steigt sichtbar schneller als an der Oberfläche +- [ ] 🟢 Mit `depthDecayEnabled = true` tief tauchen → Verbrauch steigt sichtbar. + *Per Default aus, damit die Laufzeiten exakt stimmen* - [ ] 🟢 20-Minuten-Flasche komplett leer tauchen → Laufzeit stimmt ungefähr, keine Drift durch Frame-Drops -## 5 · HUD +## 5 · Tiefengrenze und Tiefenrausch + +*Neu, komplett ungetestet. Die `timecycle`-Namen sind geraten — falls kein Effekt +kommt, in `config/client.lua` unter `narcosis.stages` einen anderen eintragen.* + +- [ ] ❓ 🟡 Mit Pressluft auf ~40 m → Vorwarnung „Du näherst dich der Grenze" +- [ ] ❓ 🔴 Unter 45 m → Bildschirmeffekt setzt ein, Tiefenanzeige rot und pulsierend +- [ ] ❓ 🟡 Unter 55 m → Effekt stärker, Kamera wackelt +- [ ] ❓ 🟡 Unter 65 m → Schaden, Luft geht spürbar schneller weg +- [ ] ❓ 🔴 Auftauchen über die Grenze → **alle** Effekte verschwinden wieder +- [ ] ❓ 🔴 Mit **Trimix** auf 60 m → kein Tiefenrausch +- [ ] ❓ 🟡 Ausrüstung ablegen während des Tiefenrauschs → Effekte weg +- [ ] ❓ 🟡 Sterben im Tiefenrausch → Effekte weg, kein Timecycle bleibt hängen +- [ ] ❓ 🟡 `ensure d4rk_divegear` im Tiefenrausch → Effekte weg +- [ ] 🟢 Auswahlmenü zeigt Gas und Tiefengrenze pro Flasche +- [ ] 🟢 `narcosis.enabled = false` → nichts davon passiert + +## 6 · HUD - [x] Erscheint unter Wasser mit Ausrüstung, Tiefe/Luft/Druck stimmen +- [ ] 🟡 An Land mit angelegter Ausrüstung → HUD ist da, Tiefe steht auf 0.0 m + (Finimeter ablesen ohne Inventar) - [ ] 🟡 Auftauchen → HUD blendet weich aus - [ ] 🟡 Ausrüstung ablegen während es sichtbar ist → HUD verschwindet - [ ] 🟢 Farbstufen: bernstein unter 60 s, rot unter 10 s mit Pulsieren @@ -67,7 +87,7 @@ tatsächlich im Spiel lief — nicht, was „sollte gehen". konnte ich nicht prüfen* - [ ] 🟢 Export `exports.d4rk_divegear:setHudVisible(false)` funktioniert -## 6 · Lampe +## 7 · Lampe - [x] Lampen-Anzeige im HUD schaltet um - [ ] ❓ 🔴 **Mit Scuba-Kleidung am Ped** (Component 8) → Lampe leuchtet @@ -78,7 +98,7 @@ tatsächlich im Spiel lief — nicht, was „sollte gehen". - [ ] 🟡 Ausrüstung ablegen mit Lampe an → Lampe geht aus, bleibt aus - [ ] 🟡 Sterben mit Lampe an → Lampe aus -## 7 · Ausrüstung bleibt dran +## 8 · Ausrüstung bleibt dran *Der ursprüngliche Bug. Jeder Punkt hier ist ein eigener Weg, wie die Props verschwinden konnten.* @@ -92,7 +112,7 @@ verschwinden konnten.* - [ ] 🟡 `ensure d4rk_divegear` während des Tauchens → keine Props als Leiche in der Welt zurück -## 8 · Zu zweit +## 9 · Zu zweit - [ ] 🔴 Beide mit Ausrüstung → jeder sieht Maske und Flasche des anderen - [ ] ❓ 🔴 **Beide mit Lampe** → jeder sieht das Licht des anderen @@ -101,14 +121,14 @@ verschwinden konnten.* wenn er sie an hat. *Dafür ist der Abgleich-Thread da* - [ ] 🟡 Einer stirbt/respawnt mit Lampe an → beim anderen geht sie aus -## 9 · Serverseitige Prüfung +## 10 · Serverseitige Prüfung - [ ] 🟢 Normal tauchen → **keine** Warnungen oder Korrekturen im Server-Log - [ ] 🟡 Tief tauchen mit hohem Verbrauch → Server schreibt keine Luft zurück. *Wenn doch: `maxDrainPerSecond` in `config/shared.lua` erhöhen* - [ ] 🟢 Flasche wegwerfen während sie getragen wird → kein Fehler im Log -## 10 · Performance +## 11 · Performance - [ ] 🟢 Längerer Tauchgang → `resmon` bleibt niedrig - [ ] 🟢 Mehrere Taucher mit Lampe gleichzeitig → keine spürbaren FPS-Einbrüche diff --git a/client/main.lua b/client/main.lua index 7a125ff..9bde106 100644 --- a/client/main.lua +++ b/client/main.lua @@ -55,13 +55,61 @@ local function getDepth() return math.max(0.0, waterZ - coords.z) end +--- Aktuell aktive Tiefenrausch-Stufe (Index in config.narcosis.stages), 0 = keine. +local narcosisStage = 0 +local narcosisWarned = false +local narcosisDamageBuffer = 0.0 + +--- Wie weit die aktuelle Tiefe unter der Grenze der getragenen Flasche liegt. +--- @return number Meter unterhalb der Grenze, negativ solange alles gut ist +local function getDepthOverLimit() + if not activeTank or not activeTank.maxDepth then return -math.huge end + + return currentDepth - activeTank.maxDepth +end + +--- Setzt Bildschirmeffekt und Kamerawackeln auf die angegebene Stufe. +--- @param stage number 0 = alles aus +local function applyNarcosisStage(stage) + if stage == narcosisStage then return end + + narcosisStage = stage + + ClearTimecycleModifier() + StopGameplayCamShaking(true) + + local data = stage > 0 and config.narcosis.stages[stage] + + if not data then return end + + if data.timecycle then + SetTimecycleModifier(data.timecycle) + SetTimecycleModifierStrength(data.strength or 1.0) + end + + if data.shake then + ShakeGameplayCam(data.shake, data.shakeIntensity or 0.5) + end +end + --- Verbrauchsfaktor. Tiefer tauchen kostet mehr Luft. --- Nutzt die geglättete Tiefe aus dem HUD-Thread, damit Wellengang den Verbrauch --- nicht im Sekundentakt springen lässt. local function getDecayFactor() - if not config.depthDecayEnabled then return 1.0 end + local factor = 1.0 - return math.min(config.maxDecayFactor, 1.0 + currentDepth / config.depthDecayReference) + if config.depthDecayEnabled then + factor = math.min(config.maxDecayFactor, 1.0 + currentDepth / config.depthDecayReference) + end + + -- Tiefenrausch: unter Stress atmet man mehr. + local stage = narcosisStage > 0 and config.narcosis.stages[narcosisStage] + + if stage then + factor = factor * (stage.drainFactor or 1.0) + end + + return factor end --- @param ped number? default cache.ped - beim Model-Wechsel muss der neue Ped explizit rein @@ -198,8 +246,11 @@ local function removeGear(outOfAir, immediateSync) currentGear.enabled = false setLamp(false) + applyNarcosisStage(0) activeTank = nil warned = {} + narcosisWarned = false + narcosisDamageBuffer = 0.0 deleteGear() disableScuba(outOfAir) @@ -220,7 +271,8 @@ local function chooseTank(tanks) options[i] = { title = tank.label, description = locale('info.tank_remaining', formatTime(tank.oxygen), - math.floor(tank.oxygen / tank.capacity * 100)), + math.floor(tank.oxygen / tank.capacity * 100), + tank.gas or '?', math.floor(tank.maxDepth or 0)), icon = 'fa-solid fa-bottle-water', onSelect = function() selection:resolve(tank) end } @@ -331,6 +383,8 @@ local function sendHudState(visible) reserve = pressure > 0 and pressure <= config.reservePressure, level = level, deep = currentDepth >= config.deepWarningDepth, + -- Grenze der getragenen Flasche überschritten: Tiefenrausch läuft. + overLimit = config.narcosis.enabled and getDepthOverLimit() >= 0, hasLamp = true, lamp = lampOn }) @@ -396,6 +450,51 @@ local function startHudThread() end) end +--- Wertet die Tiefe gegen die Grenze der getragenen Flasche aus und setzt Effekte, +--- Verbrauchsaufschlag und Schaden. Läuft auch über Wasser weiter, damit die +--- Effekte beim Auftauchen wieder verschwinden. +--- @param elapsed number Sekunden seit dem letzten Aufruf +local function updateNarcosis(elapsed) + if not config.narcosis.enabled then return end + + local over = getDepthOverLimit() + local stages = config.narcosis.stages + local stage = 0 + + for i = 1, #stages do + if over >= stages[i].over then stage = i end + end + + applyNarcosisStage(stage) + + -- Vorwarnung, solange noch Zeit zum Umkehren ist. + 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') + end + else + narcosisWarned = false + end + + local data = stage > 0 and stages[stage] + + if not data or (data.damagePerTick or 0) <= 0 then + narcosisDamageBuffer = 0.0 + return + end + + -- Schaden sammeln statt jeden Tick zu runden, sonst geht bei 250 ms Takt + -- alles unter 4 Schaden pro Sekunde verloren. + narcosisDamageBuffer = narcosisDamageBuffer + data.damagePerTick * elapsed + + if narcosisDamageBuffer >= 1.0 then + local damage = math.floor(narcosisDamageBuffer) + narcosisDamageBuffer -= damage + ApplyDamageToPed(cache.ped, damage, false) + end +end + --- Zieht Luft über die tatsächlich vergangene Zeit ab statt über gezählte Ticks. --- Ein Wait(1000) läuft bei Last spürbar länger als eine Sekunde - über einen --- 20-Minuten-Tauchgang summiert sich das sichtbar auf. @@ -411,6 +510,8 @@ local function startOxygenLevelDecrementerThread() local elapsed = (now - lastTick) / 1000 lastTick = now + updateNarcosis(elapsed) + if not activeTank then goto continue end if IsPedSwimmingUnderWater(cache.ped) and activeTank.oxygen > 0 then @@ -622,6 +723,7 @@ AddEventHandler('onResourceStop', function(resource) syncTank(true) setLamp(false) + applyNarcosisStage(0) deleteGear() SendNUIMessage({ action = 'state', visible = false }) SetEnableScuba(cache.ped, false) diff --git a/config/client.lua b/config/client.lua index b4beb7e..6b99b53 100644 --- a/config/client.lua +++ b/config/client.lua @@ -59,6 +59,57 @@ return { --- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter). deepWarningDepth = 25.0, + --------------------------------------------------------------------------- + -- Tiefenrausch + --------------------------------------------------------------------------- + -- Macht die Tiefengrenze mechanisch statt zur Absprache: unterhalb der + -- maxDepth der getragenen Flasche (config/shared.lua) wird es ungemütlich. + -- Mit Pressluft ab 45 m, mit Trimix erst ab 100 m - wer die tiefen Spots + -- sehen will, braucht also die richtige Flasche. + + narcosis = { + enabled = true, + + --- Wie viele Meter *vor* der Grenze schon gewarnt wird. + warnBeforeMeters = 5.0, + + --- Stufen, sobald die Grenze überschritten ist. `over` = Meter unterhalb + --- der maxDepth der Flasche. Es gilt jeweils die höchste erreichte Stufe, + --- die Reihenfolge muss also aufsteigend sein. + --- + --- timecycle Bildschirmeffekt. Namen sind Timecycle-Modifier des + --- Spiels, keine Natives - falls einer nicht existiert, + --- passiert einfach nichts. Alternativen zum Ausprobieren: + --- 'drug_flying_base', 'drug_deadman', 'REDMIST', + --- 'spectator5'. Auf nil = kein Effekt. + --- strength 0.0 bis 1.0 + --- shake Kamerawackeln, z.B. 'DRUNK_SHAKE', 'SKY_DIVING_SHAKE'. + --- Auf nil = kein Wackeln. + --- shakeIntensity Stärke des Wackelns + --- drainFactor Faktor auf den Luftverbrauch (Stress atmet mehr) + --- damagePerTick Schaden pro Sekunde. 0 = kein Schaden. + stages = { + { + over = 0.0, + timecycle = 'drug_wobbly', strength = 0.3, + shake = nil, shakeIntensity = 0.0, + drainFactor = 1.5, damagePerTick = 0 + }, + { + over = 10.0, + timecycle = 'drug_wobbly', strength = 0.7, + shake = 'DRUNK_SHAKE', shakeIntensity = 0.3, + drainFactor = 2.0, damagePerTick = 0 + }, + { + over = 20.0, + timecycle = 'drug_wobbly', strength = 1.0, + shake = 'DRUNK_SHAKE', shakeIntensity = 0.8, + drainFactor = 3.0, damagePerTick = 2 + }, + }, + }, + --- Reservedruck in bar. Darunter wird die Druckanzeige eingefärbt - in der --- Tauchpraxis die Schwelle, ab der man den Aufstieg einleitet. reservePressure = 50, diff --git a/config/shared.lua b/config/shared.lua index 21f1720..ca593be 100644 --- a/config/shared.lua +++ b/config/shared.lua @@ -3,18 +3,27 @@ local config = { --- Tauchflaschen. + --- --- capacity = Laufzeit in Sekunden bei normalem Verbrauch. --- pressure = Fülldruck in bar. Reine Anzeige: der angezeigte Druck ist --- pressure * (Restluft / capacity). Bei konstantem Volumen ist der --- Druck proportional zur verbliebenen Gasmenge, das passt also. --- Dass alle Flaschen denselben Fülldruck haben ist gewollt - in echt --- macht das Volumen die Laufzeit, nicht der Druck. + --- maxDepth = Tiefe in Metern, bis zu der das Atemgas verträglich ist. + --- Darunter setzt Tiefenrausch ein (siehe narcosis in config/client.lua). + --- Der Grund ist real der Stickstoff in der Pressluft: tiefer wirkt er + --- narkotisch, und ab ~56 m wird auch der Sauerstoff giftig. Wer tiefer + --- will, atmet Trimix - dort ersetzt Helium einen Teil des Stickstoffs. + --- gas = Nur fürs Auswahlmenü, damit man die Flaschen unterscheiden kann. + --- --- Reihenfolge = Reihenfolge im Auswahlmenü. tanks = { - { name = 'diving_tank_small', capacity = 300, pressure = 300 }, -- 5 Minuten - { name = 'diving_tank_medium', capacity = 600, pressure = 300 }, -- 10 Minuten - { name = 'diving_tank_large', capacity = 900, pressure = 300 }, -- 15 Minuten - { name = 'diving_tank_xl', capacity = 1200, pressure = 300 }, -- 20 Minuten + { name = 'diving_tank_small', capacity = 300, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 5 Minuten + { name = 'diving_tank_medium', capacity = 600, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 10 Minuten + { name = 'diving_tank_large', capacity = 900, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 15 Minuten + { name = 'diving_tank_xl', capacity = 1200, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 20 Minuten + { name = 'diving_tank_trimix', capacity = 900, pressure = 300, maxDepth = 100.0, gas = 'Trimix' }, -- 15 Minuten, tieftauglich }, --- Flaschen ohne Metadata als voll behandeln. Betrifft alles, was frisch aus diff --git a/icons/diving_fill.png b/icons/diving_fill.png index d419cdc2d830acd59e1c0cd356a16ca0ab24f9ac..79c90fba024281e24b673e004491a1861c8f2755 100644 GIT binary patch delta 65 zcmX?Od&YJ{g`9|Ch=IA4fu)s^skVWEm4U%Nm17qt_sA(Ipi8_oe9p%J1fH&bF6*2U FngEa?6Al0X delta 65 zcmX?Od&YJ{g`9{%h>?+%k%^U&nYMv}m4QJ)Ifu~X9ytXCbP4q&&9e+Z;OXk;vd$@? F2>?rT5c&WB diff --git a/icons/diving_gear.png b/icons/diving_gear.png index 3bc5d6a6dc62af965d52436e97e894cd0e671808..935d92fd31d2e1d7f1553f18e750b1aca45478f5 100644 GIT binary patch delta 92 zcmZoRYc|`kUMj>e#K7Fjz|zXdRNKJ7%D`Zs%CUO#K_3X$i&LXOxwV~%D|wYoI{9#fkC6hHKHUXu_V>1Br`X)xFj*R gfWgSX$V}J3Le~&ok9v~kS)d*UPgg&ebxsLQ0FQDQW&i*H diff --git a/icons/diving_tank_large.png b/icons/diving_tank_large.png index c013931466f2df2f16c46fa0f1361086a9acdf66..04d88a225345a215e84ba58dab9b924184b2f287 100644 GIT binary patch delta 65 zcmaE0@W5aLkA#R}h=IA4fu)s^skVWEm4U%Nm17qtD@Z6Pz$MHe5;t!Coy-6Pp00i_ I>zopr0D0RJYXATM delta 65 zcmaE0@W5aLkA#Rph>?+%k%^U&nYMv}m4QJ)Ifu|>1qlTObP4q&&9e+Z;OXk;vd$@? F2>>Kn5F`Kq diff --git a/icons/diving_tank_medium.png b/icons/diving_tank_medium.png index c11ba022dbd468fdcd243baff71e1d17eefe7d32..8699cc1cda835039dbf433d184a21a3bce1b3573 100644 GIT binary patch delta 65 zcmaE>_f~I%q_~J-h=IA4fu)s^skVWEm4U%Nm17qt>xwHVz$MHe5;t!Coy-6Pp00i_ I>zopr0E6KZnE(I) delta 65 zcmaE>_f~I%q_~Jdh>?+%k%^U&nYMv}m4QJ)Ifu|>U2z2kbP4q&&9e+Z;OXk;vd$@? F2>>o%5K;gD diff --git a/icons/diving_tank_small.png b/icons/diving_tank_small.png index 53af1c59e5c1c36b0f76fd65da5ca2204285d895..25a6adc7efec17ae799472490954239186a9d1f4 100644 GIT binary patch delta 65 zcmeCz?$_R6Eh=IdVqk7%U}CkWni#R<=Dl^{-O#Ba0xSr#EqMOCo=$nr>mdK II;Vst0AFhp7XSbN delta 65 zcmeCz?$_R6Eh=IVVq|1xWMXAxrfpzgWnfTH&LK3}UsOQ>E@2LlXcSedWB>wBS3j3^ HP61yEFB_wW~%ZUyO3QA!%5SwK>{Q$k9*Ly%ZdL8&Fg1wpz~knUQEr9o1Ax%WAD?%lKJdF~VEoacVkR97S>pd|o-K*X<=rex#^NQ?iR2ad0XKp>jv*K#tt?`L*@`UOzP&vXa>6-cm-$4~wgi}i|x2d=ho-z@JF2y6W{xUp{tY2jc(k^6F6j{{jtk&T2SS)gn zhy3;B$y@$+b~DNJvY5}3d;5=X@}>0V#%@kri_(jNiW&$WfWQ7*b)k`Fy<`BDJxpM#VE@j62%R_ZW9r-5(L2AN5%wJFSO~ z4t8_F{IDYG<|RiFl89|Z{1Tp=%zDbn`Yx$DL8o9xQPTaKV6IoIru4-7;TOJ z9>UqbeHkznxTCSOM0{Gr`FHw!K7l{o@6SX-EM$((0i&vYzrA?Cl5bQ!t?G*8!`d8$ zazHl^=bNO)rT(Fi@cBS0khZpJUa=`N#4HofSn9o@%n15&N1QV968YVzd2mH(aUn_7 zvYGnH#DYHxaem9Qy^%4N|C;G~ynFr$?l4Y!`&$~{lB)w*&>^SIA0m|Ekz^E?VQk6= zA;FiYrL|?}{xV~o+j|#Fi3U>`L^kD3w+(;6?+%0?)Y+=-W&I{aU;OnPii4aL!kRsA zMhx?%$uhbr`jZ8Fx1SjNsI0GlC;Q<%XyYecv*mpLyYY}-*JcFlDEFc}SwNpu^=*ol zgkR-13n7oy91Y)adg!CjbCNQK8-w8B_t|mLHPjiSL}@V;K4-^6AvGzLyU(8^tUEA$ zc*>{hMz&@A>BB2D(ltK;z1AwVWo3TlMb(307?OWHy}S_;;>^$;grE;{Wo@!*VgAl{ zS2OeiClddUmht8#R;S*K@7-T9dHQrlEGn?w;|-jOG9KcSry(2y)II19Po6Q- zCEAd)>smtsLupP=IB2bcnlSg_kOkGXk(g^SQK!y@RnRT={it2G9PMs7+2S6S&M58> zZK}X8_T2%Ikyh=j$Kw_dnTc4+{=NF60CRnr{%7T?yJ7N=zDBvfDTU*06jo<~f#{I= z#c`e0wT}}*Vm9!(;+2c~)UH}O}`z`y|f5S3-;oE~z zm9SXV{@PbR@Cbi-x|MIKmvvUmEK`#sQMqjZV2(kPBeyE5-GZNnNLp6b)YOy#xQtfF zF5Rz0ZE^-)yzX!7Mop|;rul7kw;RwU$$?fuGtuaZNS5%q=V9w>$;w@gsv*YC2Bzj@ zSTdO)IV}qDJmF?fO*RUG&zQW)s(EEqI@CD@nnV*QRMJ5_Dm5cx*>!AV7gknSQO$Tp zf{iiz72RBaO*4S|W2m@@f*@BrcN>l3NF4PZckuNkB{eA?7IJZMs16U;?exusFnTlI z9sSeOlPA5cmLXX*F0>L$!o+N9{&043nn=*pC_a2P>vSDEw7kwDd0<@X#UMlWi7rVY z8yUO1;}EFX8*VC*Xw(6tdZx3p6I@VUURqj;O$)G%!q95+ z*D)l^VfkGQkdjhwAso!Q=K@S_aQp%7+wkOavb1!V=rA}l)1&1d#a}!BlY=8nLE1K? zivK$*#CL2{?y0$4+g2?x6Q+>g_8onZR2co(1DL}usTrHzoJCRvKJ|kl{#DK0NNmGG z-9lZC;|U{1blIrlWr8D*UQZlzO-uS7&ZBlmxeGP--x(Y{wbcxAI-gWH6)L`m22T<+ zV|tPQ^QmiWY%DjuUbwZlZ}hoYvRqz~LX#azJ2HduuS1PNSW8`+go`+Q z$*L^b4|r*baXw-!zM&Asm5V!eyu{NZqbA3KR^;t>9ku<~BiOQ9hy~k$xvHo!OTWY1 zE)k1NOzBjyXjH9h=h-`t=`OV7^ATw$KCf#`@_Q z2u;hkbUAHPhQ=>&vc{lhUR}QgvgUyL+mX;ofy$#JC8pWUvAM^%AQ0InM*ll_r_TCN zx6dukK0LN<{TIZkNHA>RA=7_&%)uch#{UqHvDrcW-ZR>?nmo^I2Nt^5meCR08xVe8B_BRjZ_CqJ>3-^Re9yD7PG6er&hkP5zN*vcR*J zLfF*gEXJ$Q67%odU~g|MDE~uhUhnPP zZz}A`hr8zDuoHKZ)Ip5*AI{C_(jL+~89y(>^4YWY)U>VlF}1koF|Xsq&h(HZE)j#6 zmz`JDNf|qywoEl=hN)iLS~+L{+e~b489_+J{RY%>?Xu7^*ND;FQc_aVAJ7^gytiiJ zCAP+&tB~xu5NPw^$RItYy2Xca%hNlMo;d$&`dP&OBc`;h5o*d63Rm==_!%enujlK} zOP(^{6LNWM?q@SE@%Y&@5fL^#ODERemy&^!fu?Vx zQ%YA=8cd3a*MBhZUdaY8Q82aIlYgKWJ8P)7fYAu}>?HBE5%M&hj1uyI_5ipRoOYQK z`w$2Ob`tN~(VXjj+^0{ova+%VuBYX8EJ@`Rg_}4f`gDYy>f(_~>1UfObs_|tetx2DkWTLJ43v2!69oYqo2T4&BT`--62&a3JFuy}k{w{4|==@#fm2kY>u1 z?xV+;Z`T*`J?fWz%VkwNvS(5H$cIy!XEnxZSAu_W*PK}X1qRf?k+{S|T!t2o;^Kti zo}gBkDqSyzYC8AE`F`-28WXedMx)U;*C+P% z=_hd~hv)lqvmQ_QT4)6hP6&A}x~{rN-ppl(m+tP^(GOzu$Z8v5(nZKs8xJz^S+4fX zy8N#8uIZkf94a>`|9+VRlhYk9ttDY_P)BkpjWrfwbKk{8{;m#Wh+YweK0D*mh>1=d zJCZoL@1pg~52^6`#`YU4$naoa(h@^roP#2NV zOJ{C2wr9_tb=U^x@Pkxpk9#i^3x15pnl~G8mn#o?%Ij0|URqV4IVI_fvJaDOG>NEy-qw;xmmT^&W8z`C-H__Tln>jc zlSz{B8!y;M$5nvEL`6k{#~p-)H8f02R775Qd!L@_>9xf%Cnt;hfJ|!82c3P1&aIyEtm|$*Vrlq2&4Q7;xFnBo);4WovA!oplFX zPd^`cKAahRE5K_%`qOvt2X@bzek$i#cX1@u?2NkOR4G`Rrv{5KoH!xDBU*#(tiM1@ zON&&}I_|Bd&UD05r?WHs<<11guWKz#s^jw;-4*gV3{p~IVI;o(l;P$<@y+))jou(E z8866xUcc7-1v`EExUR)mp6`6JB%MgW#nliw6>fi;q;FXj+2rkwY#6AwSIqQ7khq1k zg~c0sGCJ|MuU=u2J$PFm`v|monj(bpLO%d~81YD2U*BS}$*(Q`IZRT>hF~~gc_@m= zaS|4zu8uQNLL*$(t1MbKzfe0_mYSNY5|S(+>^k3M1R+1@Vb+jpehYRxpBvbpyxQmV zj`>n|dxxX}5;hWrXv{p-Wbt#jIh0-J$KDHeQFqH3!NuxH62|c@CK0qhL z$<}ivD!~6Euk@xYM~-8$yqV?E!RjhZ3PhI!%Ev zykC8AtXj1(1b+V9Kk%KYj{iXnlZKjeJrDEBg?k~(e#te1kkd3j@_Jy%yaIFT*HYKl zCJA;{9#RoEi~adLA0;jryUx`qiqn+-0Wi)%7H!bY%{mc_Hjt=dzWDp$%G}Y)byy4z zFwy|_3{65qp^f`#yl>|veoGn|;gWJ2V>!)K1>a@Kw;7nPZ6Lc<)NYGgE6GY_N!kY4 z*?W60Pjrx&Ow=Ml0gil7Iz;!oE3d~w6KE0Fd7_O0l8faJ^|*_r^ciay9?h`=Ti+qN z>ibo%!UVUiRZ-BTtSEl$sQ(4l+oAFC@ks;Sq%kxV`hk05VoeQ_)&~X3*igCLBxhaC zkyv{uGbK=1xV2?cV`yEstkQCE;chZ(LkvvjR|H|d7875rRY^?F)GjcJ*91vwn#>Mc zv<4-*QLfSkFSKMUPL|{P>?o<-bW4G-m}w8Q*jAGrhV5E)LZ?kNvqa6P(82iEV(j<{ zyy-v9s=nA_v+VqMK?*UC&k54{?KV zJAQvvSEtAnxWi|$*p}i#D?+AO{1;eL?PFx@i}f2DYE&B=Q^s6epszAD@h~lTsiRrQ zfs#ledu_t*+t_H3XT#}@cFTwzf3hmH_nE7_76pcu;D(C5R+Vb4gt{_vnt4I5DjsDbnqpm*q~4Ymr($ zPNSYe74@yKq@*C03g%(^Z|)^6DeONQx_WwgMuWSoRhI-&T z@wxFAl@h;3UmkCS{QH-hN=W78Y%`evXZ?F{A7#tSbDbwZ;vopc=g-*K>j5>Gg>0tH z4Ne-xyyf|TyM~oBB9w(Pao2X zF6jX(K7JKFI;sL!Rte#0nW}RiW9cRia_1(t7Jcuw(9--Nq_6MD1Gxv;3-f{q@x=u) zUo5j8cRb1&O1$~(#JZgjze<@~y1UCsKY`Js>%=vho_xI5h$cicyf&%ryL>ljZEI_b znIar~X`>t=dNn&2M|cL4VifZ_$e>uObs9akge2Y_oK7Yp5J>)3HFcK2vN9O(e@^uC z7)oEV{wJ5@SomUh>)_wev@x8?6eQw>XV~n&*7vtKWab`Yli3fuJi0xCTD~kdc8t#>2E4rFI`2utJ#3 zn#~3ke&rIbF85CP8Myba-g6yWDk6e_fb*jv`dz==9#1Qqxn4w{djd=1-V5~Pmc3e7 zT`XjGxLeDe7Wc7^Zwp7MwT_JqjN?2c$k+Lw-5aFYdQtnw>336z%Fd3W;@qIa&Tvu& zWi{;4G4F;bqPEG&$=Y`s83sI3Qs(A`Pz+OpwzWsj+;4jBQ}VOj*=omblEUk~o(CB% zE%w9eE`3G$6)POOtG&BiQ#mltC0><}bJS7xOTOGri;+A>WY)Y^uQ2;;Q zh}`+}=QCjiBVonvXDY+{90{p)QX{|Dl5kzPwpTP5W1Ikq0X`839h{w=?JX=IRo-kI z2~Up+dsp*>pKnL_%UR|#2l||_P0oh$b&Hw-a literal 0 HcmV?d00001 diff --git a/icons/diving_tank_xl.png b/icons/diving_tank_xl.png index dfe3d2e5b896a022d216ae22f9bd1f6b3263a401..35a334ecf0722364303146091eec8b0b0d455414 100644 GIT binary patch delta 65 zcmdmCxWjNmiiC(^h=IA4fu)s^skVWEm4U%Nm17qtS4b!*z$MHe5;t!Coy-6Pp00i_ I>zopr0Ebx<%>V!Z delta 65 zcmdmCxWjNmiiC(kh>?+%k%^U&nYMv}m4QJ)Ifu~X3JC=TxP&=GqES?-k^u-jUHx3v IIVCg!04`+^IRF3v diff --git a/icons/gen-icons.mjs b/icons/gen-icons.mjs index 45f699e..f9ef830 100644 --- a/icons/gen-icons.mjs +++ b/icons/gen-icons.mjs @@ -40,7 +40,8 @@ const BANDS = { small: '#4ade80', medium: '#38bdf8', large: '#fbbf24', - xl: '#f472b6' + xl: '#f472b6', + trimix: '#a78bfa' }; const defs = ` @@ -113,6 +114,17 @@ ${tank(82, 38, 38, 118, BANDS.xl)} font-size="16" font-weight="700" fill="${C.outline}">20` )); +// --- Trimix: Doppelflasche mit eigenem Farbband, deutlich abgesetzt von den +// --- Pressluft-Flaschen, weil die Verwechslung teuer waere. +writeFileSync(join(OUT, 'diving_tank_trimix.svg'), svg( + ` +${tank(46, 38, 38, 118, BANDS.trimix)} +${tank(82, 38, 38, 118, BANDS.trimix)} + + TRIMIX` +)); + // --- Tauchmaske writeFileSync(join(OUT, 'diving_gear.svg'), svg( ` @@ -145,7 +157,8 @@ writeFileSync(join(OUT, 'diving_fill.svg'), svg( // --- Kontrollblatt const NAMES = [ 'diving_gear', 'diving_fill', - 'diving_tank_small', 'diving_tank_medium', 'diving_tank_large', 'diving_tank_xl' + 'diving_tank_small', 'diving_tank_medium', 'diving_tank_large', 'diving_tank_xl', + 'diving_tank_trimix' ]; const slots = (extra = '') => diff --git a/icons/src/diving_tank_trimix.svg b/icons/src/diving_tank_trimix.svg new file mode 100644 index 0000000..39b0842 --- /dev/null +++ b/icons/src/diving_tank_trimix.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TRIMIX + diff --git a/items_for_ox_inventory.lua b/items_for_ox_inventory.lua index 0ea01e4..2deb96e 100644 --- a/items_for_ox_inventory.lua +++ b/items_for_ox_inventory.lua @@ -3,7 +3,7 @@ -- ============================================================================= -- -- Alles unten komplett markieren und innerhalb der `return {` ... `}`-Klammer --- einfügen. Sechs Items, nichts weiter anzupassen. +-- einfügen. Sieben Items, nichts weiter anzupassen. -- -- Bewusst KEINE fertige items.lua zum Ersetzen: die würde die restlichen Items -- des Servers mitlöschen. @@ -73,3 +73,11 @@ close = true, description = 'Doppelflasche, 300 bar. Reicht für etwa 20 Minuten.' }, + +['diving_tank_trimix'] = { + label = 'Trimix-Flasche (15 Min)', + weight = 11000, + stack = false, + close = true, + description = 'Helium-Sauerstoff-Stickstoff-Gemisch, 300 bar. Kein Tiefenrausch bis 100 m.' +}, diff --git a/locales/de.json b/locales/de.json index 30eaa0a..0725166 100644 --- a/locales/de.json +++ b/locales/de.json @@ -18,7 +18,8 @@ "pullout_suit": "Tauchanzug wird ausgezogen...", "filling_air": "Sauerstoff wird aufgefüllt...", "oxygen_low": "Nur noch %s Luft!", - "tank_remaining": "%s verbleibend (%d%%)" + "tank_remaining": "%s verbleibend (%d%%) · %s bis %d m", + "depth_limit": "Du näherst dich der Grenze deines Atemgases (%d m)!" }, "menu": { "choose_tank": "Tauchflasche wählen" diff --git a/locales/en.json b/locales/en.json index 44fe2af..98faa5d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -18,7 +18,8 @@ "pullout_suit": "Taking off your diving suit...", "filling_air": "Filling air...", "oxygen_low": "Only %s of air left!", - "tank_remaining": "%s remaining (%d%%)" + "tank_remaining": "%s remaining (%d%%) · %s down to %d m", + "depth_limit": "You're approaching the limit of your breathing gas (%d m)!" }, "menu": { "choose_tank": "Choose an air tank" diff --git a/nui/script.js b/nui/script.js index fc7c86a..38cf095 100644 --- a/nui/script.js +++ b/nui/script.js @@ -45,6 +45,7 @@ function render(state) { hud.classList.toggle('warn', state.level === 'warn'); hud.classList.toggle('danger', state.level === 'danger'); hud.classList.toggle('deep', !!state.deep); + hud.classList.toggle('overlimit', !!state.overLimit); lamp.classList.toggle('hidden', !state.hasLamp); lamp.classList.toggle('on', !!state.lamp); diff --git a/nui/style.css b/nui/style.css index a39a3d1..d73f09b 100644 --- a/nui/style.css +++ b/nui/style.css @@ -199,6 +199,12 @@ body { #hud.deep #depth-value { color: var(--warn); } +/* Tiefengrenze des Atemgases überschritten - Tiefenrausch läuft. */ +#hud.overlimit #depth-value { + color: var(--danger); + animation: pulse 1s ease-in-out infinite; +} + /* Reservedruck: in der Tauchpraxis die Schwelle zum Aufstieg. */ #pressure.reserve .value { color: var(--danger); } diff --git a/server/main.lua b/server/main.lua index dc7c0b2..cc5a52f 100644 --- a/server/main.lua +++ b/server/main.lua @@ -82,7 +82,9 @@ local function getTankList(source) label = (exports.ox_inventory:Items(name) or {}).label or name, oxygen = readOxygen(slot, tankConfig), capacity = tankConfig.capacity, - pressure = tankConfig.pressure + pressure = tankConfig.pressure, + maxDepth = tankConfig.maxDepth, + gas = tankConfig.gas } end end