feat(light): gesyncte Tauchlampe
Lint / Lint Resource (push) Has been cancelled

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:
2026-08-10 23:56:40 +02:00
co-authored by Claude Opus 5
parent 28ab8e796c
commit d9643620ff
7 changed files with 288 additions and 44 deletions
+51 -2
View File
@@ -4,9 +4,14 @@ local shared = require 'config.shared'
local currentGear = {
mask = 0,
tank = 0,
lamp = 0,
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 }
--- oxygen sind Restsekunden, kein Prozentwert.
local activeTank = nil
@@ -86,6 +91,12 @@ local function deleteGear()
DeleteEntity(currentGear.tank)
currentGear.tank = 0
end
if currentGear.lamp ~= 0 then
DetachEntity(currentGear.lamp, false, true)
DeleteEntity(currentGear.lamp)
currentGear.lamp = 0
end
end
--- Hängt ein Prop hart an einen Ped-Bone.
@@ -111,11 +122,34 @@ local function attachProp(ped, model, bone, offset, rotation)
return prop
end
--- Lampen-Model einmalig prüfen. lib.requestModel wirft bei ungültigen Models einen
--- Error - ohne diese Prüfung würde ein falsch geschriebener Prop-Name die komplette
--- Ausrüstung unbenutzbar machen, statt nur die Optik der Lampe zu kosten.
local lampModel = 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
local function attachGear(ped)
ped = ped or cache.ped
currentGear.tank = attachProp(ped, TANK_MODEL, TANK_BONE, vec3(-0.25, -0.25, 0.0), vec3(180.0, 90.0, 0.0))
currentGear.mask = attachProp(ped, MASK_MODEL, MASK_BONE, vec3(0.0, 0.0, 0.0), vec3(180.0, 90.0, 0.0))
if hasLampProp then
currentGear.lamp = attachProp(ped, lampModel, 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
local function isPropIntact(prop)
@@ -123,6 +157,8 @@ local function isPropIntact(prop)
end
local function isGearIntact()
if hasLampProp and not isPropIntact(currentGear.lamp) then return false end
return isPropIntact(currentGear.mask) and isPropIntact(currentGear.tank)
end
@@ -151,6 +187,7 @@ local function removeGear(outOfAir, immediateSync)
syncTank(immediateSync)
currentGear.enabled = false
setLamp(false)
activeTank = nil
warned = {}
@@ -272,8 +309,8 @@ local function sendHudState(visible)
depth = currentDepth,
level = level,
deep = currentDepth >= config.deepWarningDepth,
hasLamp = false,
lamp = false
hasLamp = true,
lamp = lampOn
})
end
@@ -441,6 +478,17 @@ RegisterNetEvent('d4rk_divegear:client:useGear', function()
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 -
--- 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
syncTank(true)
setLamp(false)
deleteGear()
SendNUIMessage({ action = 'state', visible = false })
SetEnableScuba(cache.ped, false)