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
+66
View File
@@ -0,0 +1,66 @@
// Das HUD ist reine Anzeige - es sendet nichts zurueck an den Client.
// Der Lua-Client schickt den kompletten Zustand, damit die Locale-Texte an einer
// Stelle bleiben und hier nichts uebersetzt werden muss.
const RING = 327; // 2 * PI * 52, muss zu --ring in style.css passen
const hud = document.getElementById('hud');
const gaugeFill = document.getElementById('gauge-fill');
const airValue = document.getElementById('air-value');
const depthValue = document.getElementById('depth-value');
const lamp = document.getElementById('lamp');
const labels = {
air: document.getElementById('air-label'),
depth: document.getElementById('depth-label'),
lamp: document.getElementById('lamp-label')
};
function formatTime(seconds) {
const total = Math.max(0, Math.floor(seconds));
const mm = String(Math.floor(total / 60)).padStart(2, '0');
const ss = String(total % 60).padStart(2, '0');
return `${mm}:${ss}`;
}
function render(state) {
hud.classList.toggle('hidden', !state.visible);
if (!state.visible) return;
const capacity = state.capacity > 0 ? state.capacity : 1;
const ratio = Math.min(1, Math.max(0, state.oxygen / capacity));
gaugeFill.style.strokeDashoffset = String(RING * (1 - ratio));
airValue.textContent = formatTime(state.oxygen);
depthValue.textContent = state.depth.toFixed(1);
// Warnstufen kommen als Restsekunden vom Client, damit sie zu
// config.warnAtSeconds passen statt hier nochmal definiert zu werden.
hud.classList.toggle('warn', state.level === 'warn');
hud.classList.toggle('danger', state.level === 'danger');
hud.classList.toggle('deep', !!state.deep);
lamp.classList.toggle('hidden', !state.hasLamp);
lamp.classList.toggle('on', !!state.lamp);
}
window.addEventListener('message', (event) => {
const data = event.data;
if (!data) return;
if (data.action === 'labels') {
labels.air.textContent = data.air;
labels.depth.textContent = data.depth;
labels.lamp.textContent = data.lamp;
return;
}
if (data.action === 'state') render(data);
});
// Damit sich das HUD im Browser ohne FiveM anschauen laesst.
if (typeof GetParentResourceName !== 'function') {
render({ visible: true, oxygen: 372, capacity: 600, depth: 12.4, level: 'ok', hasLamp: true, lamp: true });
}