Lint / Lint Resource (push) Has been cancelled
Macht ein Tiefenlimit mechanisch statt zur Absprache - und oeffnet die tiefen Spots fuer den, der die richtige Flasche dabei hat. Der Grund fuer die realen 40-45 m ist nicht der Druck, sondern der Stickstoff in der Pressluft: tiefer wirkt er narkotisch, ab ~56 m wird auch der Sauerstoff giftig. Wer tiefer will, atmet Trimix, wo Helium einen Teil des Stickstoffs ersetzt. Genau so umgesetzt: - maxDepth und gas pro Flasche in config/shared.lua; Pressluft 45 m, neues diving_tank_trimix 100 m - Darunter Tiefenrausch in drei Stufen: Bildschirmeffekt, Kamerawackeln, hoeherer Luftverbrauch, in der letzten Stufe Schaden. Schwellen, Effekte, Staerken und Schaden komplett in config.narcosis - Vorwarnung 5 m vor der Grenze, Tiefenanzeige im HUD wird rot und pulsiert - Effekte werden beim Auftauchen, Ablegen, Tod und Resource-Stop aufgeraeumt - Auswahlmenue zeigt Gas und Tiefengrenze pro Flasche - Icon fuer die Trimix-Flasche, violett abgesetzt Keine Dekompression - auf Wunsch bewusst weggelassen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
189 lines
8.5 KiB
JavaScript
189 lines
8.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// Erzeugt die Item-Icons als SVG. Die PNGs macht danach ImageMagick daraus:
|
|
//
|
|
// node icons/gen-icons.mjs
|
|
// for f in icons/src/*.svg; do magick -background none "$f" -resize 128x128 "icons/$(basename "$f" .svg).png"; done
|
|
//
|
|
// Nur SVG 1.1 mit Presentation-Attributen, keine CSS-Bloecke und keine Filter -
|
|
// gerendert wird mit librsvg 2.40, und das ist alt.
|
|
//
|
|
// librsvg 2.40 wirft bei jedem <text>-Element ein
|
|
// WARNING: Invalid UTF-8 string passed to pango_layout_set_text()
|
|
// Das ist harmlos und tritt auch beim minimalsten Testfall auf - die Zahlen auf
|
|
// den Flaschen rendern korrekt. Nachgeprueft, nicht ignoriert.
|
|
//
|
|
// Erzeugt nebenbei icons/_check.html: alle Icons auf Schachbrett, in Slot-Groesse
|
|
// und klein, zum Gegenschauen nach Aenderungen.
|
|
|
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
|
|
const OUT = join(process.argv[2] ?? 'icons', 'src');
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
const S = 128; // Canvas, quadratisch
|
|
|
|
const C = {
|
|
steelLight: '#cbd5e1',
|
|
steel: '#94a3b8',
|
|
steelDark: '#64748b',
|
|
valve: '#334155',
|
|
valveDark: '#1e293b',
|
|
glass: '#38bdf8',
|
|
glassDark: '#0284c7',
|
|
rubber: '#1e293b',
|
|
outline: '#0f172a'
|
|
};
|
|
|
|
/** Farbband pro Groesse, damit die Flaschen im Inventar auf einen Blick unterscheidbar sind. */
|
|
const BANDS = {
|
|
small: '#4ade80',
|
|
medium: '#38bdf8',
|
|
large: '#fbbf24',
|
|
xl: '#f472b6',
|
|
trimix: '#a78bfa'
|
|
};
|
|
|
|
const defs = `<defs>
|
|
<linearGradient id="steel" x1="0" y1="0" x2="1" y2="0">
|
|
<stop offset="0%" stop-color="${C.steelDark}"/>
|
|
<stop offset="28%" stop-color="${C.steelLight}"/>
|
|
<stop offset="55%" stop-color="${C.steel}"/>
|
|
<stop offset="100%" stop-color="${C.steelDark}"/>
|
|
</linearGradient>
|
|
<linearGradient id="glass" x1="0" y1="0" x2="0.7" y2="1">
|
|
<stop offset="0%" stop-color="${C.glass}"/>
|
|
<stop offset="100%" stop-color="${C.glassDark}"/>
|
|
</linearGradient>
|
|
</defs>`;
|
|
|
|
const svg = (body) =>
|
|
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${S} ${S}" width="${S}" height="${S}">
|
|
${defs}
|
|
${body}
|
|
</svg>
|
|
`;
|
|
|
|
/**
|
|
* Eine Tauchflasche. cx = Mittelachse, w = Breite, top/bottom = Koerper in y.
|
|
* minutes = Laufzeit aufs Band gedruckt. Ohne die Zahl sind die Einzelflaschen
|
|
* bei kleiner Darstellung nur noch an der Bandfarbe auseinanderzuhalten.
|
|
*/
|
|
function tank(cx, w, top, bottom, band, minutes) {
|
|
const x = cx - w / 2;
|
|
const h = bottom - top;
|
|
const valveW = w * 0.4;
|
|
const bandY = top + h * 0.34;
|
|
const bandH = 22;
|
|
|
|
const label = minutes
|
|
? `<text x="${cx}" y="${bandY + bandH - 6}" text-anchor="middle" font-family="sans-serif"
|
|
font-size="16" font-weight="700" fill="${C.outline}">${minutes}</text>`
|
|
: '';
|
|
|
|
return ` <g>
|
|
<rect x="${cx - valveW / 2}" y="${top - 14}" width="${valveW}" height="17" rx="3" fill="${C.valve}"/>
|
|
<circle cx="${cx}" cy="${top - 17}" r="8" fill="none" stroke="${C.valveDark}" stroke-width="4.5"/>
|
|
<rect x="${x}" y="${top}" width="${w}" height="${h}" rx="${w / 2.4}" fill="url(#steel)" stroke="${C.outline}" stroke-width="2.5"/>
|
|
<rect x="${x + w * 0.15}" y="${top + h * 0.08}" width="${w * 0.13}" height="${h * 0.5}" rx="${w * 0.065}" fill="${C.steelLight}" opacity="0.75"/>
|
|
<rect x="${x}" y="${bandY}" width="${w}" height="${bandH}" fill="${band}"/>
|
|
${label}
|
|
</g>`;
|
|
}
|
|
|
|
// --- Flaschen: gleiche Breite, wachsende Hoehe. Die Silhouette traegt die Info,
|
|
// --- das Farbband hilft zusaetzlich.
|
|
const sizes = {
|
|
diving_tank_small: { w: 42, top: 48, bottom: 114, band: BANDS.small, minutes: 5 },
|
|
diving_tank_medium: { w: 46, top: 36, bottom: 116, band: BANDS.medium, minutes: 10 },
|
|
diving_tank_large: { w: 50, top: 26, bottom: 118, band: BANDS.large, minutes: 15 }
|
|
};
|
|
|
|
for (const [name, cfg] of Object.entries(sizes)) {
|
|
writeFileSync(join(OUT, `${name}.svg`), svg(tank(64, cfg.w, cfg.top, cfg.bottom, cfg.band, cfg.minutes)));
|
|
}
|
|
|
|
// --- XL = Doppelflasche, passend zum Label. Die Zahl steht auf einem Band
|
|
// --- ueber beide Flaschen, sonst muesste sie zweimal auftauchen.
|
|
writeFileSync(join(OUT, 'diving_tank_xl.svg'), svg(
|
|
` <rect x="38" y="26" width="52" height="8" rx="4" fill="${C.valveDark}"/>
|
|
${tank(46, 38, 38, 118, BANDS.xl)}
|
|
${tank(82, 38, 38, 118, BANDS.xl)}
|
|
<rect x="27" y="65" width="74" height="23" rx="4" fill="${BANDS.xl}" stroke="${C.outline}" stroke-width="2"/>
|
|
<text x="64" y="82" text-anchor="middle" font-family="sans-serif"
|
|
font-size="16" font-weight="700" fill="${C.outline}">20</text>`
|
|
));
|
|
|
|
// --- Trimix: Doppelflasche mit eigenem Farbband, deutlich abgesetzt von den
|
|
// --- Pressluft-Flaschen, weil die Verwechslung teuer waere.
|
|
writeFileSync(join(OUT, 'diving_tank_trimix.svg'), svg(
|
|
` <rect x="38" y="26" width="52" height="8" rx="4" fill="${C.valveDark}"/>
|
|
${tank(46, 38, 38, 118, BANDS.trimix)}
|
|
${tank(82, 38, 38, 118, BANDS.trimix)}
|
|
<rect x="20" y="65" width="88" height="23" rx="4" fill="${BANDS.trimix}" stroke="${C.outline}" stroke-width="2"/>
|
|
<text x="64" y="82" text-anchor="middle" font-family="sans-serif"
|
|
font-size="14" font-weight="700" fill="${C.outline}">TRIMIX</text>`
|
|
));
|
|
|
|
// --- Tauchmaske
|
|
writeFileSync(join(OUT, 'diving_gear.svg'), svg(
|
|
` <path d="M18 62 L30 52 L30 78 L18 72 Z" fill="${C.rubber}"/>
|
|
<path d="M110 62 L98 52 L98 78 L110 72 Z" fill="${C.rubber}"/>
|
|
<rect x="22" y="60" width="84" height="9" rx="4.5" fill="${C.rubber}"/>
|
|
<path d="M34 40 h60 a12 12 0 0 1 12 12 v22 a16 16 0 0 1 -16 16 h-8 l-8 -7 h-12 l-8 7 h-8 a16 16 0 0 1 -16 -16 v-22 a12 12 0 0 1 12 -12 z"
|
|
fill="${C.rubber}" stroke="${C.outline}" stroke-width="2.5"/>
|
|
<path d="M42 50 h44 a7 7 0 0 1 7 7 v14 a10 10 0 0 1 -10 10 h-6 l-7 -6 h-12 l-7 6 h-6 a10 10 0 0 1 -10 -10 v-14 a7 7 0 0 1 7 -7 z"
|
|
fill="url(#glass)"/>
|
|
<path d="M48 55 h13 l-11 22 h-6 a5 5 0 0 1 -5 -5 v-9 z" fill="#ffffff" opacity="0.32"/>
|
|
<rect x="58" y="88" width="12" height="16" rx="4" fill="${C.valve}"/>
|
|
<circle cx="64" cy="104" r="9" fill="${C.valveDark}" stroke="${C.outline}" stroke-width="2"/>`
|
|
));
|
|
|
|
// --- Pressluft-Kartusche: liegende, gedrungene Form mit Manometer. Bewusst quer
|
|
// --- statt hochkant, damit sie sich auf einen Blick von den Flaschen abhebt.
|
|
writeFileSync(join(OUT, 'diving_fill.svg'), svg(
|
|
` <rect x="18" y="44" width="86" height="44" rx="22" fill="url(#steel)" stroke="${C.outline}" stroke-width="2.5"/>
|
|
<rect x="30" y="52" width="44" height="7" rx="3.5" fill="${C.steelLight}" opacity="0.8"/>
|
|
<rect x="24" y="44" width="9" height="44" fill="${C.glass}" opacity="0.85"/>
|
|
<rect x="88" y="44" width="9" height="44" fill="${C.glass}" opacity="0.85"/>
|
|
<rect x="99" y="58" width="14" height="16" rx="4" fill="${C.valve}"/>
|
|
<rect x="110" y="62" width="9" height="8" rx="3" fill="${C.valveDark}"/>
|
|
<circle cx="62" cy="66" r="17" fill="${C.valveDark}" stroke="${C.outline}" stroke-width="2.5"/>
|
|
<circle cx="62" cy="66" r="12" fill="${C.steelLight}"/>
|
|
<path d="M62 66 l8 -7" fill="none" stroke="${C.outline}" stroke-width="3" stroke-linecap="round"/>
|
|
<circle cx="62" cy="66" r="2.5" fill="${C.outline}"/>`
|
|
));
|
|
|
|
// --- Kontrollblatt
|
|
const NAMES = [
|
|
'diving_gear', 'diving_fill',
|
|
'diving_tank_small', 'diving_tank_medium', 'diving_tank_large', 'diving_tank_xl',
|
|
'diving_tank_trimix'
|
|
];
|
|
|
|
const slots = (extra = '') =>
|
|
NAMES.map((n) => `<div class="slot${extra}"><img src="${n}.png" alt="${n}" title="${n}"></div>`).join('');
|
|
|
|
writeFileSync(join(dirname(OUT), '_check.html'), `<style>
|
|
body{margin:0;padding:16px;font:12px 'Segoe UI',sans-serif;background:#0f172a;color:#cbd5e1}
|
|
h3{margin:18px 0 8px;font-size:11px;letter-spacing:1px;text-transform:uppercase;color:#94a3b8}
|
|
.row{display:flex;gap:10px;flex-wrap:wrap;padding:12px;border-radius:10px}
|
|
.checker{background-color:#94a3b8;background-size:16px 16px;background-position:0 0,0 8px,8px -8px,-8px 0;
|
|
background-image:linear-gradient(45deg,#64748b 25%,transparent 25%),linear-gradient(-45deg,#64748b 25%,transparent 25%),
|
|
linear-gradient(45deg,transparent 75%,#64748b 75%),linear-gradient(-45deg,transparent 75%,#64748b 75%)}
|
|
.dark{background:#1e293b}
|
|
.slot{width:100px;height:100px;display:flex;align-items:center;justify-content:center}
|
|
.dark .slot{border:1px solid rgba(148,163,184,.35);border-radius:8px;background:rgba(0,0,0,.25)}
|
|
.slot img{max-width:88px;max-height:88px}
|
|
.small .slot{width:56px;height:56px}.small .slot img{max-width:48px;max-height:48px}
|
|
</style>
|
|
<h3>Transparenz</h3>
|
|
<div class="row checker">${slots()}</div>
|
|
<h3>Inventar-Slot 100px</h3>
|
|
<div class="row dark">${slots()}</div>
|
|
<h3>Klein 56px</h3>
|
|
<div class="row dark small">${slots()}</div>
|
|
`);
|
|
|
|
console.log('SVGs in', OUT, '+ _check.html');
|