From a336376a93658e2feb05d1990e20a3c6ffe8316c Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Fri, 21 Aug 2026 17:34:07 -0600 Subject: [PATCH] wolfenstein: pan/zoom editor with minimap, sliding recessed doors, enemy patrol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Editor (WolfensteinEditor.js): - Decouple the board from grid size: fixed 900px viewport with independent pan (right-drag / WASD / arrows) and zoom (wheel / +/- / F to fit) instead of squeezing the whole grid in; add a whole-level map overview minimap with click-to-jump. - Grids up to 1000 cells/side (was 48); painting past the edge auto-grows the grid (preserving content, re-sealing borders) instead of being refused. - Rebuild the toolbar as a categorized DOM radio + dropdown panel (Wall / Door / Pickup / Enemy / Patrol / Zone / Erase) instead of a flat list. - Add a Patrol tool: select a guard, then click tiles to add/remove waypoints; routes are drawn (dim for all, highlighted for the selection). - Throttle the reachability validate during paint-drag; flush it before Test Play / Export; queue game assets in preload() like ZumaEditor. Doors (WolfensteinLogic.js, WolfensteinRaycaster.js, WolfensteinView.js): - Player now opens doors by pressing Space (HUD "[SPACE] Open" prompt when one is in range); enemies still shove them open. Doors animate open/closed via a slide value (400ms) instead of popping, and stay solid for gameplay until fully open. - Render doors as recessed mid-cell geometry (real depth, producing the "H" doorway shape) via a door-aware render-only raycast — collision/LOS are unchanged; the door visibly slides sideways into a dark socket as it opens. Enemy AI (WolfensteinLogic.js): - Idle enemies with an authored patrol route ping-pong along [home, ...nodes] until they spot the player; validate patrol nodes. Rendering (WolfensteinView.js, assets, data): - Add textured wall rendering: per-column 1px source-texel sampling from the new walls.png spritesheet, darkened with a 'multiply' grey to match the existing side/fog shading, with per-wall-type flat-color fallback. - Wire in assets/images/wolfenstein/walls.png and point the art manifest at it. Misc: - level-e1m1.json: reworked to a taller (20-row) map exercising the new wall/door types; playerRadius 0.25 -> 0.35. - sprites.md: document exact wall-sheet size, the sliding/recessed door behavior, guard sheet usage, and standalone image sizes/proportions. --- assets/gamedata/wolfenstein/level-e1m1.json | 238 ++++++-- assets/images/wolfenstein/walls.png | Bin 0 -> 40343 bytes assets/images/wolfenstein/walls.psd | Bin 0 -> 112799 bytes data/wolfenstein-artwork.json | 2 +- data/wolfenstein-rules.json | 2 +- src/games/wolfenstein/WolfensteinEditor.js | 525 ++++++++++++++++-- src/games/wolfenstein/WolfensteinGame.js | 9 +- src/games/wolfenstein/WolfensteinLogic.js | 113 +++- src/games/wolfenstein/WolfensteinRaycaster.js | 76 ++- src/games/wolfenstein/WolfensteinView.js | 116 +++- src/games/wolfenstein/sprites.md | 122 +++- 11 files changed, 1041 insertions(+), 162 deletions(-) create mode 100644 assets/images/wolfenstein/walls.png create mode 100644 assets/images/wolfenstein/walls.psd diff --git a/assets/gamedata/wolfenstein/level-e1m1.json b/assets/gamedata/wolfenstein/level-e1m1.json index c67ef62..8876cc8 100644 --- a/assets/gamedata/wolfenstein/level-e1m1.json +++ b/assets/gamedata/wolfenstein/level-e1m1.json @@ -1,11 +1,11 @@ { "version": 1, - "id": "e1m1", + "id": "level-new", "name": "First Blood", - "campaignId": "episode1", + "campaignId": null, "missionIndex": 0, "width": 12, - "height": 10, + "height": 20, "cellSize": 64, "walls": [ [ @@ -27,8 +27,8 @@ 0, 0, 0, - 1, 0, + 3, 0, 0, 0, @@ -41,8 +41,8 @@ 0, 0, 0, - 1, 0, + 3, 0, 0, 0, @@ -70,11 +70,25 @@ 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 1 + ], + [ + 1, + 3, + 3, + 3, + 3, + 3, + 0, + 0, + 0, + 0, 0, 1 ], @@ -84,25 +98,11 @@ 0, 0, 0, - 1, - 1, + 3, 0, 0, 0, 0, - 1 - ], - [ - 1, - 0, - 0, - 0, - 0, - 1, - 1, - 0, - 0, - 0, 0, 1 ], @@ -126,7 +126,7 @@ 0, 0, 0, - 0, + 3, 0, 0, 0, @@ -136,17 +136,157 @@ ], [ 1, + 0, + 0, + 0, + 0, + 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 1, 1, 1, 1 + ], + [ + 1, + 3, + 3, + 3, + 3, + 3, + 2, + 0, + 2, + 2, + 2, + 2 + ], + [ + 1, + 2, + 2, + 2, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 2 + ], + [ + 1, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2 + ], + [ + 1, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 1, + 2, + 0, + 0, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 1, + 2, + 0, + 0, + 2, + 4, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 1, + 2, + 0, + 0, + 2, + 4, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 1, + 2, + 0, + 0, + 2, + 4, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 1, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 1, + 2, + 2, + 2, + 2, + 4, + 4, + 4, + 4, + 4, + 4, + 4 ] ], "playerStart": { @@ -156,38 +296,40 @@ }, "doors": [ { - "x": 4, + "x": 5, "y": 3, "orientation": "vertical" + }, + { + "x": 5, + "y": 7, + "orientation": "vertical" + }, + { + "x": 7, + "y": 9, + "orientation": "vertical" + }, + { + "x": 4, + "y": 18, + "orientation": "vertical" } ], "enemies": [ { "type": "guard", - "x": 8.5, - "y": 1.5, - "facing": 180 - } - ], - "items": [ - { - "type": "ammo", - "x": 3.5, - "y": 7.5 - }, - { - "type": "health", - "x": 9.5, - "y": 7.5 + "x": 2.5, + "y": 18.5, + "facing": 180, + "patrol": [] } ], + "items": [], "exit": { "x": 10.5, - "y": 1.5, + "y": 14.5, "radius": 0.6 }, - "briefing": [ - "Clear the outpost.", - "Find the exit." - ] + "briefing": [] } diff --git a/assets/images/wolfenstein/walls.png b/assets/images/wolfenstein/walls.png new file mode 100644 index 0000000000000000000000000000000000000000..c49ccb9968a2f06b5a1412d3d1e634461e8abe73 GIT binary patch literal 40343 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 l_F2)+ba`(y}dSk_lwFlZTq&& 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 ~y50L?;mfQ4+Yw1NgRqmjsY8ZIUecvlpI$l3rF zlR{k!bgS}UWHPlt0ct@h7y?;TF1aWGA##Omx5{`$y$ZA2Y&<5DVbi0%C=FYk~g4) zsMQIAP(u!HwlTe@%35=LOxk^^fszMn;k1~rI?Hn$bmM^If zz!|XtKZ4~0vM#OX4R1TW=fL~O%dQf@$-c~ zbiTVA90q4YqYDrkp@HoDFzVzwB#{aAYk) zu{j9$D^881*1hjMt~2QBfFm{Qs<}XRX{5?37zx0D04S26)KkR=a8GV9nUp*kI!p9A ziPfU5wSiDWCAE3IHeH?4E;DG*j`l_+2bWPIVDqtIa|p4qTEs=INuiblO@tbhfoHO) zO$xZt+J@RjpR1-$?QJv~Yi!O2r3n=L3YkEq6G^aVW8)xM#*CF*8kYl#dEm*aB}#*( zR#_v~nH?YksME=8W;f_`R5~3Psay`NT&k%7CsR!&%0DPL3)u{y+Nzn=0$>3(*!5&w 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 [k, [(hex >> 16) & 255, (hex >> 8) & 255, hex & 255]]), +); -const TOOLS = [ - ['wall1', 'Wall: Stone'], - ['wall2', 'Wall: Wood'], - ['wall3', 'Wall: Blue'], - ['wall4', 'Wall: Green'], - ['door', 'Door'], - ['start', 'Player Start'], - ['exit', 'Exit'], - ['enemy', 'Guard'], - ['ammo', 'Ammo Pickup'], - ['health', 'Health Pickup'], - ['erase', 'Erase'], +// One radio row per category; a category with `options` gets a dropdown +// next to its radio (even a category with only one option today, per +// Door/Enemy — more sub-types land there later), a category with `options: +// null` (just Erase) gets its label alone. The tool id actually used by +// applyTool()/WALL_TOOLS is either the selected option's id, or — for a +// dropdown-less category — the category's own id. +const CATEGORIES = [ + { id: 'wall', label: 'Wall', options: [ + { id: 'wall1', label: 'Stone' }, + { id: 'wall2', label: 'Wood' }, + { id: 'wall3', label: 'Blue' }, + { id: 'wall4', label: 'Green' }, + ] }, + { id: 'door', label: 'Door', options: [ + { id: 'door', label: 'Normal' }, + ] }, + { id: 'pickup', label: 'Pickup', options: [ + { id: 'health', label: 'Health' }, + { id: 'ammo', label: 'Ammo' }, + ] }, + { id: 'enemy', label: 'Enemy', options: [ + { id: 'enemy', label: 'Guard' }, + ] }, + // Not a placement tool — a mode. Click an enemy to select it (see + // applyPatrolTool), then click floor tiles to append/remove waypoints on + // its route. No sub-types, so no dropdown, same as Erase. + { id: 'patrol', label: 'Patrol', options: null }, + { id: 'zone', label: 'Zone', options: [ + { id: 'start', label: 'Player Start' }, + { id: 'exit', label: 'Exit' }, + ] }, + { id: 'erase', label: 'Erase', options: null }, ]; export default class WolfensteinEditor extends Phaser.Scene { @@ -40,16 +83,35 @@ export default class WolfensteinEditor extends Phaser.Scene { init(data) { this.resume = !!data?.resume; } + preload() { + // The editor boots straight out of PreloadScene, so the lazy game data + // (wolfenstein-rules/campaigns JSON, sprite art — see + // data/assetManifest.js) that GameRoomScene would normally fetch on + // first entry isn't loaded yet. Test Play starts WolfensteinGame + // directly, bypassing that fetch too, so queue it here instead — same + // fix as ZumaEditor.js's preload(). + queueGameAssets(this, 'wolfenstein'); + } + create() { this.level = this.resume ? (this.registry.get('wolfenstein-editor-state') ?? this.defaultLevel()) : this.defaultLevel(); this.tool = 'wall1'; this.undoStack = []; this.result = { valid: false, issues: [], reachable: false }; + this.panState = null; + this._validateTimer = null; + this.selectedEnemy = null; // an object reference into level.enemies, not an index — see applyPatrolTool this.add.rectangle(GAME_WIDTH / 2, GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, 0x101018); this.add.rectangle(BOARD_X + BOARD_SIZE / 2, BOARD_Y + BOARD_SIZE / 2, BOARD_SIZE + 8, BOARD_SIZE + 8, 0x000000) .setStrokeStyle(2, COLORS.accent); this.g = this.add.graphics().setDepth(5); + // The board is now a fixed-size window onto a possibly much larger grid + // (panned/zoomed independently) — clip strictly to the board rect so a + // fractional pan offset never bleeds a partial cell past its border. + const maskShape = this.make.graphics({ x: 0, y: 0, add: false }); + maskShape.fillRect(BOARD_X, BOARD_Y, BOARD_SIZE, BOARD_SIZE); + this.g.setMask(maskShape.createGeometryMask()); this.manifestLevels = []; fetch('data/wolfenstein-campaigns.json').then((r) => r.json()) @@ -60,8 +122,10 @@ export default class WolfensteinEditor extends Phaser.Scene { this.buildToolbar(); this.buildLoadPanel(); + this.buildMinimap(); this.bindPointer(); this.bindKeys(); + this.fitToGrid(); this.rebuildModel(); } @@ -81,14 +145,48 @@ export default class WolfensteinEditor extends Phaser.Scene { }; } - // ── Coordinate transforms ──────────────────────────────────────────────── + // ── Right-panel layout shared by the load panel and the minimap ──────────── - get cellPx() { return Math.min(BOARD_SIZE / this.level.width, BOARD_SIZE / this.level.height); } - toBoard(cx, cy) { const k = this.cellPx; return [BOARD_X + cx * k, BOARD_Y + cy * k]; } - toCell(px, py) { const k = this.cellPx; return [Math.floor((px - BOARD_X) / k), Math.floor((py - BOARD_Y) / k)]; } - onBoard(p) { - const w = this.cellPx * this.level.width, h = this.cellPx * this.level.height; - return p.x >= BOARD_X && p.x <= BOARD_X + w && p.y >= BOARD_Y && p.y <= BOARD_Y + h; + get rpX() { return BOARD_X + BOARD_SIZE + 90 + 260 + 40; } + get rpW() { return 420; } + + // ── Coordinate transforms ──────────────────────────────────────────────── + // (viewX, viewY) is the world cell shown at the board's top-left corner; + // `zoom` is screen pixels per cell. Both are independent of grid size. + + toBoard(cx, cy) { const k = this.zoom; return [BOARD_X + (cx - this.viewX) * k, BOARD_Y + (cy - this.viewY) * k]; } + toCell(px, py) { const k = this.zoom; return [Math.floor(this.viewX + (px - BOARD_X) / k), Math.floor(this.viewY + (py - BOARD_Y) / k)]; } + onBoard(p) { return p.x >= BOARD_X && p.x <= BOARD_X + BOARD_SIZE && p.y >= BOARD_Y && p.y <= BOARD_Y + BOARD_SIZE; } + + /** [x0, y0, x1, y1) cell range currently on screen, clamped to the grid. */ + visibleCellRange() { + const x0 = Math.max(0, Math.floor(this.viewX)); + const y0 = Math.max(0, Math.floor(this.viewY)); + const x1 = Math.min(this.level.width, Math.ceil(this.viewX + BOARD_SIZE / this.zoom) + 1); + const y1 = Math.min(this.level.height, Math.ceil(this.viewY + BOARD_SIZE / this.zoom) + 1); + return [x0, y0, x1, y1]; + } + + /** Reset pan/zoom to show the whole grid (or as much as MIN_ZOOM allows). */ + fitToGrid() { + this.zoom = Phaser.Math.Clamp(Math.min(BOARD_SIZE / this.level.width, BOARD_SIZE / this.level.height), MIN_ZOOM, MAX_ZOOM); + this.viewX = 0; + this.viewY = 0; + this.draw(); + } + + setView(vx, vy) { + const visW = BOARD_SIZE / this.zoom, visH = BOARD_SIZE / this.zoom; + this.viewX = Phaser.Math.Clamp(vx, -visW * 0.5, Math.max(-visW * 0.5, this.level.width - visW * 0.5)); + this.viewY = Phaser.Math.Clamp(vy, -visH * 0.5, Math.max(-visH * 0.5, this.level.height - visH * 0.5)); + this.draw(); + } + + zoomAt(anchorX, anchorY, factor) { + const worldX = this.viewX + (anchorX - BOARD_X) / this.zoom; + const worldY = this.viewY + (anchorY - BOARD_Y) / this.zoom; + this.zoom = Phaser.Math.Clamp(this.zoom * factor, MIN_ZOOM, MAX_ZOOM); + this.setView(worldX - (anchorX - BOARD_X) / this.zoom, worldY - (anchorY - BOARD_Y) / this.zoom); } // ── Toolbar ─────────────────────────────────────────────────────────────── @@ -98,17 +196,12 @@ export default class WolfensteinEditor extends Phaser.Scene { const bw = 260; this.add.text(tx + bw / 2, 34, 'WOLFENSTEIN EDITOR', { fontFamily: FONT, fontSize: '26px', color: COLORS.goldHex }).setOrigin(0.5); - this.toolButtons = {}; - TOOLS.forEach(([id, label], i) => { - const y = 80 + i * 58; - const btn = new Button(this, tx + bw / 2, y, label, () => this.setTool(id), { width: bw, height: 48, fontSize: 18 }); - this.toolButtons[id] = btn; - }); - this.setTool(this.tool); + const panelBottom = this.buildToolPanel(tx, bw); - let y = 80 + TOOLS.length * 58 + 30; + let y = panelBottom + 30; this.metaName = new Button(this, tx + bw / 2, y, `Name: ${this.level.name}`, () => this.renameLevel(), { width: bw, height: 48, fontSize: 16, variant: 'ghost' }); y += 58; new Button(this, tx + bw / 2, y, 'Resize Grid', () => this.resizeLevel(), { width: bw, height: 48, fontSize: 18 }); y += 58; + new Button(this, tx + bw / 2, y, 'Fit View', () => this.fitToGrid(), { width: bw, height: 48, fontSize: 18 }); y += 58; new Button(this, tx + bw / 2, y, 'New Blank Level', () => this.newLevel(), { width: bw, height: 48, fontSize: 18 }); y += 66; this.issueText = this.add.text(tx, y, '', { @@ -119,9 +212,70 @@ export default class WolfensteinEditor extends Phaser.Scene { new Button(this, tx + bw / 2, y, '⬇ Export Level', () => this.exportLevel(), { width: bw, height: 56, fontSize: 20 }); } - setTool(id) { - this.tool = id; - for (const [key, btn] of Object.entries(this.toolButtons)) btn.setActive(key === id); + /** Which category/option a tool id belongs to — drives both the initial radio-checked/option-selected HTML and (indirectly) which id gets applied when a row's dropdown or dropdown-less radio fires. */ + findCategoryFor(toolId) { + for (const cat of CATEGORIES) { + if (!cat.options) { if (cat.id === toolId) return [cat, null]; continue; } + if (cat.options.some((o) => o.id === toolId)) return [cat, toolId]; + } + return [CATEGORIES[0], CATEGORIES[0].options[0].id]; + } + + /** + * One radio button per category (mutually exclusive via a shared `name`, + * so this is a native HTML radio group — not hand-rolled), a dropdown next + * to any category with `options` for its sub-type. Built as a DOM element + * (like the Load Level panel below it) rather than Phaser widgets — native + * radios/selects are simpler and more robust than reimplementing them in + * Graphics. Returns the panel's bottom y so buildToolbar() can lay out the + * rest of the column beneath it without hardcoding its (CSS-driven) height. + */ + buildToolPanel(tx, bw) { + const rowH = 34, rowGap = 8, pad = 12, border = 2; + const top = 70; + const panelH = border * 2 + pad * 2 + CATEGORIES.length * rowH + (CATEGORIES.length - 1) * rowGap; + const [activeCat, activeOpt] = this.findCategoryFor(this.tool); + const inputCss = 'background:#1e1a12; color:#f2ead8; border:1px solid #c8a84b; border-radius:4px; padding:3px 4px; font-size:13px;'; + + const rows = CATEGORIES.map((cat, i) => { + const checked = cat.id === activeCat.id ? 'checked' : ''; + const select = cat.options + ? `` + : ''; + const marginBottom = i === CATEGORIES.length - 1 ? 0 : rowGap; + return ` +
+ + + ${select} +
`; + }).join(''); + + const el = document.createElement('div'); + el.style.cssText = `width:${bw}px; font-family:"Julius Sans One",sans-serif; color:${COLORS.textHex}; font-size:15px;`; + el.innerHTML = `
${rows}
`; + this.toolPanelDom = this.add.dom(tx + bw / 2, top + panelH / 2, el); + + el.querySelectorAll('input[name="wf-tool"]').forEach((radio) => { + radio.addEventListener('change', () => { + const cat = CATEGORIES.find((c) => c.id === radio.value); + const select = el.querySelector(`select[data-cat="${radio.value}"]`); + this.tool = select ? select.value : cat.id; + this.draw(); // patrol-route overlay only shows while the Patrol tool is active + }); + }); + el.querySelectorAll('select[data-cat]').forEach((select) => { + select.addEventListener('change', () => { + el.querySelector(`#wf-tool-${select.dataset.cat}`).checked = true; + this.tool = select.value; + this.draw(); + }); + }); + + return top + panelH; } renameLevel() { @@ -130,12 +284,12 @@ export default class WolfensteinEditor extends Phaser.Scene { } resizeLevel() { - const wStr = window.prompt('Grid width (cells):', String(this.level.width)); + const wStr = window.prompt(`Grid width (cells, up to ${MAX_GRID}):`, String(this.level.width)); if (!wStr) return; - const hStr = window.prompt('Grid height (cells):', String(this.level.height)); + const hStr = window.prompt(`Grid height (cells, up to ${MAX_GRID}):`, String(this.level.height)); if (!hStr) return; - const w = Phaser.Math.Clamp(parseInt(wStr, 10) || this.level.width, 4, 48); - const h = Phaser.Math.Clamp(parseInt(hStr, 10) || this.level.height, 4, 48); + const w = Phaser.Math.Clamp(parseInt(wStr, 10) || this.level.width, 4, MAX_GRID); + const h = Phaser.Math.Clamp(parseInt(hStr, 10) || this.level.height, 4, MAX_GRID); this.pushUndo(); const walls = Array.from({ length: h }, (_, y) => Array.from({ length: w }, (_, x) => { const border = x === 0 || y === 0 || x === w - 1 || y === h - 1; @@ -150,16 +304,24 @@ export default class WolfensteinEditor extends Phaser.Scene { playerStart: this.level.playerStart && this.level.playerStart.x < w - 1 && this.level.playerStart.y < h - 1 ? this.level.playerStart : null, exit: this.level.exit && this.level.exit.x < w - 1 && this.level.exit.y < h - 1 ? this.level.exit : null, }; + this.fitToGrid(); this.rebuildModel(); } - newLevel() { this.pushUndo(); this.level = this.defaultLevel(); this.metaName.setLabel(`Name: ${this.level.name}`); this.rebuildModel(); } + newLevel() { + this.pushUndo(); + this.level = this.defaultLevel(); + this.selectedEnemy = null; + this.metaName.setLabel(`Name: ${this.level.name}`); + this.fitToGrid(); + this.rebuildModel(); + } // ── Load panel (DOM) ──────────────────────────────────────────────────── buildLoadPanel() { - const rx = BOARD_X + BOARD_SIZE + 90 + 260 + 40; - const rw = 420; + const rx = this.rpX; + const rw = this.rpW; const input = 'background:#1e1a12; color:#f2ead8; border:1px solid #c8a84b; border-radius:6px; padding:7px 9px; font-size:15px; width:100%;'; const el = document.createElement('div'); el.style.cssText = `width:${rw}px; font-family:"Julius Sans One",sans-serif; color:${COLORS.textHex};`; @@ -228,33 +390,194 @@ export default class WolfensteinEditor extends Phaser.Scene { playerStart: clone.playerStart ?? null, exit: clone.exit ?? null, }; + this.selectedEnemy = null; this.metaName?.setLabel(`Name: ${this.level.name}`); + this.fitToGrid(); this.rebuildModel(); this.flashLoadWarn(`Loaded ${fileName}`); } + // ── Map overview (minimap) ─────────────────────────────────────────────── + + buildMinimap() { + const x = this.rpX; + const y = 330; + const size = MINIMAP_SIZE; + this.minimapX = x; + this.minimapY = y; + this.minimapSize = size; + + this.add.text(x + size / 2, y - 18, 'MAP OVERVIEW', { fontFamily: FONT, fontSize: '16px', color: COLORS.goldHex }).setOrigin(0.5); + this.add.rectangle(x + size / 2, y + size / 2, size + 8, size + 8, 0x000000).setStrokeStyle(2, COLORS.accent); + + const key = 'wf-editor-minimap'; + if (this.textures.exists(key)) this.textures.remove(key); + this.minimapTexture = this.textures.createCanvas(key, size, size); + this.minimapCtx = this.minimapTexture.context; + this.minimapImage = this.add.image(x, y, key).setOrigin(0, 0) + .setInteractive(new Phaser.Geom.Rectangle(0, 0, size, size), Phaser.Geom.Rectangle.Contains); + this.minimapBox = this.add.graphics(); + + this.minimapImage.on('pointerdown', (p) => this.jumpToMinimap(p)); + this.minimapImage.on('pointermove', (p) => { if (p.isDown) this.jumpToMinimap(p); }); + + this.add.text(x, y + size + 16, + 'Right-drag or arrows/WASD: pan\nMouse wheel or +/-: zoom\nF or Fit View: whole level\nClick map above: jump there\n\n' + + 'Patrol tool: click a guard to\nselect it, then click tiles to\nadd/remove its route nodes', + { fontFamily: FONT, fontSize: '13px', color: COLORS.textHex, lineSpacing: 5 }); + } + + jumpToMinimap(pointer) { + const lx = pointer.x - this.minimapX, ly = pointer.y - this.minimapY; + if (lx < 0 || ly < 0 || lx > this.minimapSize || ly > this.minimapSize) return; + const cx = (lx / this.minimapSize) * this.level.width; + const cy = (ly / this.minimapSize) * this.level.height; + this.setView(cx - (BOARD_SIZE / this.zoom) / 2, cy - (BOARD_SIZE / this.zoom) / 2); + } + + /** Downsampled whole-grid overview — cost is bounded by minimap resolution, not grid size. Called whenever level data settles. */ + drawMinimapContent() { + if (!this.minimapCtx) return; + const size = this.minimapSize; + const lvl = this.level; + const ctx = this.minimapCtx; + const img = ctx.createImageData(size, size); + const sx = lvl.width / size, sy = lvl.height / size; + for (let py = 0; py < size; py++) { + const cy = Math.min(lvl.height - 1, Math.floor(py * sy)); + const row = lvl.walls[cy]; + for (let px = 0; px < size; px++) { + const cx = Math.min(lvl.width - 1, Math.floor(px * sx)); + const wt = row[cx]; + const [r, gr, b] = wt > 0 ? (MINIMAP_WALL_RGB[wt] ?? [136, 136, 136]) : [30, 30, 38]; + const idx = (py * size + px) * 4; + img.data[idx] = r; img.data[idx + 1] = gr; img.data[idx + 2] = b; img.data[idx + 3] = 255; + } + } + ctx.putImageData(img, 0, 0); + this.minimapTexture.refresh(); + } + + /** Cheap viewport-rectangle overlay — redrawn on every pan/zoom, independent of the (throttled) content redraw. */ + drawMinimapBox() { + if (!this.minimapBox) return; + const g = this.minimapBox; + g.clear(); + const size = this.minimapSize; + const sx = size / this.level.width, sy = size / this.level.height; + const bx = this.minimapX + Phaser.Math.Clamp(this.viewX, 0, this.level.width) * sx; + const by = this.minimapY + Phaser.Math.Clamp(this.viewY, 0, this.level.height) * sy; + const bw = Math.min(size, (BOARD_SIZE / this.zoom) * sx); + const bh = Math.min(size, (BOARD_SIZE / this.zoom) * sy); + g.lineStyle(2, 0xffffff, 0.9); + g.strokeRect(bx, by, bw, bh); + } + // ── Input ───────────────────────────────────────────────────────────────── bindPointer() { - this.input.on('pointerdown', (p) => this.handleClick(p, true)); - this.input.on('pointermove', (p) => { if (p.isDown) this.handleClick(p, false); }); + this.input.mouse?.disableContextMenu(); + this.input.on('pointerdown', (p) => { + if (p.rightButtonDown()) { this.panState = { x: p.x, y: p.y, viewX: this.viewX, viewY: this.viewY }; return; } + this.handleClick(p, true); + }); + this.input.on('pointermove', (p) => { + if (this.panState) { + const dx = (p.x - this.panState.x) / this.zoom, dy = (p.y - this.panState.y) / this.zoom; + this.setView(this.panState.viewX - dx, this.panState.viewY - dy); + return; + } + if (p.isDown) this.handleClick(p, false); + }); + this.input.on('pointerup', () => { + if (this.panState) { this.panState = null; return; } + this.flushValidate(); + }); + this.input.on('wheel', (p, _over, _dx, dy) => { + if (!this.onBoard(p)) return; + this.zoomAt(p.x, p.y, dy > 0 ? 0.85 : 1 / 0.85); + }); } handleClick(p, isDown) { if (!this.onBoard(p)) return; - const [cx, cy] = this.toCell(p.x, p.y); - if (cx < 0 || cy < 0 || cx >= this.level.width || cy >= this.level.height) return; + let [cx, cy] = this.toCell(p.x, p.y); const paintTools = new Set(['wall1', 'wall2', 'wall3', 'wall4', 'erase']); if (!isDown && !paintTools.has(this.tool)) return; + + const outOfRange = cx < 0 || cy < 0 || cx >= this.level.width || cy >= this.level.height; + if (outOfRange && this.tool === 'erase') return; // nothing to erase in the void + + // A single click is cheap to fully validate even on a huge grid; a + // drag-continuation shares the undo entry pushed at the stroke's start. if (isDown) this.pushUndo(); + + if (outOfRange) { + if (!this.growToInclude(cx, cy)) return; // refused: would exceed MAX_GRID + [cx, cy] = this.toCell(p.x, p.y); // growth shifted viewX/viewY; recompute for the same screen point + } + this.applyTool(cx, cy); - this.rebuildModel(); + if (isDown) { + this.rebuildModel(); + } else { + // Mid-drag paint stroke: keep painting responsive (draw is now + // viewport-clipped, so it stays cheap regardless of grid size) and let + // the reachability check catch up after the stroke settles instead of + // running on every pointermove. + this.draw(); + this.scheduleValidate(); + } + } + + /** + * Expand the grid in whatever direction(s) are needed so cell (cx, cy) + * becomes valid, preserving all existing content (walls/doors/entities + * shift, never lose data) and re-sealing the new outer edge with border + * walls — the same closed-boundary invariant resizeLevel() maintains. + * Growing left/up shifts viewX/viewY by the same padding so the on-screen + * view doesn't jump. Returns false (no-op) if the grid is already in range + * or growth would exceed MAX_GRID. + */ + growToInclude(cx, cy) { + const lvl = this.level; + const padLeft = Math.max(0, -cx); + const padTop = Math.max(0, -cy); + const padRight = Math.max(0, cx - (lvl.width - 1)); + const padBottom = Math.max(0, cy - (lvl.height - 1)); + if (!padLeft && !padTop && !padRight && !padBottom) return true; + + const newW = lvl.width + padLeft + padRight; + const newH = lvl.height + padTop + padBottom; + if (newW > MAX_GRID || newH > MAX_GRID) return false; + + const walls = Array.from({ length: newH }, (_, y) => Array.from({ length: newW }, (_, x) => { + const oy = y - padTop, ox = x - padLeft; + if (oy >= 0 && oy < lvl.height && ox >= 0 && ox < lvl.width) return lvl.walls[oy][ox]; + return 0; // freshly grown territory starts as open floor + })); + for (let x = 0; x < newW; x++) { walls[0][x] = 1; walls[newH - 1][x] = 1; } + for (let y = 0; y < newH; y++) { walls[y][0] = 1; walls[y][newW - 1] = 1; } + + const shift = (e) => ({ ...e, x: e.x + padLeft, y: e.y + padTop }); + this.level = { + ...lvl, width: newW, height: newH, walls, + doors: lvl.doors.map(shift), + enemies: lvl.enemies.map(shift), + items: lvl.items.map(shift), + playerStart: lvl.playerStart ? shift(lvl.playerStart) : null, + exit: lvl.exit ? shift(lvl.exit) : null, + }; + this.viewX += padLeft; + this.viewY += padTop; + return true; } applyTool(x, y) { const lvl = this.level; if (WALL_TOOLS[this.tool]) { lvl.walls[y][x] = WALL_TOOLS[this.tool]; this.clearEntitiesAt(x, y); return; } if (this.tool === 'erase') { lvl.walls[y][x] = 0; this.clearEntitiesAt(x, y); return; } + if (this.tool === 'patrol') { this.applyPatrolTool(x, y); return; } lvl.walls[y][x] = 0; // every remaining tool places on floor if (this.tool === 'door') { @@ -266,15 +589,42 @@ export default class WolfensteinEditor extends Phaser.Scene { lvl.exit = { x: x + 0.5, y: y + 0.5, radius: 0.6 }; } else if (this.tool === 'enemy') { const i = lvl.enemies.findIndex((e) => Math.floor(e.x) === x && Math.floor(e.y) === y); - if (i >= 0) lvl.enemies.splice(i, 1); else lvl.enemies.push({ type: 'guard', x: x + 0.5, y: y + 0.5, facing: 180 }); + if (i >= 0) { + if (lvl.enemies[i] === this.selectedEnemy) this.selectedEnemy = null; + lvl.enemies.splice(i, 1); + } else { + lvl.enemies.push({ type: 'guard', x: x + 0.5, y: y + 0.5, facing: 180, patrol: [] }); + } } else if (this.tool === 'ammo' || this.tool === 'health') { const i = lvl.items.findIndex((it) => Math.floor(it.x) === x && Math.floor(it.y) === y); if (i >= 0) lvl.items.splice(i, 1); else lvl.items.push({ type: this.tool, x: x + 0.5, y: y + 0.5 }); } } + /** + * Patrol mode: click a guard to make it the active one for editing (see + * `this.selectedEnemy`, an object reference — indices don't survive other + * tools splicing the enemies array); once one is active, click any other + * cell to append a waypoint at its center, or click an existing waypoint + * to remove it. Never touches the walls grid (unlike every other + * non-erase tool) — this is metadata editing, not placement. + */ + applyPatrolTool(x, y) { + const lvl = this.level; + const clickedEnemy = lvl.enemies.find((e) => Math.floor(e.x) === x && Math.floor(e.y) === y); + if (clickedEnemy) { this.selectedEnemy = clickedEnemy; return; } + if (!this.selectedEnemy || !lvl.enemies.includes(this.selectedEnemy)) { this.selectedEnemy = null; return; } + + const enemy = this.selectedEnemy; + enemy.patrol = enemy.patrol ?? []; + const i = enemy.patrol.findIndex((n) => Math.floor(n.x) === x && Math.floor(n.y) === y); + if (i >= 0) enemy.patrol.splice(i, 1); else enemy.patrol.push({ x: x + 0.5, y: y + 0.5 }); + } + clearEntitiesAt(x, y) { const lvl = this.level; + const removedEnemy = lvl.enemies.find((e) => Math.floor(e.x) === x && Math.floor(e.y) === y); + if (removedEnemy && removedEnemy === this.selectedEnemy) this.selectedEnemy = null; lvl.doors = lvl.doors.filter((d) => !(d.x === x && d.y === y)); lvl.enemies = lvl.enemies.filter((e) => !(Math.floor(e.x) === x && Math.floor(e.y) === y)); lvl.items = lvl.items.filter((it) => !(Math.floor(it.x) === x && Math.floor(it.y) === y)); @@ -284,6 +634,25 @@ export default class WolfensteinEditor extends Phaser.Scene { bindKeys() { this.input.keyboard.on('keydown-Z', (ev) => { if (ev.ctrlKey || ev.metaKey) this.undo(); }); + this.input.keyboard.on('keydown-F', () => this.fitToGrid()); + const boardCenter = () => [BOARD_X + BOARD_SIZE / 2, BOARD_Y + BOARD_SIZE / 2]; + this.input.keyboard.on('keydown-PLUS', () => this.zoomAt(...boardCenter(), 1.25)); + this.input.keyboard.on('keydown-NUMPAD_ADD', () => this.zoomAt(...boardCenter(), 1.25)); + this.input.keyboard.on('keydown-MINUS', () => this.zoomAt(...boardCenter(), 0.8)); + this.input.keyboard.on('keydown-NUMPAD_SUBTRACT', () => this.zoomAt(...boardCenter(), 0.8)); + this.cursors = this.input.keyboard.createCursorKeys(); + this.wasd = this.input.keyboard.addKeys('W,A,S,D'); + } + + update(_time, delta) { + if (!this.cursors || this.panState) return; + const speed = (500 / this.zoom) * (delta / 1000); // ~constant on-screen pan speed at any zoom + let dx = 0, dy = 0; + if (this.cursors.left.isDown || this.wasd.A.isDown) dx -= speed; + if (this.cursors.right.isDown || this.wasd.D.isDown) dx += speed; + if (this.cursors.up.isDown || this.wasd.W.isDown) dy -= speed; + if (this.cursors.down.isDown || this.wasd.S.isDown) dy += speed; + if (dx || dy) this.setView(this.viewX + dx, this.viewY + dy); } // ── Undo / model ────────────────────────────────────────────────────────── @@ -296,10 +665,23 @@ export default class WolfensteinEditor extends Phaser.Scene { const snap = this.undoStack.pop(); if (!snap) return; this.level = JSON.parse(snap); + this.selectedEnemy = null; // level was wholesale-replaced; old reference can't match anything in it + this.setView(this.viewX, this.viewY); // re-clamp in case the undone edit resized the grid this.rebuildModel(); } - rebuildModel() { + rebuildModel() { this.runValidate(); } + + scheduleValidate() { + if (this._validateTimer) this._validateTimer.remove(false); + this._validateTimer = this.time.delayedCall(150, () => this.runValidate()); + } + + /** Cancels any pending debounce and validates immediately — call before anything that reads `this.result` (Test Play, Export). */ + flushValidate() { this.runValidate(); } + + runValidate() { + if (this._validateTimer) { this._validateTimer.remove(false); this._validateTimer = null; } try { const model = buildLevelModel(this.level); this.result = validateLevel(model); @@ -308,6 +690,7 @@ export default class WolfensteinEditor extends Phaser.Scene { } this.issueText?.setText(this.result.issues.length ? `⚠ ${this.result.issues.slice(0, 4).join('\n')}` : 'Level OK'); this.draw(); + this.drawMinimapContent(); } // ── Drawing ─────────────────────────────────────────────────────────────── @@ -315,20 +698,22 @@ export default class WolfensteinEditor extends Phaser.Scene { draw() { const g = this.g; g.clear(); - const k = this.cellPx; + const k = this.zoom; const lvl = this.level; + const [x0, y0, x1, y1] = this.visibleCellRange(); - for (let y = 0; y < lvl.height; y++) { - for (let x = 0; x < lvl.width; x++) { - const wt = lvl.walls[y][x]; + for (let y = y0; y < y1; y++) { + const row = lvl.walls[y]; + for (let x = x0; x < x1; x++) { + const wt = row[x]; const [bx, by] = this.toBoard(x, y); g.fillStyle(wt > 0 ? (WALL_COLORS[wt] ?? 0x888888) : 0x2a2a32, 1); g.fillRect(bx, by, k - 1, k - 1); } } g.lineStyle(1, 0x000000, 0.3); - for (let x = 0; x <= lvl.width; x++) { const [bx] = this.toBoard(x, 0); g.lineBetween(bx, BOARD_Y, bx, BOARD_Y + lvl.height * k); } - for (let y = 0; y <= lvl.height; y++) { const [, by] = this.toBoard(0, y); g.lineBetween(BOARD_X, by, BOARD_X + lvl.width * k, by); } + for (let x = x0; x <= x1; x++) { const [bx] = this.toBoard(x, 0); g.lineBetween(bx, BOARD_Y, bx, BOARD_Y + BOARD_SIZE); } + for (let y = y0; y <= y1; y++) { const [, by] = this.toBoard(0, y); g.lineBetween(BOARD_X, by, BOARD_X + BOARD_SIZE, by); } g.fillStyle(0xb08040, 1); for (const d of lvl.doors) { const [bx, by] = this.toBoard(d.x, d.y); g.fillRect(bx, by, k - 1, k - 1); } @@ -349,11 +734,50 @@ export default class WolfensteinEditor extends Phaser.Scene { if (it.type === 'ammo') g.fillRect(bx - k * 0.15, by - k * 0.15, k * 0.3, k * 0.3); else { g.fillStyle(0xe06c75, 1); g.fillCircle(bx, by, k * 0.2); g.fillStyle(0xd4a017, 1); } } + + if (this.tool === 'patrol') this.drawPatrolRoutes(); + + this.drawMinimapBox(); + } + + /** + * Every enemy with a route gets a dim line; the selected one (see + * applyPatrolTool) gets a bright highlighted line plus a ring around the + * enemy itself, so it's obvious which guard you're currently editing. + * Home (the enemy's own spawn point) is always node 0 of the walked path, + * even though it isn't stored in `patrol` — matches stepPatrol(). + */ + drawPatrolRoutes() { + const g = this.g; + const lvl = this.level; + const k = this.zoom; + for (const e of lvl.enemies) { + if (!e.patrol || !e.patrol.length) continue; + const selected = e === this.selectedEnemy; + const path = [{ x: e.x, y: e.y }, ...e.patrol]; + g.lineStyle(selected ? 3 : 2, selected ? 0xffe066 : 0x887a3a, selected ? 1 : 0.55); + for (let i = 0; i < path.length - 1; i++) { + const [ax, ay] = this.toBoard(path[i].x, path[i].y); + const [bx, by] = this.toBoard(path[i + 1].x, path[i + 1].y); + g.lineBetween(ax, ay, bx, by); + } + g.fillStyle(selected ? 0xffe066 : 0x887a3a, selected ? 1 : 0.7); + for (const node of e.patrol) { + const [nx, ny] = this.toBoard(node.x, node.y); + g.fillCircle(nx, ny, k * 0.16); + } + } + if (this.selectedEnemy && lvl.enemies.includes(this.selectedEnemy)) { + const [sx, sy] = this.toBoard(this.selectedEnemy.x, this.selectedEnemy.y); + g.lineStyle(3, 0xffe066, 1); + g.strokeCircle(sx, sy, k * 0.45); + } } // ── Test play / export ─────────────────────────────────────────────────── testPlay() { + this.flushValidate(); if (this.result.issues.length) return; this.registry.set('wolfenstein-editor-state', JSON.parse(JSON.stringify(this.level))); this.scene.start('WolfensteinGame', { @@ -372,6 +796,7 @@ export default class WolfensteinEditor extends Phaser.Scene { } exportLevel() { + this.flushValidate(); if (this.result.issues.length) return; this.download(`level-${this.level.id}.json`, this.level); } diff --git a/src/games/wolfenstein/WolfensteinGame.js b/src/games/wolfenstein/WolfensteinGame.js index 51b8c36..fda32b0 100644 --- a/src/games/wolfenstein/WolfensteinGame.js +++ b/src/games/wolfenstein/WolfensteinGame.js @@ -44,7 +44,7 @@ export default class WolfensteinGame extends Phaser.Scene { this._mouseFireHeld = false; this._lastAutosave = 0; - this.keys = this.input.keyboard.addKeys('W,A,S,D,CTRL,ONE,TWO,ESC'); + this.keys = this.input.keyboard.addKeys('W,A,S,D,CTRL,ONE,TWO,ESC,SPACE'); this._bindPointerLock(); @@ -220,6 +220,7 @@ export default class WolfensteinGame extends Phaser.Scene { Logic.setFireHeld(this.state, this._mouseFireHeld || k.CTRL.isDown); if (Phaser.Input.Keyboard.JustDown(k.ONE)) Logic.switchWeapon(this.state, 'fists'); if (Phaser.Input.Keyboard.JustDown(k.TWO)) Logic.switchWeapon(this.state, 'pistol'); + if (Phaser.Input.Keyboard.JustDown(k.SPACE)) Logic.openNearestDoor(this.state); } // ------------------------------------------------------------- loop @@ -296,12 +297,15 @@ export default class WolfensteinGame extends Phaser.Scene { objs.toast = this.add.text(GAME_WIDTH / 2, GAME_HEIGHT - 170, '', { fontFamily: '"Julius Sans One"', fontSize: '26px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(21).setAlpha(0); objs.crosshair = this.add.text(GAME_WIDTH / 2, VIEW_H / 2, '+', { fontSize: '38px', color: COLORS.textHex }).setOrigin(0.5).setDepth(15); objs.lockHint = this.add.text(GAME_WIDTH / 2, 60, 'Click to aim', { fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.mutedHex }).setOrigin(0.5).setDepth(21); + // Doors no longer open automatically on approach (see openNearestDoor) — + // without this, there's no way to discover that Space is the interact key. + objs.doorHint = this.add.text(GAME_WIDTH / 2, VIEW_H / 2 + 46, '[SPACE] Open', { fontFamily: '"Julius Sans One"', fontSize: '22px', color: COLORS.goldHex }).setOrigin(0.5).setDepth(21); return objs; } _setHudVisible(visible) { for (const o of Object.values(this.hud)) o.setVisible(visible); - if (visible) this.hud.lockHint.setVisible(!this._locked); + if (visible) { this.hud.lockHint.setVisible(!this._locked); this.hud.doorHint.setVisible(false); } } _updateHud() { @@ -311,6 +315,7 @@ export default class WolfensteinGame extends Phaser.Scene { const w = this.rules.weaponById[p.weapon]; this.hud.ammo.setText(w.kind === 'projectile' ? `AMMO ${p.ammo[p.weapon] ?? 0}` : ''); this.hud.lockHint.setVisible(!this._locked); + this.hud.doorHint.setVisible(Logic.hasOpenableDoorNearby(this.state)); } _toast(msg) { diff --git a/src/games/wolfenstein/WolfensteinLogic.js b/src/games/wolfenstein/WolfensteinLogic.js index f9ff292..3870a1d 100644 --- a/src/games/wolfenstein/WolfensteinLogic.js +++ b/src/games/wolfenstein/WolfensteinLogic.js @@ -24,13 +24,26 @@ export const DOOR_WALL_TYPE = 9; export function createState(level, rules) { const walls = level.walls.map((row) => row.slice()); const doors = (level.doors ?? []).map((d, i) => ({ - id: i, x: d.x, y: d.y, orientation: d.orientation ?? 'vertical', open: false, timer: 0, + id: i, x: d.x, y: d.y, orientation: d.orientation ?? 'vertical', + // slide: 0 (closed) -> 1 (fully open), animated by stepDoors at + // DOOR_SLIDE_MS. target is where it's animating toward; the cell only + // stops blocking movement/sight once slide reaches 1 exactly — no + // squeezing through a half-open door — so the visual recede (see + // WolfensteinView._drawDoorColumn) never has to jump partway through. + slide: 0, target: 0, timer: 0, })); for (const d of doors) walls[d.y][d.x] = DOOR_WALL_TYPE; const enemies = (level.enemies ?? []).map((e, i) => ({ id: i, defId: e.type, x: e.x, y: e.y, angle: ((e.facing ?? 0) * Math.PI) / 180, health: rules.enemyById[e.type].health, state: 'idle', cooldownMs: 0, dead: false, + // Patrol route, editor-authored: an idle (never-yet-alerted) enemy walks + // home -> patrol[0] -> patrol[1] -> ... -> home, ping-ponging forever, + // until it spots the player (see stepPatrol). homeX/Y is the spawn point + // itself, since e.x/y move once patrolling starts. + homeX: e.x, homeY: e.y, + patrol: (e.patrol ?? []).map((n) => ({ x: n.x, y: n.y })), + patrolIndex: 0, patrolDir: 1, })); const pickups = (level.items ?? []).map((it, i) => ({ id: i, itemId: it.type, x: it.x, y: it.y, taken: false })); @@ -165,29 +178,66 @@ function stepPlayer(state, rules) { } // --------------------------------------------------------------------------- -// Doors — open on proximity, auto-close once nobody is near +// Doors — player must press Space near one to open it (see openNearestDoor, +// called from WolfensteinGame on a Space keydown); enemies still shove doors +// open on approach, since they have no equivalent of pressing a key. Either +// way, once cracked open at all, nobody standing near it lets it finish +// closing. A door that reaches full auto-close idle time with the area clear +// starts sliding shut again on its own. // --------------------------------------------------------------------------- -function isNear(state, x, y, radius) { - const p = state.player; - if (!p.dead && Math.hypot(p.x - x, p.y - y) < radius) return true; - for (const e of state.enemies) if (!e.dead && Math.hypot(e.x - x, e.y - y) < radius) return true; +const DOOR_SLIDE_MS = 400; +const DOOR_RADIUS = 0.9; + +function enemyNearDoor(state, cx, cy) { + for (const e of state.enemies) if (!e.dead && Math.hypot(e.x - cx, e.y - cy) < DOOR_RADIUS) return true; return false; } +function anyoneNearDoor(state, cx, cy) { + const p = state.player; + if (!p.dead && Math.hypot(p.x - cx, p.y - cy) < DOOR_RADIUS) return true; + return enemyNearDoor(state, cx, cy); +} + +function findOpenableDoor(state) { + const p = state.player; + if (p.dead) return null; + let best = null, bestDist = Infinity; + for (const d of state.doors) { + if (d.target === 1) continue; // already open or opening + const dist = Math.hypot(p.x - (d.x + 0.5), p.y - (d.y + 0.5)); + if (dist <= DOOR_RADIUS + 0.2 && dist < bestDist) { best = d; bestDist = dist; } + } + return best; +} + +/** Player interact (Space): open the nearest closed/closing door in range, if any. */ +export function openNearestDoor(state) { + const door = findOpenableDoor(state); + if (door) { door.target = 1; state.events.push({ t: 'doorOpen', x: door.x, y: door.y }); } +} + +/** For a HUD prompt ("[SPACE] Open") — true iff a Space press right now would do something. */ +export function hasOpenableDoorNearby(state) { return !!findOpenableDoor(state); } function stepDoors(state, rules) { for (const d of state.doors) { - const near = isNear(state, d.x + 0.5, d.y + 0.5, 0.9); - if (near) { - if (!d.open) { d.open = true; state.events.push({ t: 'doorOpen', x: d.x, y: d.y }); } - d.timer = rules.constants.doorAutoCloseMs; - state.map.walls[d.y][d.x] = 0; - } else if (d.open) { - d.timer -= rules.stepMs; - if (d.timer <= 0) { - d.open = false; - state.map.walls[d.y][d.x] = DOOR_WALL_TYPE; - state.events.push({ t: 'doorClose', x: d.x, y: d.y }); + const cx = d.x + 0.5, cy = d.y + 0.5; + if (d.target === 0 && enemyNearDoor(state, cx, cy)) d.target = 1; // AI push-through, no "Space" for them + if (d.target === 0 && d.slide > 0 && anyoneNearDoor(state, cx, cy)) d.target = 1; // never finish closing on someone in it + + const wasOpen = d.slide >= 1; + if (d.target === 1 && d.slide < 1) d.slide = Math.min(1, d.slide + rules.stepMs / DOOR_SLIDE_MS); + else if (d.target === 0 && d.slide > 0) d.slide = Math.max(0, d.slide - rules.stepMs / DOOR_SLIDE_MS); + const isOpen = d.slide >= 1; + if (isOpen !== wasOpen) state.map.walls[d.y][d.x] = isOpen ? 0 : DOOR_WALL_TYPE; + + if (d.target === 1 && d.slide >= 1) { + if (!wasOpen || anyoneNearDoor(state, cx, cy)) { + d.timer = rules.constants.doorAutoCloseMs; + } else { + d.timer -= rules.stepMs; + if (d.timer <= 0) { d.target = 0; state.events.push({ t: 'doorClose', x: d.x, y: d.y }); } } } } @@ -208,7 +258,8 @@ function stepEnemyAI(state, rules) { const canSee = distToPlayer <= def.detectRange && hasLineOfSight(state.map, e.x, e.y, p.x, p.y); if (e.state === 'idle') { - if (canSee) { e.state = 'alert'; state.events.push({ t: 'enemyAlert', id: e.id }); } + if (canSee) { e.state = 'alert'; state.events.push({ t: 'enemyAlert', id: e.id }); continue; } + stepPatrol(state, rules, e, def); continue; } if (canSee) e.state = 'chase'; @@ -240,6 +291,28 @@ function stepEnemyAI(state, rules) { } } +const PATROL_ARRIVE_DIST = 0.12; + +/** Ping-pong an idle enemy along [home, ...patrol]; a no-op if it has no route. */ +function stepPatrol(state, rules, e, def) { + if (!e.patrol || !e.patrol.length) return; + const path = [{ x: e.homeX, y: e.homeY }, ...e.patrol]; + if (e.patrolIndex >= path.length) e.patrolIndex = path.length - 1; + const target = path[e.patrolIndex]; + const dx = target.x - e.x, dy = target.y - e.y; + const dist = Math.hypot(dx, dy); + if (dist <= PATROL_ARRIVE_DIST) { + let next = e.patrolIndex + e.patrolDir; + if (next < 0 || next >= path.length) { e.patrolDir *= -1; next = e.patrolIndex + e.patrolDir; } + e.patrolIndex = next; + return; + } + const mvx = (dx / dist) * def.speed * rules.dt; + const mvy = (dy / dist) * def.speed * rules.dt; + moveWithCollision(state.map, e, mvx, mvy, def.radius); + e.angle = Math.atan2(dy, dx); +} + // --------------------------------------------------------------------------- // Weapons // --------------------------------------------------------------------------- @@ -497,6 +570,10 @@ export function validateLevel(level) { for (const e of level.enemies ?? []) { const c = { x: Math.floor(e.x), y: Math.floor(e.y) }; if (level.walls[c.y]?.[c.x] > 0) issues.push(`enemy spawn (${e.x},${e.y}) sits inside a wall cell`); + for (const n of e.patrol ?? []) { + const nc = { x: Math.floor(n.x), y: Math.floor(n.y) }; + if (level.walls[nc.y]?.[nc.x] > 0) issues.push(`enemy patrol node (${n.x},${n.y}) sits inside a wall cell`); + } } for (const it of level.items ?? []) { const c = { x: Math.floor(it.x), y: Math.floor(it.y) }; diff --git a/src/games/wolfenstein/WolfensteinRaycaster.js b/src/games/wolfenstein/WolfensteinRaycaster.js index be820ba..c2a3941 100644 --- a/src/games/wolfenstein/WolfensteinRaycaster.js +++ b/src/games/wolfenstein/WolfensteinRaycaster.js @@ -11,23 +11,73 @@ // also exactly the parametric "t" of the ray equation P = origin + dir*t, // which is what the swept-bullet and line-of-sight callers rely on (t=1 means // "exactly at the point dir was aimed at"). +// +// Door cells get real recessed-mid-plane geometry (see intersectDoorMidplane) +// when a caller opts in via `doorAware` — WolfensteinView's render raycast +// does; collision/line-of-sight/bullet callers in WolfensteinLogic don't, so +// a door stays a simple full-cell solid for gameplay purposes exactly as +// before (see stepDoors' note on why passability only flips at slide===1). +// This is a rendering-only enhancement, not a change to what's walkable. const DEFAULT_MAX_STEPS = 256; +// Duplicated from WolfensteinLogic.DOOR_WALL_TYPE rather than imported — +// WolfensteinLogic imports FROM this module, so importing back would be +// circular. Keep these in sync if either changes. +const DOOR_WALL_TYPE = 9; + /** True outside the grid too, so a ray/AABB check never has to special-case the border. */ export function isWallCell(map, cellX, cellY) { if (cellX < 0 || cellY < 0 || cellX >= map.width || cellY >= map.height) return true; return map.walls[cellY][cellX] > 0; } +/** + * Sub-cell intersection test for a door's mid-cell plane — a door sits at + * half the cell's depth along its blocking axis, not flush with either + * face, so walls either side of it read as thick blocks with the (thinner) + * door recessed between them: the "H" shape, door as the crossbar. The + * blocking axis is inferred from geometry (which pair of neighbors is + * walled off), not the doors[] `orientation` field, which nothing actually + * sets to anything but 'vertical'. + * + * Returns null when the ray's own trajectory would clear the cell sideways + * (cross into a neighboring cell) before ever reaching that mid-plane — the + * caller should then keep stepping the DDA as if this cell were transparent + * rather than treat it as a miss; stepping on lets the DDA reach the actual + * flanking wall cell and hit it at its own true (nearer, unrecessed) + * distance, which is what makes that wall's inward face visible at grazing + * angles — the two verticals of the "H," rendered as ordinary wall hits, + * not anything faked here. + */ +function intersectDoorMidplane(map, x, y, dirX, dirY, cellX, cellY) { + const blocksEW = isWallCell(map, cellX, cellY - 1) && isWallCell(map, cellX, cellY + 1); + if (blocksEW) { + if (Math.abs(dirX) < 1e-9) return null; + const t = (cellX + 0.5 - x) / dirX; + if (t <= 0) return null; + const along = y + t * dirY - cellY; + if (along < 0 || along > 1) return null; + return { perpDist: t, side: 0, textureX: along }; + } + if (Math.abs(dirY) < 1e-9) return null; + const t = (cellY + 0.5 - y) / dirY; + if (t <= 0) return null; + const along = x + t * dirX - cellX; + if (along < 0 || along > 1) return null; + return { perpDist: t, side: 1, textureX: along }; +} + /** * March a ray from (x,y) along direction (dirX,dirY) — need not be unit * length; distances/`perpDist` come out scaled to that vector's own length — * until it exits the first solid cell. Returns null if it runs off the map * edge or exceeds maxSteps without a hit (should never happen inside a - * validated closed level). + * validated closed level). `doorAware` opts into recessed door-plane + * geometry (see intersectDoorMidplane) instead of treating a door cell as an + * ordinary flush solid. */ -export function castRay(map, x, y, dirX, dirY, maxSteps = DEFAULT_MAX_STEPS) { +export function castRay(map, x, y, dirX, dirY, maxSteps = DEFAULT_MAX_STEPS, doorAware = false) { let mapX = Math.floor(x); let mapY = Math.floor(y); @@ -48,12 +98,18 @@ export function castRay(map, x, y, dirX, dirY, maxSteps = DEFAULT_MAX_STEPS) { if (mapX < 0 || mapY < 0 || mapX >= map.width || mapY >= map.height) return null; const wallType = map.walls[mapY][mapX]; - if (wallType > 0) { - const perpDist = side === 0 ? (sideDistX - deltaDistX) : (sideDistY - deltaDistY); - let wallX = side === 0 ? (y + perpDist * dirY) : (x + perpDist * dirX); - wallX -= Math.floor(wallX); - return { perpDist: Math.max(perpDist, 1e-4), side, mapX, mapY, wallType, textureX: wallX }; + if (wallType === 0) continue; + + if (doorAware && wallType === DOOR_WALL_TYPE) { + const doorHit = intersectDoorMidplane(map, x, y, dirX, dirY, mapX, mapY); + if (doorHit) return { ...doorHit, mapX, mapY, wallType }; + continue; // ray grazes past this door cell without reaching its mid-plane } + + const perpDist = side === 0 ? (sideDistX - deltaDistX) : (sideDistY - deltaDistY); + let wallX = side === 0 ? (y + perpDist * dirY) : (x + perpDist * dirX); + wallX -= Math.floor(wallX); + return { perpDist: Math.max(perpDist, 1e-4), side, mapX, mapY, wallType, textureX: wallX }; } return null; } @@ -69,8 +125,8 @@ export function makeCamera(x, y, angle, fov) { return { x, y, angle, dirX, dirY, planeX: -dirY * planeScale, planeY: dirX * planeScale }; } -/** One castRay per screen column, camera-space (fisheye-free). */ -export function castColumns(map, camera, numColumns) { +/** One castRay per screen column, camera-space (fisheye-free). `doorAware` — see castRay — is what WolfensteinView passes to get recessed door geometry. */ +export function castColumns(map, camera, numColumns, doorAware = false) { const { x, y, dirX, dirY, planeX, planeY } = camera; const maxSteps = fullMapSteps(map); const out = new Array(numColumns); @@ -78,7 +134,7 @@ export function castColumns(map, camera, numColumns) { const cameraX = (2 * col) / numColumns - 1; const rdx = dirX + planeX * cameraX; const rdy = dirY + planeY * cameraX; - out[col] = castRay(map, x, y, rdx, rdy, maxSteps); + out[col] = castRay(map, x, y, rdx, rdy, maxSteps, doorAware); } return out; } diff --git a/src/games/wolfenstein/WolfensteinView.js b/src/games/wolfenstein/WolfensteinView.js index 8d8890b..9f3b398 100644 --- a/src/games/wolfenstein/WolfensteinView.js +++ b/src/games/wolfenstein/WolfensteinView.js @@ -5,10 +5,13 @@ // .refresh()) rather than a WebGL shader — renderer-agnostic (works under // both Phaser CANVAS and WEBGL, unlike Super Kart's Mode 7 shader), and // conceptually the same canvas-manipulation approach Excitebike and Super -// Kart's track pre-rasterizer already use elsewhere in this repo. Flat-shaded -// per-column rects for now; when painted wall textures land, the same -// per-column loop swaps fillRect for a drawImage source-column stretch — the -// architecture doesn't change. +// Kart's track pre-rasterizer already use elsewhere in this repo. Per column, +// a single source-texel-wide slice of the `wolfenstein-walls` sheet (picked +// via the raycaster's `textureX`) is drawImage-stretched to the column's +// screen width, then darkened with a 'multiply' composite rect to reproduce +// the same side/fog shading flat colors used. Falls back to a flat-shaded +// fillRect (the original placeholder look) per wall type whose frame isn't +// loaded — lets art land wall-type-by-wall-type instead of all at once. // // Sprites (enemies/pickups/in-flight bullets): pooled billboarded Phaser // Images, positioned via the same camera-space projection math as the walls, @@ -17,11 +20,24 @@ import { castColumns } from './WolfensteinRaycaster.js'; import { WALL_COLORS, ensureSprites } from './WolfensteinArt.js'; +import { DOOR_WALL_TYPE } from './WolfensteinLogic.js'; export const VIEW_W = 1920; export const VIEW_H = 940; export const NUM_COLUMNS = 480; +// wallType -> frame index in the `wolfenstein-walls` sheet, matching +// WolfensteinArt.WALL_COLORS' order. No entry for type 9 (door) — doors are +// never texture-sampled, they get their own recessed-slide treatment in +// _drawDoorColumn regardless of whether painted wall art exists. +const WALL_FRAME = { 1: 0, 2: 1, 3: 2, 4: 3 }; + +// A dark, unshaded fill for the portion of a door that's already slid past — +// meant to read as a shadowed pocket the panel has receded into, not more +// wall. Kept flat (no side/fog shading) so it reads as a distinct material, +// not just a darker version of the door itself. +const DOOR_SOCKET_COLOR = '#141018'; + export default class WolfensteinView { constructor(scene, rules) { this.scene = scene; @@ -32,42 +48,100 @@ export default class WolfensteinView { if (scene.textures.exists(key)) scene.textures.remove(key); this.canvasTexture = scene.textures.createCanvas(key, VIEW_W, VIEW_H); this.ctx = this.canvasTexture.context; + this.ctx.imageSmoothingEnabled = false; this.image = scene.add.image(0, 0, key).setOrigin(0, 0).setDepth(10); this.colWidth = VIEW_W / NUM_COLUMNS; this.depthBuffer = new Array(NUM_COLUMNS).fill(Infinity); this.spritePool = new Map(); + + this.wallTexture = scene.textures.exists('wolfenstein-walls') + ? scene.textures.get('wolfenstein-walls') : null; } render(state, camera) { - this._drawWalls(state.map, camera); + this._drawWalls(state.map, state.doors, camera); this._drawSprites(state, camera); } - _drawWalls(map, camera) { + _drawWalls(map, doors, camera) { const ctx = this.ctx; ctx.fillStyle = '#2b2b2b'; ctx.fillRect(0, 0, VIEW_W, VIEW_H / 2); ctx.fillStyle = '#4a4a4a'; ctx.fillRect(0, VIEW_H / 2, VIEW_W, VIEW_H / 2); - const cols = castColumns(map, camera, NUM_COLUMNS); + const cols = castColumns(map, camera, NUM_COLUMNS, true); for (let i = 0; i < NUM_COLUMNS; i++) { const hit = cols[i]; this.depthBuffer[i] = hit ? hit.perpDist : Infinity; if (!hit) continue; const lineHeight = VIEW_H / hit.perpDist; - let drawStart = -lineHeight / 2 + VIEW_H / 2; if (drawStart < 0) drawStart = 0; - let drawEnd = lineHeight / 2 + VIEW_H / 2; if (drawEnd > VIEW_H) drawEnd = VIEW_H; - const base = WALL_COLORS[hit.wallType] ?? 0xaaaaaa; - ctx.fillStyle = shadeColor(base, hit); + const rawStart = -lineHeight / 2 + VIEW_H / 2; + const rawEnd = lineHeight / 2 + VIEW_H / 2; + const drawStart = Math.max(0, rawStart); + const drawEnd = Math.min(VIEW_H, rawEnd); + if (drawEnd <= drawStart) continue; const x = Math.floor(i * this.colWidth); const w = Math.ceil(this.colWidth) + 1; - ctx.fillRect(x, Math.floor(drawStart), w, Math.ceil(drawEnd - drawStart)); + const y = Math.floor(drawStart); + const h = Math.ceil(drawEnd - drawStart); + + if (hit.wallType === DOOR_WALL_TYPE) { + this._drawDoorColumn(ctx, x, y, w, h, hit, doors); + continue; + } + + const frame = this.wallTexture?.frames[WALL_FRAME[hit.wallType]]; + if (frame) { + const srcX = frame.cutX + Math.min(frame.width - 1, Math.floor(hit.textureX * frame.width)); + // rawStart/rawEnd (not drawStart/drawEnd) are the true, unclipped + // extent of this column — up close that's far taller than the + // screen. Crop the source by the same fraction that got clipped off + // the destination so the vertical zoom keeps pace with the + // horizontal one instead of freezing once the wall overflows VIEW_H. + const srcY = frame.cutY + ((drawStart - rawStart) / lineHeight) * frame.height; + const srcH = ((drawEnd - drawStart) / lineHeight) * frame.height; + ctx.drawImage(frame.source.image, srcX, srcY, 1, srcH, x, y, w, h); + ctx.globalCompositeOperation = 'multiply'; + ctx.fillStyle = shadeGrey(hit); + ctx.fillRect(x, y, w, h); + ctx.globalCompositeOperation = 'source-over'; + } else { + const base = WALL_COLORS[hit.wallType] ?? 0xaaaaaa; + ctx.fillStyle = shadeColor(base, hit); + ctx.fillRect(x, y, w, h); + } } this.canvasTexture.refresh(); } + /** + * hit.perpDist already IS the recessed mid-plane distance here — the + * render raycast opts into door-aware geometry (see castColumns(..., + * true) above and WolfensteinRaycaster's intersectDoorMidplane), so this + * column's (x,y,w,h), computed by the caller exactly like any other wall, + * is already correctly smaller/farther than a flush wall would be. A + * grazing column whose ray clears the door cell sideways instead hits the + * real flanking wall cell at ITS true distance via the normal wall path + * above — that's what makes the wall's inward face visible next to the + * door, the two verticals of the "H." Nothing extra to fake here. + * + * `textureX` is where this column's ray crosses the door's face (0..1 + * along it, computed at the mid-plane); a door slides such that the low- + * textureX edge stays put and the far edge recedes first, so as `slide` + * climbs, columns flip from "still there" to "already receded" in + * textureX order — the door visibly sliding sideways into its socket. + */ + _drawDoorColumn(ctx, x, y, w, h, hit, doors) { + const door = doors.find((d) => d.x === hit.mapX && d.y === hit.mapY); + const visibleFrac = 1 - (door?.slide ?? 0); + ctx.fillStyle = hit.textureX < visibleFrac + ? shadeColor(WALL_COLORS[DOOR_WALL_TYPE] ?? 0xb08040, hit) + : DOOR_SOCKET_COLOR; + ctx.fillRect(x, y, w, h); + } + _drawSprites(state, camera) { const live = new Set(); const sprites = []; @@ -132,11 +206,23 @@ export default class WolfensteinView { } } -function shadeColor(hex, hit) { - let r = (hex >> 16) & 255, g = (hex >> 8) & 255, b = hex & 255; +function shadeMul(hit) { const sideMul = hit.side === 1 ? 0.72 : 1; const fog = Math.max(0.28, 1 - hit.perpDist / 14); - const m = sideMul * fog; + return sideMul * fog; +} + +function shadeColor(hex, hit) { + let r = (hex >> 16) & 255, g = (hex >> 8) & 255, b = hex & 255; + const m = shadeMul(hit); r = Math.round(r * m); g = Math.round(g * m); b = Math.round(b * m); return `rgb(${r},${g},${b})`; } + +// A solid grey drawn with 'multiply' compositing scales every channel of +// whatever's underneath by the same factor shadeColor() applies to a flat +// fill — the same side/fog darkening, applied to a drawn texture instead. +function shadeGrey(hit) { + const v = Math.round(255 * shadeMul(hit)); + return `rgb(${v},${v},${v})`; +} diff --git a/src/games/wolfenstein/sprites.md b/src/games/wolfenstein/sprites.md index 6186ca1..4c1102e 100644 --- a/src/games/wolfenstein/sprites.md +++ b/src/games/wolfenstein/sprites.md @@ -7,30 +7,118 @@ path is set (see `assetManifest.js`'s `wolfenstein` entry). ## Wall textures — `sheets.walls` -A horizontal spritesheet, one 64×64 frame per wall type, in the order used by -`WolfensteinArt.WALL_COLORS`: frame 0 = type 1 (stone), frame 1 = type 2 -(wood), frame 2 = type 3 (blue-tile), frame 3 = type 4 (green-tile). A door -(closed) reuses frame index matching `DOOR_WALL_TYPE`'s placeholder color -(tan) — add a 5th frame if a distinct door texture is wanted. +Per `data/wolfenstein-artwork.json`: `frameWidth: 64, frameHeight: 64`, +loaded as a plain Phaser spritesheet (frames read left-to-right, top-to-bottom +— a single horizontal row is simplest). **Exact size: a 256×64 PNG, 4 frames, +one 64×64 tile per wall type**, in `WolfensteinArt.WALL_COLORS` insertion +order: frame 0 = type 1 (stone), frame 1 = type 2 (wood), frame 2 = type 3 +(blue-tile), frame 3 = type 4 (green-tile). Doors (`DOOR_WALL_TYPE`, type 9) +are **not** part of this sheet at all — `WolfensteinView._drawDoorColumn` +(as of 2026-08-21) handles type 9 as a special case before the texture path +even runs, rendering the flat placeholder tan color (`WALL_COLORS[9]`) for +whatever fraction of the door hasn't yet slid open, and a flat dark "socket" +fill for the rest (see the sliding-door doc below) — so a 5th sheet frame +wouldn't currently be read even if added. Painting a real door texture means +teaching `_drawDoorColumn` to sample a frame the same way the wall path +does, not just adding a 320×64 sheet. + +### Sliding, recessed doors + +Doors animate open/closed (`WolfensteinLogic.js`'s `stepDoors`, `slide` field +0→1 over `DOOR_SLIDE_MS`) rather than popping instantly, and the cell stays +solid for *gameplay* (collision/LOS/bullets) through the entire animation — +it only becomes passable once `slide` reaches exactly 1 (see stepDoors' +comment for why). That's unrelated to the visual, though: doors also render +recessed to the middle of the wall's depth — real geometry, not a flat-color +trick — via `WolfensteinRaycaster.js`'s `intersectDoorMidplane`, which +`WolfensteinView`'s render-only raycast opts into with `castColumns(..., +true)` (the `doorAware` flag; every gameplay raycast in `WolfensteinLogic.js` +omits it and still sees a door as an ordinary flush full-cell solid, so this +never touches what's actually walkable/shootable). + +A door's blocking axis is inferred from geometry (which pair of neighbor +cells is walled off) rather than the `doors[]` `orientation` field, which +nothing in the codebase actually sets to anything but `'vertical'`. Given +that axis, the door plane sits at the cell's exact midpoint (e.g. `x = +doorCellX + 0.5` for a door blocking east-west travel) instead of at the +near face like a normal wall — a straight-on ray is `perpDist`-recessed by +0.5 relative to where a flush wall would sit. Critically, a ray whose +trajectory would clear the door cell sideways (cross into a neighboring +cell) *before* reaching that mid-plane isn't given a fake hit at all — +`castRay` just keeps stepping the DDA, so the ray goes on to hit the actual +flanking wall cell at *its own* true, unrecessed distance. That's what +produces the "H" shape: the door is the crossbar, visibly set back between +two verticals that are ordinary wall hits, not anything faked in +`_drawDoorColumn` — confirmed with a sweep of rays across a doorway (see +git history around 2026-08-21 for the script) showing a symmetric run of +door hits at the recessed distance, flanked on both sides by wall hits at +their own, nearer distances, with no discontinuity at the transition. + +`_drawDoorColumn` itself is now simple: `hit.perpDist`/`(x,y,w,h)` already +reflect the recessed mid-plane (computed by the caller exactly like any +other wall), so it only has to pick door-texture-tan vs. dark socket color +per column, using `hit.textureX` (now measured at the mid-plane, 0..1 along +the door's face) against `1 - slide` — the same left-to-right slide-open +wipe as before, just riding on correct geometry instead of a projection +trick. + +**Now wired up** (as of 2026-08-21): `WolfensteinView._drawWalls` reads +`hit.textureX` (the raycaster's fractional wall-face position) and +`drawImage`s a 1px-wide source-texel column, stretched to the destination +column's screen width, then darkens it with a `'multiply'`-composited grey +rect using the same side/fog factor the flat-color fallback uses — matches +the shading, just applied to a texture instead of a solid fill. Falls back +to the old flat-shaded `fillRect` per wall type whose frame isn't loaded +(`this.wallTexture` missing, or no `WALL_FRAME` entry for that type — true +for type 9/doors today), so art can land wall-type-by-wall-type. The texture +is grabbed once in the constructor (`scene.textures.get('wolfenstein-walls')`), +which the manifest loader has already populated by the time a game scene is +entered — no per-frame lookup cost. ## Guard enemy — `sheets.guard` -128×128 frames. Minimum for parity with the current placeholder: one idle -pose. For proper directional billboarding later: 8 facing angles × {idle, -walk (2-4 frames), shoot, die} — not required for the MVP renderer, which -only ever shows a single billboard sprite per enemy. +Per the artwork JSON: `frameWidth: 128, frameHeight: 128`. **Minimum useful +size: a single 128×128 PNG (1 frame)** — that's all the current renderer can +show. `WolfensteinView._drawSprites` calls `img.setTexture('wolf-guard')` +with no frame index, so Phaser always displays frame 0 (top-left) of +whatever sheet is loaded under that key; there is no facing/animation frame +selection logic anywhere in the code yet. Painting extra frames into the +sheet is harmless (they'll just sit unused) but won't animate or turn to +face the player until that selection logic is written. + +If/when directional billboarding is worth building, the natural layout to +target is a grid, frame index = `row * cols + col` in Phaser's row-major +order: **8 rows (one per 45° facing angle, starting from "facing camera" and +going clockwise) × N columns** for whatever animation set is wanted per +angle, e.g. `{idle, walk1, walk2, shoot, die1, die2}` (6 cols → a 768×1024 +sheet). That's a proposal, not a spec anything reads — pick a smaller set +(e.g. idle + walk + shoot only, 3 cols) if painting 48 frames is too much +up front, since nothing currently depends on the exact column count. ## Standalone images — `artwork[]` +These are loaded as plain images (no frame slicing), so any resolution +works, but matching the placeholder's proportions keeps in-world scale +consistent with `WolfensteinView`'s `s.scale` multipliers (`setDisplaySize` +is applied on top, so a differently-sized source image just gets stretched — +same aspect ratio is what matters most): + - `wolfenstein-item-pistol`, `wolfenstein-item-ammo`, `wolfenstein-item-health` - — pickup icons, square, transparent background. -- `wolfenstein-bullet` — small bright sprite for a pistol round in flight. -- `wolfenstein-muzzle` — muzzle-flash flash sprite (currently unused by the - scene beyond the painted placeholder texture; wire up on `weaponFired` - events once sfx/vfx pass lands). -- `wolfenstein-title` — optional main-menu backdrop art (the menu currently - renders a flat panel with title text, matching `TAScreens.js`'s style - before `ta-background` was painted). + — pickup icons. Placeholder is 64×64; square, transparent background. + Rendered at billboard scale 0.45 (`WolfensteinView._drawSprites`). +- `wolfenstein-bullet` — pistol round in flight. Placeholder is 16×16, a + small bright circle; rendered at scale 0.18, so keep it simple/legible at + tiny sizes. +- `wolfenstein-muzzle` — muzzle-flash sprite. Placeholder is 48×48. Not + currently spawned by the scene at all (no code creates a `wolf-muzzle` + image instance yet, painted or not) — wire it up on `weaponFired` events + once a sfx/vfx pass lands. +- `wolfenstein-title` — optional main-menu backdrop art. Not read anywhere + in `WolfensteinScreens.js` today; the menu renders a flat panel + title + text only (`backdrop()`'s `GAME_WIDTH`×`GAME_HEIGHT` rectangle, matching + `TAScreens.js`'s style before `ta-background` was painted). If added, + size it to the shared canvas, **1920×1080**, and code would need to draw + it behind that panel. ## Sound effects