Files
d4rk_divegear/ENTSCHEIDUNGEN.md
D4rkst3randClaude Opus 5 1ed6a924f8 chore: aufgeraeumt nach der Abnahme
Lampen-Prop entfernt. Seit der Umstellung auf die spieleigene Scuba-Lampe war es
funktionslos: standardmaessig aus, nie verifiziert, und eine Polizei-Taschenlampe
am Kopf haette neben dem nativen Licht nur Deko gespielt. Damit fallen auch die
Debug-Befehle /divelamp und /divelampcheck weg, samt config.debug und vier
Config-Werten.

Zwoelf Upstream-Locales geloescht. Sie hatten 8 von 26 Keys - im Spiel waeren
dort rohe Key-Namen erschienen. Gepflegt werden DE und EN.

PLAN.md war ein Vorab-Plan mit Phasen und Aufwandsschaetzungen und damit
erledigt. Ersetzt durch ENTSCHEIDUNGEN.md: warum die Dinge so gebaut sind und
welche Fallstricke dahinterstecken - onCache-Timing, consume, decay,
SetMetadata, die Atemgas-Grenze, das transform-origin am Zeiger. Also das, was
beim naechsten Anfassen wirklich hilft.

README von den Abschnitten befreit, die es nicht mehr gibt. Bilder und Icons neu
erzeugt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:16:04 +02:00

6.5 KiB

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 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:

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.

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.