feat(hud): NUI-Overlay mit Luft und Tauchtiefe
Lint / Lint Resource (push) Has been cancelled

Ersetzt die qbx.drawText2d-Anzeige (rohe Zahl plus Uhr-Emoji in der Ecke, jeden
Frame neu gezeichnet) durch ein NUI-Overlay.

- Luft als Ring mit mm:ss, Farbstufen aus config.warnAtSeconds abgeleitet,
  Pulsieren im kritischen Bereich
- Tiefe in Metern ueber GetWaterHeight, geglaettet damit Wellengang die Zahl
  nicht zappeln laesst; Einfaerbung ab config.deepWarningDepth
- Platz fuer den Lampen-Status, wird in Phase 5 befuellt
- Sichtbar nur mit angelegter Ausruestung unter Wasser, mit Fade
- Updates gedrosselt auf 4/s statt jeden Frame
- Kein backdrop-filter (bekannter FiveM-Bug)

Die geglaettete Tiefe speist auch den tiefenabhaengigen Verbrauch, der bisher
bei jedem Tick neu und ungeglaettet gemessen wurde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 23:49:03 +02:00
co-authored by Claude Opus 5
parent d661152bc9
commit 28ab8e796c
9 changed files with 384 additions and 10 deletions
+69 -10
View File
@@ -14,6 +14,20 @@ local activeTank = nil
--- Bereits ausgelöste Warnschwellen des laufenden Tauchgangs.
local warned = {}
--- Geglättete Tauchtiefe in Metern. Wird vom HUD-Thread gepflegt und sowohl für
--- die Anzeige als auch für den tiefenabhängigen Verbrauch benutzt.
local currentDepth = 0.0
--- Grenzen für die Farbstufen im HUD, aus config.warnAtSeconds abgeleitet.
local warnThreshold, dangerThreshold = 0, 0
for i = 1, #config.warnAtSeconds do
local threshold = config.warnAtSeconds[i]
if threshold > warnThreshold then warnThreshold = threshold end
if dangerThreshold == 0 or threshold < dangerThreshold then dangerThreshold = threshold end
end
local MASK_MODEL = `p_d_scuba_mask_s`
local TANK_MODEL = `p_s_scuba_tank_s`
local MASK_BONE = 12844
@@ -37,10 +51,12 @@ local function getDepth()
end
--- Verbrauchsfaktor. Tiefer tauchen kostet mehr Luft.
--- Nutzt die geglättete Tiefe aus dem HUD-Thread, damit Wellengang den Verbrauch
--- nicht im Sekundentakt springen lässt.
local function getDecayFactor()
if not config.depthDecayEnabled then return 1.0 end
return math.min(config.maxDecayFactor, 1.0 + getDepth() / config.depthDecayReference)
return math.min(config.maxDecayFactor, 1.0 + currentDepth / config.depthDecayReference)
end
--- @param ped number? default cache.ped - beim Model-Wechsel muss der neue Ped explizit rein
@@ -236,18 +252,60 @@ local function takeOffSuit()
end
end
local function startOxygenLevelDrawTextThread()
--- @param visible boolean
local function sendHudState(visible)
local oxygen = activeTank and activeTank.oxygen or 0.0
local capacity = activeTank and activeTank.capacity or 1
local level = 'ok'
if oxygen <= dangerThreshold then
level = 'danger'
elseif oxygen <= warnThreshold then
level = 'warn'
end
SendNUIMessage({
action = 'state',
visible = visible,
oxygen = oxygen,
capacity = capacity,
depth = currentDepth,
level = level,
deep = currentDepth >= config.deepWarningDepth,
hasLamp = false,
lamp = false
})
end
--- Pflegt die geglättete Tiefe und schiebt den Zustand ins NUI.
--- Läuft auch über Wasser weiter, weil getDecayFactor die Tiefe braucht - nur die
--- Anzeige wird ausgeblendet.
local function startHudThread()
CreateThread(function()
local wasVisible = false
SendNUIMessage({
action = 'labels',
air = locale('hud.air'),
depth = locale('hud.depth'),
lamp = locale('hud.lamp')
})
while currentGear.enabled do
if IsPedSwimmingUnderWater(cache.ped) and activeTank then
qbx.drawText2d({
text = formatTime(activeTank.oxygen)..'',
coords = vec2(1.0, 1.42),
scale = 0.45
})
currentDepth = currentDepth + (getDepth() - currentDepth) * 0.25
local visible = config.hudEnabled and activeTank ~= nil and IsPedSwimmingUnderWater(cache.ped)
if visible or wasVisible then
sendHudState(visible)
wasVisible = visible
end
Wait(0)
Wait(config.hudUpdateIntervalMs)
end
currentDepth = 0.0
sendHudState(false)
end)
end
@@ -370,7 +428,7 @@ local function putOnSuit()
currentGear.enabled = true
-- Initiate breathing suit audio
startOxygenLevelDecrementerThread()
startOxygenLevelDrawTextThread()
startHudThread()
startGearWatchdogThread()
end
end
@@ -406,6 +464,7 @@ AddEventHandler('onResourceStop', function(resource)
syncTank(true)
deleteGear()
SendNUIMessage({ action = 'state', visible = false })
SetEnableScuba(cache.ped, false)
SetPedMaxTimeUnderwater(cache.ped, config.maxTimeUnderwater)
end)