diff --git a/assets/gamedata/wolfenstein/level-e1m1.json b/assets/gamedata/wolfenstein/level-e1m1.json index c610450..65956f1 100644 --- a/assets/gamedata/wolfenstein/level-e1m1.json +++ b/assets/gamedata/wolfenstein/level-e1m1.json @@ -4,7 +4,7 @@ "name": "First Blood", "campaignId": null, "missionIndex": 0, - "width": 19, + "width": 20, "height": 21, "cellSize": 64, "walls": [ @@ -27,6 +27,7 @@ 1, 1, 1, + 1, 1 ], [ @@ -48,6 +49,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -69,6 +71,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -90,6 +93,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -111,6 +115,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -132,6 +137,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -153,6 +159,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -174,6 +181,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -195,6 +203,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -216,6 +225,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -237,6 +247,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -258,6 +269,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -279,6 +291,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -300,6 +313,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -321,6 +335,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -342,6 +357,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -363,6 +379,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -384,6 +401,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -405,6 +423,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -426,6 +445,7 @@ 0, 0, 0, + 1, 1 ], [ @@ -447,6 +467,7 @@ 1, 1, 1, + 1, 1 ] ], @@ -544,5 +565,37 @@ "y": 15, "frame": 1 } + ], + "objects": [ + { + "x": 8, + "y": 17, + "frame": 1 + }, + { + "x": 8, + "y": 19, + "frame": 1 + }, + { + "x": 12, + "y": 17, + "frame": 1 + }, + { + "x": 12, + "y": 19, + "frame": 1 + }, + { + "x": 8, + "y": 9, + "frame": 0 + }, + { + "x": 6, + "y": 9, + "frame": 0 + } ] } diff --git a/assets/images/wolfenstein/objects.png b/assets/images/wolfenstein/objects.png new file mode 100644 index 0000000..7e20ca1 Binary files /dev/null and b/assets/images/wolfenstein/objects.png differ diff --git a/assets/images/wolfenstein/objects.psd b/assets/images/wolfenstein/objects.psd new file mode 100644 index 0000000..c7ff169 Binary files /dev/null and b/assets/images/wolfenstein/objects.psd differ diff --git a/assets/images/wolfenstein/pickups.png b/assets/images/wolfenstein/pickups.png new file mode 100644 index 0000000..705ffbc Binary files /dev/null and b/assets/images/wolfenstein/pickups.png differ diff --git a/assets/images/wolfenstein/pickups.psd b/assets/images/wolfenstein/pickups.psd new file mode 100644 index 0000000..0aef2cc Binary files /dev/null and b/assets/images/wolfenstein/pickups.psd differ diff --git a/data/wolfenstein-artwork.json b/data/wolfenstein-artwork.json index 88a996d..9be42df 100644 --- a/data/wolfenstein-artwork.json +++ b/data/wolfenstein-artwork.json @@ -4,7 +4,8 @@ "walls": { "key": "wolfenstein-walls", "path": "assets/images/wolfenstein/walls.png", "frameWidth": 64, "frameHeight": 64 }, "guard": { "key": "wolfenstein-guard-sheet", "path": "assets/images/wolfenstein/enemies.png", "frameWidth": 128, "frameHeight": 128 }, "doors": { "key": "wolfenstein-doors", "path": "assets/images/wolfenstein/doors.png", "frameWidth": 64, "frameHeight": 64 }, - "wallArt": { "key": "wolfenstein-wall-art", "path": "assets/images/wolfenstein/wall-art.png", "frameWidth": 64, "frameHeight": 64 } + "wallArt": { "key": "wolfenstein-wall-art", "path": "assets/images/wolfenstein/wall-art.png", "frameWidth": 64, "frameHeight": 64 }, + "objects": { "key": "wolfenstein-objects", "path": "assets/images/wolfenstein/objects.png", "frameWidth": 64, "frameHeight": 64 } }, "artwork": [ { "key": "wolfenstein-item-pistol", "path": null }, diff --git a/data/wolfenstein-objects.json b/data/wolfenstein-objects.json new file mode 100644 index 0000000..661e174 --- /dev/null +++ b/data/wolfenstein-objects.json @@ -0,0 +1,7 @@ +{ + "_readme": "Frame registry for the objects decal sheet (assets/images/wolfenstein/objects.png, sheets.objects in wolfenstein-artwork.json, 64x64 frames, transparent PNGs). Objects are solid props placed on open floor in the editor — they block movement but not sight/bullets, and can't be picked up. To add a new object: paint the next 64x64 tile into objects.png, then append an entry here — WolfensteinEditor.js's Object dropdown reads this file directly, no code changes needed.", + "frames": [ + { "frame": 0, "id": "tall-bush", "name": "Tall Bush" }, + { "frame": 1, "id": "gold-eagle", "name": "Gold Eagle" } + ] +} diff --git a/src/games/wolfenstein/WolfensteinArt.js b/src/games/wolfenstein/WolfensteinArt.js index 21e1427..23343aa 100644 --- a/src/games/wolfenstein/WolfensteinArt.js +++ b/src/games/wolfenstein/WolfensteinArt.js @@ -22,6 +22,7 @@ export function ensureSprites(scene) { paintBullet(scene); paintMuzzleFlash(scene); paintWeaponPistol(scene); + paintObject(scene); } function paintGuard(scene) { @@ -94,3 +95,18 @@ function paintWeaponPistol(scene) { g.generateTexture('wolf-weapon-pistol', 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(); +} diff --git a/src/games/wolfenstein/WolfensteinEditor.js b/src/games/wolfenstein/WolfensteinEditor.js index 0cc5fad..8453ab2 100644 --- a/src/games/wolfenstein/WolfensteinEditor.js +++ b/src/games/wolfenstein/WolfensteinEditor.js @@ -63,6 +63,7 @@ const FACING_DIRS = { // applyTool()/WALL_TOOLS is either the selected option's id, or — for a // dropdown-less category — the category's own id. const WALLART_PREFIX = 'wallart:'; +const OBJECT_PREFIX = 'object:'; const CATEGORIES = [ { id: 'wall', label: 'Wall', options: [ @@ -81,6 +82,11 @@ const CATEGORIES = [ { id: 'door', label: 'Door', options: [ { id: 'door', label: 'Normal' }, ] }, + // Same dynamic-dropdown treatment as Wall Art (populated from + // data/wolfenstein-objects.json, see refreshObjectOptions), except an + // object is placed on open FLOOR (walls[y][x] === 0) rather than an + // existing wall — the exact complementary requirement to Wall Art's. + { id: 'object', label: 'Object', options: [] }, { id: 'pickup', label: 'Pickup', options: [ { id: 'health', label: 'Health' }, { id: 'ammo', label: 'Ammo' }, @@ -150,6 +156,13 @@ export default class WolfensteinEditor extends Phaser.Scene { this.refreshWallArtOptions(); }).catch(() => {}); + this.objectFrames = []; + fetch('data/wolfenstein-objects.json').then((r) => r.json()) + .then((d) => { + this.objectFrames = d.frames ?? []; + this.refreshObjectOptions(); + }).catch(() => {}); + this.buildToolbar(); this.buildLoadPanel(); this.buildMinimap(); @@ -169,7 +182,7 @@ export default class WolfensteinEditor extends Phaser.Scene { campaignId: null, missionIndex: 0, width: W, height: H, cellSize: 64, walls, playerStart: { x: 1.5, y: 1.5, angle: 0 }, - doors: [], enemies: [], items: [], wallArt: [], + doors: [], enemies: [], items: [], wallArt: [], objects: [], exit: { x: W - 1.5, y: H - 1.5, radius: 0.6 }, briefing: [], }; @@ -334,6 +347,7 @@ export default class WolfensteinEditor extends Phaser.Scene { enemies: this.level.enemies.filter((e) => e.x < w - 1 && e.y < h - 1), items: this.level.items.filter((it) => it.x < w - 1 && it.y < h - 1), wallArt: (this.level.wallArt ?? []).filter((wa) => wa.x < w - 1 && wa.y < h - 1), + objects: (this.level.objects ?? []).filter((o) => o.x < w - 1 && o.y < h - 1), 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, }; @@ -402,6 +416,13 @@ export default class WolfensteinEditor extends Phaser.Scene { select.innerHTML = this.wallArtFrames.map((f) => ``).join(''); } + /** Patches the Object category's