Update ranged weapon quickslot text to match stackable items

This commit is contained in:
Peter Stockings
2026-01-21 14:00:55 +11:00
parent 516bf6e3c9
commit 219c1c8899
3 changed files with 144 additions and 29 deletions

View File

@@ -9,7 +9,9 @@ export const ITEMS: Record<string, Item> = {
spriteIndex: 57,
stats: {
hp: 5
}
},
stackable: true,
quantity: 1
},
"iron_sword": {
id: "iron_sword",

View File

@@ -0,0 +1,113 @@
import { describe, it, expect, beforeEach } from "vitest";
import { ItemManager } from "../../scenes/systems/ItemManager";
import type { World, CombatantActor, Item } from "../../core/types";
import { EntityManager } from "../../engine/EntityManager";
describe("ItemManager - Stacking Logic", () => {
let itemManager: ItemManager;
let entityManager: EntityManager;
let world: World;
let player: CombatantActor;
beforeEach(() => {
world = {
width: 10,
height: 10,
tiles: [],
actors: new Map(),
exit: { x: 9, y: 9 }
} as any;
entityManager = new EntityManager(world);
itemManager = new ItemManager(world, entityManager);
player = {
id: 0,
pos: { x: 1, y: 1 },
category: "combatant",
isPlayer: true,
type: "player",
inventory: { gold: 0, items: [] },
stats: {} as any,
equipment: {} as any,
speed: 1,
energy: 0
};
world.actors.set(0, player);
});
it("should stack stackable items when picked up", () => {
const potion: Item = {
id: "potion",
name: "Potion",
type: "Consumable",
textureKey: "items",
spriteIndex: 0,
stackable: true,
quantity: 1
};
// First potion
itemManager.spawnItem(potion, { x: 1, y: 1 });
itemManager.tryPickup(player);
expect(player.inventory!.items.length).toBe(1);
expect(player.inventory!.items[0].quantity).toBe(1);
// Second potion
itemManager.spawnItem(potion, { x: 1, y: 1 });
itemManager.tryPickup(player);
expect(player.inventory!.items.length).toBe(1);
expect(player.inventory!.items[0].quantity).toBe(2);
});
it("should NOT stack non-stackable items", () => {
const sword: Item = {
id: "sword",
name: "Sword",
type: "Weapon",
weaponType: "melee",
textureKey: "items",
spriteIndex: 1,
stackable: false,
stats: { attack: 1 }
} as any;
// First sword
itemManager.spawnItem(sword, { x: 1, y: 1 });
itemManager.tryPickup(player);
expect(player.inventory!.items.length).toBe(1);
// Second sword
itemManager.spawnItem(sword, { x: 1, y: 1 });
itemManager.tryPickup(player);
expect(player.inventory!.items.length).toBe(2);
});
it("should sum quantities of stackable items correctly", () => {
const ammo: Item = {
id: "ammo",
name: "Ammo",
type: "Ammo",
textureKey: "items",
spriteIndex: 2,
stackable: true,
quantity: 10,
ammoType: "9mm"
};
itemManager.spawnItem(ammo, { x: 1, y: 1 });
itemManager.tryPickup(player);
expect(player.inventory!.items[0].quantity).toBe(10);
const moreAmmo = { ...ammo, quantity: 5 };
itemManager.spawnItem(moreAmmo, { x: 1, y: 1 });
itemManager.tryPickup(player);
expect(player.inventory!.items[0].quantity).toBe(15);
});
});

View File

@@ -108,37 +108,37 @@ export class QuickSlotComponent {
}
bgGraphics.strokeRect(0, 0, slotSize, slotSize);
if (foundItem) {
const texture = foundItem.textureKey ?? "items";
const sprite = this.scene.add.sprite(slotSize / 2, slotSize / 2, texture, foundItem.spriteIndex);
// PD items are 16x16, slot is 48x48. Scale up slightly
sprite.setScale(2.5);
slot.add(sprite);
if (foundItem) {
const texture = foundItem.textureKey ?? "items";
const sprite = this.scene.add.sprite(slotSize / 2, slotSize / 2, texture, foundItem.spriteIndex);
// PD items are 16x16, slot is 48x48. Scale up slightly
sprite.setScale(2.5);
slot.add(sprite);
// Add count in bottom-right corner (white with x prefix)
const count = player.inventory.items.filter(it => it.id === desiredId).length;
if (count > 1) {
const countText = this.scene.add.text(slotSize - 3, slotSize - 3, `x${count}`, {
fontSize: "11px",
color: "#ffffff",
fontStyle: "bold"
}).setOrigin(1, 1);
slot.add(countText);
}
// Unified Label (Bottom-Right)
let labelText = "";
if (foundItem.stackable) {
// Sum quantities for stackable items
const totalQuantity = player.inventory.items
.filter(it => it.id === desiredId)
.reduce((sum, it) => sum + (it.quantity || 1), 0);
labelText = `x${totalQuantity}`;
} else if (foundItem.type === "Weapon" && foundItem.weaponType === "ranged" && foundItem.stats) {
// Show ammo for non-stackable ranged weapons
labelText = `${foundItem.stats.currentAmmo}/${foundItem.stats.magazineSize}`;
}
// Add Ammo Counter for Ranged Weapons (Top-Right)
if (foundItem.type === "Weapon" && foundItem.weaponType === "ranged" && foundItem.stats) {
const ammoText = `${foundItem.stats.currentAmmo}/${foundItem.stats.magazineSize}`;
const ammoDisplay = this.scene.add.text(slotSize - 2, 2, ammoText, {
fontSize: "10px",
color: "#00FF00", // Green text
fontStyle: "bold",
stroke: "#000000",
strokeThickness: 2
}).setOrigin(1, 0); // Top-right anchor
slot.add(ammoDisplay);
if (labelText) {
const display = this.scene.add.text(slotSize - 3, slotSize - 3, labelText, {
fontSize: "11px",
color: "#ffffff",
fontStyle: "bold",
stroke: "#000000",
strokeThickness: 2
}).setOrigin(1, 1);
slot.add(display);
}
}
}
} else {
this.itemMap[i] = null;
// Reset bg