docs: vollstaendige README mit HUD-Renderings, Luft-Ring mittig
Lint / Lint Resource (push) Has been cancelled
Lint / Lint Resource (push) Has been cancelled
HUD-Layout symmetrisch: Tiefe links, Luft-Ring mittig, Druck rechts, Lampe zentriert darunter. Umgestellt auf Grid, damit die drei Werte garantiert auf einer Mittelachse sitzen und die Lampen-Leiste ohne Ausgleichs-Margins in einer zweiten Zeile haengt. README: - Drei HUD-Renderings (normal, Warnung, kritisch) als SVG. Keine In-Game-Bilder - hier laeuft kein GTA -, sondern massstabsgetreu aus denselben Werten wie style.css erzeugt, per docs/gen-hud-svg.mjs reproduzierbar - Vollstaendige Config-Referenz fuer client.lua und shared.lua - Item-Tabelle mit Laufzeit, Fuelldruck, Gewicht, dazu die Metadata-Keys und warum kein decay gesetzt werden darf - Installation, Bedienung, Exports, Balancing-Hinweis - Bekannte Unsicherheiten offen benannt Item-Snippet mit klarem Copy-Paste-Header. Bewusst keine fertige items.lua zum Ersetzen: die wuerde die restlichen Items des Servers mitloeschen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,36 +1,97 @@
|
|||||||
# d4rk_divegear
|
# d4rk_divegear
|
||||||
|
|
||||||
Tauchausrüstung für QBox. Fork von [qbx_divegear](https://github.com/Qbox-project/qbx_divegear)
|
Tauchausrüstung für QBox. Fork von [qbx_divegear](https://github.com/Qbox-project/qbx_divegear)
|
||||||
mit Flaschengrößen, gesyncter Lampe, Tiefenanzeige und Ausrüstung, die bei
|
mit Flaschengrößen, gesyncter Lampe, Tiefen- und Druckanzeige — und Ausrüstung,
|
||||||
Animationen nicht verschwindet.
|
die bei Animationen nicht mehr verschwindet.
|
||||||
|
|
||||||
|
## HUD
|
||||||
|
|
||||||
|
> 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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Warnung** — Restluft unter der ersten Schwelle, Tiefe über `deepWarningDepth`,
|
||||||
|
Druck unter `reservePressure`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Kritisch** — letzte Sekunden, der Ring pulsiert
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Das HUD erscheint nur mit angelegter Ausrüstung unter Wasser und blendet sich
|
||||||
|
weich ein und aus.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Vier Flaschengrößen** — 5, 10, 15 und 20 Minuten. Die Restluft liegt in der
|
- **Vier Flaschengrößen** — 5, 10, 15 und 20 Minuten. Die Restluft liegt in der
|
||||||
Item-Metadata und überlebt Ablegen, Relog, Resource-Restart und Tod. Der
|
Item-Metadata und überlebt Ablegen, Relog, Resource-Restart und Tod. Der
|
||||||
Füllstand ist als Balken direkt im Inventar-Slot sichtbar.
|
Füllstand ist als Balken direkt im Inventar-Slot sichtbar.
|
||||||
- **Gesyncte Tauchlampe** — an der Ausrüstung, An/Aus per Taste (Standard `L`).
|
- **Manometer in bar** — physikalisch korrekt aus dem Restanteil abgeleitet,
|
||||||
Andere Spieler sehen den Lichtkegel.
|
Reservedruck wird eingefärbt.
|
||||||
- **NUI-HUD** — Restluft als Ring mit `mm:ss`, Tauchtiefe in Metern und
|
- **Gesyncte Tauchlampe** — an der Ausrüstung, An/Aus per Taste. Andere Spieler
|
||||||
Manometer in bar, mit Warnstufen. Nur sichtbar mit angelegter Ausrüstung
|
sehen den Lichtkegel, und zwar in die Richtung, in die tatsächlich geschaut wird.
|
||||||
unter Wasser, und ausblendbar für Unterwasser-Screenshots.
|
- **Tiefenanzeige** und **tiefenabhängiger Verbrauch** — tiefer tauchen kostet
|
||||||
- **Tiefenabhängiger Verbrauch** — tiefer tauchen kostet mehr Luft. Abschaltbar.
|
mehr Luft. Abschaltbar.
|
||||||
- **Ausrüstung bleibt** — bei Emotes, Ragdoll, Kleiderwechsel und aufräumenden
|
- **Ausrüstung bleibt** — bei Emotes, Ragdoll, Kleiderwechsel und aufräumenden
|
||||||
Fremd-Scripts.
|
Fremd-Scripts.
|
||||||
|
- **HUD ausblendbar** für Unterwasser-Screenshots.
|
||||||
- **Serverseitige Prüfung** — die Restluft kann nur sinken, und nicht schneller
|
- **Serverseitige Prüfung** — die Restluft kann nur sinken, und nicht schneller
|
||||||
als physikalisch möglich.
|
als physikalisch möglich.
|
||||||
|
|
||||||
## Abhängigkeiten
|
## Abhängigkeiten
|
||||||
|
|
||||||
`qbx_core` · `ox_lib` · `ox_inventory`
|
[`qbx_core`](https://github.com/Qbox-project/qbx_core) ·
|
||||||
|
[`ox_lib`](https://github.com/overextended/ox_lib) ·
|
||||||
|
[`ox_inventory`](https://github.com/overextended/ox_inventory)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Resource nach `resources/` und in der `server.cfg` starten.
|
1. Ordner nach `resources/` und in der `server.cfg` starten — **nach**
|
||||||
2. Die Einträge aus [`items_for_ox_inventory.lua`](items_for_ox_inventory.lua) in
|
`ox_inventory` und `qbx_core`:
|
||||||
`ox_inventory/data/items.lua` kopieren.
|
|
||||||
3. Item-Icons nach `cdn.d4rkst3r.de/items/` legen (`diving_gear.png`,
|
```cfg
|
||||||
`diving_fill.png`, `diving_tank_small.png` … `diving_tank_xl.png`).
|
ensure d4rk_divegear
|
||||||
|
```
|
||||||
|
|
||||||
|
2. [`items_for_ox_inventory.lua`](items_for_ox_inventory.lua) komplett markieren
|
||||||
|
und in `ox_inventory/data/items.lua` innerhalb der `return { … }`-Klammer
|
||||||
|
einfügen. Sechs Items, nichts weiter anzupassen.
|
||||||
|
|
||||||
|
3. Icons nach `cdn.d4rkst3r.de/items/` legen: `diving_gear.png`,
|
||||||
|
`diving_fill.png`, `diving_tank_small.png`, `diving_tank_medium.png`,
|
||||||
|
`diving_tank_large.png`, `diving_tank_xl.png`.
|
||||||
|
|
||||||
|
Es gibt bewusst **keine** fertige `items.lua` zum Ersetzen — die würde die
|
||||||
|
restlichen Items des Servers überschreiben.
|
||||||
|
|
||||||
|
## Items
|
||||||
|
|
||||||
|
| Item | Label | Laufzeit | Fülldruck | Gewicht |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `diving_gear` | Tauchausrüstung | — | — | 5000 |
|
||||||
|
| `diving_fill` | Pressluft-Kartusche | — | — | 1000 |
|
||||||
|
| `diving_tank_small` | Tauchflasche (5 Min) | 300 s | 300 bar | 4000 |
|
||||||
|
| `diving_tank_medium` | Tauchflasche (10 Min) | 600 s | 300 bar | 7000 |
|
||||||
|
| `diving_tank_large` | Tauchflasche (15 Min) | 900 s | 300 bar | 10000 |
|
||||||
|
| `diving_tank_xl` | Tauchflasche (20 Min) | 1200 s | 300 bar | 13000 |
|
||||||
|
|
||||||
|
Dass alle Flaschen mit demselben Fülldruck starten, ist gewollt: in echt macht das
|
||||||
|
Volumen die Laufzeit, nicht der Druck. Die große Flasche hält 300 bar einfach länger.
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
| Key | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `oxygen` | Restluft in Sekunden — die eigentliche Wahrheit |
|
||||||
|
| `durability` | Füllstand in Prozent, nur für den Balken im Inventar-Slot |
|
||||||
|
|
||||||
|
**Kein `decay = true` auf den Flaschen.** ox_inventory löscht Items mit `decay`,
|
||||||
|
sobald `durability` 0 erreicht — die leere Flasche würde also verschwinden statt
|
||||||
|
auffüllbar zu bleiben.
|
||||||
|
|
||||||
## Bedienung
|
## Bedienung
|
||||||
|
|
||||||
@@ -38,46 +99,124 @@ Animationen nicht verschwindet.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Ausrüstung an-/ausziehen | `diving_gear` benutzen |
|
| Ausrüstung an-/ausziehen | `diving_gear` benutzen |
|
||||||
| Flasche wählen | Menü beim Anlegen (entfällt bei nur einer Flasche) |
|
| Flasche wählen | Menü beim Anlegen (entfällt bei nur einer Flasche) |
|
||||||
| Flasche auffüllen | `diving_fill` benutzen, nicht unter Wasser |
|
| Flasche auffüllen | `diving_fill` benutzen — nicht unter Wasser, nicht bei voller Flasche |
|
||||||
| Lampe an/aus | `L` (in den FiveM-Tastenbelegungen umlegbar) |
|
| Lampe an/aus | `L` |
|
||||||
| HUD ausblenden | Eigene Taste, standardmäßig unbelegt — in den FiveM-Tastenbelegungen unter "FiveM" setzen |
|
| HUD ein-/ausblenden | Eigene Taste, standardmäßig unbelegt |
|
||||||
|
|
||||||
### HUD ausblenden für Screenshots
|
Beide Tasten sind vom Spieler in den FiveM-Einstellungen unter *Tastenbelegung →
|
||||||
|
FiveM* umlegbar.
|
||||||
Drei Wege, damit es unabhängig von der eingesetzten HUD-Resource funktioniert:
|
|
||||||
|
|
||||||
1. **Automatisch** — das Tauch-HUD verschwindet mit dem Spiel-HUD. Greift bei
|
|
||||||
allem, was `DisplayHud(false)` setzt (Screenshot-Scripts, HUD-Toggles,
|
|
||||||
Cinematic-Modes). Abschaltbar über `hudFollowGameHud`.
|
|
||||||
2. **Eigene Taste** — `hudToggleKey` in der Config, oder vom Spieler selbst belegt.
|
|
||||||
3. **Export** — für andere Resources:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
exports.d4rk_divegear:setHudVisible(false)
|
|
||||||
local shown = exports.d4rk_divegear:isHudVisible()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Konfiguration
|
## Konfiguration
|
||||||
|
|
||||||
| Datei | Inhalt |
|
### `config/shared.lua` — Client und Server
|
||||||
|---|---|
|
|
||||||
| [`config/client.lua`](config/client.lua) | Timings, Verbrauch, Tiefe, HUD, Lampe |
|
|
||||||
| [`config/shared.lua`](config/shared.lua) | Flaschen und ihre Laufzeit, Sync-Intervall, Anti-Cheat-Grenze |
|
|
||||||
|
|
||||||
Laufzeiten ändert man über `capacity` (Sekunden) in `config/shared.lua`. Wer den
|
| Option | Default | Bedeutung |
|
||||||
Tiefenfaktor (`maxDecayFactor`) hochdreht, muss `maxDrainPerSecond` mitziehen —
|
|---|---|---|
|
||||||
sonst schreibt der Server ehrlichen Spielern in großer Tiefe Luft zurück.
|
| `tanks` | 4 Flaschen | `name`, `capacity` (Sekunden), `pressure` (bar) |
|
||||||
|
| `syncIntervalSeconds` | `15` | Wie oft die Restluft an den Server gemeldet wird. Bei einem Crash gehen maximal so viele Sekunden verloren |
|
||||||
|
| `maxDrainPerSecond` | `5.0` | Obergrenze der serverseitigen Prüfung |
|
||||||
|
|
||||||
|
### `config/client.lua`
|
||||||
|
|
||||||
|
**Ablauf**
|
||||||
|
|
||||||
|
| Option | Default | Bedeutung |
|
||||||
|
|---|---|---|
|
||||||
|
| `putOnSuitTimeMs` / `takeOffSuitTimeMs` / `refillTankTimeMs` | `5000` | Dauer der Progressbars |
|
||||||
|
| `decayRate` | `1.0` | Sekunden Luft pro echter Sekunde, vor dem Tiefenfaktor |
|
||||||
|
| `warnAtSeconds` | `{60, 30, 10}` | Warnschwellen, jede einmal pro Tauchgang |
|
||||||
|
| `removeGearOnDeath` | `true` | Ausrüstung beim Tod abnehmen. Die Flasche bleibt mit ihrer Restluft im Inventar |
|
||||||
|
| `gearWatchdogIntervalMs` | `1000` | Prüfintervall, ob die Props noch am Ped hängen |
|
||||||
|
| `maxTimeUnderwater` | `50.0` | Sekunden unter Wasser ohne Ausrüstung (Vanilla-Verhalten) |
|
||||||
|
| `maxTimeUnderwaterOutOfAir` | `1.0` | Sekunden, sobald die Flasche leer ist |
|
||||||
|
|
||||||
|
**Tiefe**
|
||||||
|
|
||||||
|
| Option | Default | Bedeutung |
|
||||||
|
|---|---|---|
|
||||||
|
| `depthDecayEnabled` | `true` | Verbrauch steigt mit der Tiefe |
|
||||||
|
| `depthDecayReference` | `30.0` | Faktor `1 + tiefe / referenz` |
|
||||||
|
| `maxDecayFactor` | `4.0` | Deckel für den Faktor |
|
||||||
|
| `deepWarningDepth` | `25.0` | Ab hier wird die Tiefenanzeige eingefärbt |
|
||||||
|
|
||||||
|
**HUD**
|
||||||
|
|
||||||
|
| Option | Default | Bedeutung |
|
||||||
|
|---|---|---|
|
||||||
|
| `hudEnabled` | `true` | Ohne HUD bleibt die Ausrüstung voll nutzbar |
|
||||||
|
| `hudUpdateIntervalMs` | `250` | 4 Updates pro Sekunde |
|
||||||
|
| `hudFollowGameHud` | `true` | Mit dem Spiel-HUD ausblenden |
|
||||||
|
| `hudToggleKey` | `''` | Eigene Taste, leer = unbelegt |
|
||||||
|
| `reservePressure` | `50` | Ab hier wird die Druckanzeige rot |
|
||||||
|
|
||||||
|
**Lampe**
|
||||||
|
|
||||||
|
| Option | Default | Bedeutung |
|
||||||
|
|---|---|---|
|
||||||
|
| `lampKey` | `'l'` | Vorbelegung |
|
||||||
|
| `lampProp` | `'prop_cs_police_torch'` | Optik. Ungültiges Model → Warnung im Log, Licht funktioniert trotzdem |
|
||||||
|
| `lampOffset` | `vec3(0, 0.15, 0)` | Position relativ zum Kopf-Bone |
|
||||||
|
| `lampDirection` | `vec3(0, 1, 0)` | Richtung des Kegels, bone-relativ |
|
||||||
|
| `lampColour` | `{225, 245, 255}` | RGB |
|
||||||
|
| `lampDistance` / `lampBrightness` / `lampRoundness` / `lampRadius` / `lampFalloff` | `30/6/1/14/1.5` | Form des Lichtkegels |
|
||||||
|
| `lampShadows` | `true` | `false` nutzt `DrawSpotLight` statt der Schattenvariante — flacher, aber spürbar schneller |
|
||||||
|
| `lampDrawDistance` | `45.0` | Ab hier werden fremde Lampen nicht mehr gezeichnet |
|
||||||
|
| `lampMaxLights` | `4` | Gleichzeitige Lampen, die nächstgelegenen gewinnen |
|
||||||
|
|
||||||
|
> **Beim Balancing beachten:** `maxDrainPerSecond` (shared) muss über
|
||||||
|
> `maxDecayFactor` (client) liegen. Sonst hält der Server den ehrlichen Verbrauch
|
||||||
|
> in großer Tiefe für Cheat und schreibt Luft zurück.
|
||||||
|
|
||||||
|
## Exports
|
||||||
|
|
||||||
|
```lua
|
||||||
|
exports.d4rk_divegear:setHudVisible(false) -- HUD ausblenden
|
||||||
|
local shown = exports.d4rk_divegear:isHudVisible()
|
||||||
|
```
|
||||||
|
|
||||||
|
Gedacht für HUD- und Screenshot-Resources. Zusätzlich folgt das HUD automatisch
|
||||||
|
`IsHudHidden()`, greift also bei allem was `DisplayHud(false)` setzt, ohne dass
|
||||||
|
etwas angebunden werden muss.
|
||||||
|
|
||||||
|
## Wie die Lampe synchron wird
|
||||||
|
|
||||||
|
Licht ist in GTA nie synchronisiert — `DrawSpotLight` zeichnet nur lokal für den
|
||||||
|
aktuellen Frame. Der Lampenzustand wird deshalb als Statebag repliziert
|
||||||
|
(`LocalPlayer.state:set('divelight', …, true)`), und jeder Client zeichnet die
|
||||||
|
Lampen aller Taucher in seiner Nähe selbst.
|
||||||
|
|
||||||
|
Die Richtung kommt aus **zwei bone-relativen Punkten** am Kopf-Bone, nicht aus
|
||||||
|
`GetEntityForwardVector`: beim Tauchen liegt der Ped waagerecht im Wasser, der
|
||||||
|
Entity-Forward würde stur horizontal leuchten. Die Offsets von `GetPedBoneCoords`
|
||||||
|
sind laut Native-Doku *"relative to the bone's rotation"*, die Differenz zweier
|
||||||
|
solcher Punkte enthält also den Pitch.
|
||||||
|
|
||||||
## Entwicklung
|
## Entwicklung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node ../.tools/lint.mjs # Lua-Lint (CfxLua-tauglich)
|
node ../.tools/lint.mjs # Lua-Lint, CfxLua-tauglich
|
||||||
node ../.tools/nui-preview.mjs nui # HUD im Browser anschauen
|
node ../.tools/nui-preview.mjs nui # HUD im Browser anschauen
|
||||||
|
node docs/gen-hud-svg.mjs docs # Renderings für diese README neu bauen
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Umbau ist in [PLAN.md](PLAN.md) dokumentiert, inklusive der offenen Punkte.
|
Der Umbau ist in [PLAN.md](PLAN.md) dokumentiert, inklusive der offenen Punkte.
|
||||||
|
|
||||||
|
## Bekannte Unsicherheiten
|
||||||
|
|
||||||
|
Bisher **nicht im Spiel getestet** — geprüft sind Lua-Syntax und Globals, die
|
||||||
|
Locale-JSONs und das HUD im Browser.
|
||||||
|
|
||||||
|
- **`lampProp`** — der Prop-Name ist ungeprüft. Falls falsch: Warnung im Log, das
|
||||||
|
Licht funktioniert weiter, nur ohne sichtbares Prop. Ersatz finden:
|
||||||
|
[forge.plebmasters.de/objects](https://forge.plebmasters.de/objects)
|
||||||
|
- **`lampDirection`** — welche lokale Achse des Kopf-Bones nach vorne zeigt, hängt
|
||||||
|
am Skelett. Leuchtet der Kegel falsch herum: Achse tauschen oder Vorzeichen drehen.
|
||||||
|
- **`IsHudHidden()`** — ob die Native auf `DisplayHud(false)` anspringt, ist
|
||||||
|
ungetestet. Falls nicht, greifen Taste und Export weiterhin.
|
||||||
|
- Nach einem Relog muss die Ausrüstung neu angelegt werden. Die Restluft geht
|
||||||
|
dabei nicht verloren, die liegt auf der Flasche.
|
||||||
|
|
||||||
## Lizenz
|
## Lizenz
|
||||||
|
|
||||||
GPL-3.0, wie das Original. Upstream-Historie ist erhalten und liegt auf dem
|
GPL-3.0, wie das Original. Die Upstream-Historie ist erhalten und liegt auf dem
|
||||||
Remote `upstream`.
|
Remote `upstream`.
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#!/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.
|
||||||
|
//
|
||||||
|
// Nutzung: node docs/gen-hud-svg.mjs [ziel-ordner] (default: docs)
|
||||||
|
|
||||||
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
const OUT = process.argv[2] ?? 'docs';
|
||||||
|
mkdirSync(OUT, { recursive: true });
|
||||||
|
|
||||||
|
const C = {
|
||||||
|
accent: '#38bdf8',
|
||||||
|
warn: '#fbbf24',
|
||||||
|
danger: '#f87171',
|
||||||
|
text: '#e2e8f0',
|
||||||
|
muted: '#94a3b8',
|
||||||
|
panel: '#09121c',
|
||||||
|
border: '#94a3b8'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 W = 520, 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 R = 52 * (GAUGE / 120); // r=52 im viewBox 120, auf 92px skaliert
|
||||||
|
const SW = 8 * (GAUGE / 120);
|
||||||
|
const CIRC = 2 * Math.PI * R;
|
||||||
|
|
||||||
|
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 = `<tspan font-size="13" font-weight="500" fill="${C.muted}" dx="3">${unit}</tspan>`;
|
||||||
|
return `<text x="${x}" y="${CAP_Y}" text-anchor="${anchor}" font-family="${FONT}" font-size="9" font-weight="600" letter-spacing="1.4" fill="${C.muted}">${label}</text>
|
||||||
|
<text x="${x}" y="${VAL_Y}" text-anchor="${anchor}" font-family="${FONT}" font-size="26" font-weight="600" fill="${colour}">${value}${unitTspan}</text>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build({ file, title, oxygen, capacity, depth, pressure, level, deep, reserve, lamp }) {
|
||||||
|
const ratio = Math.max(0, Math.min(1, oxygen / capacity));
|
||||||
|
const ring = level === 'danger' ? C.danger : level === 'warn' ? C.warn : C.accent;
|
||||||
|
const lampColour = lamp ? C.accent : C.muted;
|
||||||
|
const lampX = CX - 43;
|
||||||
|
|
||||||
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="${title}">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#0d3550"/>
|
||||||
|
<stop offset="100%" stop-color="#071a26"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="${W}" height="${H}" fill="url(#water)"/>
|
||||||
|
|
||||||
|
<rect x="${PX}" y="${PY}" width="${PW}" height="${PH}" rx="16"
|
||||||
|
fill="${C.panel}" fill-opacity="0.72" stroke="${C.border}" stroke-opacity="0.18"/>
|
||||||
|
|
||||||
|
<g transform="rotate(-90 ${CX} ${CY})">
|
||||||
|
<circle cx="${CX}" cy="${CY}" r="${R.toFixed(2)}" fill="none" stroke="${C.muted}" stroke-opacity="0.16" stroke-width="${SW.toFixed(2)}" stroke-linecap="round"/>
|
||||||
|
<circle cx="${CX}" cy="${CY}" r="${R.toFixed(2)}" fill="none" stroke="${ring}" stroke-width="${SW.toFixed(2)}" stroke-linecap="round"
|
||||||
|
stroke-dasharray="${CIRC.toFixed(2)}" stroke-dashoffset="${(CIRC * (1 - ratio)).toFixed(2)}"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="${CX}" y="${CY - 1}" text-anchor="middle" font-family="${FONT}" font-size="21" font-weight="600" fill="${ring}">${fmt(oxygen)}</text>
|
||||||
|
<text x="${CX}" y="${CY + 15}" text-anchor="middle" font-family="${FONT}" font-size="9" font-weight="600" letter-spacing="1.4" fill="${C.muted}">LUFT</text>
|
||||||
|
|
||||||
|
${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)}
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<rect x="${lampX}" y="${PY + PAD_Y + GAUGE + ROW_GAP}" width="86" height="${LAMP_H}" rx="8" fill="${lampColour}" fill-opacity="${lamp ? 0.15 : 0.1}"/>
|
||||||
|
<circle cx="${lampX + 14}" cy="${PY + PAD_Y + GAUGE + ROW_GAP + LAMP_H / 2}" r="4" fill="${lampColour}"/>
|
||||||
|
<text x="${lampX + 24}" y="${PY + PAD_Y + GAUGE + ROW_GAP + LAMP_H / 2 + 3.5}" font-family="${FONT}" font-size="10" font-weight="600" letter-spacing="1.2" fill="${lampColour}">LAMPE</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
writeFileSync(join(OUT, file), svg);
|
||||||
|
console.log(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
build({
|
||||||
|
file: 'hud-normal.svg', title: 'Tauch-HUD: normaler Zustand',
|
||||||
|
oxygen: 545, capacity: 1200, depth: 12.4, pressure: 136, level: 'ok', 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
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 221" width="520" height="221" role="img" aria-label="Tauch-HUD: kritischer Luftstand">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#0d3550"/>
|
||||||
|
<stop offset="100%" stop-color="#071a26"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="520" height="221" fill="url(#water)"/>
|
||||||
|
|
||||||
|
<rect x="86" y="35" width="348" height="151" rx="16"
|
||||||
|
fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
|
||||||
|
|
||||||
|
<g transform="rotate(-90 260 95)">
|
||||||
|
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/>
|
||||||
|
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#f87171" stroke-width="6.13" stroke-linecap="round"
|
||||||
|
stroke-dasharray="250.49" stroke-dashoffset="247.15"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="260" y="94" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="21" font-weight="600" fill="#f87171">00:08</text>
|
||||||
|
<text x="260" y="110" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">LUFT</text>
|
||||||
|
|
||||||
|
<text x="192" y="84" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">TIEFE</text>
|
||||||
|
<text x="192" y="110" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#fbbf24">31.7<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">m</tspan></text>
|
||||||
|
<text x="328" y="84" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">DRUCK</text>
|
||||||
|
<text x="328" y="110" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#f87171">4<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">bar</tspan></text>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<rect x="217" y="149" width="86" height="23" rx="8" fill="#38bdf8" fill-opacity="0.15"/>
|
||||||
|
<circle cx="231" cy="160.5" r="4" fill="#38bdf8"/>
|
||||||
|
<text x="241" y="164" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="10" font-weight="600" letter-spacing="1.2" fill="#38bdf8">LAMPE</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 221" width="520" height="221" role="img" aria-label="Tauch-HUD: normaler Zustand">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#0d3550"/>
|
||||||
|
<stop offset="100%" stop-color="#071a26"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="520" height="221" fill="url(#water)"/>
|
||||||
|
|
||||||
|
<rect x="86" y="35" width="348" height="151" rx="16"
|
||||||
|
fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
|
||||||
|
|
||||||
|
<g transform="rotate(-90 260 95)">
|
||||||
|
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/>
|
||||||
|
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#38bdf8" stroke-width="6.13" stroke-linecap="round"
|
||||||
|
stroke-dasharray="250.49" stroke-dashoffset="136.73"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="260" y="94" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="21" font-weight="600" fill="#38bdf8">09:05</text>
|
||||||
|
<text x="260" y="110" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">LUFT</text>
|
||||||
|
|
||||||
|
<text x="192" y="84" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">TIEFE</text>
|
||||||
|
<text x="192" y="110" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#e2e8f0">12.4<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">m</tspan></text>
|
||||||
|
<text x="328" y="84" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">DRUCK</text>
|
||||||
|
<text x="328" y="110" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#e2e8f0">136<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">bar</tspan></text>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<rect x="217" y="149" width="86" height="23" rx="8" fill="#38bdf8" fill-opacity="0.15"/>
|
||||||
|
<circle cx="231" cy="160.5" r="4" fill="#38bdf8"/>
|
||||||
|
<text x="241" y="164" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="10" font-weight="600" letter-spacing="1.2" fill="#38bdf8">LAMPE</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 221" width="520" height="221" role="img" aria-label="Tauch-HUD: Warnstufe und Reservedruck">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="water" x1="0" y1="0" x2="0.6" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#0d3550"/>
|
||||||
|
<stop offset="100%" stop-color="#071a26"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width="520" height="221" fill="url(#water)"/>
|
||||||
|
|
||||||
|
<rect x="86" y="35" width="348" height="151" rx="16"
|
||||||
|
fill="#09121c" fill-opacity="0.72" stroke="#94a3b8" stroke-opacity="0.18"/>
|
||||||
|
|
||||||
|
<g transform="rotate(-90 260 95)">
|
||||||
|
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#94a3b8" stroke-opacity="0.16" stroke-width="6.13" stroke-linecap="round"/>
|
||||||
|
<circle cx="260" cy="95" r="39.87" fill="none" stroke="#fbbf24" stroke-width="6.13" stroke-linecap="round"
|
||||||
|
stroke-dasharray="250.49" stroke-dashoffset="224.88"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text x="260" y="94" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="21" font-weight="600" fill="#fbbf24">01:32</text>
|
||||||
|
<text x="260" y="110" text-anchor="middle" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">LUFT</text>
|
||||||
|
|
||||||
|
<text x="192" y="84" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">TIEFE</text>
|
||||||
|
<text x="192" y="110" text-anchor="end" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#fbbf24">27.5<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">m</tspan></text>
|
||||||
|
<text x="328" y="84" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="9" font-weight="600" letter-spacing="1.4" fill="#94a3b8">DRUCK</text>
|
||||||
|
<text x="328" y="110" text-anchor="start" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="26" font-weight="600" fill="#f87171">31<tspan font-size="13" font-weight="500" fill="#94a3b8" dx="3">bar</tspan></text>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<rect x="217" y="149" width="86" height="23" rx="8" fill="#94a3b8" fill-opacity="0.1"/>
|
||||||
|
<circle cx="231" cy="160.5" r="4" fill="#94a3b8"/>
|
||||||
|
<text x="241" y="164" font-family="'Segoe UI',Roboto,system-ui,sans-serif" font-size="10" font-weight="600" letter-spacing="1.2" fill="#94a3b8">LAMPE</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,4 +1,12 @@
|
|||||||
-- Zum Reinkopieren in ox_inventory/data/items.lua
|
-- =============================================================================
|
||||||
|
-- COPY-PASTE NACH ox_inventory/data/items.lua
|
||||||
|
-- =============================================================================
|
||||||
|
--
|
||||||
|
-- Alles unten komplett markieren und innerhalb der `return {` ... `}`-Klammer
|
||||||
|
-- einfügen. Sechs Items, nichts weiter anzupassen.
|
||||||
|
--
|
||||||
|
-- Bewusst KEINE fertige items.lua zum Ersetzen: die würde die restlichen Items
|
||||||
|
-- des Servers mitlöschen.
|
||||||
--
|
--
|
||||||
-- WICHTIG: kein `decay = true` auf den Flaschen. Die durability trägt hier den
|
-- WICHTIG: kein `decay = true` auf den Flaschen. Die durability trägt hier den
|
||||||
-- Füllstand, und ox_inventory löscht Items mit decay, sobald durability 0 erreicht
|
-- Füllstand, und ox_inventory löscht Items mit decay, sobald durability 0 erreicht
|
||||||
|
|||||||
+14
-18
@@ -6,6 +6,11 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="hud" class="hidden">
|
<div id="hud" class="hidden">
|
||||||
|
<div class="readout readout--right">
|
||||||
|
<span id="depth-label" class="caption">TIEFE</span>
|
||||||
|
<div class="value"><span id="depth-value">0.0</span><span class="unit">m</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="gauge">
|
<div class="gauge">
|
||||||
<svg viewBox="0 0 120 120" aria-hidden="true">
|
<svg viewBox="0 0 120 120" aria-hidden="true">
|
||||||
<circle class="gauge-track" cx="60" cy="60" r="52"></circle>
|
<circle class="gauge-track" cx="60" cy="60" r="52"></circle>
|
||||||
@@ -17,25 +22,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="readouts">
|
<div class="readout" id="pressure">
|
||||||
<div class="readout-row">
|
<span id="pressure-label" class="caption">DRUCK</span>
|
||||||
<div class="readout">
|
<div class="value"><span id="pressure-value">0</span><span class="unit">bar</span></div>
|
||||||
<span id="depth-label" class="caption">TIEFE</span>
|
</div>
|
||||||
<div class="value"><span id="depth-value">0.0</span><span class="unit">m</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="readout" id="pressure">
|
<div id="lamp" class="lamp">
|
||||||
<span id="pressure-label" class="caption">DRUCK</span>
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<div class="value"><span id="pressure-value">0</span><span class="unit">bar</span></div>
|
<path d="M9 21h6v-1H9v1zm3-19a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2z"></path>
|
||||||
</div>
|
</svg>
|
||||||
</div>
|
<span id="lamp-label">LAMPE</span>
|
||||||
|
|
||||||
<div id="lamp" class="lamp">
|
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
||||||
<path d="M9 21h6v-1H9v1zm3-19a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2z"></path>
|
|
||||||
</svg>
|
|
||||||
<span id="lamp-label">LAMPE</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+32
-17
@@ -24,15 +24,22 @@ body {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Grid statt Flex: so sitzen Tiefe, Ring und Druck garantiert auf einer
|
||||||
|
gemeinsamen Mittelachse, und die Lampen-Leiste hängt in einer zweiten Zeile
|
||||||
|
mittig unter dem Ring - ohne Ausgleichs-Margins. */
|
||||||
#hud {
|
#hud {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 7vh;
|
bottom: 7vh;
|
||||||
transform: translateX(-50%) translateY(0);
|
transform: translateX(-50%) translateY(0);
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 18px;
|
justify-items: center;
|
||||||
padding: 14px 22px 14px 14px;
|
column-gap: 22px;
|
||||||
|
row-gap: 8px;
|
||||||
|
padding: 14px 22px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||||
@@ -53,7 +60,8 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 92px;
|
width: 92px;
|
||||||
height: 92px;
|
height: 92px;
|
||||||
flex-shrink: 0;
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gauge svg {
|
.gauge svg {
|
||||||
@@ -107,22 +115,28 @@ body {
|
|||||||
|
|
||||||
/* --- Tiefe + Lampe --- */
|
/* --- Tiefe + Lampe --- */
|
||||||
|
|
||||||
.readouts {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.readout-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.readout {
|
.readout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
min-width: 76px;
|
min-width: 84px;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tiefe spiegelt den Druck: Werte zeigen jeweils zum Ring hin. */
|
||||||
|
.readout--right {
|
||||||
|
align-items: flex-end;
|
||||||
|
grid-column: 1;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readout--right .value {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pressure {
|
||||||
|
grid-column: 3;
|
||||||
|
justify-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
@@ -143,9 +157,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lamp {
|
.lamp {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: flex-start; /* sonst zieht der Flex-Container die Leiste auf volle Breite */
|
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 5px 9px;
|
padding: 5px 9px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
Reference in New Issue
Block a user