Lint / Lint Resource (push) Has been cancelled
GTA bringt seit v1493 eine eigene Lampe an der Tauchausruestung mit:
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. Damit faellt der komplette
DrawSpotLight-Aufbau weg: Kegelwerte, Farbe, Schatten, Distanzfilter,
Lichter-Limit, die bone-relative Richtungsrechnung und das Sortieren der
Kandidaten pro Frame. client/light.lua schrumpft von ~110 auf ~55 Zeilen.
Gefunden ueber wobozkyng/esx_scuba.
- Der Flag ist ein lokaler Ped-Zustand, der Statebag bleibt also. Gesetzt wird
er per AddStateBagChangeHandler und zusaetzlich alle 500ms nachgezogen, weil
Peds beim Streaming, Respawn und Model-Wechsel neu erzeugt werden
- Die Lampe haengt an der Scuba-Kleidung (Component 8). Das Outfit wird bewusst
nicht angefasst; wer sie nicht traegt, bekommt beim Einschalten eine Meldung
statt stiller Wirkungslosigkeit
- /divelampcheck sagt, ob das aktuelle Outfit eine Lampe hat
- Prop ist jetzt optional und per Default aus
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
281 lines
12 KiB
Markdown
281 lines
12 KiB
Markdown
# 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
|
||
|
||

|
||
|
||
**Warnung** — Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth`,
|
||
Druck unter `reservePressure`
|
||
|
||

|
||
|
||
**Kritisch** — letzte Sekunden, der Ring pulsiert
|
||
|
||

|
||
|
||
Das HUD erscheint nur mit angelegter Ausrüstung unter Wasser und blendet sich
|
||
weich ein und aus.
|
||
|
||
## 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** und **tiefenabhängiger Verbrauch** — tiefer tauchen kostet
|
||
mehr Luft. Abschaltbar.
|
||
- **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. Sechs Items, nichts weiter anzupassen.
|
||
|
||
3. Die sechs 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 | Fülldruck | 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 | 300 bar | 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_large.png" width="40"> | `diving_tank_large` | Tauchflasche (15 Min) | 900 s | 300 bar | 10000 |
|
||
| <img src="icons/diving_tank_xl.png" width="40"> | `diving_tank_xl` | Tauchflasche (20 Min) | 1200 s | 300 bar | 13000 |
|
||
|
||
`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` | 4 Flaschen | `name`, `capacity` (Sekunden), `pressure` (bar) |
|
||
| `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` | `true` | Verbrauch steigt mit der Tiefe |
|
||
| `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 |
|
||
|
||
**HUD**
|
||
|
||
| Option | Default | Bedeutung |
|
||
|---|---|---|
|
||
| `hudEnabled` | `true` | Ohne HUD bleibt die Ausrüstung voll nutzbar |
|
||
| `hudUpdateIntervalMs` | `250` | 4 Updates pro Sekunde |
|
||
| `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.
|
||
|
||
## 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.
|
||
|
||
## Bekannte Unsicherheiten
|
||
|
||
Bisher **nicht im Spiel getestet** — geprüft sind Lua-Syntax und Globals, die
|
||
Locale-JSONs und das HUD im Browser.
|
||
|
||
- **`lampProp`** — der Prop-Name ist ungeprüft. Falls falsch: Warnung im Log, das
|
||
Licht funktioniert weiter, nur ohne sichtbares Prop. Ersatz finden:
|
||
[forge.plebmasters.de/objects](https://forge.plebmasters.de/objects)
|
||
- **`lampDirection`** — welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt
|
||
am Skelett. Leuchtet der Kegel falsch herum: Achse tauschen oder Vorzeichen drehen.
|
||
- **`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.
|
||
|
||
## Lizenz
|
||
|
||
GPL-3.0, wie das Original. Die Upstream-Historie ist erhalten und liegt auf dem
|
||
Remote `upstream`.
|