Licht ist in GTA nie synchronisiert - DrawSpotLight zeichnet nur lokal fuer den aktuellen Frame. "Gesynct" heisst deshalb: der Zustand wird als Statebag repliziert und jeder Client zeichnet die Lampen aller Taucher in seiner Naehe selbst. Muster uebernommen aus d4rk_phone (Statebag, adaptives Polling, Distanzfilter). client/light.lua ist ein reiner Renderer ohne gemeinsamen Zustand mit main.lua - er kommt mit dem Statebag allein aus und kennt weder Ausruestung noch Flasche. - Richtung ueber zwei bone-relative Punkte am Kopf-Bone statt ueber GetEntityForwardVector: beim Tauchen liegt der Ped waagerecht im Wasser, der Entity-Forward wuerde stur horizontal leuchten. Die Offsets von GetPedBoneCoords sind laut Native-Doku "relative to the bone's rotation", die Differenz zweier solcher Punkte enthaelt also den Pitch - Toggle per lib.addKeybind, Standard L, vom Spieler umlegbar - Distanzfilter und Limit auf die naechsten N Lampen, Schatten abschaltbar - Prop-Model wird vorher mit IsModelValid geprueft: lib.requestModel wirft bei ungueltigen Models einen Error, der sonst die ganze Ausruestung lahmlegt - Lampen-Status im HUD Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,17 +58,19 @@ ohne Luft absaufen). Beide Werte sind jetzt als `maxTimeUnderwater` /
|
|||||||
|
|
||||||
### Phase 2 — Sauerstoff-Logik neu (~1 h)
|
### Phase 2 — Sauerstoff-Logik neu (~1 h)
|
||||||
|
|
||||||
- [ ] Umstellung von "Punkte" auf **Restsekunden** (`oxygenSeconds`)
|
- [x] Umstellung von "Punkte" auf **Restsekunden** (`oxygenSeconds`)
|
||||||
- [ ] Decrementer-Thread auf `GetGameTimer()`-Delta statt gezählter `Wait(1000)`
|
- [x] Decrementer-Thread auf `GetGameTimer()`-Delta statt gezählter `Wait(1000)`
|
||||||
(driftfrei, überlebt Frame-Drops)
|
(driftfrei, überlebt Frame-Drops)
|
||||||
- [ ] Float-Vergleiche raus, Warnschwellen explizit als Config-Liste
|
- [x] Float-Vergleiche raus, Warnschwellen explizit als Config-Liste
|
||||||
(z.B. `warnAtSeconds = { 60, 30, 10 }`), jede Schwelle einmal auslösen
|
(z.B. `warnAtSeconds = { 60, 30, 10 }`), jede Schwelle einmal auslösen
|
||||||
- [ ] Anzeige als `mm:ss` statt roher Zahl
|
- [x] Anzeige als `mm:ss` statt roher Zahl
|
||||||
- [ ] Persistenz: Restluft alle ~15 s und beim Ablegen per Server-Event in die
|
- [x] Persistenz: Restluft alle ~15 s und beim Ablegen per Server-Event in die
|
||||||
ox_inventory-Metadata des Tank-Items zurückschreiben
|
ox_inventory-Metadata des Tank-Items zurückschreiben
|
||||||
(`exports.ox_inventory:SetMetadata`)
|
(`exports.ox_inventory:SetMetadata`)
|
||||||
- [ ] Beim Anlegen Metadata lesen → Restluft übernehmen
|
- [x] Beim Anlegen Metadata lesen → Restluft übernehmen
|
||||||
- [ ] Beim Spawn/Resource-Start: war die Gear angelegt, direkt wieder anziehen
|
- [ ] **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
|
**Serverseitig validieren:** Der Client schickt den O2-Wert. Server prüft
|
||||||
Plausibilität (nur Abnahme, nicht mehr als seit letztem Update vergehen konnte),
|
Plausibilität (nur Abnahme, nicht mehr als seit letztem Update vergehen konnte),
|
||||||
@@ -85,13 +87,14 @@ Vier Items mit unterschiedlicher `capacity` in Sekunden:
|
|||||||
| `diving_tank_large` | 15 min | 900 |
|
| `diving_tank_large` | 15 min | 900 |
|
||||||
| `diving_tank_xl` | 20 min | 1200 |
|
| `diving_tank_xl` | 20 min | 1200 |
|
||||||
|
|
||||||
- [ ] Items in `config.lua` definieren (Label, capacity, Gewicht, Prop-Scale)
|
- [x] `capacity` in `config/shared.lua` (Client *und* Server brauchen sie), Label
|
||||||
- [ ] **`items_for_ox_inventory.lua`** als Snippet-Datei zum manuellen Reinkopieren
|
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
|
in `ox_inventory/data/items.lua` — kein `RegisterItem`-Export
|
||||||
- [ ] `metadata.oxygen` (Restsekunden) + `metadata.durability` (Prozent, damit der
|
- [x] `metadata.oxygen` (Restsekunden) + `metadata.durability` (Prozent, damit der
|
||||||
Füllstand direkt im Inventar-Slot sichtbar ist)
|
Füllstand direkt im Inventar-Slot sichtbar ist)
|
||||||
- [ ] `diving_fill` füllt bis `capacity` des jeweiligen Tanks statt auf Fixwert
|
- [x] `diving_fill` füllt bis `capacity` des jeweiligen Tanks statt auf Fixwert
|
||||||
- [ ] Verbrauch skaliert mit Tiefe: `decayRate * (1 + depth / 30)` — physikalisch
|
- [x] Verbrauch skaliert mit Tiefe: `decayRate * (1 + depth / 30)` — physikalisch
|
||||||
korrekt und macht große Flaschen erst sinnvoll (per Config abschaltbar)
|
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
|
**Optik:** `p_s_scuba_tank_s` ist das einzige Basegame-Tank-Prop. Größenunterschied
|
||||||
@@ -104,42 +107,49 @@ Tiefenanzeige und O2-Anzeige zusammen als ein NUI-Overlay — `qbx.drawText2d`
|
|||||||
(aktuell `oxygenLevel..'⏱'` in der Bildschirmecke) fliegt raus.
|
(aktuell `oxygenLevel..'⏱'` in der Bildschirmecke) fliegt raus.
|
||||||
|
|
||||||
**Datenseite (~30 min):**
|
**Datenseite (~30 min):**
|
||||||
- [ ] Tiefe = Wasseroberfläche − Ped-Z, via `GetWaterHeight` (Fallback
|
- [x] Tiefe = Wasseroberfläche − Ped-Z, via `GetWaterHeight` (Fallback
|
||||||
`GetWaterHeightNoWaves`)
|
`GetWaterHeightNoWaves`)
|
||||||
- [ ] Wert geglättet (Lerp) damit die Zahl nicht zappelt
|
- [x] Wert geglättet (Lerp) damit die Zahl nicht zappelt
|
||||||
- [ ] Liefert gleichzeitig den Faktor für den tiefenabhängigen Verbrauch aus Phase 3
|
- [x] Liefert gleichzeitig den Faktor für den tiefenabhängigen Verbrauch aus Phase 3
|
||||||
|
|
||||||
**NUI (~2 h):**
|
**NUI (~2 h):**
|
||||||
- [ ] O2 als Balken/Ring + `mm:ss` Restzeit, Farbwechsel an den Warnschwellen aus
|
- [x] O2 als Balken/Ring + `mm:ss` Restzeit, Farbwechsel an den Warnschwellen aus
|
||||||
Phase 2, Pulsieren im kritischen Bereich
|
Phase 2, Pulsieren im kritischen Bereich
|
||||||
- [ ] Tiefe in Metern, Warnfarbe ab Config-Tiefe
|
- [x] Tiefe in Metern, Warnfarbe ab Config-Tiefe
|
||||||
- [ ] Slot für den Lampen-Status (wird in Phase 5 befüllt)
|
- [x] Slot für den Lampen-Status (wird in Phase 5 befüllt)
|
||||||
- [ ] Sichtbar nur wenn Gear angelegt **und** unter Wasser, mit Fade-In/Out
|
- [x] Sichtbar nur wenn Gear angelegt **und** unter Wasser, mit Fade-In/Out
|
||||||
- [ ] Updates gedrosselt per `SendNUIMessage` (~4/s statt jeden Frame)
|
- [x] Updates gedrosselt per `SendNUIMessage` (~4/s statt jeden Frame)
|
||||||
- [ ] Kein `backdrop-filter` (bekannter FiveM-Bug → schwarzer Hintergrund)
|
- [x] Kein `backdrop-filter` (bekannter FiveM-Bug → schwarzer Hintergrund)
|
||||||
- [ ] Position/Scale in `config/client.lua` einstellbar
|
- [x] `hudEnabled`, `hudUpdateIntervalMs` und `deepWarningDepth` in `config/client.lua`
|
||||||
|
- [ ] **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)
|
### Phase 5 — Gesyncte Taschenlampe (~2–3 h)
|
||||||
|
|
||||||
Licht ist in GTA nie synchronisiert — jeder Client zeichnet das Licht für jeden
|
Licht ist in GTA nie synchronisiert — jeder Client zeichnet das Licht für jeden
|
||||||
Taucher in seiner Nähe selbst.
|
Taucher in seiner Nähe selbst.
|
||||||
|
|
||||||
- [ ] Torch-Prop mit derselben harten Attach-Methode aus Phase 1 anhängen
|
- [x] Torch-Prop mit derselben harten Attach-Methode aus Phase 1 anhängen
|
||||||
(Kopf-Bone oder Hand, Config)
|
(Kopf-Bone oder Hand, Config)
|
||||||
- [ ] Toggle per `RegisterKeyMapping`/`lib.addKeybind`
|
- [x] Toggle per `RegisterKeyMapping`/`lib.addKeybind`
|
||||||
- [ ] State replizieren: `LocalPlayer.state:set('divelight', true, true)`
|
- [x] State replizieren: `LocalPlayer.state:set('divelight', true, true)`
|
||||||
— Statebag statt Event-Spam
|
— Statebag statt Event-Spam
|
||||||
- [ ] Draw-Thread: über `GetActivePlayers()`, `Player(serverId).state.divelight`
|
- [x] Draw-Thread: über `GetActivePlayers()`, `Player(serverId).state.divelight`
|
||||||
prüfen, `DrawSpotLightWithShadow` zeichnen
|
prüfen, `DrawSpotLightWithShadow` zeichnen
|
||||||
- [ ] **Richtung aus dem Prop, nicht aus dem Ped:** der Ped pitcht beim Tauchen,
|
- [x] **Richtung anders gelöst als geplant:** statt Prop-Matrix und Objekt-Pool
|
||||||
`GetEntityForwardVector` geht schief. Position + Richtung über
|
werden zwei *bone-relative* Punkte am Kopf-Bone benutzt. Die Offsets von
|
||||||
`GetOffsetFromEntityInWorldCoords(torchProp, 0, 1, 0)` aus der Prop-Matrix
|
`GetPedBoneCoords` sind laut Native-Doku "relative to the bone's rotation",
|
||||||
- [ ] Fremde Props: einmal pro Sekunde `GetGamePool('CObject')` nach Torch-Model
|
die Differenz zweier solcher Punkte ergibt also die Blickrichtung inklusive
|
||||||
filtern, per `GetEntityAttachedTo` dem Ped zuordnen, Ergebnis cachen.
|
Pitch. Das löst das Problem (`GetEntityForwardVector` würde beim waagerecht
|
||||||
Fallback wenn nicht gefunden: Kopf-Bone + Ped-Matrix
|
im Wasser liegenden Ped stur horizontal leuchten), ohne fremde Props über
|
||||||
- [ ] Performance: Distanzfilter ~40–50 m, max. 3–4 gleichzeitige Lichter
|
`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)
|
(`DrawSpotLightWithShadow` ist teuer)
|
||||||
- [ ] Lampen-Status (An/Aus) in das HUD aus Phase 4 einhängen — keine Batterie,
|
- [x] Lampen-Status (An/Aus) in das HUD aus Phase 4 einhängen — keine Batterie,
|
||||||
die Lampe läuft solange Sauerstoff da ist
|
die Lampe läuft solange Sauerstoff da ist
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -149,6 +159,11 @@ Taucher in seiner Nähe selbst.
|
|||||||
Die Config bleibt beim Upstream-Pattern (`config/client.lua` + `require`) statt beim
|
Die Config bleibt beim Upstream-Pattern (`config/client.lua` + `require`) statt beim
|
||||||
`shared_scripts`-Template, damit spätere Upstream-Änderungen mergebar bleiben.
|
`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/
|
d4rk_divegear/
|
||||||
├── fxmanifest.lua
|
├── fxmanifest.lua
|
||||||
@@ -156,10 +171,8 @@ d4rk_divegear/
|
|||||||
│ ├── client.lua # Timings, Tiefe, Licht, HUD
|
│ ├── client.lua # Timings, Tiefe, Licht, HUD
|
||||||
│ └── shared.lua # Tank-Definitionen (Client + Server brauchen capacity)
|
│ └── shared.lua # Tank-Definitionen (Client + Server brauchen capacity)
|
||||||
├── client/
|
├── client/
|
||||||
│ ├── main.lua # Gear an/aus, Watchdog
|
│ ├── main.lua # Gear an/aus, Watchdog, Sauerstoff, HUD-Bridge
|
||||||
│ ├── oxygen.lua # Decrementer, Persistenz-Sync
|
│ └── light.lua # Statebag + Draw-Thread, ohne gemeinsamen Zustand
|
||||||
│ ├── light.lua # Statebag + Draw-Thread
|
|
||||||
│ └── hud.lua # Tiefenberechnung + NUI-Bridge
|
|
||||||
├── nui/
|
├── nui/
|
||||||
│ ├── index.html
|
│ ├── index.html
|
||||||
│ ├── style.css
|
│ ├── style.css
|
||||||
@@ -205,10 +218,25 @@ Am 2026-08-10 festgelegt:
|
|||||||
→ Phase 3: `decayRate * (1 + depth / 30)`, braucht den Tiefenwert aus Phase 4.
|
→ Phase 3: `decayRate * (1 + depth / 30)`, braucht den Tiefenwert aus Phase 4.
|
||||||
Deshalb Phase 4 vor dem Feintuning der Verbrauchskurve fertigstellen.
|
Deshalb Phase 4 vor dem Feintuning der Verbrauchskurve fertigstellen.
|
||||||
|
|
||||||
## Offene Fragen
|
---
|
||||||
|
|
||||||
1. **Gitea:** Repo unter `git.d4rkst3r.de/D4rkst3r/d4rk_divegear` — soll ich das
|
## Offen
|
||||||
Remote vorbereiten? (Push machst du selbst.)
|
|
||||||
|
Alles unten ist **ungetestet im Spiel** — es gibt hier keinen FiveM-Server. Geprüft
|
||||||
|
sind nur Lua-Syntax und Globals (`node ../.tools/lint.mjs`, 0 Warnungen), die
|
||||||
|
Locale-JSONs und das HUD im Browser.
|
||||||
|
|
||||||
|
1. **Lampen-Prop `prop_cs_police_torch`** — Name ungeprüft. Ist er falsch, meldet
|
||||||
|
das Script eine Warnung und leuchtet ohne sichtbares Prop weiter.
|
||||||
|
Ersatz suchen: [forge.plebmasters.de/objects](https://forge.plebmasters.de/objects)
|
||||||
|
2. **`lampDirection`** — welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt
|
||||||
|
am Skelett. Leuchtet der Kegel falsch herum, in `config/client.lua` die Achse
|
||||||
|
tauschen oder das Vorzeichen drehen.
|
||||||
|
3. **Auto-Anziehen nach Relog** (siehe Phase 2) und **HUD-Position in der Config**
|
||||||
|
(Phase 4) 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -221,5 +249,10 @@ Am 2026-08-10 festgelegt:
|
|||||||
- Relog mit halbvoller Flasche → Restluft stimmt
|
- Relog mit halbvoller Flasche → Restluft stimmt
|
||||||
- Resource-Restart während des Tauchens → Gear wird wiederhergestellt
|
- Resource-Restart während des Tauchens → Gear wird wiederhergestellt
|
||||||
- Zwei Spieler, beide mit Lampe → jeder sieht das Licht des anderen
|
- Zwei Spieler, beide mit Lampe → jeder sieht das Licht des anderen
|
||||||
|
- Lampe an, senkrecht nach unten tauchen → Kegel folgt der Blickrichtung nach unten
|
||||||
|
(das ist der Fall, an dem `GetEntityForwardVector` gescheitert wäre)
|
||||||
- Tank leer unter Wasser → Scuba aus, Ertrinken setzt ein
|
- Tank leer unter Wasser → Scuba aus, Ertrinken setzt ein
|
||||||
|
- Mehrere Flaschen im Inventar → Auswahlmenü mit Füllstand pro Flasche
|
||||||
|
- Volle Flasche auffüllen → Meldung, Kartusche wird **nicht** verbraucht
|
||||||
|
- Flasche im Inventar ansehen → durability-Balken zeigt den Füllstand
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
local config = require 'config.client'
|
||||||
|
|
||||||
|
-- Reiner Renderer: zeichnet die Lampen aller Taucher in der Nähe, gesteuert
|
||||||
|
-- ausschließlich über den replizierten Statebag. Kein gemeinsamer Zustand mit
|
||||||
|
-- client/main.lua - das hier läuft auch für Spieler, deren Ausrüstung wir gar
|
||||||
|
-- nicht kennen.
|
||||||
|
--
|
||||||
|
-- Licht ist in GTA nie synchronisiert: DrawSpotLight zeichnet nur lokal, für den
|
||||||
|
-- aktuellen Frame. "Gesynct" heißt deshalb, dass jeder Client das Licht jedes
|
||||||
|
-- anderen Tauchers selbst zeichnet.
|
||||||
|
|
||||||
|
local STATE_KEY = 'divelight'
|
||||||
|
local HEAD_BONE = 31086 -- SKEL_Head
|
||||||
|
|
||||||
|
--- Kandidaten des aktuellen Frames. Wird wiederverwendet statt jedes Frame neu
|
||||||
|
--- angelegt, damit der GC bei Dauerbetrieb nichts zu tun bekommt.
|
||||||
|
local candidates = {}
|
||||||
|
|
||||||
|
--- Position und Richtung der Lampe eines Peds.
|
||||||
|
---
|
||||||
|
--- Die Offsets von GetPedBoneCoords sind laut Native-Doku "relative to the bone's
|
||||||
|
--- rotation" - zwei bone-relative Punkte ergeben also die echte Blickrichtung
|
||||||
|
--- inklusive Pitch. Genau das wird beim Tauchen gebraucht: der Ped liegt waagerecht
|
||||||
|
--- im Wasser, GetEntityForwardVector würde die Lampe stur horizontal leuchten lassen.
|
||||||
|
local function getLampVectors(ped)
|
||||||
|
local offset = config.lampOffset
|
||||||
|
local direction = config.lampDirection
|
||||||
|
|
||||||
|
local from = GetPedBoneCoords(ped, HEAD_BONE, offset.x, offset.y, offset.z)
|
||||||
|
local ahead = GetPedBoneCoords(ped, HEAD_BONE,
|
||||||
|
offset.x + direction.x,
|
||||||
|
offset.y + direction.y,
|
||||||
|
offset.z + direction.z)
|
||||||
|
|
||||||
|
local delta = ahead - from
|
||||||
|
local length = #delta
|
||||||
|
|
||||||
|
if length <= 0 then return end
|
||||||
|
|
||||||
|
return from, delta / length
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawLamp(ped)
|
||||||
|
local from, direction = getLampVectors(ped)
|
||||||
|
|
||||||
|
if not from then return end
|
||||||
|
|
||||||
|
local colour = config.lampColour
|
||||||
|
|
||||||
|
if config.lampShadows then
|
||||||
|
DrawSpotLightWithShadow(from.x, from.y, from.z, direction.x, direction.y, direction.z,
|
||||||
|
colour[1], colour[2], colour[3],
|
||||||
|
config.lampDistance, config.lampBrightness, config.lampRoundness,
|
||||||
|
config.lampRadius, config.lampFalloff, 0)
|
||||||
|
else
|
||||||
|
DrawSpotLight(from.x, from.y, from.z, direction.x, direction.y, direction.z,
|
||||||
|
colour[1], colour[2], colour[3],
|
||||||
|
config.lampDistance, config.lampBrightness, config.lampRoundness,
|
||||||
|
config.lampRadius, config.lampFalloff)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
local players = GetActivePlayers()
|
||||||
|
local myCoords = GetEntityCoords(cache.ped)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for i = 1, #players do
|
||||||
|
local player = players[i]
|
||||||
|
local state = Player(GetPlayerServerId(player)).state
|
||||||
|
|
||||||
|
if state and state[STATE_KEY] then
|
||||||
|
local ped = GetPlayerPed(player)
|
||||||
|
|
||||||
|
if ped ~= 0 and DoesEntityExist(ped) then
|
||||||
|
local distance = #(myCoords - GetEntityCoords(ped))
|
||||||
|
|
||||||
|
if distance < config.lampDrawDistance then
|
||||||
|
count += 1
|
||||||
|
local entry = candidates[count]
|
||||||
|
|
||||||
|
if entry then
|
||||||
|
entry.ped, entry.distance = ped, distance
|
||||||
|
else
|
||||||
|
candidates[count] = { ped = ped, distance = distance }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reste des letzten Frames abschneiden, sonst sortiert table.sort veraltete
|
||||||
|
-- Einträge mit und ein alter, näherer Eintrag verdrängt einen echten.
|
||||||
|
for i = #candidates, count + 1, -1 do
|
||||||
|
candidates[i] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Nur sortieren wenn tatsächlich mehr Lampen da sind als gezeichnet werden
|
||||||
|
-- dürfen. DrawSpotLightWithShadow ist teuer, in einer vollen Höhle sonst
|
||||||
|
-- der erstbeste Frame-Killer.
|
||||||
|
if count > config.lampMaxLights then
|
||||||
|
table.sort(candidates, function(a, b) return a.distance < b.distance end)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, math.min(count, config.lampMaxLights) do
|
||||||
|
drawLamp(candidates[i].ped)
|
||||||
|
end
|
||||||
|
|
||||||
|
Wait(count > 0 and 0 or 300)
|
||||||
|
end
|
||||||
|
end)
|
||||||
+51
-2
@@ -4,9 +4,14 @@ local shared = require 'config.shared'
|
|||||||
local currentGear = {
|
local currentGear = {
|
||||||
mask = 0,
|
mask = 0,
|
||||||
tank = 0,
|
tank = 0,
|
||||||
|
lamp = 0,
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- Lampe an? Der eigentliche Lichtkegel wird in client/light.lua gezeichnet,
|
||||||
|
--- gesteuert über den replizierten Statebag.
|
||||||
|
local lampOn = false
|
||||||
|
|
||||||
--- Aktuell getragene Flasche. { slot, name, label, oxygen, capacity }
|
--- Aktuell getragene Flasche. { slot, name, label, oxygen, capacity }
|
||||||
--- oxygen sind Restsekunden, kein Prozentwert.
|
--- oxygen sind Restsekunden, kein Prozentwert.
|
||||||
local activeTank = nil
|
local activeTank = nil
|
||||||
@@ -86,6 +91,12 @@ 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.
|
||||||
@@ -111,11 +122,34 @@ 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 = joaat(config.lampProp)
|
||||||
|
local hasLampProp = IsModelValid(lampModel) or IsModelInCdimage(lampModel)
|
||||||
|
|
||||||
|
if 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, MASK_BONE, vec3(0.1, 0.05, 0.0), vec3(180.0, 90.0, 0.0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param state boolean
|
||||||
|
local function setLamp(state)
|
||||||
|
if not currentGear.enabled then state = false end
|
||||||
|
|
||||||
|
lampOn = state
|
||||||
|
-- Repliziert: client/light.lua zeichnet daraus die Lampen aller Taucher.
|
||||||
|
LocalPlayer.state:set('divelight', state, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function isPropIntact(prop)
|
local function isPropIntact(prop)
|
||||||
@@ -123,6 +157,8 @@ 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
|
||||||
|
|
||||||
@@ -151,6 +187,7 @@ local function removeGear(outOfAir, immediateSync)
|
|||||||
syncTank(immediateSync)
|
syncTank(immediateSync)
|
||||||
|
|
||||||
currentGear.enabled = false
|
currentGear.enabled = false
|
||||||
|
setLamp(false)
|
||||||
activeTank = nil
|
activeTank = nil
|
||||||
warned = {}
|
warned = {}
|
||||||
|
|
||||||
@@ -272,8 +309,8 @@ local function sendHudState(visible)
|
|||||||
depth = currentDepth,
|
depth = currentDepth,
|
||||||
level = level,
|
level = level,
|
||||||
deep = currentDepth >= config.deepWarningDepth,
|
deep = currentDepth >= config.deepWarningDepth,
|
||||||
hasLamp = false,
|
hasLamp = true,
|
||||||
lamp = false
|
lamp = lampOn
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -441,6 +478,17 @@ RegisterNetEvent('d4rk_divegear:client:useGear', function()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
lib.addKeybind({
|
||||||
|
name = 'divegear_lamp',
|
||||||
|
description = locale('keybind.toggle_lamp'),
|
||||||
|
defaultKey = config.lampKey,
|
||||||
|
onPressed = function()
|
||||||
|
if not currentGear.enabled then return end
|
||||||
|
|
||||||
|
setLamp(not lampOn)
|
||||||
|
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.
|
||||||
---
|
---
|
||||||
@@ -463,6 +511,7 @@ AddEventHandler('onResourceStop', function(resource)
|
|||||||
if resource ~= GetCurrentResourceName() then return end
|
if resource ~= GetCurrentResourceName() then return end
|
||||||
|
|
||||||
syncTank(true)
|
syncTank(true)
|
||||||
|
setLamp(false)
|
||||||
deleteGear()
|
deleteGear()
|
||||||
SendNUIMessage({ action = 'state', visible = false })
|
SendNUIMessage({ action = 'state', visible = false })
|
||||||
SetEnableScuba(cache.ped, false)
|
SetEnableScuba(cache.ped, false)
|
||||||
|
|||||||
@@ -38,4 +38,45 @@ return {
|
|||||||
|
|
||||||
--- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter).
|
--- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter).
|
||||||
deepWarningDepth = 25.0,
|
deepWarningDepth = 25.0,
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
-- Taschenlampe
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Taste zum An- und Ausschalten. Der Spieler kann sie in den FiveM-
|
||||||
|
--- Tastenbelegungen unter "FiveM" umlegen.
|
||||||
|
lampKey = 'l',
|
||||||
|
|
||||||
|
--- Prop am Kopf. Rein optisch - das Licht selbst hängt nicht daran.
|
||||||
|
--- Ist das Model ungültig, wird nur das Licht gezeichnet und eine Warnung
|
||||||
|
--- ins Log geschrieben. Alternativen: siehe forge.plebmasters.de/objects
|
||||||
|
lampProp = 'prop_cs_police_torch',
|
||||||
|
|
||||||
|
--- Position des Lichts relativ zum Kopf-Bone (bone-relativ, nicht Weltkoordinaten).
|
||||||
|
lampOffset = vec3(0.0, 0.15, 0.0),
|
||||||
|
|
||||||
|
--- Richtung des Lichtkegels, ebenfalls bone-relativ. Leuchtet der Kegel in die
|
||||||
|
--- falsche Richtung, hier die Achse tauschen oder das Vorzeichen drehen -
|
||||||
|
--- welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt am Skelett.
|
||||||
|
lampDirection = vec3(0.0, 1.0, 0.0),
|
||||||
|
|
||||||
|
--- Lichtfarbe als { r, g, b }, 0-255. Leicht kühl für Unterwasser.
|
||||||
|
lampColour = { 225, 245, 255 },
|
||||||
|
|
||||||
|
lampDistance = 30.0, -- Reichweite in Metern
|
||||||
|
lampBrightness = 6.0, -- Helligkeit
|
||||||
|
lampRoundness = 1.0, -- Weichzeichnung des Kegelrands
|
||||||
|
lampRadius = 14.0, -- Öffnungswinkel
|
||||||
|
lampFalloff = 1.5, -- Abfall zum Rand hin
|
||||||
|
|
||||||
|
--- Schatten kosten deutlich Performance. Auf false wird DrawSpotLight statt
|
||||||
|
--- DrawSpotLightWithShadow benutzt - sieht flacher aus, läuft aber spürbar besser.
|
||||||
|
lampShadows = true,
|
||||||
|
|
||||||
|
--- Ab dieser Entfernung werden fremde Lampen nicht mehr gezeichnet (Meter).
|
||||||
|
lampDrawDistance = 45.0,
|
||||||
|
|
||||||
|
--- Wie viele Lampen gleichzeitig gezeichnet werden. Die nächstgelegenen
|
||||||
|
--- gewinnen. Schützt vor Frame-Einbrüchen bei Gruppentauchgängen.
|
||||||
|
lampMaxLights = 4,
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -15,7 +15,10 @@ shared_scripts {
|
|||||||
|
|
||||||
server_script 'server/main.lua'
|
server_script 'server/main.lua'
|
||||||
|
|
||||||
client_script 'client/main.lua'
|
client_scripts {
|
||||||
|
'client/main.lua',
|
||||||
|
'client/light.lua',
|
||||||
|
}
|
||||||
|
|
||||||
ui_page 'nui/index.html'
|
ui_page 'nui/index.html'
|
||||||
|
|
||||||
|
|||||||
@@ -26,5 +26,8 @@
|
|||||||
"air": "Luft",
|
"air": "Luft",
|
||||||
"depth": "Tiefe",
|
"depth": "Tiefe",
|
||||||
"lamp": "Lampe"
|
"lamp": "Lampe"
|
||||||
|
},
|
||||||
|
"keybind": {
|
||||||
|
"toggle_lamp": "Tauchlampe an/aus"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,5 +26,8 @@
|
|||||||
"air": "Air",
|
"air": "Air",
|
||||||
"depth": "Depth",
|
"depth": "Depth",
|
||||||
"lamp": "Lamp"
|
"lamp": "Lamp"
|
||||||
|
},
|
||||||
|
"keybind": {
|
||||||
|
"toggle_lamp": "Toggle dive lamp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user