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>
@@ -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.
|
||||||
@@ -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)
|
|
||||||
@@ -201,64 +201,13 @@ auf z.B. `2`.
|
|||||||
|
|
||||||
| Option | Default | Bedeutung |
|
| 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 |
|
| `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
|
> **Beim Balancing beachten:** `maxDrainPerSecond` (shared) muss über
|
||||||
> `maxDecayFactor` (client) liegen. Sonst hält der Server den ehrlichen Verbrauch
|
> `maxDecayFactor` (client) liegen. Sonst hält der Server den ehrlichen Verbrauch
|
||||||
> in großer Tiefe für Cheat und schreibt Luft zurück.
|
> 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 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
|
## Exports
|
||||||
|
|
||||||
```lua
|
```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
|
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.
|
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
|
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.
|
Der Flag ist ein **lokaler** Ped-Zustand und wird nicht von selbst repliziert.
|
||||||
Deshalb läuft der Zustand über einen Statebag
|
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 `<text>`-Element, auch im minimalsten Testfall — die Zahlen
|
> passiert bei jedem `<text>`-Element, auch im minimalsten Testfall — die Zahlen
|
||||||
> kommen korrekt raus.
|
> 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
|
Die Abnahme läuft über die
|
||||||
[Issues](https://git.d4rkst3r.de/D4rkst3r/d4rk_divegear/issues?labels=test) —
|
[Issues](https://git.d4rkst3r.de/D4rkst3r/d4rk_divegear/issues?labels=test) —
|
||||||
ein Issue pro Testbereich, mit `prio:`-Labels und `ungeprueft` für alles, was
|
ein Issue pro Testbereich, mit `prio:`-Labels und `ungeprueft` für alles, was
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ local shared = require 'config.shared'
|
|||||||
local currentGear = {
|
local currentGear = {
|
||||||
mask = 0,
|
mask = 0,
|
||||||
tank = 0,
|
tank = 0,
|
||||||
lamp = 0,
|
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +139,6 @@ local function deleteGear()
|
|||||||
DeleteEntity(currentGear.tank)
|
DeleteEntity(currentGear.tank)
|
||||||
currentGear.tank = 0
|
currentGear.tank = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
if currentGear.lamp ~= 0 then
|
|
||||||
DetachEntity(currentGear.lamp, false, true)
|
|
||||||
DeleteEntity(currentGear.lamp)
|
|
||||||
currentGear.lamp = 0
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Hängt ein Prop hart an einen Ped-Bone.
|
--- Hängt ein Prop hart an einen Ped-Bone.
|
||||||
@@ -171,25 +164,11 @@ local function attachProp(ped, model, bone, offset, rotation)
|
|||||||
return prop
|
return prop
|
||||||
end
|
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
|
--- @param ped number? default cache.ped
|
||||||
local function attachGear(ped)
|
local function attachGear(ped)
|
||||||
ped = ped or cache.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.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))
|
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
|
end
|
||||||
|
|
||||||
--- @param state boolean
|
--- @param state boolean
|
||||||
@@ -217,8 +196,6 @@ local function isPropIntact(prop)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function isGearIntact()
|
local function isGearIntact()
|
||||||
if hasLampProp and not isPropIntact(currentGear.lamp) then return false end
|
|
||||||
|
|
||||||
return isPropIntact(currentGear.mask) and isPropIntact(currentGear.tank)
|
return isPropIntact(currentGear.mask) and isPropIntact(currentGear.tank)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -672,57 +649,6 @@ lib.addKeybind({
|
|||||||
end
|
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 <x> <y> <z> <rx> <ry> <rz> - 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 -
|
--- 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.
|
--- alle attached Props sind weg und die Scuba-Flags gelten für den alten Ped.
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
return {
|
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,
|
putOnSuitTimeMs = 5000,
|
||||||
takeOffSuitTimeMs = 5000,
|
takeOffSuitTimeMs = 5000,
|
||||||
refillTankTimeMs = 5000,
|
refillTankTimeMs = 5000,
|
||||||
@@ -142,27 +137,12 @@ return {
|
|||||||
lampKey = 'l',
|
lampKey = 'l',
|
||||||
|
|
||||||
--- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit
|
--- Die Lampe ist die spieleigene Scuba-Lampe (SetEnableScubaGearLight, seit
|
||||||
--- v1493). Sie sitzt an der richtigen Stelle und leuchtet in Blickrichtung -
|
--- v1493). Sie sitzt an der richtigen Stelle, leuchtet in Blickrichtung und
|
||||||
--- deshalb gibt es hier weder Offsets noch Farb- und Kegelwerte.
|
--- funktioniert auch ohne Scuba-Kleidung am Ped - deshalb gibt es hier weder
|
||||||
---
|
--- Prop noch Offsets noch Farb- und Kegelwerte.
|
||||||
--- Sie funktioniert auch ohne Scuba-Kleidung am Ped (ingame bestätigt), das
|
|
||||||
--- Outfit wird also nicht angefasst.
|
|
||||||
|
|
||||||
--- Wie oft der Lampen-Zustand auf die Peds in der Nähe nachgezogen wird (ms).
|
--- 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
|
--- Nötig, weil Peds beim Streaming, Respawn und Model-Wechsel neu erzeugt
|
||||||
--- werden und den Flag dabei verlieren.
|
--- werden und den Flag dabei verlieren.
|
||||||
lampReconcileIntervalMs = 500,
|
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),
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"error": {
|
|
||||||
"not_standing_up": "これを着用するには地面に立っている必要があります...",
|
|
||||||
"need_otube": "酸素を補充する必要があります!予備の空気源を入手してください!",
|
|
||||||
"underwater": "水中ではこの操作はできません..."
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
"took_out": "装備を脱ぎました!",
|
|
||||||
"tube_filled": "エアタンクの補充に成功しました!"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"put_suit": "ダイビングスーツを着用中...",
|
|
||||||
"pullout_suit": "ダイビングスーツを脱いでいます...",
|
|
||||||
"filling_air": "空気を充填中..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||