Files
d4rk_divegear/README.md
T
D4rkst3randClaude Opus 5 e5f47610a1
Lint / Lint Resource (push) Has been cancelled
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 <noreply@anthropic.com>
2026-08-11 09:11:12 +02:00

320 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# d4rk_divegear
Tauchausrüstung für QBox. Fork von [qbx_divegear](https://github.com/Qbox-project/qbx_divegear)
mit Flaschengrößen, gesyncter Lampe, Tiefen- und Druckanzeige — und Ausrüstung,
die bei Animationen nicht mehr verschwindet.
## HUD
> Die Bilder sind maßstabsgetreue Renderings des Overlays aus denselben Werten wie
> `nui/style.css` — keine In-Game-Aufnahmen. Erzeugt mit `docs/gen-hud-svg.mjs`.
**Normal** — Luft, Tiefe, Fülldruck, Lampe an
![HUD, normaler Zustand](docs/hud-normal.svg)
**Warnung** — Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth`,
Druck unter `reservePressure`
![HUD, Warnstufe](docs/hud-warnung.svg)
**Kritisch** — letzte Sekunden, der Ring pulsiert
![HUD, kritischer Luftstand](docs/hud-kritisch.svg)
Das HUD erscheint, sobald die Ausrüstung angelegt ist — also auch an Land, damit
sich der Flaschendruck am Finimeter ablesen lässt, ohne das Inventar zu öffnen.
Wer es nur unter Wasser will: `hudOnlyUnderwater = true`.
## Features
- **Vier Flaschengrößen** — 5, 10, 15 und 20 Minuten. Die Restluft liegt in der
Item-Metadata und überlebt Ablegen, Relog, Resource-Restart und Tod. Der
Füllstand ist als Balken direkt im Inventar-Slot sichtbar.
- **Manometer in bar** — physikalisch korrekt aus dem Restanteil abgeleitet,
Reservedruck wird eingefärbt.
- **Gesyncte Tauchlampe** — an der Ausrüstung, An/Aus per Taste. Andere Spieler
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.
- **Serverseitige Prüfung** — die Restluft kann nur sinken, und nicht schneller
als physikalisch möglich.
## Abhängigkeiten
[`qbx_core`](https://github.com/Qbox-project/qbx_core) ·
[`ox_lib`](https://github.com/overextended/ox_lib) ·
[`ox_inventory`](https://github.com/overextended/ox_inventory)
## Installation
1. Ordner nach `resources/` und in der `server.cfg` starten — **nach**
`ox_inventory` und `qbx_core`:
```cfg
ensure d4rk_divegear
```
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. Sieben Items, nichts weiter anzupassen.
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
restlichen Items des Servers überschreiben.
## Items
| 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_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 | Pressluft | 45 m | 4000 |
| <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 | Pressluft | 45 m | 10000 |
| <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 Tube*). Neu sind nur die vier Flaschen: im Original war
die Luft eine Client-Variable, die an keinem Item hing — eine Sauerstoffflasche
als Item gab es dort gar nicht. Für unterschiedliche Laufzeiten und persistente
Restluft braucht es aber ein Item, an dem beides hängen kann.
Ein Taucher trägt damit zwei Dinge: die Ausrüstung und eine Flasche. Die
Kartusche nur, wer unterwegs nachfüllen will.
Die Icons liegen als 128×128 PNG mit transparentem Hintergrund in
[`icons/`](icons), die SVG-Quellen in `icons/src/`. Die Laufzeit steht als Zahl
auf dem Farbband — ohne die sind die Einzelflaschen im Inventar nur noch an der
Bandfarbe auseinanderzuhalten. Wer `capacity` ändert, sollte die Zahlen in
`icons/gen-icons.mjs` mitziehen.
Dass alle Flaschen mit demselben Fülldruck starten, ist gewollt: in echt macht das
Volumen die Laufzeit, nicht der Druck. Die große Flasche hält 300 bar einfach länger.
### Metadata
| Key | Bedeutung |
|---|---|
| `oxygen` | Restluft in Sekunden — die eigentliche Wahrheit |
| `durability` | Füllstand in Prozent, nur für den Balken im Inventar-Slot |
**Kein `decay = true` auf den Flaschen.** ox_inventory löscht Items mit `decay`,
sobald `durability` 0 erreicht — die leere Flasche würde also verschwinden statt
auffüllbar zu bleiben.
## Bedienung
Die Flasche wird **nicht** separat angezogen. Sie muss nur im Inventar liegen —
beim Anlegen der Ausrüstung fragt das Script, welche angeschlossen werden soll.
| Aktion | Wie |
|---|---|
| Ausrüstung an-/ausziehen | `diving_gear` benutzen |
| Flasche wählen | Menü beim Anlegen (entfällt bei nur einer Flasche) |
| Flasche auffüllen | `diving_fill` benutzen — nicht unter Wasser, nicht bei voller Flasche |
| Lampe an/aus | `L` |
| HUD ein-/ausblenden | Eigene Taste, standardmäßig unbelegt |
Beide Tasten sind vom Spieler in den FiveM-Einstellungen unter *Tastenbelegung →
FiveM* umlegbar.
## Konfiguration
### `config/shared.lua` — Client und Server
| Option | Default | Bedeutung |
|---|---|---|
| `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 |
### `config/client.lua`
**Ablauf**
| Option | Default | Bedeutung |
|---|---|---|
| `putOnSuitTimeMs` / `takeOffSuitTimeMs` / `refillTankTimeMs` | `5000` | Dauer der Progressbars |
| `decayRate` | `1.0` | Sekunden Luft pro echter Sekunde, vor dem Tiefenfaktor |
| `warnAtSeconds` | `{60, 30, 10}` | Warnschwellen, jede einmal pro Tauchgang |
| `removeGearOnDeath` | `true` | Ausrüstung beim Tod abnehmen. Die Flasche bleibt mit ihrer Restluft im Inventar |
| `gearWatchdogIntervalMs` | `1000` | Prüfintervall, ob die Props noch am Ped hängen |
| `maxTimeUnderwater` | `50.0` | Sekunden unter Wasser ohne Ausrüstung (Vanilla-Verhalten) |
| `maxTimeUnderwaterOutOfAir` | `1.0` | Sekunden, sobald die Flasche leer ist |
**Tiefe**
| Option | Default | Bedeutung |
|---|---|---|
| `depthDecayEnabled` | `false` | Verbrauch steigt mit der Tiefe. Aus, damit die 5/10/15/20 Minuten exakt stimmen |
| `depthDecayReference` | `30.0` | Faktor `1 + tiefe / referenz` |
| `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 |
|---|---|---|
| `hudEnabled` | `true` | Ohne HUD bleibt die Ausrüstung voll nutzbar |
| `hudUpdateIntervalMs` | `250` | 4 Updates pro Sekunde |
| `hudOnlyUnderwater` | `false` | Auf `false` schon sichtbar, sobald die Ausrüstung an ist — Finimeter ablesen ohne Inventar |
| `hudFollowGameHud` | `true` | Mit dem Spiel-HUD ausblenden |
| `hudToggleKey` | `''` | Eigene Taste, leer = unbelegt |
| `reservePressure` | `50` | Ab hier wird die Druckanzeige rot |
**Lampe**
| Option | Default | Bedeutung |
|---|---|---|
| `lampKey` | `'l'` | Vorbelegung |
| `lampReconcileIntervalMs` | `500` | Wie oft der Lampen-Zustand auf die Peds in der Nähe nachgezogen wird |
| `lampProp` | `''` | Optionales Prop am Kopf, rein optisch. Die Lampe leuchtet unabhängig davon |
| `lampBone` / `lampPropOffset` / `lampPropRotation` | `12844` / `vec3(0,0,0)` | Sitz des optionalen Props — mit `/divelamp` einstellen |
> **Beim Balancing beachten:** `maxDrainPerSecond` (shared) muss über
> `maxDecayFactor` (client) liegen. Sonst hält der Server den ehrlichen Verbrauch
> in großer Tiefe für Cheat und schreibt Luft zurück.
## Debug-Befehle
Mit `debug = true` in `config/client.lua`:
```
/divelampcheck Hat das aktuelle Outfit eine Scuba-Lampe?
/divelamp <x> <y> <z> <rx> <ry> <rz> Sitz des optionalen Lampen-Props
```
`/divelamp` hängt das Prop sofort neu an und schreibt die fertige Config-Zeile ins
Log — kein Resource-Restart pro Versuch. Alternativ mit einem Prop-Attach-Editor
wie [noted_propattacher](https://github.com/NotedDevelopment/noted_propattacher)
ausmessen.
> Ein Tauchlampen-Prop gibt es im Basegame nicht. Was es gibt, sind Varianten
> 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 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
```lua
exports.d4rk_divegear:setHudVisible(false) -- HUD ausblenden
local shown = exports.d4rk_divegear:isHudVisible()
```
Gedacht für HUD- und Screenshot-Resources. Zusätzlich folgt das HUD automatisch
`IsHudHidden()`, greift also bei allem was `DisplayHud(false)` setzt, ohne dass
etwas angebunden werden muss.
## Wie die Lampe funktioniert
Es wird kein Licht selbst gezeichnet. GTA bringt seit v1493 (Cayo Perico) eine
eigene Lampe an der Tauchausrüstung mit:
```lua
SetEnableScubaGearLight(ped, toggle) -- 0xEE2476B9EE4A094F
IsScubaGearLightEnabled(ped) -- 0x88274C11CF0D866D
```
Die sitzt an der richtigen Stelle, leuchtet in Blickrichtung und braucht weder
Prop noch Offsets noch einen Draw-Thread.
> **Voraussetzung:** Die Lampe hängt an der echten **Scuba-Kleidung** des Peds
> (Component 8). Trägt der Spieler die nicht, tut die Native nichts. Diese
> Resource fasst das Outfit bewusst **nicht** an — das gehört ins Kleidungs-System.
> Beim Einschalten ohne passendes Outfit gibt es eine Meldung statt stiller Wirkungslosigkeit.
> Mit `debug = true` sagt `/divelampcheck`, ob das aktuelle Outfit eine Lampe hat.
Der Flag ist ein **lokaler** Ped-Zustand und wird nicht von selbst repliziert.
Deshalb läuft der Zustand über einen Statebag
(`LocalPlayer.state:set('divelight', …, true)`), und jeder Client setzt den Flag
für die Taucher in seiner Nähe selbst — sofort per `AddStateBagChangeHandler`,
plus ein Abgleich alle `lampReconcileIntervalMs`, weil Peds beim Streaming,
Respawn und Model-Wechsel neu erzeugt werden und den Flag dabei verlieren.
## Entwicklung
```bash
node ../.tools/lint.mjs # Lua-Lint, CfxLua-tauglich
node ../.tools/nui-preview.mjs nui # HUD im Browser anschauen
node docs/gen-hud-svg.mjs docs # Renderings für diese README neu bauen
# Item-Icons neu bauen (SVG -> PNG, braucht ImageMagick mit librsvg)
node icons/gen-icons.mjs
for f in icons/src/*.svg; do
magick -background none "$f" -resize 128x128 "icons/$(basename "$f" .svg).png"
done
```
`gen-icons.mjs` schreibt außerdem `icons/_check.html` — alle Icons auf
Schachbrett, in Slot-Größe und klein, zum Gegenschauen nach Änderungen.
> librsvg 2.40 wirft beim Rendern der Zahlen ein
> `Invalid UTF-8 string passed to pango_layout_set_text()`. Das ist harmlos und
> passiert bei jedem `<text>`-Element, auch im minimalsten Testfall — die Zahlen
> kommen korrekt raus.
Der Umbau ist in [PLAN.md](PLAN.md) dokumentiert, inklusive der offenen Punkte.
Die Testliste liegt in [TESTING.md](TESTING.md).
## Offene Punkte
Ingame getestet sind Anlegen und Ablegen, Flaschenauswahl, Luftverbrauch, HUD mit
Tiefe und Druck sowie die Lampe. Noch offen:
- **Scuba-Lampe ohne passendes Outfit** — sie hängt an der Scuba-Kleidung
(Component 8). Wer die nicht trägt, bekommt eine Meldung. Soll die Ausrüstung
das Outfit selbst setzen, muss das noch gebaut werden (inklusive Sichern und
Wiederherstellen der vorherigen Kleidung).
- **`IsHudHidden()`** — ob die Native auf `DisplayHud(false)` anspringt, ist
ungetestet. Falls nicht, greifen Taste und Export weiterhin.
- **Nach einem Relog** muss die Ausrüstung neu angelegt werden. Die Restluft geht
dabei nicht verloren, die liegt auf der Flasche.
- **HUD-Position** ist nur in `nui/style.css` einstellbar, nicht in der Config.
- **Balancing** — die Laufzeiten sind gesetzt, aber nicht am Spielgefühl geprüft.
## Lizenz
GPL-3.0, wie das Original. Die Upstream-Historie ist erhalten und liegt auf dem
Remote `upstream`.