diff --git a/src/components/workers/hochhaus/HochhausWelt.jsx b/src/components/workers/hochhaus/HochhausWelt.jsx new file mode 100644 index 0000000..48583b6 --- /dev/null +++ b/src/components/workers/hochhaus/HochhausWelt.jsx @@ -0,0 +1,233 @@ +// Die Spielwelt des Hochhauses (Hustler-Pixelwelt T5, Spec E1/E4/E5/E8/E14): 1:1 aus dem Markup der Zeichenfläche (entwurf/markup.html), Klassen mit hk- davor. +// Himmel, Funkeln, Mond, die 2400 x 1200 große Bühne (per transform verschoben und gezoomt) mit Welt-, Licht-, Haus- und Fensterebenen, Neon, Antenne, Aufzug, +// Raumschilder, Bildschirme, Radar, Briefkasten-Zähler, Tresor-/Keller-Text, den Etagen (Knöpfe, Freigabe und Keller sind Links) und den 19 Hustlern; davor der Schnee. +// Alles hängt am Dienst-Zustand: Figur und Zusätze nach Status (figurZustand), graue Raum-Ebene samt Papierschild nur für nicht angeschlossene Such-Räume. +// Die Kamera (Rad, Ziehen, Pinch, Tasten) steckt in useKamera. `children` (die Regie-Ebene) liegt in der Bühne über den Figuren, in Weltkoordinaten. +// Kein CSS-Import (die Stile hängt WorkersPage ein: styles/hustler/hochhaus.css, hustler-ui.css, hustler-welt.css), keine Bilder, kein innerHTML. +import { forwardRef, useImperativeHandle } from 'react' +import { anzeigeName, figurZustand, schilder } from '../../../lib/hustlerSicht.js' +import { AUFZUG, ETAGEN, ETAGEN_SPALTE, HAUS_BILD, HUSTLER, RAEUME, SCHILDER } from '../../../lib/hustlerWelt.js' +import { FLOOR_TITLES, ROLES, ROOM_TITLES } from '../../../lib/workersRoster.js' +import useKamera from './useKamera.js' + +// Etagen, die ein Bildschirm sind (Link statt Zoom): Text und Label wörtlich aus der Zeichenfläche +const LINK_ETAGEN = Object.freeze({ + freigabe: Object.freeze({ tab: 'freigabe', text: 'Freigabe ▸', label: 'Freigabe: Freigabe-Pult öffnen' }), + keller: Object.freeze({ tab: 'botleads', text: 'Keller · Bot-Leads ▸', label: 'Keller · Bot-Leads: Bot-Leads öffnen' }), +}) + +const HAUS_RECHTECK = Object.freeze({ left: HAUS_BILD.x, top: HAUS_BILD.y, width: HAUS_BILD.w, height: HAUS_BILD.h }) + +const PX = (n) => `${n}px` + +// Feste, je Figur verschiedene Startverschiebung der Animation (deterministisch aus dem Index: 7 und 19 sind teilerfremd, 19 Figuren -> 19 Werte) +const versatz = (index, schritt = 7, anzahl = 19) => `-${(((index * schritt) % anzahl) / 10).toFixed(1)}s` + +function geraeteDichte() { + try { return window.devicePixelRatio || 1 } catch { return 1 } +} + +// Lage des „nicht angeschlossen“-Zettels: die Daten kennen drei Räume, alle acht Such-Räume haben dieselbe Lage relativ zum Raum (x0 + 16, y0 + 1) +function zettelLage(raum) { + const bekannt = SCHILDER.nichtAngeschlossen.raeume[raum] + if (bekannt) return bekannt + const r = RAEUME[raum] + return { left: r.x0 + 16, top: r.y0 + 1 } +} + +const HochhausWelt = forwardRef(function HochhausWelt({ state, workers, onZeige, onOpen, gesperrt = false, children }, ref) { + const kam = useKamera({ gesperrt }) + useImperativeHandle(ref, () => ({ ganz: kam.ganz }), [kam.ganz]) + + const pausiert = state?.paused === true + const tafel = schilder(state?.kpis) + const figuren = ROLES.map((rolle, index) => ({ + id: rolle.workerId, + index, + f: figurZustand(rolle.workerId, workers?.[rolle.workerId], { paused: pausiert }), + })) + const grau = new Set(figuren.filter((x) => x.f.raumGrau).map((x) => x.f.raum)) + + const { s, x, y } = kam + const etikett = Math.min(18, Math.max(8, 15 / s)).toFixed(2) + const namePx = Math.min(14, Math.max(4, 12 / s)).toFixed(2) + const buehneStil = { + transform: `translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) scale(${s.toFixed(4)})`, + imageRendering: s * geraeteDichte() < 1 ? 'auto' : 'pixelated', + '--hk-etikett': `${etikett}px`, + '--hk-name': `${namePx}px`, + } + + const zeige = (id) => () => { + if (kam.nachZug()) return + if (typeof onZeige === 'function') onZeige(id) + } + // Freigabe und Keller sind echte Links; ein einfacher Klick wechselt den Bildschirm, Strg/Cmd/Umschalt/Mitteltaste öffnen wie jeder Link + const linkKlick = (tab) => (e) => { + if (kam.nachZug()) { e.preventDefault(); e.stopPropagation(); return } + if (e.ctrlKey || e.metaKey || e.shiftKey || e.button === 1) return + e.preventDefault() + if (typeof onOpen === 'function') onOpen(tab) + } + + return ( +
+