From 30c40c9f69a051368d57b989b236c1ef343a6346 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 11 Aug 2026 09:55:45 +0200 Subject: [PATCH] feat(hud): Dreier-Konsole mit Finimeter und Tiefenmesser Statt zweier Zahlenfelder jetzt zwei Zeigerinstrumente links und rechts vom Luft-Ring - wie eine echte Tauchkonsole. Der Ring bleibt in der Mitte, weil die Restzeit die Zahl ist, auf die es ankommt. - Finimeter 0-300 bar, roter Bereich = Reserve unter reservePressure - Tiefenmesser 0-depthDialMax, roter Bereich = Tiefengrenze des Atemgases. Der faengt bei Pressluft bei 45 m an und bei Trimix erst bei 100 m - der Gefahrenbereich haengt also an der getragenen Flasche. Gibt es real nicht, hilft im Spiel aber sofort beim Einschaetzen - Skalenzahlen bewusst weggelassen: bei 96 px kollidieren sie mit allem und liest ohnehin niemand. Zeiger zeigt die Tendenz, die Zahl in der Uhr den Wert - Zeiger und Wert faerben sich rot, sobald der rote Bereich erreicht ist Beim Bauen gefunden: CSS transform-origin auf dem Zeiger wird zusaetzlich zum rotate(winkel 50 50) im Attribut angewendet, der Drehpunkt wandert dadurch doppelt und der Zeiger landet ausserhalb der Uhr. transform-origin ist raus. README-Renderings auf die Konsole umgebaut. Co-Authored-By: Claude Opus 5 --- README.md | 15 ++-- client/main.lua | 6 ++ config/client.lua | 5 ++ docs/gen-hud-svg.mjs | 134 ++++++++++++++++++---------- docs/hud-kritisch.svg | 33 +++---- docs/hud-normal.svg | 33 +++---- docs/hud-warnung.svg | 33 +++---- nui/index.html | 37 ++++++-- nui/script.js | 111 ++++++++++++++++++++--- nui/style.css | 198 +++++++++++++++++++++++++++--------------- 10 files changed, 404 insertions(+), 201 deletions(-) diff --git a/README.md b/README.md index c4cec6b..0a64441 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,20 @@ die bei Animationen nicht mehr verschwindet. > Die Bilder sind maßstabsgetreue Renderings des Overlays aus denselben Werten wie > `nui/style.css` — keine In-Game-Aufnahmen. Erzeugt mit `docs/gen-hud-svg.mjs`. -**Normal** — Luft, Tiefe, Fülldruck, Lampe an +Eine Dreier-Konsole wie am echten Tauchgerät: **Finimeter** links, Restluft als +Ring in der Mitte, **Tiefenmesser** rechts. + +**Normal** — Druck im grünen Bereich, flach getaucht, Lampe an ![HUD, normaler Zustand](docs/hud-normal.svg) -**Warnung** — Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth`, -Druck unter `reservePressure` +**Warnung** — Zeiger in der Reserve (roter Bereich ab `reservePressure`), +Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth` ![HUD, Warnstufe](docs/hud-warnung.svg) -**Kritisch** — letzte Sekunden, der Ring pulsiert +**Kritisch** — unter der Tiefengrenze des Atemgases: der Zeiger steht im roten +Bereich des Tiefenmessers, die Uhr pulsiert, Tiefenrausch läuft ![HUD, kritischer Luftstand](docs/hud-kritisch.svg) @@ -186,7 +190,8 @@ auf z.B. `2`. | `hudOnlyUnderwater` | `false` | Auf `false` schon sichtbar, sobald die Ausrüstung an ist — Finimeter ablesen ohne Inventar | | `hudFollowGameHud` | `true` | Mit dem Spiel-HUD ausblenden | | `hudToggleKey` | `''` | Eigene Taste, leer = unbelegt | -| `reservePressure` | `50` | Ab hier wird die Druckanzeige rot | +| `reservePressure` | `50` | Roter Bereich des Finimeters, ab hier färbt sich der Zeiger | +| `depthDialMax` | `100.0` | Skalenende des Tiefenmessers. Tiefer schlägt der Zeiger an, die Zahl stimmt weiter | **Lampe** diff --git a/client/main.lua b/client/main.lua index 766226c..8491568 100644 --- a/client/main.lua +++ b/client/main.lua @@ -382,7 +382,13 @@ local function sendHudState(visible) oxygen = oxygen, capacity = capacity, depth = currentDepth, + -- Skalenwerte für die beiden Zeigerinstrumente. Kommen mit, damit die + -- Config an einer Stelle bleibt und das NUI nichts nachrechnen muss. + depthScale = config.depthDialMax, + depthLimit = activeTank and activeTank.maxDepth or nil, pressure = pressure, + maxPressure = activeTank and activeTank.pressure or nil, + reservePressure = config.reservePressure, reserve = pressure > 0 and pressure <= config.reservePressure, level = level, deep = currentDepth >= config.deepWarningDepth, diff --git a/config/client.lua b/config/client.lua index e19ac22..feed1f5 100644 --- a/config/client.lua +++ b/config/client.lua @@ -59,6 +59,11 @@ return { --- Ab dieser Tiefe wird die Tiefenanzeige eingefärbt (Meter). deepWarningDepth = 25.0, + --- Skalenende des Tiefenmessers (Meter). Echte Tiefenmesser gehen meist bis + --- 60 m; hier lieber so weit, dass auch Trimix-Tauchgänge auf der Skala + --- bleiben. Tiefer schlägt der Zeiger an, die Zahl in der Mitte stimmt weiter. + depthDialMax = 100.0, + --------------------------------------------------------------------------- -- Tiefenrausch --------------------------------------------------------------------------- diff --git a/docs/gen-hud-svg.mjs b/docs/gen-hud-svg.mjs index 3e84047..7ceb528 100644 --- a/docs/gen-hud-svg.mjs +++ b/docs/gen-hud-svg.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node // Erzeugt die HUD-Renderings für die README aus denselben Werten wie nui/style.css. -// Keine In-Game-Screenshots - eine massstabsgetreue Nachbildung des Overlays. +// Keine In-Game-Screenshots - eine massstabsgetreue Nachbildung der Konsole. // // Nutzung: node docs/gen-hud-svg.mjs [ziel-ordner] (default: docs) @@ -17,44 +17,78 @@ const C = { text: '#e2e8f0', muted: '#94a3b8', panel: '#09121c', - border: '#94a3b8' + face: '#0b1622' }; -// Geometrie spiegelt das Grid aus style.css: -// padding 14/22, Spalten 84 | 92 | 84 mit column-gap 22, row-gap 8, Lampe 23 hoch. -const PAD_X = 22, PAD_Y = 14, COL = 84, GAUGE = 92, GAP = 22, ROW_GAP = 8, LAMP_H = 23; -const PW = PAD_X * 2 + COL * 2 + GAUGE + GAP * 2; -const PH = PAD_Y * 2 + GAUGE + ROW_GAP + LAMP_H; +const F = "'Segoe UI',Roboto,system-ui,sans-serif"; -const W = 520, H = PH + 70; +// Masse spiegeln das Grid aus style.css: padding 14/20, column-gap 20, +// Uhr 96, Ring 92, Beschriftung darunter, Lampe in Zeile 2. +const PAD_X = 20, PAD_Y = 14, DIAL = 96, GAUGE = 92, GAP = 20; +const ROW1 = 112, ROW_GAP = 6, LAMP_H = 23; +const PW = PAD_X * 2 + DIAL * 2 + GAUGE + GAP * 2; +const PH = PAD_Y * 2 + ROW1 + ROW_GAP + LAMP_H; + +const W = 560, H = PH + 70; const PX = (W - PW) / 2, PY = (H - PH) / 2; -const GX = PX + PAD_X + COL + GAP; -const CX = GX + GAUGE / 2, CY = PY + PAD_Y + GAUGE / 2; +const LEFT_CX = PX + PAD_X + DIAL / 2; +const RIGHT_CX = PX + PW - PAD_X - DIAL / 2; +const MID_CX = PX + PW / 2; +const DIAL_CY = PY + PAD_Y + DIAL / 2; +const GAUGE_CY = PY + PAD_Y + ROW1 / 2; -const R = 52 * (GAUGE / 120); // r=52 im viewBox 120, auf 92px skaliert -const SW = 8 * (GAUGE / 120); -const CIRC = 2 * Math.PI * R; +const n = (v) => Number(v.toFixed(2)); +const rad = (d) => ((d - 90) * Math.PI) / 180; +const pt = (cx, cy, r, d) => [cx + r * Math.cos(rad(d)), cy + r * Math.sin(rad(d))]; -const DEPTH_X = PX + PAD_X + COL; // rechtsbündig, zeigt zum Ring -const PRESS_X = GX + GAUGE + GAP; // linksbündig, zeigt zum Ring -const CAP_Y = PY + PAD_Y + 35; -const VAL_Y = PY + PAD_Y + 61; - -const FONT = "'Segoe UI',Roboto,system-ui,sans-serif"; const fmt = (s) => `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(Math.floor(s % 60)).padStart(2, '0')}`; -function readout(x, anchor, label, value, unit, colour) { - const unitTspan = `${unit}`; - return `${label} - ${value}${unitTspan}`; +/** Zeigerinstrument. 270-Grad-Skala, unten offen - wie ein echtes Finimeter. */ +function dial(cx, cy, { value, max, unit, label, zoneFrom, zoneTo, needleColour, valueColour, decimals = 0 }) { + // 96px Uhr, Geometrie aus dem viewBox 0 0 100 100 skaliert + const k = DIAL / 100; + const R = 42 * k, A0 = -135, SWEEP = 270; + const ang = (v) => A0 + SWEEP * Math.min(1, Math.max(0, v / (max || 1))); + + let s = ``; + + if (zoneFrom !== undefined && zoneFrom < max) { + const a = ang(zoneFrom), b = ang(Math.min(zoneTo, max)); + const [x1, y1] = pt(cx, cy, R - 2 * k, a); + const [x2, y2] = pt(cx, cy, R - 2 * k, b); + s += ``; + } + + for (let i = 0; i <= 10; i++) { + const a = A0 + (SWEEP * i) / 10, major = i % 2 === 0; + const [x1, y1] = pt(cx, cy, R - 6 * k, a); + const [x2, y2] = pt(cx, cy, (major ? R - 13 * k : R - 10 * k), a); + s += ``; + } + + const [tipX, tipY] = pt(cx, cy, 33 * k, ang(value)); + const [tailX, tailY] = pt(cx, cy, -8 * k, ang(value)); + s += ``; + s += ``; + + s += `${value.toFixed(decimals)}${unit}`; + s += `${label}`; + + return s; } -function build({ file, title, oxygen, capacity, depth, pressure, level, deep, reserve, lamp }) { +function build({ file, title, oxygen, capacity, level, depth, depthLimit, pressure, reservePressure, lamp, deep }) { const ratio = Math.max(0, Math.min(1, oxygen / capacity)); - const ring = level === 'danger' ? C.danger : level === 'warn' ? C.warn : C.accent; + const ringColour = level === 'danger' ? C.danger : level === 'warn' ? C.warn : C.accent; + + const R = 52 * (GAUGE / 120), SW = 8 * (GAUGE / 120), CIRC = 2 * Math.PI * R; + + const overLimit = depth >= depthLimit; + const depthColour = overLimit ? C.danger : deep ? C.warn : C.text; + const inReserve = pressure <= reservePressure; + const pressureColour = inReserve ? C.danger : C.text; const lampColour = lamp ? C.accent : C.muted; - const lampX = CX - 43; const svg = ` @@ -65,27 +99,30 @@ function build({ file, title, oxygen, capacity, depth, pressure, level, deep, re + - + ${dial(LEFT_CX, DIAL_CY, { + value: pressure, max: 300, unit: 'bar', label: 'DRUCK', + zoneFrom: 0, zoneTo: reservePressure, + needleColour: pressureColour, valueColour: pressureColour + })} - - - + + + + ${fmt(oxygen)} + LUFT - ${fmt(oxygen)} - LUFT + ${dial(RIGHT_CX, DIAL_CY, { + value: depth, max: 100, unit: 'm', label: 'TIEFE', decimals: 1, + zoneFrom: depthLimit, zoneTo: 100, + needleColour: depthColour, valueColour: depthColour + })} - ${readout(DEPTH_X, 'end', 'TIEFE', depth.toFixed(1), 'm', deep ? C.warn : C.text)} - ${readout(PRESS_X, 'start', 'DRUCK', String(Math.round(pressure)), 'bar', reserve ? C.danger : C.text)} - - - - - LAMPE - + + + LAMPE `; @@ -95,17 +132,18 @@ function build({ file, title, oxygen, capacity, depth, pressure, level, deep, re build({ file: 'hud-normal.svg', title: 'Tauch-HUD: normaler Zustand', - oxygen: 545, capacity: 1200, depth: 12.4, pressure: 136, level: 'ok', lamp: true + oxygen: 545, capacity: 1200, level: 'ok', + depth: 12.4, depthLimit: 45, pressure: 136, reservePressure: 50, lamp: true }); build({ file: 'hud-warnung.svg', title: 'Tauch-HUD: Warnstufe und Reservedruck', - oxygen: 92, capacity: 900, depth: 27.5, pressure: 31, - level: 'warn', deep: true, reserve: true, lamp: false + oxygen: 92, capacity: 900, level: 'warn', + depth: 27.5, depthLimit: 45, deep: true, pressure: 31, reservePressure: 50, lamp: false }); build({ - file: 'hud-kritisch.svg', title: 'Tauch-HUD: kritischer Luftstand', - oxygen: 8, capacity: 600, depth: 31.7, pressure: 4, - level: 'danger', deep: true, reserve: true, lamp: true + file: 'hud-kritisch.svg', title: 'Tauch-HUD: unter der Tiefengrenze des Atemgases', + oxygen: 38, capacity: 900, level: 'danger', + depth: 52.4, depthLimit: 45, deep: true, pressure: 13, reservePressure: 50, lamp: true }); diff --git a/docs/hud-kritisch.svg b/docs/hud-kritisch.svg index f8b6d4d..7808dcb 100644 --- a/docs/hud-kritisch.svg +++ b/docs/hud-kritisch.svg @@ -1,4 +1,4 @@ - + @@ -6,28 +6,21 @@ - + + - + 13barDRUCK - - - + + + + 00:38 + LUFT - 00:08 - LUFT + 52.4mTIEFE - TIEFE - 31.7m - DRUCK - 4bar - - - - - LAMPE - + + + LAMPE diff --git a/docs/hud-normal.svg b/docs/hud-normal.svg index 4f753e0..5273f6f 100644 --- a/docs/hud-normal.svg +++ b/docs/hud-normal.svg @@ -1,4 +1,4 @@ - + @@ -6,28 +6,21 @@ - + + - + 136barDRUCK - - - + + + + 09:05 + LUFT - 09:05 - LUFT + 12.4mTIEFE - TIEFE - 12.4m - DRUCK - 136bar - - - - - LAMPE - + + + LAMPE diff --git a/docs/hud-warnung.svg b/docs/hud-warnung.svg index e4ccf45..ca5c262 100644 --- a/docs/hud-warnung.svg +++ b/docs/hud-warnung.svg @@ -1,4 +1,4 @@ - + @@ -6,28 +6,21 @@ - + + - + 31barDRUCK - - - + + + + 01:32 + LUFT - 01:32 - LUFT + 27.5mTIEFE - TIEFE - 27.5m - DRUCK - 31bar - - - - - LAMPE - + + + LAMPE diff --git a/nui/index.html b/nui/index.html index 62f4577..be92647 100644 --- a/nui/index.html +++ b/nui/index.html @@ -6,11 +6,24 @@