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