diff --git a/assets/gamedata/wolfenstein/level-e1m1.json b/assets/gamedata/wolfenstein/level-e1m1.json index 65956f1..ab79517 100644 --- a/assets/gamedata/wolfenstein/level-e1m1.json +++ b/assets/gamedata/wolfenstein/level-e1m1.json @@ -527,7 +527,28 @@ "patrol": [] } ], - "items": [], + "items": [ + { + "type": "ammo-clip", + "x": 1.5, + "y": 8.5 + }, + { + "type": "ammo-clip", + "x": 10.5, + "y": 13.5 + }, + { + "type": "health", + "x": 1.5, + "y": 9.5 + }, + { + "type": "health", + "x": 10.5, + "y": 12.5 + } + ], "exit": { "x": 13.5, "y": 18.5, @@ -568,34 +589,34 @@ ], "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, + "x": 6.5, + "y": 9.5, "frame": 0 }, { - "x": 6, - "y": 9, + "x": 8.5, + "y": 9.5, "frame": 0 + }, + { + "x": 8.5, + "y": 17.5, + "frame": 1 + }, + { + "x": 8.5, + "y": 19.5, + "frame": 1 + }, + { + "x": 12.5, + "y": 17.5, + "frame": 1 + }, + { + "x": 12.5, + "y": 19.5, + "frame": 1 } ] } diff --git a/assets/images/wolfenstein/doors.png b/assets/images/wolfenstein/doors.png index ebb855e..62909e0 100644 Binary files a/assets/images/wolfenstein/doors.png and b/assets/images/wolfenstein/doors.png differ diff --git a/assets/images/wolfenstein/doors.psd b/assets/images/wolfenstein/doors.psd index cbbf73d..0343bc2 100644 Binary files a/assets/images/wolfenstein/doors.psd and b/assets/images/wolfenstein/doors.psd differ diff --git a/assets/images/wolfenstein/pickups.png b/assets/images/wolfenstein/pickups.png index 705ffbc..a7b4ee3 100644 Binary files a/assets/images/wolfenstein/pickups.png and b/assets/images/wolfenstein/pickups.png differ diff --git a/assets/images/wolfenstein/pickups.psd b/assets/images/wolfenstein/pickups.psd index 0aef2cc..fe38f6a 100644 Binary files a/assets/images/wolfenstein/pickups.psd and b/assets/images/wolfenstein/pickups.psd differ diff --git a/assets/images/wolfenstein/weapon_gattling.png b/assets/images/wolfenstein/weapon_gatling.png similarity index 100% rename from assets/images/wolfenstein/weapon_gattling.png rename to assets/images/wolfenstein/weapon_gatling.png diff --git a/data/wolfenstein-artwork.json b/data/wolfenstein-artwork.json index 9be42df..1aa5c88 100644 --- a/data/wolfenstein-artwork.json +++ b/data/wolfenstein-artwork.json @@ -5,15 +5,17 @@ "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 }, - "objects": { "key": "wolfenstein-objects", "path": "assets/images/wolfenstein/objects.png", "frameWidth": 64, "frameHeight": 64 } + "objects": { "key": "wolfenstein-objects", "path": "assets/images/wolfenstein/objects.png", "frameWidth": 64, "frameHeight": 64 }, + "pickups": { "key": "wolfenstein-pickups", "path": "assets/images/wolfenstein/pickups.png", "frameWidth": 64, "frameHeight": 64 } }, "artwork": [ - { "key": "wolfenstein-item-pistol", "path": null }, - { "key": "wolfenstein-item-ammo", "path": null }, - { "key": "wolfenstein-item-health", "path": null }, { "key": "wolfenstein-bullet", "path": null }, { "key": "wolfenstein-muzzle", "path": null }, { "key": "wolfenstein-title", "path": null }, - { "key": "wolfenstein-weapon-pistol", "path": "assets/images/wolfenstein/weapon_pistol.png" } + { "key": "wolfenstein-weapon-pistol", "path": "assets/images/wolfenstein/weapon_pistol.png" }, + { "key": "wolfenstein-weapon-shotgun", "path": "assets/images/wolfenstein/weapon_shotgun.png" }, + { "key": "wolfenstein-weapon-machinegun", "path": "assets/images/wolfenstein/weapon_machinegun.png" }, + { "key": "wolfenstein-weapon-gatling", "path": "assets/images/wolfenstein/weapon_gatling.png" }, + { "key": "wolfenstein-weapon-plasmarifle", "path": "assets/images/wolfenstein/weapon_plasma.png" } ] } diff --git a/data/wolfenstein-rules.json b/data/wolfenstein-rules.json index dc37b02..394f4d8 100644 --- a/data/wolfenstein-rules.json +++ b/data/wolfenstein-rules.json @@ -12,18 +12,32 @@ "projectileCap": 64 }, "ammoTypes": [ - { "id": "9mm", "name": "9mm Rounds", "damageMin": 8, "damageMax": 12 } + { "id": "9mm", "name": "9mm Rounds", "damageMin": 8, "damageMax": 12, "maxAmmo": 200 }, + { "id": "shells", "name": "Shotgun Shells", "damageMin": 40, "damageMax": 60, "maxAmmo": 40 }, + { "id": "plasma", "name": "Plasma Cells", "damageMin": 16, "damageMax": 24, "maxAmmo": 150 } ], "weapons": [ { "id": "fists", "name": "Fists", "kind": "melee", "damage": 15, "range": 0.9, "arcDeg": 100, "cooldownMs": 500, "fireMode": "auto" }, - { "id": "pistol", "name": "Pistol", "kind": "projectile", "ammoType": "9mm", "fireMode": "semi", "speed": 11, "cooldownMs": 600, "ammoCost": 1, "maxAmmo": 50, "startAmmo": 8, "hitRadius": 0.18, "ttlSec": 3 } + { "id": "pistol", "name": "Pistol", "kind": "projectile", "ammoType": "9mm", "fireMode": "semi", "speed": 11, "cooldownMs": 600, "ammoCost": 1, "startAmmo": 8, "hitRadius": 0.18, "ttlSec": 3 }, + { "id": "shotgun", "name": "Shotgun", "kind": "projectile", "ammoType": "shells", "fireMode": "semi", "speed": 10, "cooldownMs": 1200, "ammoCost": 1, "hitRadius": 0.22, "ttlSec": 3 }, + { "id": "machinegun", "name": "Machine Gun", "kind": "projectile", "ammoType": "9mm", "fireMode": "auto", "speed": 11, "cooldownMs": 333, "ammoCost": 1, "hitRadius": 0.18, "ttlSec": 3 }, + { "id": "gatling", "name": "Gatling Gun", "kind": "projectile", "ammoType": "9mm", "fireMode": "auto", "speed": 11, "cooldownMs": 167, "ammoCost": 1, "hitRadius": 0.18, "ttlSec": 3 }, + { "id": "plasmarifle", "name": "Plasma Rifle", "kind": "projectile", "ammoType": "plasma", "fireMode": "burst", "burstCount": 8, "burstIntervalMs": 125, "speed": 14, "ammoCost": 1, "hitRadius": 0.2, "ttlSec": 3 } ], "enemies": [ { "id": "guard", "name": "Guard", "health": 20, "stunMs": 200, "speed": 2.0, "radius": 0.35, "detectRange": 8, "meleeRange": 0.9, "meleeDamage": 8, "meleeCooldownMs": 800, "fireRange": 7, "rangedWeapon": "pistol" } ], "items": [ - { "id": "pistol", "name": "Pistol", "kind": "weapon", "grantsWeapon": "pistol", "ammo": 6 }, - { "id": "ammo", "name": "Ammo Clip", "kind": "ammo", "amount": 8 }, - { "id": "health", "name": "Medkit", "kind": "health", "amount": 25 } + { "id": "pistol", "name": "Pistol", "kind": "weapon", "grantsWeapon": "pistol", "ammo": 6, "frame": 0 }, + { "id": "shotgun", "name": "Shotgun", "kind": "weapon", "grantsWeapon": "shotgun", "ammo": 5, "frame": 1 }, + { "id": "machinegun", "name": "Machine Gun", "kind": "weapon", "grantsWeapon": "machinegun", "ammo": 20, "frame": 2 }, + { "id": "gatling", "name": "Gatling Gun", "kind": "weapon", "grantsWeapon": "gatling", "ammo": 30, "frame": 3 }, + { "id": "plasmarifle", "name": "Plasma Rifle", "kind": "weapon", "grantsWeapon": "plasmarifle", "ammo": 16, "frame": 4 }, + { "id": "ammo-clip", "name": "Pistol Clip", "kind": "ammo", "ammoType": "9mm", "amount": 10, "frame": 5 }, + { "id": "ammo", "name": "Ammo Box", "kind": "ammo", "ammoType": "9mm", "amount": 50, "frame": 6 }, + { "id": "shotgun-shells", "name": "Shotgun Shells", "kind": "ammo", "ammoType": "shells", "amount": 5, "frame": 7 }, + { "id": "plasma-cell", "name": "Plasma Cell", "kind": "ammo", "ammoType": "plasma", "amount": 30, "frame": 8 }, + { "id": "health", "name": "Small Medpack", "kind": "health", "amount": 25, "frame": 9 }, + { "id": "health-large", "name": "Large Medpack", "kind": "health", "amount": 50, "frame": 10 } ] } diff --git a/src/games/wolfenstein/WolfensteinArt.js b/src/games/wolfenstein/WolfensteinArt.js index 23343aa..83f1271 100644 --- a/src/games/wolfenstein/WolfensteinArt.js +++ b/src/games/wolfenstein/WolfensteinArt.js @@ -16,13 +16,16 @@ export const WALL_COLORS = { export function ensureSprites(scene) { if (scene.textures.exists('wolf-guard')) return; paintGuard(scene); - paintItem(scene, 'wolf-item-pistol', 0xd8d8d8); - paintItem(scene, 'wolf-item-ammo', 0xd4a017); - paintItem(scene, 'wolf-item-health', 0xe06c75); paintBullet(scene); paintMuzzleFlash(scene); - paintWeaponPistol(scene); paintObject(scene); + paintPickup(scene); + paintSparkle(scene); + paintWeaponPistol(scene); + paintWeaponShotgun(scene); + paintWeaponMachinegun(scene); + paintWeaponGatling(scene); + paintWeaponPlasmarifle(scene); } function paintGuard(scene) { @@ -38,14 +41,36 @@ function paintGuard(scene) { g.destroy(); } -function paintItem(scene, key, color) { +// 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(color, 1); + 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(key, S, S); + 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(); } @@ -96,6 +121,59 @@ function paintWeaponPistol(scene) { 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(); +} + // 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 diff --git a/src/games/wolfenstein/WolfensteinEditor.js b/src/games/wolfenstein/WolfensteinEditor.js index 8453ab2..df07a7e 100644 --- a/src/games/wolfenstein/WolfensteinEditor.js +++ b/src/games/wolfenstein/WolfensteinEditor.js @@ -64,6 +64,7 @@ const FACING_DIRS = { // dropdown-less category — the category's own id. const WALLART_PREFIX = 'wallart:'; const OBJECT_PREFIX = 'object:'; +const PICKUP_PREFIX = 'pickup:'; const CATEGORIES = [ { id: 'wall', label: 'Wall', options: [ @@ -87,10 +88,15 @@ const CATEGORIES = [ // 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' }, - ] }, + // Same dynamic-dropdown treatment as Wall Art/Object, except populated + // from wolfenstein-rules.json's `items` array (see refreshPickupOptions) + // since a pickup needs kind-specific gameplay fields (health amount, ammo + // type, granted weapon...), not just a cosmetic frame — rules.json is + // already the one source of truth for that, `frame` is just one more + // field on each entry. Placement (applyPickupTool) bulldozes onto floor + // like Door/Enemy/Start/Exit, unlike Object/Wall Art's stricter + // floor-only/wall-only requirements. + { id: 'pickup', label: 'Pickup', options: [] }, { id: 'enemy', label: 'Enemy', options: [ { id: 'enemy', label: 'Guard' }, ] }, @@ -163,6 +169,13 @@ export default class WolfensteinEditor extends Phaser.Scene { this.refreshObjectOptions(); }).catch(() => {}); + this.pickupItems = []; + fetch('data/wolfenstein-rules.json').then((r) => r.json()) + .then((d) => { + this.pickupItems = d.items ?? []; + this.refreshPickupOptions(); + }).catch(() => {}); + this.buildToolbar(); this.buildLoadPanel(); this.buildMinimap(); @@ -423,6 +436,13 @@ export default class WolfensteinEditor extends Phaser.Scene { select.innerHTML = this.objectFrames.map((f) => ``).join(''); } + /** Patches the Pickup category's