351 lines
14 KiB
JavaScript
351 lines
14 KiB
JavaScript
// 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();
|
|
}
|