fix(light): Lampen-Sitz konfigurierbar + Debug-Befehle
Lint / Lint Resource (push) Has been cancelled

Offset und Rotation des Lampen-Props waren hart im Code, und die Rotation
(180, 90, 0) war stumpf von der Maske uebernommen - daher stand die Lampe quer
im Gesicht. d4rk_phone haengt sein Prop mit komplett null Offset und Rotation
an, das ist der bessere Ausgangspunkt.

- lampBone, lampPropOffset, lampPropRotation in die Config; Prop und Licht
  haengen jetzt am selben Bone, damit der Strahl aus der Lampe kommt
- lampProp = '' erlaubt, dann nur Licht ohne Prop
- Lichtwerte (Farbe, Reichweite, Helligkeit, Kegel) aus d4rk_phone uebernommen
  statt neu geraten; lampShadows wie dort auf false
- /divelamp und /divelight stellen beides im laufenden Spiel ein und geben die
  Config-Zeile zum Uebernehmen aus (config.debug)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:41:22 +02:00
co-authored by Claude Opus 5
parent 043450c54c
commit 037acf1bfe
4 changed files with 132 additions and 27 deletions
+31 -5
View File
@@ -172,12 +172,14 @@ FiveM* umlegbar.
| Option | Default | Bedeutung | | Option | Default | Bedeutung |
|---|---|---| |---|---|---|
| `lampKey` | `'l'` | Vorbelegung | | `lampKey` | `'l'` | Vorbelegung |
| `lampProp` | `'prop_cs_police_torch'` | Optik. Ungültiges Model → Warnung im Log, Licht funktioniert trotzdem | | `lampBone` | `12844` | Bone für Prop **und** Licht — derselbe, damit der Strahl aus der Lampe kommt. `28422` = Hand statt Kopf |
| `lampOffset` | `vec3(0, 0.15, 0)` | Position relativ zum Kopf-Bone | | `lampProp` | `'prop_cs_police_torch'` | Optik. Ungültiges Model → Warnung im Log, Licht funktioniert trotzdem. `''` = kein Prop |
| `lampPropOffset` / `lampPropRotation` | `vec3(0,0,0)` | Sitz des Props am Bone — mit `/divelamp` einstellen |
| `lampOffset` | `vec3(0,0,0)` | Ursprung des Lichtkegels, bone-relativ |
| `lampDirection` | `vec3(0, 1, 0)` | Richtung des Kegels, bone-relativ | | `lampDirection` | `vec3(0, 1, 0)` | Richtung des Kegels, bone-relativ |
| `lampColour` | `{225, 245, 255}` | RGB | | `lampColour` | `{255, 250, 235}` | RGB |
| `lampDistance` / `lampBrightness` / `lampRoundness` / `lampRadius` / `lampFalloff` | `30/6/1/14/1.5` | Form des Lichtkegels | | `lampDistance` / `lampBrightness` / `lampRoundness` / `lampRadius` / `lampFalloff` | `28/3/0/16/2` | Form des Lichtkegels, Werte aus `d4rk_phone` übernommen |
| `lampShadows` | `true` | `false` nutzt `DrawSpotLight` statt der Schattenvariante — flacher, aber spürbar schneller | | `lampShadows` | `false` | `true` nutzt `DrawSpotLightWithShadow` — sieht besser aus, kostet aber deutlich Performance |
| `lampDrawDistance` | `45.0` | Ab hier werden fremde Lampen nicht mehr gezeichnet | | `lampDrawDistance` | `45.0` | Ab hier werden fremde Lampen nicht mehr gezeichnet |
| `lampMaxLights` | `4` | Gleichzeitige Lampen, die nächstgelegenen gewinnen | | `lampMaxLights` | `4` | Gleichzeitige Lampen, die nächstgelegenen gewinnen |
@@ -185,6 +187,30 @@ FiveM* umlegbar.
> `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.
## Lampe einstellen
Wo Prop und Lichtkegel am Kopf sitzen, hängt daran, wie das Prop-Model selbst
ausgerichtet ist — das lässt sich nicht am Schreibtisch erraten. Mit
`debug = true` in `config/client.lua` gibt es zwei Befehle, die die Werte im
laufenden Spiel ändern und die fertige Config-Zeile ins Log schreiben:
```
/divelamp <x> <y> <z> <rx> <ry> <rz> Sitz des Lampen-Props
/divelight <x> <y> <z> <dx> <dy> <dz> Ursprung und Richtung des Lichtkegels
```
Das Prop wird sofort neu angehängt, das Licht zieht ohne Verzögerung mit — kein
Resource-Restart pro Versuch. Sind die Werte gut, die ausgegebenen Zeilen in die
Config übernehmen und `debug` wieder ausschalten.
Alternativ mit einem Prop-Attach-Editor wie
[noted_propattacher](https://github.com/NotedDevelopment/noted_propattacher)
ausmessen: Prop spawnen, mit der Maus an die Stelle ziehen, Werte übernehmen.
> Im Basegame gibt es **keine** Tauchlampe. `prop_cs_police_torch` ist eine
> Polizei-Taschenlampe; Varianten davon sind `prop_police_torch` und
> `prop_scn_police_torch`. Wer eine echte Tauchlampe will, braucht ein Custom-Model.
## Exports ## Exports
```lua ```lua
+2 -3
View File
@@ -10,7 +10,6 @@ local config = require 'config.client'
-- anderen Tauchers selbst zeichnet. -- anderen Tauchers selbst zeichnet.
local STATE_KEY = 'divelight' local STATE_KEY = 'divelight'
local HEAD_BONE = 31086 -- SKEL_Head
--- Kandidaten des aktuellen Frames. Wird wiederverwendet statt jedes Frame neu --- Kandidaten des aktuellen Frames. Wird wiederverwendet statt jedes Frame neu
--- angelegt, damit der GC bei Dauerbetrieb nichts zu tun bekommt. --- angelegt, damit der GC bei Dauerbetrieb nichts zu tun bekommt.
@@ -26,8 +25,8 @@ local function getLampVectors(ped)
local offset = config.lampOffset local offset = config.lampOffset
local direction = config.lampDirection local direction = config.lampDirection
local from = GetPedBoneCoords(ped, HEAD_BONE, offset.x, offset.y, offset.z) local from = GetPedBoneCoords(ped, config.lampBone, offset.x, offset.y, offset.z)
local ahead = GetPedBoneCoords(ped, HEAD_BONE, local ahead = GetPedBoneCoords(ped, config.lampBone,
offset.x + direction.x, offset.x + direction.x,
offset.y + direction.y, offset.y + direction.y,
offset.z + direction.z) offset.z + direction.z)
+57 -4
View File
@@ -125,10 +125,10 @@ end
--- Lampen-Model einmalig prüfen. lib.requestModel wirft bei ungültigen Models einen --- 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 --- 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. --- Ausrüstung unbenutzbar machen, statt nur die Optik der Lampe zu kosten.
local lampModel = joaat(config.lampProp) local lampModel = config.lampProp ~= '' and joaat(config.lampProp) or nil
local hasLampProp = IsModelValid(lampModel) or IsModelInCdimage(lampModel) local hasLampProp = lampModel ~= nil and (IsModelValid(lampModel) or IsModelInCdimage(lampModel))
if not hasLampProp then 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)) lib.print.warn(('lampProp "%s" ist kein gültiges Model - die Lampe leuchtet, das Prop fehlt.'):format(config.lampProp))
end end
@@ -139,7 +139,7 @@ local function attachGear(ped)
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 if hasLampProp then
currentGear.lamp = attachProp(ped, lampModel, MASK_BONE, vec3(0.1, 0.05, 0.0), vec3(180.0, 90.0, 0.0)) currentGear.lamp = attachProp(ped, lampModel, config.lampBone, config.lampPropOffset, config.lampPropRotation)
end end
end end
@@ -536,6 +536,59 @@ 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)
RegisterCommand('divelight', function(_, args)
local offset, direction = readVec(args, 1), readVec(args, 4)
if not offset or not direction then
lib.print.info('/divelight <x> <y> <z> <dx> <dy> <dz> - Ursprung und Richtung des Lichtkegels')
return
end
config.lampOffset, config.lampDirection = offset, direction
lib.print.info(('lampOffset = %s,\nlampDirection = %s,'):format(fmtVec(offset), fmtVec(direction)))
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.
--- ---
+42 -15
View File
@@ -1,4 +1,9 @@
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,
@@ -61,31 +66,53 @@ return {
--- Tastenbelegungen unter "FiveM" umlegen. --- Tastenbelegungen unter "FiveM" umlegen.
lampKey = 'l', lampKey = 'l',
--- Prop am Kopf. Rein optisch - das Licht selbst hängt nicht daran. --- Bone, an dem Lampe und Licht hängen. Beides derselbe Bone, damit der
--- Ist das Model ungültig, wird nur das Licht gezeichnet und eine Warnung --- Strahl auch sichtbar aus der Lampe kommt.
--- ins Log geschrieben. Alternativen: siehe forge.plebmasters.de/objects --- 12844 = Kopf (wie die Maske) - Strahl folgt der Blickrichtung
--- 28422 = Hand (wie das Prop in d4rk_phone) - Handlampe, aber beim
--- Schwimmen sind die Arme seitlich ausgestreckt, der Kegel
--- wandert also mit den Schwimmzügen
lampBone = 12844,
--- Prop am Kopf. Rein optisch - das Licht hängt nicht daran, es wird nur
--- vom selben Bone aus gezeichnet. Ist das Model ungültig, leuchtet es
--- trotzdem und es gibt eine Warnung im Log.
--- Im Basegame gibt es keine Tauchlampe. Alternativen sind Varianten
--- derselben Polizei-Lampe: 'prop_police_torch', 'prop_scn_police_torch'.
--- Auf '' setzen = gar kein Prop, nur Licht.
lampProp = 'prop_cs_police_torch', lampProp = 'prop_cs_police_torch',
--- Position des Lichts relativ zum Kopf-Bone (bone-relativ, nicht Weltkoordinaten). --- Sitz des Props am Bone. Wie in d4rk_phone bewusst auf null: das Prop sitzt
lampOffset = vec3(0.0, 0.15, 0.0), --- dann genau im Bone statt auf geratenen Offsets. Beim Kopf-Bone darf ein
--- kleiner Versatz nach vorne/seitlich nötig sein, damit die Lampe nicht im
--- Schädel steckt - mit /divelamp im Spiel einstellen statt hier zu raten.
lampPropOffset = vec3(0.0, 0.0, 0.0),
lampPropRotation = vec3(0.0, 0.0, 0.0),
--- Ursprung des Lichtkegels relativ zum Bone (bone-relativ, nicht Welt).
--- Sollte ungefähr dort liegen, wo das Prop sitzt.
lampOffset = vec3(0.0, 0.0, 0.0),
--- Richtung des Lichtkegels, ebenfalls bone-relativ. Leuchtet der Kegel in die --- Richtung des Lichtkegels, ebenfalls bone-relativ. Leuchtet der Kegel in die
--- falsche Richtung, hier die Achse tauschen oder das Vorzeichen drehen - --- falsche Richtung, hier die Achse tauschen oder das Vorzeichen drehen -
--- welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt am Skelett. --- welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt am Skelett.
lampDirection = vec3(0.0, 1.0, 0.0), lampDirection = vec3(0.0, 1.0, 0.0),
--- Lichtfarbe als { r, g, b }, 0-255. Leicht kühl für Unterwasser. --- Lichtwerte aus d4rk_phone übernommen - dort im Betrieb erprobt, statt hier
lampColour = { 225, 245, 255 }, --- neu geraten. Leicht warmweiß wie eine echte Taschenlampe.
lampColour = { 255, 250, 235 },
lampDistance = 30.0, -- Reichweite in Metern lampDistance = 28.0, -- Reichweite in Metern
lampBrightness = 6.0, -- Helligkeit lampBrightness = 3.0, -- Helligkeit
lampRoundness = 1.0, -- Weichzeichnung des Kegelrands lampRoundness = 0.0, -- Weichzeichnung des Kegelrands
lampRadius = 14.0, -- Öffnungswinkel lampRadius = 16.0, -- Öffnungswinkel
lampFalloff = 1.5, -- Abfall zum Rand hin lampFalloff = 2.0, -- Abfall zum Rand hin
--- Schatten kosten deutlich Performance. Auf false wird DrawSpotLight statt --- Schatten kosten deutlich Performance. d4rk_phone nutzt bewusst das
--- DrawSpotLightWithShadow benutzt - sieht flacher aus, läuft aber spürbar besser. --- schattenlose DrawSpotLight, deshalb hier auch der Default - unter Wasser
lampShadows = true, --- fällt der Unterschied kaum auf, in einer Höhle mit mehreren Tauchern
--- dagegen sehr wohl.
lampShadows = false,
--- Ab dieser Entfernung werden fremde Lampen nicht mehr gezeichnet (Meter). --- Ab dieser Entfernung werden fremde Lampen nicht mehr gezeichnet (Meter).
lampDrawDistance = 45.0, lampDrawDistance = 45.0,