feat: Tiefengrenze ueber das Atemgas
Lint / Lint Resource (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 09:11:12 +02:00
co-authored by Claude Opus 5
parent e8ffe8870d
commit e5f47610a1
20 changed files with 315 additions and 32 deletions
+46 -11
View File
@@ -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. sehen den Lichtkegel, und zwar in die Richtung, in die tatsächlich geschaut wird.
- **Tiefenanzeige** in Metern. Optional steigt der Luftverbrauch mit der Tiefe - **Tiefenanzeige** in Metern. Optional steigt der Luftverbrauch mit der Tiefe
(`depthDecayEnabled`, per Default aus). (`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 - **Ausrüstung bleibt** — bei Emotes, Ragdoll, Kleiderwechsel und aufräumenden
Fremd-Scripts. Fremd-Scripts.
- **HUD ausblendbar** für Unterwasser-Screenshots. - **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 2. [`items_for_ox_inventory.lua`](items_for_ox_inventory.lua) komplett markieren
und in `ox_inventory/data/items.lua` innerhalb der `return { … }`-Klammer 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`. 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 Es gibt bewusst **keine** fertige `items.lua` zum Ersetzen — die würde die
@@ -70,14 +72,17 @@ restlichen Items des Servers überschreiben.
## Items ## Items
| Icon | Item | Label | Laufzeit | Fülldruck | Gewicht | | Icon | Item | Label | Laufzeit | Gas | max. Tiefe | Gewicht |
|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| <img src="icons/diving_gear.png" width="40"> | `diving_gear` | Tauchausrüstung | — | — | 5000 | | <img src="icons/diving_gear.png" width="40"> | `diving_gear` | Tauchausrüstung | — | — | — | 5000 |
| <img src="icons/diving_fill.png" width="40"> | `diving_fill` | Pressluft-Kartusche | — | — | 1000 | | <img src="icons/diving_fill.png" width="40"> | `diving_fill` | Pressluft-Kartusche | — | — | — | 1000 |
| <img src="icons/diving_tank_small.png" width="40"> | `diving_tank_small` | Tauchflasche (5 Min) | 300 s | 300 bar | 4000 | | <img src="icons/diving_tank_small.png" width="40"> | `diving_tank_small` | Tauchflasche (5 Min) | 300 s | Pressluft | 45 m | 4000 |
| <img src="icons/diving_tank_medium.png" width="40"> | `diving_tank_medium` | Tauchflasche (10 Min) | 600 s | 300 bar | 7000 | | <img src="icons/diving_tank_medium.png" width="40"> | `diving_tank_medium` | Tauchflasche (10 Min) | 600 s | Pressluft | 45 m | 7000 |
| <img src="icons/diving_tank_large.png" width="40"> | `diving_tank_large` | Tauchflasche (15 Min) | 900 s | 300 bar | 10000 | | <img src="icons/diving_tank_large.png" width="40"> | `diving_tank_large` | Tauchflasche (15 Min) | 900 s | Pressluft | 45 m | 10000 |
| <img src="icons/diving_tank_xl.png" width="40"> | `diving_tank_xl` | Tauchflasche (20 Min) | 1200 s | 300 bar | 13000 | | <img src="icons/diving_tank_xl.png" width="40"> | `diving_tank_xl` | Tauchflasche (20 Min) | 1200 s | Pressluft | 45 m | 13000 |
| <img src="icons/diving_tank_trimix.png" width="40"> | `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_fill` stammen aus dem Original (dort hießen sie
*Diving Gear* und *Diving Tube*). Neu sind nur die vier Flaschen: im Original war *Diving Gear* und *Diving Tube*). Neu sind nur die vier Flaschen: im Original war
@@ -130,7 +135,7 @@ FiveM* umlegbar.
| Option | Default | Bedeutung | | 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 | | `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 | | `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 | | `maxDrainPerSecond` | `5.0` | Obergrenze der serverseitigen Prüfung |
@@ -158,6 +163,14 @@ FiveM* umlegbar.
| `maxDecayFactor` | `4.0` | Deckel für den Faktor | | `maxDecayFactor` | `4.0` | Deckel für den Faktor |
| `deepWarningDepth` | `25.0` | Ab hier wird die Tiefenanzeige eingefärbt | | `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** **HUD**
| Option | Default | Bedeutung | | Option | Default | Bedeutung |
@@ -200,6 +213,28 @@ ausmessen.
> derselben Polizei-Taschenlampe: `prop_cs_police_torch`, `prop_police_torch`, > derselben Polizei-Taschenlampe: `prop_cs_police_torch`, `prop_police_torch`,
> `prop_scn_police_torch`. Für die Beleuchtung braucht es das Prop ohnehin nicht. > `prop_scn_police_torch`. Für die Beleuchtung braucht es das Prop ohnehin nicht.
## Tiefengrenze und Tiefenrausch
Warum 4045 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 ## Exports
```lua ```lua
+28 -8
View File
@@ -32,7 +32,7 @@ tatsächlich im Spiel lief — nicht, was „sollte gehen".
- [ ] 🟡 Flasche im Inventar ansehen → `durability`-Balken zeigt den Füllstand - [ ] 🟡 Flasche im Inventar ansehen → `durability`-Balken zeigt den Füllstand
- [ ] 🟡 Flasche einem anderen Spieler geben → Restluft geht mit - [ ] 🟡 Flasche einem anderen Spieler geben → Restluft geht mit
- [ ] 🟡 Flasche in eine Kiste legen und wieder rausnehmen → Restluft bleibt - [ ] 🟡 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 ## 3 · Auffüllen
@@ -50,13 +50,33 @@ tatsächlich im Spiel lief — nicht, was „sollte gehen".
- [ ] 🔴 Untertauchen → Luft läuft, Anzeige zählt runter - [ ] 🔴 Untertauchen → Luft läuft, Anzeige zählt runter
- [ ] 🟡 Warnungen bei 60 / 30 / 10 Sekunden → je genau **einmal** - [ ] 🟡 Warnungen bei 60 / 30 / 10 Sekunden → je genau **einmal**
- [ ] 🔴 Flasche leer unter Wasser → Meldung, Scuba aus, Ertrinken setzt ein - [ ] 🔴 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, - [ ] 🟢 20-Minuten-Flasche komplett leer tauchen → Laufzeit stimmt ungefähr,
keine Drift durch Frame-Drops 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 - [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 - [ ] 🟡 Auftauchen → HUD blendet weich aus
- [ ] 🟡 Ausrüstung ablegen während es sichtbar ist → HUD verschwindet - [ ] 🟡 Ausrüstung ablegen während es sichtbar ist → HUD verschwindet
- [ ] 🟢 Farbstufen: bernstein unter 60 s, rot unter 10 s mit Pulsieren - [ ] 🟢 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* konnte ich nicht prüfen*
- [ ] 🟢 Export `exports.d4rk_divegear:setHudVisible(false)` funktioniert - [ ] 🟢 Export `exports.d4rk_divegear:setHudVisible(false)` funktioniert
## 6 · Lampe ## 7 · Lampe
- [x] Lampen-Anzeige im HUD schaltet um - [x] Lampen-Anzeige im HUD schaltet um
- [ ] ❓ 🔴 **Mit Scuba-Kleidung am Ped** (Component 8) → Lampe leuchtet - [ ] ❓ 🔴 **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 - [ ] 🟡 Ausrüstung ablegen mit Lampe an → Lampe geht aus, bleibt aus
- [ ] 🟡 Sterben mit Lampe an → Lampe 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 *Der ursprüngliche Bug. Jeder Punkt hier ist ein eigener Weg, wie die Props
verschwinden konnten.* verschwinden konnten.*
@@ -92,7 +112,7 @@ verschwinden konnten.*
- [ ] 🟡 `ensure d4rk_divegear` während des Tauchens → keine Props als Leiche - [ ] 🟡 `ensure d4rk_divegear` während des Tauchens → keine Props als Leiche
in der Welt zurück in der Welt zurück
## 8 · Zu zweit ## 9 · Zu zweit
- [ ] 🔴 Beide mit Ausrüstung → jeder sieht Maske und Flasche des anderen - [ ] 🔴 Beide mit Ausrüstung → jeder sieht Maske und Flasche des anderen
- [ ] ❓ 🔴 **Beide mit Lampe** → jeder sieht das Licht 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* wenn er sie an hat. *Dafür ist der Abgleich-Thread da*
- [ ] 🟡 Einer stirbt/respawnt mit Lampe an → beim anderen geht sie aus - [ ] 🟡 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 - [ ] 🟢 Normal tauchen → **keine** Warnungen oder Korrekturen im Server-Log
- [ ] 🟡 Tief tauchen mit hohem Verbrauch → Server schreibt keine Luft zurück. - [ ] 🟡 Tief tauchen mit hohem Verbrauch → Server schreibt keine Luft zurück.
*Wenn doch: `maxDrainPerSecond` in `config/shared.lua` erhöhen* *Wenn doch: `maxDrainPerSecond` in `config/shared.lua` erhöhen*
- [ ] 🟢 Flasche wegwerfen während sie getragen wird → kein Fehler im Log - [ ] 🟢 Flasche wegwerfen während sie getragen wird → kein Fehler im Log
## 10 · Performance ## 11 · Performance
- [ ] 🟢 Längerer Tauchgang → `resmon` bleibt niedrig - [ ] 🟢 Längerer Tauchgang → `resmon` bleibt niedrig
- [ ] 🟢 Mehrere Taucher mit Lampe gleichzeitig → keine spürbaren FPS-Einbrüche - [ ] 🟢 Mehrere Taucher mit Lampe gleichzeitig → keine spürbaren FPS-Einbrüche
+105 -3
View File
@@ -55,13 +55,61 @@ local function getDepth()
return math.max(0.0, waterZ - coords.z) return math.max(0.0, waterZ - coords.z)
end 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. --- Verbrauchsfaktor. Tiefer tauchen kostet mehr Luft.
--- Nutzt die geglättete Tiefe aus dem HUD-Thread, damit Wellengang den Verbrauch --- Nutzt die geglättete Tiefe aus dem HUD-Thread, damit Wellengang den Verbrauch
--- nicht im Sekundentakt springen lässt. --- nicht im Sekundentakt springen lässt.
local function getDecayFactor() 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 end
--- @param ped number? default cache.ped - beim Model-Wechsel muss der neue Ped explizit rein --- @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 currentGear.enabled = false
setLamp(false) setLamp(false)
applyNarcosisStage(0)
activeTank = nil activeTank = nil
warned = {} warned = {}
narcosisWarned = false
narcosisDamageBuffer = 0.0
deleteGear() deleteGear()
disableScuba(outOfAir) disableScuba(outOfAir)
@@ -220,7 +271,8 @@ local function chooseTank(tanks)
options[i] = { options[i] = {
title = tank.label, title = tank.label,
description = locale('info.tank_remaining', formatTime(tank.oxygen), 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', icon = 'fa-solid fa-bottle-water',
onSelect = function() selection:resolve(tank) end onSelect = function() selection:resolve(tank) end
} }
@@ -331,6 +383,8 @@ local function sendHudState(visible)
reserve = pressure > 0 and pressure <= config.reservePressure, reserve = pressure > 0 and pressure <= config.reservePressure,
level = level, level = level,
deep = currentDepth >= config.deepWarningDepth, deep = currentDepth >= config.deepWarningDepth,
-- Grenze der getragenen Flasche überschritten: Tiefenrausch läuft.
overLimit = config.narcosis.enabled and getDepthOverLimit() >= 0,
hasLamp = true, hasLamp = true,
lamp = lampOn lamp = lampOn
}) })
@@ -396,6 +450,51 @@ local function startHudThread()
end) end)
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. --- 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 --- Ein Wait(1000) läuft bei Last spürbar länger als eine Sekunde - über einen
--- 20-Minuten-Tauchgang summiert sich das sichtbar auf. --- 20-Minuten-Tauchgang summiert sich das sichtbar auf.
@@ -411,6 +510,8 @@ local function startOxygenLevelDecrementerThread()
local elapsed = (now - lastTick) / 1000 local elapsed = (now - lastTick) / 1000
lastTick = now lastTick = now
updateNarcosis(elapsed)
if not activeTank then goto continue end if not activeTank then goto continue end
if IsPedSwimmingUnderWater(cache.ped) and activeTank.oxygen > 0 then if IsPedSwimmingUnderWater(cache.ped) and activeTank.oxygen > 0 then
@@ -622,6 +723,7 @@ AddEventHandler('onResourceStop', function(resource)
syncTank(true) syncTank(true)
setLamp(false) setLamp(false)
applyNarcosisStage(0)
deleteGear() deleteGear()
SendNUIMessage({ action = 'state', visible = false }) SendNUIMessage({ action = 'state', visible = false })
SetEnableScuba(cache.ped, false) SetEnableScuba(cache.ped, false)
+51
View File
@@ -59,6 +59,57 @@ return {
--- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter). --- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter).
deepWarningDepth = 25.0, 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 --- Reservedruck in bar. Darunter wird die Druckanzeige eingefärbt - in der
--- Tauchpraxis die Schwelle, ab der man den Aufstieg einleitet. --- Tauchpraxis die Schwelle, ab der man den Aufstieg einleitet.
reservePressure = 50, reservePressure = 50,
+13 -4
View File
@@ -3,18 +3,27 @@
local config = { local config = {
--- Tauchflaschen. --- Tauchflaschen.
---
--- capacity = Laufzeit in Sekunden bei normalem Verbrauch. --- capacity = Laufzeit in Sekunden bei normalem Verbrauch.
--- pressure = Fülldruck in bar. Reine Anzeige: der angezeigte Druck ist --- pressure = Fülldruck in bar. Reine Anzeige: der angezeigte Druck ist
--- pressure * (Restluft / capacity). Bei konstantem Volumen ist der --- pressure * (Restluft / capacity). Bei konstantem Volumen ist der
--- Druck proportional zur verbliebenen Gasmenge, das passt also. --- Druck proportional zur verbliebenen Gasmenge, das passt also.
--- Dass alle Flaschen denselben Fülldruck haben ist gewollt - in echt --- Dass alle Flaschen denselben Fülldruck haben ist gewollt - in echt
--- macht das Volumen die Laufzeit, nicht der Druck. --- 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ü. --- Reihenfolge = Reihenfolge im Auswahlmenü.
tanks = { tanks = {
{ name = 'diving_tank_small', capacity = 300, pressure = 300 }, -- 5 Minuten { name = 'diving_tank_small', capacity = 300, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 5 Minuten
{ name = 'diving_tank_medium', capacity = 600, pressure = 300 }, -- 10 Minuten { name = 'diving_tank_medium', capacity = 600, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 10 Minuten
{ name = 'diving_tank_large', capacity = 900, pressure = 300 }, -- 15 Minuten { name = 'diving_tank_large', capacity = 900, pressure = 300, maxDepth = 45.0, gas = 'Pressluft' }, -- 15 Minuten
{ name = 'diving_tank_xl', capacity = 1200, pressure = 300 }, -- 20 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 --- Flaschen ohne Metadata als voll behandeln. Betrifft alles, was frisch aus
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

+15 -2
View File
@@ -40,7 +40,8 @@ const BANDS = {
small: '#4ade80', small: '#4ade80',
medium: '#38bdf8', medium: '#38bdf8',
large: '#fbbf24', large: '#fbbf24',
xl: '#f472b6' xl: '#f472b6',
trimix: '#a78bfa'
}; };
const defs = `<defs> const defs = `<defs>
@@ -113,6 +114,17 @@ ${tank(82, 38, 38, 118, BANDS.xl)}
font-size="16" font-weight="700" fill="${C.outline}">20</text>` font-size="16" font-weight="700" fill="${C.outline}">20</text>`
)); ));
// --- Trimix: Doppelflasche mit eigenem Farbband, deutlich abgesetzt von den
// --- Pressluft-Flaschen, weil die Verwechslung teuer waere.
writeFileSync(join(OUT, 'diving_tank_trimix.svg'), svg(
` <rect x="38" y="26" width="52" height="8" rx="4" fill="${C.valveDark}"/>
${tank(46, 38, 38, 118, BANDS.trimix)}
${tank(82, 38, 38, 118, BANDS.trimix)}
<rect x="20" y="65" width="88" height="23" rx="4" fill="${BANDS.trimix}" stroke="${C.outline}" stroke-width="2"/>
<text x="64" y="82" text-anchor="middle" font-family="sans-serif"
font-size="14" font-weight="700" fill="${C.outline}">TRIMIX</text>`
));
// --- Tauchmaske // --- Tauchmaske
writeFileSync(join(OUT, 'diving_gear.svg'), svg( writeFileSync(join(OUT, 'diving_gear.svg'), svg(
` <path d="M18 62 L30 52 L30 78 L18 72 Z" fill="${C.rubber}"/> ` <path d="M18 62 L30 52 L30 78 L18 72 Z" fill="${C.rubber}"/>
@@ -145,7 +157,8 @@ writeFileSync(join(OUT, 'diving_fill.svg'), svg(
// --- Kontrollblatt // --- Kontrollblatt
const NAMES = [ const NAMES = [
'diving_gear', 'diving_fill', '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 = '') => const slots = (extra = '') =>
+34
View File
@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
<defs>
<linearGradient id="steel" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="28%" stop-color="#cbd5e1"/>
<stop offset="55%" stop-color="#94a3b8"/>
<stop offset="100%" stop-color="#64748b"/>
</linearGradient>
<linearGradient id="glass" x1="0" y1="0" x2="0.7" y2="1">
<stop offset="0%" stop-color="#38bdf8"/>
<stop offset="100%" stop-color="#0284c7"/>
</linearGradient>
</defs>
<rect x="38" y="26" width="52" height="8" rx="4" fill="#1e293b"/>
<g>
<rect x="38.4" y="24" width="15.200000000000001" height="17" rx="3" fill="#334155"/>
<circle cx="46" cy="21" r="8" fill="none" stroke="#1e293b" stroke-width="4.5"/>
<rect x="27" y="38" width="38" height="80" rx="15.833333333333334" fill="url(#steel)" stroke="#0f172a" stroke-width="2.5"/>
<rect x="32.7" y="44.4" width="4.94" height="40" rx="2.47" fill="#cbd5e1" opacity="0.75"/>
<rect x="27" y="65.2" width="38" height="22" fill="#a78bfa"/>
</g>
<g>
<rect x="74.4" y="24" width="15.200000000000001" height="17" rx="3" fill="#334155"/>
<circle cx="82" cy="21" r="8" fill="none" stroke="#1e293b" stroke-width="4.5"/>
<rect x="63" y="38" width="38" height="80" rx="15.833333333333334" fill="url(#steel)" stroke="#0f172a" stroke-width="2.5"/>
<rect x="68.7" y="44.4" width="4.94" height="40" rx="2.47" fill="#cbd5e1" opacity="0.75"/>
<rect x="63" y="65.2" width="38" height="22" fill="#a78bfa"/>
</g>
<rect x="20" y="65" width="88" height="23" rx="4" fill="#a78bfa" stroke="#0f172a" stroke-width="2"/>
<text x="64" y="82" text-anchor="middle" font-family="sans-serif"
font-size="14" font-weight="700" fill="#0f172a">TRIMIX</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+9 -1
View File
@@ -3,7 +3,7 @@
-- ============================================================================= -- =============================================================================
-- --
-- Alles unten komplett markieren und innerhalb der `return {` ... `}`-Klammer -- 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 -- Bewusst KEINE fertige items.lua zum Ersetzen: die würde die restlichen Items
-- des Servers mitlöschen. -- des Servers mitlöschen.
@@ -73,3 +73,11 @@
close = true, close = true,
description = 'Doppelflasche, 300 bar. Reicht für etwa 20 Minuten.' 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.'
},
+2 -1
View File
@@ -18,7 +18,8 @@
"pullout_suit": "Tauchanzug wird ausgezogen...", "pullout_suit": "Tauchanzug wird ausgezogen...",
"filling_air": "Sauerstoff wird aufgefüllt...", "filling_air": "Sauerstoff wird aufgefüllt...",
"oxygen_low": "Nur noch %s Luft!", "oxygen_low": "Nur noch %s Luft!",
"tank_remaining": "%s verbleibend (%d%%)" "tank_remaining": "%s verbleibend (%d%%) · %s bis %d m",
"depth_limit": "Du näherst dich der Grenze deines Atemgases (%d m)!"
}, },
"menu": { "menu": {
"choose_tank": "Tauchflasche wählen" "choose_tank": "Tauchflasche wählen"
+2 -1
View File
@@ -18,7 +18,8 @@
"pullout_suit": "Taking off your diving suit...", "pullout_suit": "Taking off your diving suit...",
"filling_air": "Filling air...", "filling_air": "Filling air...",
"oxygen_low": "Only %s of air left!", "oxygen_low": "Only %s of air left!",
"tank_remaining": "%s remaining (%d%%)" "tank_remaining": "%s remaining (%d%%) · %s down to %d m",
"depth_limit": "You're approaching the limit of your breathing gas (%d m)!"
}, },
"menu": { "menu": {
"choose_tank": "Choose an air tank" "choose_tank": "Choose an air tank"
+1
View File
@@ -45,6 +45,7 @@ function render(state) {
hud.classList.toggle('warn', state.level === 'warn'); hud.classList.toggle('warn', state.level === 'warn');
hud.classList.toggle('danger', state.level === 'danger'); hud.classList.toggle('danger', state.level === 'danger');
hud.classList.toggle('deep', !!state.deep); hud.classList.toggle('deep', !!state.deep);
hud.classList.toggle('overlimit', !!state.overLimit);
lamp.classList.toggle('hidden', !state.hasLamp); lamp.classList.toggle('hidden', !state.hasLamp);
lamp.classList.toggle('on', !!state.lamp); lamp.classList.toggle('on', !!state.lamp);
+6
View File
@@ -199,6 +199,12 @@ body {
#hud.deep #depth-value { color: var(--warn); } #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. */ /* Reservedruck: in der Tauchpraxis die Schwelle zum Aufstieg. */
#pressure.reserve .value { color: var(--danger); } #pressure.reserve .value { color: var(--danger); }
+3 -1
View File
@@ -82,7 +82,9 @@ local function getTankList(source)
label = (exports.ox_inventory:Items(name) or {}).label or name, label = (exports.ox_inventory:Items(name) or {}).label or name,
oxygen = readOxygen(slot, tankConfig), oxygen = readOxygen(slot, tankConfig),
capacity = tankConfig.capacity, capacity = tankConfig.capacity,
pressure = tankConfig.pressure pressure = tankConfig.pressure,
maxDepth = tankConfig.maxDepth,
gas = tankConfig.gas
} }
end end
end end