diff --git a/ENTSCHEIDUNGEN.md b/ENTSCHEIDUNGEN.md new file mode 100644 index 0000000..46be15e --- /dev/null +++ b/ENTSCHEIDUNGEN.md @@ -0,0 +1,140 @@ +# Entscheidungen und Fallstricke + +Warum die Dinge so gebaut sind, wie sie gebaut sind. Wer hier etwas ändern will, +sollte den jeweiligen Absatz vorher gelesen haben — das meiste davon hat genau +einmal wehgetan. + +Fork von [qbx_divegear](https://github.com/Qbox-project/qbx_divegear) v1.0.1. Die +Upstream-Historie liegt vollständig auf dem Remote `upstream`. + +--- + +## Was am Original nicht funktionierte + +1. **Der Sauerstoff war eine lokale Client-Variable**, die an keinem Item hing. + Nicht persistent, nicht übertragbar, weg bei Relog. Damit war weder eine + Flaschengröße noch eine ehrliche Restluft-Anzeige möglich. +2. **Die Laufzeit war verrechnet:** `startingOxygenLevel = 100` bei `decayRate = 1` + pro Sekunde ergab 100 Sekunden, nicht Minuten. +3. **Die Props waren soft-pinned angehängt.** Mit `useSoftPinning = true` darf die + Physik-Engine die Verbindung bei Ragdoll und harten Anim-Übergängen lösen — + genau deshalb verschwand die Ausrüstung ständig. + +--- + +## Ausrüstung + +**Attachment hart, nicht soft.** `useSoftPinning = false`; laut Native-Doku heißt +das *"attached entity will not detach when fixed"*. Zusätzlich prüft ein Watchdog +jede Sekunde, ob beide Props noch am **aktuellen** Ped hängen, und hängt sie sonst +neu an. Er setzt auch `SetEnableScuba` und `SetPedMaxTimeUnderwater` neu, weil das +Spiel beides bei Respawn und Fahrzeugausstieg zurücksetzt. + +**`lib.onCache('ped')` gibt den neuen Ped als Parameter — `cache.ped` nicht.** +ox_lib ruft die Callbacks über `Citizen.CreateThreadNow` auf, *bevor* der neue Wert +in den Cache geschrieben wird. Wer im Handler `cache.ped` benutzt, hängt die Props +an den gerade verschwundenen Ped. Das ist der Kleiderladen-Fall, für den der +Handler überhaupt existiert. + +**`SetPedMaxTimeUnderwater` hat zwei Werte, das ist Absicht.** `maxTimeUnderwater` +(50 s) beim Ablegen = normal weiterschwimmen. `maxTimeUnderwaterOutOfAir` (1 s) +bei leerer Flasche = absaufen. Sieht nach Inkonsistenz aus, ist keine. + +**Besitz wird laufend geprüft.** Ausrüstung und Flasche können das Inventar mitten +im Tauchgang verlassen — weggegeben, abgelegt, vom Admin entfernt. Der Client hält +nur eine Slot-Nummer; ohne Gegenprüfung taucht man unbegrenzt weiter. +`ox_inventory:updateInventory` feuert bei jeder Änderung, dort wird beim Server +nachgefragt. Wurde nur umsortiert, bindet der Client auf den neuen Slot um. + +## Items + +**Kein `consume` bei Items mit `CreateUseableItem`.** ox_inventory verzweigt so: + +```lua +elseif consume then -- interner Pfad +elseif not item.weapon and server.UseItem then -- hier erst CreateUseableItem +``` + +In Lua ist `0` wahr. `consume = 0` landet im ersten Zweig, der qbx-Callback feuert +nie, und beim Benutzen passiert **gar nichts** — ohne Fehlermeldung. Steht so nicht +in der ox-Doku, nur im Quellcode. + +**Kein `decay` auf den Flaschen.** `metadata.durability` trägt hier den Füllstand. +ox_inventory löscht Items mit `decay`, sobald `durability` 0 erreicht — die leere +Flasche würde also verschwinden statt auffüllbar zu bleiben. + +**`SetMetadata` ersetzt die Metadata-Tabelle komplett**, es merged nicht. Deshalb +wird der bestehende Inhalt kopiert, bevor `oxygen` und `durability` gesetzt werden. + +**Flaschen ohne Metadata gelten als voll** (`newTanksFull`). Eine frisch gekaufte +Flasche hat kein `oxygen` — ohne diese Regel wäre sie leer und man müsste erst eine +Kartusche verbrauchen, um überhaupt tauchen zu können. + +## Sauerstoff + +**Restsekunden statt Punkte**, und der Verbrauch läuft über `GetGameTimer()`-Deltas +statt gezählter `Wait(1000)`. Ein Tick dauert unter Last länger als eine Sekunde; +über einen 20-Minuten-Tauchgang summiert sich das sichtbar. + +**Der Server clampt.** Der Client meldet die Restluft, der Server akzeptiert nur +Abnahme und höchstens `maxDrainPerSecond` pro echter Sekunde. Wer den Tiefenfaktor +oder den Narkose-`drainFactor` hochdreht, muss diesen Wert mitziehen — sonst +bekommen ehrliche Spieler in großer Tiefe Luft zurückgeschrieben. + +## Tiefe + +**Die Grenze hängt am Atemgas, nicht an einer unsichtbaren Wand.** Real begrenzt +nicht der Druck, sondern der Stickstoff in der Pressluft: tiefer wirkt er +narkotisch, ab etwa 56 m wird auch der Sauerstoff giftig. Wer tiefer will, atmet +Trimix. Genau so umgesetzt — `maxDepth` pro Flasche, darunter Tiefenrausch in +Stufen. + +**Bei 160 m ist Schluss, das macht die Engine.** Dagegen hilft kein Atemgas, +deshalb dort nur eine Warnung und kein eigener Effekt. Trimix hört mit 100 m +bewusst darüber auf. + +## Lampe + +**Die spieleigene Scuba-Lampe, kein Eigenbau.** GTA hat seit v1493 +`SetEnableScubaGearLight` (`0xEE2476B9EE4A094F`). Die sitzt richtig, leuchtet in +Blickrichtung und macht Prop, Offsets, Kegelwerte und einen Draw-Thread +überflüssig. Gefunden über [esx_scuba](https://github.com/wobozkyng/esx_scuba). + +Der dortige Hinweis, das Licht hänge am Kleidungs-Component 8, **trifft nicht zu** — +ingame geprüft, sie funktioniert auch ohne Scuba-Kleidung. Das Outfit wird nicht +angefasst. + +**Der Flag ist lokal und repliziert sich nicht von selbst.** Deshalb der Umweg über +einen Statebag: jeder Client setzt den Flag für die Taucher in seiner Nähe. Der +Abgleich-Thread ist nötig, weil Peds beim Streaming, Respawn und Model-Wechsel neu +erzeugt werden und den Flag dabei verlieren. + +## HUD + +**Zeigerinstrumente ohne Skalenzahlen.** Bei 96 px kollidieren Zahlen mit Strichen, +Zeiger und rotem Bereich, und gelesen werden sie ohnehin nicht. Der Zeiger zeigt +die Tendenz, die Zahl darunter den Wert. + +**Kein `transform-origin` am Zeiger.** Das `rotate(winkel 50 50)` im +transform-Attribut bringt den Drehpunkt schon mit; CSS rechnet eine zusätzliche +Origin obendrauf und der Zeiger landet außerhalb der Uhr. + +**Das HUD skaliert mit der Bildschirmhöhe.** Entworfen auf 1080p; FiveM rendert das +NUI in der echten Auflösung, ohne Nachskalieren wäre es auf 4K halb so groß. + +**Kein `backdrop-filter`** — bekannter FiveM-Bug, wird nach Updates zu einem +schwarzen Kasten. + +--- + +## Offen + +- Nach einem Relog muss die Ausrüstung neu angelegt werden. Die Restluft überlebt, + die liegt auf der Flasche. +- Die HUD-Position ist nur in `nui/style.css` einstellbar, nicht in der Config. +- `IsHudHidden()` — ob die Native auf `DisplayHud(false)` anspringt, ist ungeprüft. + Falls nicht, greifen Taste und Export weiterhin. +- Die `timecycle`-Namen des Tiefenrauschs sind nicht verifiziert. Kommt kein Effekt, + in `config/client.lua` unter `narcosis.stages` einen anderen eintragen. +- Balancing: Laufzeiten und Narkose-Stufen sind gesetzt, aber nicht über längere + Zeit am Spielgefühl geprüft. diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index ef14695..0000000 --- a/PLAN.md +++ /dev/null @@ -1,264 +0,0 @@ -# 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 3–5 bauen darauf auf. - -### Phase 1 — Gear-Persistenz (Bugfix, ~1 h) - -Zuerst, weil kleinster Aufwand, echter Bug, und die Taschenlampe dieselbe -Attach-Logik nutzt. - -- [x] `attachGear()` auf harte Attachment umstellen: `useSoftPinning = false`, - `p9 = false`, `syncRot = true` — ausgelagert in einen `attachProp()`-Helper, - den Phase 5 für die Lampe mitbenutzt. Laut Native-Doku heißt `useSoftPinning` - false: *"attached entity will not detach when fixed"* — genau das wollen wir -- [x] `isGearIntact()` Helper: `DoesEntityExist` + `GetEntityAttachedTo(...) == cache.ped` - für beide Props -- [x] Watchdog-Thread (`gearWatchdogIntervalMs`, default 1000 ms) solange - `currentGear.enabled`: bei Defekt `deleteGear()` + `attachGear()`, und - `enableScuba()` neu setzen (`SetEnableScuba`/`SetPedMaxTimeUnderwater` werden bei - Respawn/Fahrzeugausstieg vom Spiel zurückgesetzt) -- [x] `lib.onCache('ped', ...)` → bei Model-Wechsel (Kleiderladen, Skin-Change) - Props neu anhängen, Scuba-State neu setzen -- [x] Death-Handler: `IsPedDeadOrDying`-Check im Watchdog statt eigenem Event — - keine Abhängigkeit zu qbx_medical o.ä. Sichern der Restluft kommt in Phase 2 dazu -- [x] Gemeinsamer `removeGear(outOfAir)`-Teardown für Ablegen, leere Flasche und Tod -- [x] `onResourceStop` Cleanup-Handler für Props - -**Zu `SetPedMaxTimeUnderwater`:** war doch kein Bug. Die `50.0` beim Ablegen und die -`1.0` bei leerer Flasche sind zwei verschiedene Absichten (normal weiterschwimmen vs. -ohne Luft absaufen). Beide Werte sind jetzt als `maxTimeUnderwater` / -`maxTimeUnderwaterOutOfAir` in der Config statt als Magic Number im Code. - -### Phase 2 — Sauerstoff-Logik neu (~1 h) - -- [x] Umstellung von "Punkte" auf **Restsekunden** (`oxygenSeconds`) -- [x] Decrementer-Thread auf `GetGameTimer()`-Delta statt gezählter `Wait(1000)` - (driftfrei, überlebt Frame-Drops) -- [x] Float-Vergleiche raus, Warnschwellen explizit als Config-Liste - (z.B. `warnAtSeconds = { 60, 30, 10 }`), jede Schwelle einmal auslösen -- [x] Anzeige als `mm:ss` statt roher Zahl -- [x] 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`) -- [x] Beim Anlegen Metadata lesen → Restluft übernehmen -- [ ] **Offen:** Beim Spawn/Resource-Start die angelegte Ausrüstung automatisch - wieder anziehen. Die Restluft überlebt bereits (liegt auf der Flasche), aber - nach einem Relog steht man ohne Maske da und muss sie neu anlegen - -**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 | - -- [x] `capacity` in `config/shared.lua` (Client *und* Server brauchen sie), Label - und Gewicht im Item-Snippet. Kein Prop-Scale — siehe Optik-Hinweis unten -- [x] **`items_for_ox_inventory.lua`** als Snippet-Datei zum manuellen Reinkopieren - in `ox_inventory/data/items.lua` — kein `RegisterItem`-Export -- [x] `metadata.oxygen` (Restsekunden) + `metadata.durability` (Prozent, damit der - Füllstand direkt im Inventar-Slot sichtbar ist) -- [x] `diving_fill` füllt bis `capacity` des jeweiligen Tanks statt auf Fixwert -- [x] 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):** -- [x] Tiefe = Wasseroberfläche − Ped-Z, via `GetWaterHeight` (Fallback - `GetWaterHeightNoWaves`) -- [x] Wert geglättet (Lerp) damit die Zahl nicht zappelt -- [x] Liefert gleichzeitig den Faktor für den tiefenabhängigen Verbrauch aus Phase 3 - -**NUI (~2 h):** -- [x] O2 als Balken/Ring + `mm:ss` Restzeit, Farbwechsel an den Warnschwellen aus - Phase 2, Pulsieren im kritischen Bereich -- [x] Tiefe in Metern, Warnfarbe ab Config-Tiefe -- [x] Slot für den Lampen-Status (wird in Phase 5 befüllt) -- [x] Sichtbar nur wenn Gear angelegt **und** unter Wasser, mit Fade-In/Out -- [x] Updates gedrosselt per `SendNUIMessage` (~4/s statt jeden Frame) -- [x] Kein `backdrop-filter` (bekannter FiveM-Bug → schwarzer Hintergrund) -- [x] `hudEnabled`, `hudUpdateIntervalMs` und `deepWarningDepth` in `config/client.lua` -- [x] **Nachgereicht:** Manometer in bar. `pressure * (Restluft / capacity)` — bei - konstantem Volumen ist der Druck proportional zur verbliebenen Gasmenge, die - lineare Abbildung ist also korrekt. Alle Flaschen starten bei 300 bar; dass - die große Flasche denselben Druck zeigt wie die kleine, ist gewollt (in echt - macht das Volumen die Laufzeit). Unter `reservePressure` wird rot eingefärbt -- [x] **Nachgereicht:** HUD ausblendbar für Unterwasser-Screenshots. Drei Wege, - weil `qbx_core` selbst keinen HUD-Toggle hat (es feuert nur `hud:client:*` - an eine separate HUD-Resource): `IsHudHidden()` folgen, eigene Taste, - und der Export `setHudVisible(bool)` -- [ ] **Offen:** Position und Größe des HUD sind bisher nur in `nui/style.css` - einstellbar, nicht in der Config - -### Phase 5 — Gesyncte Taschenlampe (~2–3 h) - -**Nachträglich komplett ersetzt.** Der ganze Abschnitt unten beschreibt den -DrawSpotLight-Eigenbau — der ist raus. GTA hat seit v1493 eine eigene Lampe an der -Tauchausrüstung (`SetEnableScubaGearLight`, `0xEE2476B9EE4A094F`), gefunden über -[wobozkyng/esx_scuba](https://github.com/wobozkyng/esx_scuba). Die sitzt richtig, -leuchtet in Blickrichtung und macht Prop, Offsets, Kegelwerte, Distanzfilter und -Draw-Thread überflüssig. Geblieben ist nur der Statebag, weil der Flag ein lokaler -Ped-Zustand ist. Einschränkung: die Lampe hängt an der Scuba-Kleidung. - -Der ursprüngliche Plan zum Vergleich: - -- [x] Torch-Prop mit derselben harten Attach-Methode aus Phase 1 anhängen - (Kopf-Bone oder Hand, Config) -- [x] Toggle per `RegisterKeyMapping`/`lib.addKeybind` -- [x] State replizieren: `LocalPlayer.state:set('divelight', true, true)` - — Statebag statt Event-Spam -- [x] Draw-Thread: über `GetActivePlayers()`, `Player(serverId).state.divelight` - prüfen, `DrawSpotLightWithShadow` zeichnen -- [x] **Richtung anders gelöst als geplant:** statt Prop-Matrix und Objekt-Pool - werden zwei *bone-relative* Punkte am Kopf-Bone benutzt. Die Offsets von - `GetPedBoneCoords` sind laut Native-Doku "relative to the bone's rotation", - die Differenz zweier solcher Punkte ergibt also die Blickrichtung inklusive - Pitch. Das löst das Problem (`GetEntityForwardVector` würde beim waagerecht - im Wasser liegenden Ped stur horizontal leuchten), ohne fremde Props über - `GetGamePool('CObject')` suchen und cachen zu müssen — der Renderer braucht - dadurch überhaupt keinen Prop-Zugriff und kommt mit dem Statebag allein aus -- [x] Prop-Model wird über `IsModelValid`/`IsModelInCdimage` geprüft, weil - `lib.requestModel` bei ungültigen Models einen Error wirft. Ein falscher - Prop-Name kostet so nur die Optik, nicht die ganze Ausrüstung -- [x] Performance: Distanzfilter ~40–50 m, max. 3–4 gleichzeitige Lichter - (`DrawSpotLightWithShadow` ist teuer) -- [x] 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. - -Tatsächlich gebaut (die geplante Aufteilung in `oxygen.lua`/`hud.lua` ist entfallen — -die teilen sich alle denselben Zustand mit `main.lua`, eine Trennung hätte nur -Plumbing erzeugt. `light.lua` hat dagegen eine echte Naht: es kommt mit dem -Statebag allein aus und kennt weder Ausrüstung noch Flasche): - -``` -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, Sauerstoff, HUD-Bridge -│ └── light.lua # Statebag + Draw-Thread, ohne gemeinsamen Zustand -├── 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 | 2–3 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. - ---- - -## Offen - -Ingame getestet (2026-08-11): Anlegen/Ablegen, Flaschenauswahl, Luftverbrauch, -HUD mit Tiefe und Druck, Lampe. Zwei Bugs kamen dabei raus und sind gefixt — -`consume = 0` im Item-Snippet (der qbx-Callback feuerte nie) und leere -Metadata bei frisch gekauften Flaschen. - -1. **Scuba-Lampe braucht die Scuba-Kleidung** (Component 8). Auf Wunsch des Users - fasst die Resource das Outfit nicht an — wer keine trägt, bekommt eine Meldung. - Soll die Ausrüstung die Kleidung selbst setzen, muss das inklusive Sichern und - Wiederherstellen gebaut werden (wie in wobozkyng/esx_scuba). -2. **`IsHudHidden()`** — ungetestet, ob die Native auf `DisplayHud(false)` - anspringt. Falls nicht, greifen Taste und Export weiterhin. -3. **Auto-Anziehen nach Relog** und **HUD-Position in der Config** sind nicht - umgesetzt. -4. **Balancing:** `maxDrainPerSecond = 5.0` muss über `maxDecayFactor = 4.0` liegen, - sonst bekommen ehrliche Spieler in großer Tiefe vom Server Luft zurückgeschrieben. - Wer den Tiefenfaktor hochdreht, muss beides nachziehen. Die Laufzeiten selbst - sind noch nicht am Spielgefühl geprüft. - ---- - -## Testplan - -Als Issues im Repo, ein Issue pro Testbereich: -[git.d4rkst3r.de/D4rkst3r/d4rk_divegear/issues](https://git.d4rkst3r.de/D4rkst3r/d4rk_divegear/issues?labels=test) diff --git a/README.md b/README.md index dcd842a..e81347d 100644 --- a/README.md +++ b/README.md @@ -201,64 +201,13 @@ auf z.B. `2`. | Option | Default | Bedeutung | |---|---|---| -| `lampKey` | `'l'` | Vorbelegung | +| `lampKey` | `'l'` | Vorbelegung der Taste | | `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 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 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. - -### Die Grenze der Engine - -Unabhängig von allem hier ist bei **160 m** Schluss — das macht GTA selbst über den -Wasserdruck, dagegen hilft kein Atemgas. Ingame gemessen. - -Deshalb gibt es dafür nur eine Warnung (`engineDepthLimit`, 20 m vorher). Trimix -hört mit 100 m bewusst darüber auf; dazwischen liegt die Zone, in der es zwar -ungemütlich wird, aber noch nichts tödlich ist. - ## Exports ```lua @@ -287,7 +236,7 @@ Sie funktioniert **auch ohne Scuba-Kleidung** am Ped — ingame bestätigt. Der Hinweis in [esx_scuba](https://github.com/wobozkyng/esx_scuba), das Licht hänge am Kleidungs-Component, trifft hier also nicht zu. Das Outfit wird nicht angefasst. Sollte die Native bei einem Ped-Model doch nicht anschlagen, gibt es beim -Einschalten eine Meldung, und `/divelampcheck` (mit `debug = true`) sagt es direkt. +Einschalten eine Meldung statt stiller Wirkungslosigkeit. Der Flag ist ein **lokaler** Ped-Zustand und wird nicht von selbst repliziert. Deshalb läuft der Zustand über einen Statebag @@ -321,7 +270,8 @@ Schachbrett, in Slot-Größe und klein, zum Gegenschauen nach Änderungen. > passiert bei jedem ``-Element, auch im minimalsten Testfall — die Zahlen > kommen korrekt raus. -Der Umbau ist in [PLAN.md](PLAN.md) dokumentiert, inklusive der offenen Punkte. +Warum die Dinge so gebaut sind — und welche Fallstricke dahinterstecken — steht +in [ENTSCHEIDUNGEN.md](ENTSCHEIDUNGEN.md), zusammen mit den offenen Punkten. Die Abnahme läuft über die [Issues](https://git.d4rkst3r.de/D4rkst3r/d4rk_divegear/issues?labels=test) — ein Issue pro Testbereich, mit `prio:`-Labels und `ungeprueft` für alles, was diff --git a/client/main.lua b/client/main.lua index c5ac1ed..471599e 100644 --- a/client/main.lua +++ b/client/main.lua @@ -4,7 +4,6 @@ local shared = require 'config.shared' local currentGear = { mask = 0, tank = 0, - lamp = 0, enabled = false } @@ -140,12 +139,6 @@ local function deleteGear() DeleteEntity(currentGear.tank) currentGear.tank = 0 end - - if currentGear.lamp ~= 0 then - DetachEntity(currentGear.lamp, false, true) - DeleteEntity(currentGear.lamp) - currentGear.lamp = 0 - end end --- Hängt ein Prop hart an einen Ped-Bone. @@ -171,25 +164,11 @@ local function attachProp(ped, model, bone, offset, rotation) return prop end ---- Lampen-Model einmalig prüfen. lib.requestModel wirft bei ungültigen Models einen ---- Error - ohne diese Prüfung würde ein falsch geschriebener Prop-Name die komplette ---- Ausrüstung unbenutzbar machen, statt nur die Optik der Lampe zu kosten. -local lampModel = config.lampProp ~= '' and joaat(config.lampProp) or nil -local hasLampProp = lampModel ~= nil and (IsModelValid(lampModel) or IsModelInCdimage(lampModel)) - -if lampModel and not hasLampProp then - lib.print.warn(('lampProp "%s" ist kein gültiges Model - die Lampe leuchtet, das Prop fehlt.'):format(config.lampProp)) -end - --- @param ped number? default cache.ped local function attachGear(ped) ped = ped or cache.ped currentGear.tank = attachProp(ped, TANK_MODEL, TANK_BONE, vec3(-0.25, -0.25, 0.0), vec3(180.0, 90.0, 0.0)) currentGear.mask = attachProp(ped, MASK_MODEL, MASK_BONE, vec3(0.0, 0.0, 0.0), vec3(180.0, 90.0, 0.0)) - - if hasLampProp then - currentGear.lamp = attachProp(ped, lampModel, config.lampBone, config.lampPropOffset, config.lampPropRotation) - end end --- @param state boolean @@ -217,8 +196,6 @@ local function isPropIntact(prop) end local function isGearIntact() - if hasLampProp and not isPropIntact(currentGear.lamp) then return false end - return isPropIntact(currentGear.mask) and isPropIntact(currentGear.tank) end @@ -672,57 +649,6 @@ lib.addKeybind({ end }) ---- Wo Prop und Lichtkegel am Bone sitzen, lässt sich nicht am Schreibtisch ---- erraten - das hängt daran, wie das Model selbst ausgerichtet ist. Diese Befehle ---- ändern die Werte im laufenden Spiel und geben die Zeile zum Übernehmen aus, ---- damit nicht pro Versuch die Resource neu gestartet werden muss. ---- ---- lib.require cached Module, main.lua und light.lua teilen sich also dieselbe ---- config-Tabelle - das Licht zieht sofort mit. -if config.debug then - --- @return vector3? - local function readVec(args, from) - local x, y, z = tonumber(args[from]), tonumber(args[from + 1]), tonumber(args[from + 2]) - - if not x or not y or not z then return end - - return vec3(x, y, z) - end - - local function fmtVec(v) - return ('vec3(%.2f, %.2f, %.2f)'):format(v.x, v.y, v.z) - end - - RegisterCommand('divelamp', function(_, args) - local offset, rotation = readVec(args, 1), readVec(args, 4) - - if not offset or not rotation then - lib.print.info('/divelamp - Sitz des Lampen-Props') - return - end - - config.lampPropOffset, config.lampPropRotation = offset, rotation - - if currentGear.enabled then - deleteGear() - attachGear() - end - - lib.print.info(('lampPropOffset = %s,\nlampPropRotation = %s,'):format(fmtVec(offset), fmtVec(rotation))) - end, false) - - --- Die Spiel-Lampe braucht die Scuba-Kleidung am Ped. Dieser Befehl sagt, ob - --- das aktuelle Outfit sie überhaupt hat - sonst rätselt man, warum nichts - --- leuchtet. - RegisterCommand('divelampcheck', function() - SetEnableScubaGearLight(cache.ped, true) - local works = IsScubaGearLightEnabled(cache.ped) - SetEnableScubaGearLight(cache.ped, lampOn) - - lib.print.info(('Scuba-Lampe am aktuellen Outfit: %s'):format(works and 'ja' or 'nein')) - end, false) -end - --- Beim Model-Wechsel (Kleiderladen, Skin-Change) ist der Ped ein anderer - --- alle attached Props sind weg und die Scuba-Flags gelten für den alten Ped. --- diff --git a/config/client.lua b/config/client.lua index 3e7a0b6..b4f23cf 100644 --- a/config/client.lua +++ b/config/client.lua @@ -1,9 +1,4 @@ return { - --- Schaltet /divelamp und /divelight frei, mit denen sich Sitz und - --- Leuchtrichtung der Lampe im laufenden Spiel einstellen lassen - ohne - --- Resource-Restart pro Versuch. Auf dem Live-Server ausschalten. - debug = false, - putOnSuitTimeMs = 5000, takeOffSuitTimeMs = 5000, refillTankTimeMs = 5000, @@ -142,27 +137,12 @@ return { lampKey = 'l', --- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit - --- v1493). Sie sitzt an der richtigen Stelle und leuchtet in Blickrichtung - - --- deshalb gibt es hier weder Offsets noch Farb- und Kegelwerte. - --- - --- Sie funktioniert auch ohne Scuba-Kleidung am Ped (ingame bestätigt), das - --- Outfit wird also nicht angefasst. + --- v1493). Sie sitzt an der richtigen Stelle, leuchtet in Blickrichtung und + --- funktioniert auch ohne Scuba-Kleidung am Ped - deshalb gibt es hier weder + --- Prop noch Offsets noch Farb- und Kegelwerte. --- Wie oft der Lampen-Zustand auf die Peds in der Nähe nachgezogen wird (ms). --- Nötig, weil Peds beim Streaming, Respawn und Model-Wechsel neu erzeugt --- werden und den Flag dabei verlieren. lampReconcileIntervalMs = 500, - - --- Optionales Prop am Kopf, rein optisch. Die Lampe leuchtet unabhängig davon. - --- Standardmäßig aus, weil die Spiel-Lampe schon an der Ausrüstung sitzt. - --- Ein Basegame-Tauchlampen-Prop gibt es nicht; die Polizei-Taschenlampe - --- ('prop_cs_police_torch', 'prop_police_torch', 'prop_scn_police_torch') - --- passt optisch nur bedingt. - lampProp = '', - - --- Bone und Sitz des optionalen Props. 12844 = Kopf (wie die Maske). - --- Mit /divelamp im Spiel einstellen statt hier zu raten. - lampBone = 12844, - lampPropOffset = vec3(0.0, 0.0, 0.0), - lampPropRotation = vec3(0.0, 0.0, 0.0), } diff --git a/icons/diving_fill.png b/icons/diving_fill.png index 79c90fb..5c78982 100644 Binary files a/icons/diving_fill.png and b/icons/diving_fill.png differ diff --git a/icons/diving_gear.png b/icons/diving_gear.png index 935d92f..e7462c7 100644 Binary files a/icons/diving_gear.png and b/icons/diving_gear.png differ diff --git a/icons/diving_tank_large.png b/icons/diving_tank_large.png index 04d88a2..0e9f3f1 100644 Binary files a/icons/diving_tank_large.png and b/icons/diving_tank_large.png differ diff --git a/icons/diving_tank_medium.png b/icons/diving_tank_medium.png index 8699cc1..a3369bb 100644 Binary files a/icons/diving_tank_medium.png and b/icons/diving_tank_medium.png differ diff --git a/icons/diving_tank_small.png b/icons/diving_tank_small.png index 25a6adc..3072460 100644 Binary files a/icons/diving_tank_small.png and b/icons/diving_tank_small.png differ diff --git a/icons/diving_tank_trimix.png b/icons/diving_tank_trimix.png index 35e96e4..f02334d 100644 Binary files a/icons/diving_tank_trimix.png and b/icons/diving_tank_trimix.png differ diff --git a/icons/diving_tank_xl.png b/icons/diving_tank_xl.png index 35a334e..91d0455 100644 Binary files a/icons/diving_tank_xl.png and b/icons/diving_tank_xl.png differ diff --git a/locales/ar.json b/locales/ar.json deleted file mode 100644 index 44aeeb6..0000000 --- a/locales/ar.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "/divingsuit لنزع بدلة الغوص الخاصة بك", - "need_otube": "أنت لا ترتدي معدات الغوص", - "underwater": "الوقوف لاستخدام خزان الأكسجين" - }, - "success": { - "took_out": "جاري اخد لباس الغوض", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "وضعت بدلة الغوص", - "pullout_suit": "ترك بدلة الغوص", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/da.json b/locales/da.json deleted file mode 100644 index eb4ba4a..0000000 --- a/locales/da.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Du skal stå op for at tage dykkerudstyret på", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Du tog din våddragt af", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Tag en dykkerdragt på", - "pullout_suit": "Træk en dykker dragt ud..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json deleted file mode 100644 index 3f9af80..0000000 --- a/locales/es.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Tienes que estar de pie para ponerte el equipo de buceo", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Te quitaste tu traje de neopreno", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Ponerse equipo de buceo", - "pullout_suit": "Saca un equipo de buceo..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/et.json b/locales/et.json deleted file mode 100644 index 53044ce..0000000 --- a/locales/et.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Sukeldumisvarustuse selga panemiseks peate püsti seisma", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Sa võtsid sukeldumisülikonna seljast", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Pane selga sukeldumisvarustust", - "pullout_suit": "Võta välja sukeldumisvarustus..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json deleted file mode 100644 index 37679fe..0000000 --- a/locales/fr.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Dalış teçhizatını takmak için ayağa kalkman gerekiyor", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Dalgıç giysini çıkardın", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Dalış kıyafeti giy", - "pullout_suit": "Dalgıç kıyafeti çıkarılıyor..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/is.json b/locales/is.json deleted file mode 100644 index c40dd45..0000000 --- a/locales/is.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Þú þarft að standa upp til að setja á þig köfunarbúnaðinn", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Þú tókst blautbúninginn þinn af þér", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Farðu í köfunargalla", - "pullout_suit": "Dragðu fram köfunargalla..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json deleted file mode 100644 index adb8b33..0000000 --- a/locales/it.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Devi essere in piedi per indossare l'attrezzatura subacquea", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Ti sei tolto la muta da sub", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Indossando muta da sub..", - "pullout_suit": "Rimuovendo muta da sub..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/ja.json b/locales/ja.json deleted file mode 100644 index 3e8b3fd..0000000 --- a/locales/ja.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "これを着用するには地面に立っている必要があります...", - "need_otube": "酸素を補充する必要があります!予備の空気源を入手してください!", - "underwater": "水中ではこの操作はできません..." - }, - "success": { - "took_out": "装備を脱ぎました!", - "tube_filled": "エアタンクの補充に成功しました!" - }, - "info": { - "put_suit": "ダイビングスーツを着用中...", - "pullout_suit": "ダイビングスーツを脱いでいます...", - "filling_air": "空気を充填中..." - } -} \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json deleted file mode 100644 index 43befe0..0000000 --- a/locales/nl.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Je moet rechtop staan ​​om de duikuitrusting aan te trekken", - "need_otube": "You need to refill your oxygen! Get a replacement air supply!", - "underwater": "You cannot do this underwater..." - }, - "success": { - "took_out": "Je hebt je wetsuit uitgedaan", - "tube_filled": "You've successfully refilled your air tank!" - }, - "info": { - "put_suit": "Doe een duikpak aan", - "pullout_suit": "Trek een duikpak uit..", - "filling_air": "Filling air..." - } -} \ No newline at end of file diff --git a/locales/pt-br.json b/locales/pt-br.json deleted file mode 100644 index f386884..0000000 --- a/locales/pt-br.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Você precisa estar em terra firme para colocar isso...", - "need_otube": "Você precisa reabastecer seu oxigênio! Consiga um suprimento de ar de reposição!", - "underwater": "Você não pode fazer isso debaixo d'água..." - }, - "success": { - "took_out": "Você tirou seu equipamento!", - "tube_filled": "Você reabasteceu com sucesso seu tanque de ar!" - }, - "info": { - "put_suit": "Colocando seu traje de mergulho...", - "pullout_suit": "Tirando seu traje de mergulho...", - "filling_air": "Reabastecendo ar..." - } -} diff --git a/locales/pt.json b/locales/pt.json deleted file mode 100644 index ad3b0db..0000000 --- a/locales/pt.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Precisas de estar num terreno sólido para colocar isto...", - "need_otube": "Precisas de reabastecer o teu oxigénio! Obtêm um abastecimento de ar de substituição!", - "underwater": "Não podes fazer isto debaixo de água..." - }, - "success": { - "took_out": "Tiraste o teu equipamento!", - "tube_filled": "Reabasteceste com sucesso o teu tanque de ar!" - }, - "info": { - "put_suit": "A vestir o teu fato de mergulho...", - "pullout_suit": "A tirar o teu fato de mergulho...", - "filling_air": "A encher o ar..." - } -} diff --git a/locales/tr.json b/locales/tr.json deleted file mode 100644 index d87f581..0000000 --- a/locales/tr.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "not_standing_up": "Bunu giymek için sağlam zeminde olmalısınız...", - "need_otube": "Oksijeninizi doldurmanız gerekiyor! Yedek hava tankı alın!", - "underwater": "Bunu su altında yapamazsınız..." - }, - "success": { - "took_out": "Ekipmanınızı çıkardınız!", - "tube_filled": "Hava tankınızı başarıyla doldurdunuz!" - }, - "info": { - "put_suit": "Dalış kıyafetinizi giyiyorsunuz...", - "pullout_suit": "Dalış kıyafetinizi çıkarıyorsunuz...", - "filling_air": "Hava tankı dolduruluyor..." - } -}