Files
d4rk_divegear/PLAN.md
T
D4rkst3randClaude Opus 5 c6b35ebe28
Lint / Lint Resource (push) Has been cancelled
chore: fork als d4rk_divegear aufsetzen
- Umbauplan (PLAN.md) mit 5 Phasen: Gear-Persistenz, Sauerstoff-Logik,
  Flaschengroessen, NUI-HUD (Tiefe + O2), gesyncte Taschenlampe
- fxmanifest auf D4rkst3r/Gitea umgestellt
- Qbox-spezifische CI entfernt (Discord-Webhooks, GitHub-Releases,
  Issue-Projektzuordnung) - laeuft auf Gitea ins Leere bzw. gegen fremde
  Endpunkte. lint.yml bleibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 23:12:59 +02:00

220 lines
9.5 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 — Umbauplan
Fork von [Qbox-project/qbx_divegear](https://github.com/Qbox-project/qbx_divegear) (v1.0.1).
Die Upstream-Historie ist vollständig erhalten und liegt auf dem Remote `upstream`,
damit spätere Upstream-Änderungen mergebar bleiben.
**Ziel:** Gesyncte Taschenlampe, verschiedene Flaschengrößen mit echter Laufzeit,
Tiefenanzeige, und Ausrüstung die bei Animationen nicht mehr verschwindet.
---
## Ausgangslage (Analyse des Originals)
`client/main.lua` hat 173 Zeilen, `server/main.lua` 10. Drei strukturelle Probleme,
die die neuen Features blockieren:
1. **Sauerstoff ist eine lokale Client-Variable** (`local oxygenLevel = 0`) — nicht
persistent, nicht am Item hängend, weg bei Relog/Restart. Blockiert Flaschengrößen.
2. **Laufzeit-Rechenfehler:** `startingOxygenLevel = 100` bei `decayRate = 1` pro
Sekunde ergibt **100 Sekunden**, nicht Minuten. Die Vergleiche
`oxygenLevel % 10 == 0` und `oxygenLevel == 0` (client/main.lua:121, :124) brechen,
sobald `decayRate` fraktional wird.
3. **Attachment ist soft-pinned** (client/main.lua:70, :75) — die Physik-Engine darf
die Props bei Ragdoll/Anims lösen. Kein Ped-Swap-Handling, kein Watchdog.
---
## Phasen
Reihenfolge ist bewusst gewählt: Phase 1 und 2 sind Fundament, Phase 35 bauen darauf auf.
### Phase 1 — Gear-Persistenz (Bugfix, ~1 h)
Zuerst, weil kleinster Aufwand, echter Bug, und die Taschenlampe dieselbe
Attach-Logik nutzt.
- [ ] `attachGear()` auf harte Attachment umstellen: `useSoftPinning = false`,
`p9 = false`, `fixedRot = true`
- [ ] `isGearIntact()` Helper: `DoesEntityExist` + `GetEntityAttachedTo(...) == cache.ped`
für beide Props
- [ ] Watchdog-Thread (1000 ms) solange `currentGear.enabled`: bei Defekt
`deleteGear()` + `attachGear()`, und `enableScuba()` unbedingt neu setzen
(`SetEnableScuba`/`SetPedMaxTimeUnderwater` werden bei Respawn/Fahrzeugausstieg
vom Spiel zurückgesetzt)
- [ ] `lib.onCache('ped', ...)` → bei Model-Wechsel (Kleiderladen, Skin-Change)
Props neu anhängen, Scuba-State neu setzen
- [ ] Death-Handler: beim Tod Gear sofort ablegen (ohne Progressbar), Restluft
vorher sichern — Flasche bleibt im Inventar
- [ ] Inkonsistenz aufräumen: `takeOffSuit()` setzt `SetPedMaxTimeUnderwater(ped, 50.0)`,
`disableScuba()` setzt `1.0` — einen Wert festlegen
- [ ] `onResourceStop` Cleanup-Handler für Props
### Phase 2 — Sauerstoff-Logik neu (~1 h)
- [ ] Umstellung von "Punkte" auf **Restsekunden** (`oxygenSeconds`)
- [ ] Decrementer-Thread auf `GetGameTimer()`-Delta statt gezählter `Wait(1000)`
(driftfrei, überlebt Frame-Drops)
- [ ] Float-Vergleiche raus, Warnschwellen explizit als Config-Liste
(z.B. `warnAtSeconds = { 60, 30, 10 }`), jede Schwelle einmal auslösen
- [ ] Anzeige als `mm:ss` statt roher Zahl
- [ ] Persistenz: Restluft alle ~15 s und beim Ablegen per Server-Event in die
ox_inventory-Metadata des Tank-Items zurückschreiben
(`exports.ox_inventory:SetMetadata`)
- [ ] Beim Anlegen Metadata lesen → Restluft übernehmen
- [ ] Beim Spawn/Resource-Start: war die Gear angelegt, direkt wieder anziehen
**Serverseitig validieren:** Der Client schickt den O2-Wert. Server prüft
Plausibilität (nur Abnahme, nicht mehr als seit letztem Update vergehen konnte),
sonst ist das ein trivialer Cheat-Vektor.
### Phase 3 — Flaschengrößen (~1 h, nach Phase 2 trivial)
Vier Items mit unterschiedlicher `capacity` in Sekunden:
| Item | Laufzeit | capacity |
|---|---|---|
| `diving_tank_small` | 5 min | 300 |
| `diving_tank_medium` | 10 min | 600 |
| `diving_tank_large` | 15 min | 900 |
| `diving_tank_xl` | 20 min | 1200 |
- [ ] Items in `config.lua` definieren (Label, capacity, Gewicht, Prop-Scale)
- [ ] **`items_for_ox_inventory.lua`** als Snippet-Datei zum manuellen Reinkopieren
in `ox_inventory/data/items.lua` — kein `RegisterItem`-Export
- [ ] `metadata.oxygen` (Restsekunden) + `metadata.durability` (Prozent, damit der
Füllstand direkt im Inventar-Slot sichtbar ist)
- [ ] `diving_fill` füllt bis `capacity` des jeweiligen Tanks statt auf Fixwert
- [ ] Verbrauch skaliert mit Tiefe: `decayRate * (1 + depth / 30)` — physikalisch
korrekt und macht große Flaschen erst sinnvoll (per Config abschaltbar)
**Optik:** `p_s_scuba_tank_s` ist das einzige Basegame-Tank-Prop. Größenunterschied
über `SetEntityScale` ist bei attached Objects unzuverlässig — entweder akzeptieren,
dass alle vier gleich aussehen, oder Custom-Model. Für v1: gleich aussehen.
### Phase 4 — NUI-HUD: Tiefe + Sauerstoff (~2,5 h)
Tiefenanzeige und O2-Anzeige zusammen als ein NUI-Overlay — `qbx.drawText2d`
(aktuell `oxygenLevel..'⏱'` in der Bildschirmecke) fliegt raus.
**Datenseite (~30 min):**
- [ ] Tiefe = Wasseroberfläche Ped-Z, via `GetWaterHeight` (Fallback
`GetWaterHeightNoWaves`)
- [ ] Wert geglättet (Lerp) damit die Zahl nicht zappelt
- [ ] Liefert gleichzeitig den Faktor für den tiefenabhängigen Verbrauch aus Phase 3
**NUI (~2 h):**
- [ ] O2 als Balken/Ring + `mm:ss` Restzeit, Farbwechsel an den Warnschwellen aus
Phase 2, Pulsieren im kritischen Bereich
- [ ] Tiefe in Metern, Warnfarbe ab Config-Tiefe
- [ ] Slot für den Lampen-Status (wird in Phase 5 befüllt)
- [ ] Sichtbar nur wenn Gear angelegt **und** unter Wasser, mit Fade-In/Out
- [ ] Updates gedrosselt per `SendNUIMessage` (~4/s statt jeden Frame)
- [ ] Kein `backdrop-filter` (bekannter FiveM-Bug → schwarzer Hintergrund)
- [ ] Position/Scale in `config/client.lua` einstellbar
### Phase 5 — Gesyncte Taschenlampe (~23 h)
Licht ist in GTA nie synchronisiert — jeder Client zeichnet das Licht für jeden
Taucher in seiner Nähe selbst.
- [ ] Torch-Prop mit derselben harten Attach-Methode aus Phase 1 anhängen
(Kopf-Bone oder Hand, Config)
- [ ] Toggle per `RegisterKeyMapping`/`lib.addKeybind`
- [ ] State replizieren: `LocalPlayer.state:set('divelight', true, true)`
— Statebag statt Event-Spam
- [ ] Draw-Thread: über `GetActivePlayers()`, `Player(serverId).state.divelight`
prüfen, `DrawSpotLightWithShadow` zeichnen
- [ ] **Richtung aus dem Prop, nicht aus dem Ped:** der Ped pitcht beim Tauchen,
`GetEntityForwardVector` geht schief. Position + Richtung über
`GetOffsetFromEntityInWorldCoords(torchProp, 0, 1, 0)` aus der Prop-Matrix
- [ ] Fremde Props: einmal pro Sekunde `GetGamePool('CObject')` nach Torch-Model
filtern, per `GetEntityAttachedTo` dem Ped zuordnen, Ergebnis cachen.
Fallback wenn nicht gefunden: Kopf-Bone + Ped-Matrix
- [ ] Performance: Distanzfilter ~4050 m, max. 34 gleichzeitige Lichter
(`DrawSpotLightWithShadow` ist teuer)
- [ ] Lampen-Status (An/Aus) in das HUD aus Phase 4 einhängen — keine Batterie,
die Lampe läuft solange Sauerstoff da ist
---
## Zielstruktur
Die Config bleibt beim Upstream-Pattern (`config/client.lua` + `require`) statt beim
`shared_scripts`-Template, damit spätere Upstream-Änderungen mergebar bleiben.
```
d4rk_divegear/
├── fxmanifest.lua
├── config/
│ ├── client.lua # Timings, Tiefe, Licht, HUD
│ └── shared.lua # Tank-Definitionen (Client + Server brauchen capacity)
├── client/
│ ├── main.lua # Gear an/aus, Watchdog
│ ├── oxygen.lua # Decrementer, Persistenz-Sync
│ ├── light.lua # Statebag + Draw-Thread
│ └── hud.lua # Tiefenberechnung + NUI-Bridge
├── nui/
│ ├── index.html
│ ├── style.css
│ └── script.js
├── server/
│ └── main.lua # Useable Items, Metadata schreiben + validieren
├── locales/
│ ├── de.json
│ └── en.json
└── items_for_ox_inventory.lua # Snippet zum Reinkopieren
```
Locales: DE + EN werden gepflegt, die 12 übrigen Upstream-Sprachen bleiben liegen
(neue Keys fehlen dort → ox_lib fällt auf den Key-Namen zurück, kein Crash).
---
## Aufwand
| Phase | Aufwand |
|---|---|
| 1 — Gear-Persistenz | ~1 h |
| 2 — Sauerstoff-Logik | ~1 h |
| 3 — Flaschengrößen | ~1 h |
| 4 — NUI-HUD (Tiefe + O2) | ~2,5 h |
| 5 — Taschenlampe | 23 h |
| **Summe** | **~8 h** |
---
## Entscheidungen
Am 2026-08-10 festgelegt:
1. **Tod:** Ausrüstung wird beim Respawn abgenommen, die Flasche bleibt mit ihrer
Restluft im Inventar. → Phase 1: Death-Handler ruft `takeOffSuit()` ohne
Progressbar, Phase 2 schreibt vorher die Restluft in die Metadata.
2. **Taschenlampe:** fest an der Ausrüstung, kein eigenes Item. An/Aus per Keybind.
→ Phase 5: Torch-Prop wird zusammen mit Maske und Flasche angehängt.
3. **Batterie:** keine. Lampe läuft, solange Sauerstoff da ist.
`metadata.battery` entfällt komplett, HUD zeigt nur An/Aus.
4. **Tiefenabhängiger Verbrauch:** an per Default, in der Config abschaltbar.
→ Phase 3: `decayRate * (1 + depth / 30)`, braucht den Tiefenwert aus Phase 4.
Deshalb Phase 4 vor dem Feintuning der Verbrauchskurve fertigstellen.
## Offene Fragen
1. **Gitea:** Repo unter `git.d4rkst3r.de/D4rkst3r/d4rk_divegear` — soll ich das
Remote vorbereiten? (Push machst du selbst.)
---
## Testplan
- Anlegen/Ablegen an Land, im flachen Wasser, unter Wasser
- Emote starten (rpemotes o.ä.) → Props bleiben
- Ragdoll (aus Höhe fallen, angefahren werden) → Props bleiben
- Kleiderladen / Skin-Change → Props hängen am neuen Ped
- Relog mit halbvoller Flasche → Restluft stimmt
- Resource-Restart während des Tauchens → Gear wird wiederhergestellt
- Zwei Spieler, beide mit Lampe → jeder sieht das Licht des anderen
- Tank leer unter Wasser → Scuba aus, Ertrinken setzt ein
```