// Procedural placeholder art — the TAArt.js "stand-in keyed by kind" idiom. // Everything here is generated Graphics -> texture, so the game renders with // zero art files. Swap in painted sprites via data/wolfenstein-artwork.json // (see assetManifest.js's wolfenstein entry) — nothing in WolfensteinView.js // needs to change when that happens; it just starts finding real textures // instead of falling back to these. // Type 9 is deliberately absent — reserved for DOOR_WALL_TYPE // (WolfensteinLogic.js), never a plain wall type, so every wall addition // here skips straight from 8 to 10. export const WALL_COLORS = { 1: 0x8a8a8a, // stone 2: 0x8a5a3a, // wood 3: 0x3a5a8a, // blue-tile 4: 0x5a8a3a, // green-tile 5: 0xa87c4a, // wood planks (plain vertical-plank timber, distinct from type 2's paneled wood) 6: 0x9cc2cf, // blue plaster (pale blue-gray plaster over a wood wainscot) 7: 0xb0aca4, // gray plaster (same plaster-over-wainscot as type 6, warm gray instead of blue) 8: 0xa8362a, // red brick 9: 0xb08040, // door (WolfensteinLogic.DOOR_WALL_TYPE) 10: 0xe8ddb8, // cream tile }; // Displayed size (px) of the live health-portrait Image in the HUD bar — // exported so WolfensteinGame's setDisplaySize can never drift out of sync // with the recessed "socket" baked into paintHudBar's wolf-hud-bar texture // below, which is sized around this same constant. export const HUD_PORTRAIT_SIZE = 118; // "Blue steel plate" HUD bar palette (see paintHudBar) — kept local to this // file rather than added to src/config.js's shared warm-gold COLORS, since // it's a deliberately distinct look scoped to this one bar, the same "own // literal, not worth the coupling" tradeoff KEY_COLORS already makes in // WolfensteinGame.js/WolfensteinEditor.js. const STEEL_LIGHT = 0x5d7fa3; // top of the plate's vertical gradient (overhead sheen) const STEEL_DARK = 0x1c2c3d; // bottom of the plate's vertical gradient const STEEL_MID = 0x3d5875; // divider-strip / bezel-ring body fill const STEEL_HILITE = 0x9fc2e0; // crisp top bevel edge, rivet highlight flecks, ring outlines const STEEL_SHADOW = 0x0b141d; // crisp bottom bevel edge, recess floors, rivet shade const RIVET_BODY = 0x24384a; export function ensureSprites(scene) { if (scene.textures.exists('wolf-guard')) return; paintGuard(scene); paintBullet(scene); paintMuzzleFlash(scene); paintObject(scene); paintPickup(scene); paintSparkle(scene); paintWeaponPistol(scene); paintWeaponShotgun(scene); paintWeaponMachinegun(scene); paintWeaponGatling(scene); paintWeaponPlasmarifle(scene); paintWeaponFists(scene); paintHudBar(scene); paintProfile(scene); } function paintGuard(scene) { const S = 128; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x2b3a2b, 1); g.fillRect(S * 0.28, S * 0.15, S * 0.44, S * 0.7); g.fillStyle(0xd8b48a, 1); g.fillCircle(S * 0.5, S * 0.14, S * 0.13); g.fillStyle(0x1a2a1a, 1); g.fillRect(S * 0.18, S * 0.4, S * 0.14, S * 0.32); g.generateTexture('wolf-guard', S, S); g.destroy(); } // One flat placeholder for every pickup frame (weapon/ammo/health alike), // same "single generic stand-in, real art per-frame lands later" idiom as // paintObject — a gold rounded square reads reasonably as "an item" for any // of the 11 pickups.png frames until each is painted for real. function paintPickup(scene) { const S = 64; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0xd4a017, 1); g.fillRoundedRect(S * 0.15, S * 0.15, S * 0.7, S * 0.7, 8); g.lineStyle(3, 0x000000, 0.4); g.strokeRoundedRect(S * 0.15, S * 0.15, S * 0.7, S * 0.7, 8); g.generateTexture('wolf-pickup', S, S); g.destroy(); } // A tiny 4-point star used as the twinkling glint drawn around pickups (see // WolfensteinView._drawPickupSparkles) — additive-blended, so only its // bright core/spikes matter; no outline needed since it's never seen against // a light background at full opacity the way wolf-pickup is. function paintSparkle(scene) { const S = 16, c = S / 2; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0xfff2a8, 1); g.fillCircle(c, c, S * 0.14); g.fillStyle(0xffffff, 0.85); g.fillTriangle(c, 0, c - S * 0.1, c, c + S * 0.1, c); g.fillTriangle(c, S, c - S * 0.1, c, c + S * 0.1, c); g.fillTriangle(0, c, c, c - S * 0.1, c, c + S * 0.1); g.fillTriangle(S, c, c, c - S * 0.1, c, c + S * 0.1); g.generateTexture('wolf-sparkle', S, S); g.destroy(); } // A flat solid dot read as a blob at any size. A real round has a dark // brass/copper body (so it reads against bright walls, not just dark ones), // a hot tracer core smaller than the body (implies motion/heat, not just a // colored ball), and a tiny off-center highlight for a touch of roundness — // three concentric circles instead of one, still cheap to generate. function paintBullet(scene) { const S = 12; const cx = S / 2, cy = S / 2; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x8a5a2a, 1); g.fillCircle(cx, cy, S * 0.42); g.lineStyle(1, 0x3a2410, 0.8); g.strokeCircle(cx, cy, S * 0.42); g.fillStyle(0xfff2a8, 1); g.fillCircle(cx, cy, S * 0.22); g.fillStyle(0xffffff, 0.9); g.fillCircle(cx - S * 0.08, cy - S * 0.08, S * 0.07); g.generateTexture('wolf-bullet', S, S); g.destroy(); } function paintMuzzleFlash(scene) { const S = 48; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0xfff2a8, 0.9); g.fillCircle(S / 2, S / 2, S / 2); g.generateTexture('wolf-muzzle', S, S); g.destroy(); } // Small bottom-anchored placeholder (not a full GAME_WIDTH x GAME_HEIGHT // canvas like the real weapon_pistol.png — see WolfensteinView's // hasRealWeaponArt branch, which anchors this one bottom-center instead of // at the origin). function paintWeaponPistol(scene) { const W = 360, H = 260; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x1a1a1a, 1); g.fillRect(W * 0.38, H * 0.05, W * 0.14, H * 0.55); g.fillStyle(0x3a3a3a, 1); g.fillRect(W * 0.28, H * 0.5, W * 0.34, H * 0.16); g.fillStyle(0x1a120a, 1); g.fillRect(W * 0.34, H * 0.64, W * 0.16, H * 0.32); g.generateTexture('wolf-weapon-pistol', W, H); g.destroy(); } // Same bottom-anchored placeholder convention as paintWeaponPistol, one // simple distinct silhouette per new weapon so they're at least tellable // apart before real art lands. function paintWeaponShotgun(scene) { const W = 360, H = 260; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x5a3a1a, 1); // wood stock g.fillRect(W * 0.3, H * 0.55, W * 0.4, H * 0.4); g.fillStyle(0x2a2a2a, 1); // long double barrel g.fillRect(W * 0.36, H * 0.02, W * 0.28, H * 0.58); g.generateTexture('wolf-weapon-shotgun', W, H); g.destroy(); } function paintWeaponMachinegun(scene) { const W = 360, H = 260; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x2a2a2a, 1); // wide body g.fillRect(W * 0.22, H * 0.15, W * 0.56, H * 0.4); g.fillStyle(0x1a1a1a, 1); // barrel g.fillRect(W * 0.4, H * 0.0, W * 0.16, H * 0.2); g.fillStyle(0x0f0f0f, 1); // magazine g.fillRect(W * 0.42, H * 0.55, W * 0.14, H * 0.42); g.generateTexture('wolf-weapon-machinegun', W, H); g.destroy(); } function paintWeaponGatling(scene) { const W = 360, H = 260; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x3a3a3a, 1); // housing g.fillRect(W * 0.28, H * 0.32, W * 0.44, H * 0.5); g.fillStyle(0x1a1a1a, 1); // ring of barrels const cx = W * 0.5, cy = H * 0.14, r = W * 0.16; for (let i = 0; i < 6; i++) { const a = (i / 6) * Math.PI * 2; g.fillCircle(cx + Math.cos(a) * r * 0.5, cy + Math.sin(a) * r * 0.5, r * 0.16); } g.generateTexture('wolf-weapon-gatling', W, H); g.destroy(); } function paintWeaponPlasmarifle(scene) { const W = 360, H = 260; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x2a2a3a, 1); // body g.fillRect(W * 0.3, H * 0.3, W * 0.4, H * 0.55); g.fillStyle(0x3ad8ff, 0.85); // glowing plasma core g.fillRoundedRect(W * 0.36, H * 0.05, W * 0.28, H * 0.3, 8); g.generateTexture('wolf-weapon-plasmarifle', W, H); g.destroy(); } // Fists' one and only placeholder — unlike every projectile weapon above, // melee has no separate "hit" texture here: WolfensteinView only swaps to // the real weapon_fists_hit.png art on a swing (see _drawWeapon's // FISTS_HIT_MS window), and falls back to this same idle silhouette the // rest of the time if that real art isn't loaded, rather than inventing a // synthetic punch pose nobody asked for. function paintWeaponFists(scene) { const W = 360, H = 260; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0xd8b48a, 1); // skin tone, matches paintGuard's face color g.fillRoundedRect(W * 0.08, H * 0.15, W * 0.22, H * 0.5, 10); g.fillRoundedRect(W * 0.7, H * 0.15, W * 0.22, H * 0.5, 10); g.generateTexture('wolf-weapon-fists', W, H); g.destroy(); } // One flat placeholder for every object frame (same "single generic // stand-in, real art per-frame lands later" idiom as wolf-guard) — a // rounded block reads reasonably as "a solid obstacle" regardless of which // actual prop (bush, statue, ...) a given frame turns out to be. function paintObject(scene) { const S = 64; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x3a6b3a, 1); g.fillRoundedRect(S * 0.12, S * 0.1, S * 0.76, S * 0.85, 6); g.lineStyle(3, 0x1f3a1f, 0.8); g.strokeRoundedRect(S * 0.12, S * 0.1, S * 0.76, S * 0.85, 6); g.generateTexture('wolf-object', S, S); g.destroy(); } /** A dark base + body-colored disc + a small offset highlight fleck — reused at every rivet position across the bar (divider strips, portrait bezel corners, bookend corners). */ function drawRivet(g, x, y, r = 4) { g.fillStyle(STEEL_SHADOW, 0.9); g.fillCircle(x, y, r); g.fillStyle(RIVET_BODY, 1); g.fillCircle(x, y, r * 0.75); g.fillStyle(STEEL_HILITE, 0.8); g.fillCircle(x - r * 0.25, y - r * 0.25, r * 0.25); } /** * The bottom HUD bar's background — final art in its own right (like * paintSparkle/paintBullet), not a placeholder awaiting a real PNG. A * "blue steel plate" styled after the original Wolfenstein 3D's status * bar: a vertical steel gradient base, crisp top-highlight/bottom-shadow * bevel edges (raised-plate look), two riveted divider strips splitting it * into left-info / center-portrait / right-info sub-panels, a recessed * "readout" backing behind each side's text rows, and a recessed bezel * "socket" at center sized around HUD_PORTRAIT_SIZE for the live health * portrait (WolfensteinGame._buildHud) to sit in. All measurements here are * local canvas coordinates (0,0 top-left of this 1920x140 bake) — the * caller places the resulting image at its natural size centered on the * bar's actual screen position, so local (x,y) maps 1:1 to screen * (x, y + 940). */ function paintHudBar(scene) { const W = 1920, H = 140; const g = scene.make.graphics({ x: 0, y: 0, add: false }); // Base plate: light at top (overhead sheen) to dark at bottom. g.fillGradientStyle(STEEL_LIGHT, STEEL_LIGHT, STEEL_DARK, STEEL_DARK, 1); g.fillRect(0, 0, W, H); // Two faint diagonal specular sheens across the upper half — a cheap // brushed-metal touch, not meant to read as a distinct shape on its own. g.fillStyle(0xffffff, 0.05); g.fillTriangle(0, 0, W * 0.4, 0, W * 0.15, H * 0.5); g.fillTriangle(W * 0.55, 0, W, 0, W * 0.85, H * 0.4); // Crisp top-highlight / bottom-shadow bevel edges. g.fillStyle(STEEL_HILITE, 0.9); g.fillRect(0, 0, W, 3); g.fillStyle(STEEL_SHADOW, 0.9); g.fillRect(0, H - 3, W, 3); // Recessed "readout" backing behind each side's text rows — separates the // text zones from the raised bevel/rivet zones, reads as a sunken // instrument panel rather than text just floating on the bare plate. const backingY = 10, backingH = H - 20; g.fillStyle(STEEL_SHADOW, 0.35); g.fillRoundedRect(56, backingY, 846 - 56, backingH, 8); g.fillRoundedRect(1074, backingY, 1864 - 1074, backingH, 8); g.lineStyle(1, STEEL_HILITE, 0.25); g.strokeRoundedRect(56, backingY, 846 - 56, backingH, 8); g.strokeRoundedRect(1074, backingY, 1864 - 1074, backingH, 8); // Divider strips separating left / portrait / right sub-panels, each a // raised bar with its own light/dark side bevels and two rivets. for (const dx of [856, 1044]) { g.fillStyle(STEEL_MID, 1); g.fillRect(dx, 0, 20, H); g.fillStyle(STEEL_HILITE, 0.7); g.fillRect(dx, 0, 2, H); g.fillStyle(STEEL_SHADOW, 0.7); g.fillRect(dx + 18, 0, 2, H); drawRivet(g, dx + 10, 26); drawRivet(g, dx + 10, H - 26); } // Portrait bezel: a raised outer ring (4 corner rivets) around a // recessed inner socket exactly HUD_PORTRAIT_SIZE square, centered. const bw = 136, bh = 136, bx = W / 2 - bw / 2, by = 2; g.fillStyle(STEEL_MID, 1); g.fillRoundedRect(bx, by, bw, bh, 12); g.lineStyle(2, STEEL_HILITE, 0.8); g.strokeRoundedRect(bx, by, bw, bh, 12); const margin = (bw - HUD_PORTRAIT_SIZE) / 2; const sx = bx + margin, sy = by + margin; g.fillStyle(STEEL_SHADOW, 0.85); g.fillRoundedRect(sx, sy, HUD_PORTRAIT_SIZE, HUD_PORTRAIT_SIZE, 6); g.lineStyle(2, STEEL_SHADOW, 1); g.strokeRoundedRect(sx, sy, HUD_PORTRAIT_SIZE, HUD_PORTRAIT_SIZE, 6); drawRivet(g, bx + 8, by + 12); drawRivet(g, bx + bw - 8, by + 12); drawRivet(g, bx + 8, by + bh - 12); drawRivet(g, bx + bw - 8, by + bh - 12); // Bookend rivets near the far left/right edges, slightly larger — gives // the whole plate a structural, riveted-steel-panel finish. drawRivet(g, 34, 22, 5); drawRivet(g, 34, H - 22, 5); drawRivet(g, W - 34, 22, 5); drawRivet(g, W - 34, H - 22, 5); g.generateTexture('wolf-hud-bar', W, H); g.destroy(); } // Single-frame fallback face if wolfenstein-profile isn't loaded — same // "one generic placeholder regardless of variant" idiom as paintPickup/ // paintObject, since a static fallback has no health value to react to // anyway. WolfensteinGame._updateHud skips the per-health frame swap // entirely when this is what's showing (see this._profileHasFrames). function paintProfile(scene) { const S = 128; const g = scene.make.graphics({ x: 0, y: 0, add: false }); g.fillStyle(0x1a2230, 1); g.fillRect(0, 0, S, S); g.fillStyle(0x5a4632, 1); // hair g.fillRoundedRect(S * 0.2, S * 0.12, S * 0.6, S * 0.24, 8); g.fillStyle(0xd8b48a, 1); // skin tone, matches paintGuard's face color g.fillCircle(S * 0.5, S * 0.56, S * 0.3); g.generateTexture('wolf-profile', S, S); g.destroy(); }