feat: add upgrade scrolls
This commit is contained in:
@@ -26,6 +26,12 @@ export const CONSUMABLES = {
|
|||||||
throwable: true,
|
throwable: true,
|
||||||
stackable: true,
|
stackable: true,
|
||||||
},
|
},
|
||||||
|
upgrade_scroll: {
|
||||||
|
name: "Upgrade Scroll",
|
||||||
|
textureKey: "items",
|
||||||
|
spriteIndex: 79,
|
||||||
|
stackable: true,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const RANGED_WEAPONS = {
|
export const RANGED_WEAPONS = {
|
||||||
@@ -224,6 +230,19 @@ export function createArmour(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createUpgradeScroll(quantity = 1): ConsumableItem {
|
||||||
|
const t = CONSUMABLES["upgrade_scroll"];
|
||||||
|
return {
|
||||||
|
id: "upgrade_scroll",
|
||||||
|
name: t.name,
|
||||||
|
type: "Consumable",
|
||||||
|
textureKey: t.textureKey,
|
||||||
|
spriteIndex: t.spriteIndex,
|
||||||
|
stackable: true,
|
||||||
|
quantity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy export for backward compatibility during migration
|
// Legacy export for backward compatibility during migration
|
||||||
export const ITEMS = ALL_TEMPLATES;
|
export const ITEMS = ALL_TEMPLATES;
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export interface BaseItem {
|
|||||||
quantity?: number;
|
quantity?: number;
|
||||||
stackable?: boolean;
|
stackable?: boolean;
|
||||||
variant?: string; // ItemVariantId - stored as string to avoid circular imports
|
variant?: string; // ItemVariantId - stored as string to avoid circular imports
|
||||||
|
upgradeLevel?: number; // Enhancement level (+1, +2, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeleeWeaponItem extends BaseItem {
|
export interface MeleeWeaponItem extends BaseItem {
|
||||||
|
|||||||
64
src/engine/systems/UpgradeManager.ts
Normal file
64
src/engine/systems/UpgradeManager.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import type { Item, WeaponItem, ArmourItem } from "../../core/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages item upgrade logic for applying upgrade scrolls.
|
||||||
|
*/
|
||||||
|
export class UpgradeManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an item can be upgraded (weapons and armour only).
|
||||||
|
*/
|
||||||
|
static canUpgrade(item: Item): boolean {
|
||||||
|
return item.type === "Weapon" ||
|
||||||
|
item.type === "BodyArmour" ||
|
||||||
|
item.type === "Helmet" ||
|
||||||
|
item.type === "Gloves" ||
|
||||||
|
item.type === "Boots";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an upgrade to an item, increasing all stats by +1.
|
||||||
|
* Returns true if successful.
|
||||||
|
*/
|
||||||
|
static applyUpgrade(item: Item): boolean {
|
||||||
|
if (!this.canUpgrade(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment upgrade level
|
||||||
|
const currentLevel = item.upgradeLevel ?? 0;
|
||||||
|
item.upgradeLevel = currentLevel + 1;
|
||||||
|
|
||||||
|
// Update item name with level suffix
|
||||||
|
// Remove any existing upgrade suffix first
|
||||||
|
const baseName = item.name.replace(/\s*\+\d+$/, "");
|
||||||
|
item.name = `${baseName} +${item.upgradeLevel}`;
|
||||||
|
|
||||||
|
// Increase all numeric stats by +1
|
||||||
|
if (item.type === "Weapon") {
|
||||||
|
const weaponItem = item as WeaponItem;
|
||||||
|
if (weaponItem.stats.attack !== undefined) {
|
||||||
|
weaponItem.stats.attack += 1;
|
||||||
|
}
|
||||||
|
} else if (item.type === "BodyArmour" || item.type === "Helmet" ||
|
||||||
|
item.type === "Gloves" || item.type === "Boots") {
|
||||||
|
const armourItem = item as ArmourItem;
|
||||||
|
if (armourItem.stats.defense !== undefined) {
|
||||||
|
armourItem.stats.defense += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the display name for an item including upgrade level.
|
||||||
|
*/
|
||||||
|
static getDisplayName(item: Item): string {
|
||||||
|
if (item.upgradeLevel && item.upgradeLevel > 0) {
|
||||||
|
const baseName = item.name.replace(/\s*\+\d+$/, "");
|
||||||
|
return `${baseName} +${item.upgradeLevel}`;
|
||||||
|
}
|
||||||
|
return item.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/engine/systems/__tests__/UpgradeManager.test.ts
Normal file
66
src/engine/systems/__tests__/UpgradeManager.test.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { UpgradeManager } from '../UpgradeManager';
|
||||||
|
import { createMeleeWeapon, createArmour, createConsumable } from '../../../core/config/Items';
|
||||||
|
import type { WeaponItem, ArmourItem } from '../../../core/types';
|
||||||
|
|
||||||
|
describe('UpgradeManager', () => {
|
||||||
|
it('should correctly identify upgradeable items', () => {
|
||||||
|
const sword = createMeleeWeapon("iron_sword");
|
||||||
|
const armor = createArmour("leather_armor");
|
||||||
|
const potion = createConsumable("health_potion");
|
||||||
|
|
||||||
|
expect(UpgradeManager.canUpgrade(sword)).toBe(true);
|
||||||
|
expect(UpgradeManager.canUpgrade(armor)).toBe(true);
|
||||||
|
expect(UpgradeManager.canUpgrade(potion)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upgrade weapon stats and name', () => {
|
||||||
|
const sword = createMeleeWeapon("iron_sword") as WeaponItem;
|
||||||
|
const initialAttack = sword.stats.attack!;
|
||||||
|
const initialName = sword.name;
|
||||||
|
|
||||||
|
const success = UpgradeManager.applyUpgrade(sword);
|
||||||
|
|
||||||
|
expect(success).toBe(true);
|
||||||
|
expect(sword.stats.attack).toBe(initialAttack + 1);
|
||||||
|
expect(sword.upgradeLevel).toBe(1);
|
||||||
|
expect(sword.name).toBe(`${initialName} +1`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upgrade armour stats and name', () => {
|
||||||
|
const armor = createArmour("leather_armor") as ArmourItem;
|
||||||
|
const initialDefense = armor.stats.defense!;
|
||||||
|
const initialName = armor.name;
|
||||||
|
|
||||||
|
const success = UpgradeManager.applyUpgrade(armor);
|
||||||
|
|
||||||
|
expect(success).toBe(true);
|
||||||
|
expect(armor.stats.defense).toBe(initialDefense + 1);
|
||||||
|
expect(armor.upgradeLevel).toBe(1);
|
||||||
|
expect(armor.name).toBe(`${initialName} +1`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle sequential upgrades', () => {
|
||||||
|
const sword = createMeleeWeapon("iron_sword") as WeaponItem;
|
||||||
|
const initialAttack = sword.stats.attack!;
|
||||||
|
const initialName = sword.name;
|
||||||
|
|
||||||
|
UpgradeManager.applyUpgrade(sword); // +1
|
||||||
|
UpgradeManager.applyUpgrade(sword); // +2
|
||||||
|
|
||||||
|
expect(sword.stats.attack).toBe(initialAttack + 2);
|
||||||
|
expect(sword.upgradeLevel).toBe(2);
|
||||||
|
expect(sword.name).toBe(`${initialName} +2`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not upgrade non-upgradeable items', () => {
|
||||||
|
const potion = createConsumable("health_potion");
|
||||||
|
const initialName = potion.name;
|
||||||
|
|
||||||
|
const success = UpgradeManager.applyUpgrade(potion);
|
||||||
|
|
||||||
|
expect(success).toBe(false);
|
||||||
|
expect(potion.upgradeLevel).toBeUndefined();
|
||||||
|
expect(potion.name).toBe(initialName);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
createConsumable,
|
createConsumable,
|
||||||
createMeleeWeapon,
|
createMeleeWeapon,
|
||||||
createRangedWeapon,
|
createRangedWeapon,
|
||||||
createArmour
|
createArmour,
|
||||||
|
createUpgradeScroll
|
||||||
} from "../../core/config/Items";
|
} from "../../core/config/Items";
|
||||||
import { seededRandom } from "../../core/math";
|
import { seededRandom } from "../../core/math";
|
||||||
import * as ROT from "rot-js";
|
import * as ROT from "rot-js";
|
||||||
@@ -62,7 +63,8 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
|||||||
createMeleeWeapon("iron_sword", "sharp"), // Sharp sword variant
|
createMeleeWeapon("iron_sword", "sharp"), // Sharp sword variant
|
||||||
createConsumable("throwing_dagger", 3),
|
createConsumable("throwing_dagger", 3),
|
||||||
createRangedWeapon("pistol"),
|
createRangedWeapon("pistol"),
|
||||||
createArmour("leather_armor", "heavy") // Heavy armour variant
|
createArmour("leather_armor", "heavy"), // Heavy armour variant
|
||||||
|
createUpgradeScroll(2) // 2 Upgrade scrolls
|
||||||
] : [])
|
] : [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ export class ItemSpriteFactory {
|
|||||||
sprite.setScale(scale);
|
sprite.setScale(scale);
|
||||||
container.add(sprite);
|
container.add(sprite);
|
||||||
|
|
||||||
|
// Add upgrade level badge if item has been upgraded
|
||||||
|
if (item.upgradeLevel && item.upgradeLevel > 0) {
|
||||||
|
const badge = this.createUpgradeBadge(scene, item.upgradeLevel, scale);
|
||||||
|
container.add(badge);
|
||||||
|
}
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +115,31 @@ export class ItemSpriteFactory {
|
|||||||
return variant?.glowColor ?? null;
|
return variant?.glowColor ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a badge displaying the upgrade level (e.g., "+1").
|
||||||
|
*/
|
||||||
|
private static createUpgradeBadge(
|
||||||
|
scene: Phaser.Scene,
|
||||||
|
level: number,
|
||||||
|
scale: number
|
||||||
|
): Phaser.GameObjects.Text {
|
||||||
|
// Position at top-right corner, slightly inset
|
||||||
|
const offset = 5 * scale;
|
||||||
|
|
||||||
|
// Level text with strong outline for readability without background
|
||||||
|
const text = scene.add.text(offset, -offset, `+${level}`, {
|
||||||
|
fontSize: `${9 * scale}px`,
|
||||||
|
color: "#ffd700",
|
||||||
|
fontStyle: "bold",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
stroke: "#000000",
|
||||||
|
strokeThickness: 3
|
||||||
|
});
|
||||||
|
text.setOrigin(0.5);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an item has a variant with a glow.
|
* Checks if an item has a variant with a glow.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import GameUI from "../ui/GameUI";
|
|||||||
import { CameraController } from "./systems/CameraController";
|
import { CameraController } from "./systems/CameraController";
|
||||||
import { ItemManager } from "./systems/ItemManager";
|
import { ItemManager } from "./systems/ItemManager";
|
||||||
import { TargetingSystem } from "./systems/TargetingSystem";
|
import { TargetingSystem } from "./systems/TargetingSystem";
|
||||||
|
import { UpgradeManager } from "../engine/systems/UpgradeManager";
|
||||||
|
import { InventoryOverlay } from "../ui/components/InventoryOverlay";
|
||||||
|
|
||||||
export class GameScene extends Phaser.Scene {
|
export class GameScene extends Phaser.Scene {
|
||||||
private world!: World;
|
private world!: World;
|
||||||
@@ -229,6 +231,42 @@ export class GameScene extends Phaser.Scene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upgrade Scroll Logic
|
||||||
|
if (item.id === "upgrade_scroll") {
|
||||||
|
const uiScene = this.scene.get("GameUI") as GameUI;
|
||||||
|
// Access the public inventory component
|
||||||
|
const inventoryOverlay = uiScene.inventory;
|
||||||
|
|
||||||
|
if (inventoryOverlay && inventoryOverlay instanceof InventoryOverlay) {
|
||||||
|
// Trigger upgrade mode
|
||||||
|
inventoryOverlay.enterUpgradeMode((targetItem: any) => {
|
||||||
|
const success = UpgradeManager.applyUpgrade(targetItem);
|
||||||
|
if (success) {
|
||||||
|
// Consume scroll logic handling stacking
|
||||||
|
const scrollItem = player.inventory?.items.find(it => it.id === "upgrade_scroll");
|
||||||
|
if (scrollItem) {
|
||||||
|
if (scrollItem.stackable && scrollItem.quantity && scrollItem.quantity > 1) {
|
||||||
|
scrollItem.quantity--;
|
||||||
|
} else {
|
||||||
|
this.itemManager.removeFromInventory(player, "upgrade_scroll");
|
||||||
|
}
|
||||||
|
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Upgraded!", "#ffd700");
|
||||||
|
}
|
||||||
|
|
||||||
|
inventoryOverlay.cancelUpgradeMode();
|
||||||
|
this.emitUIUpdate();
|
||||||
|
this.commitPlayerAction({ type: "wait" });
|
||||||
|
} else {
|
||||||
|
// Should technically be prevented by UI highlights, but safety check
|
||||||
|
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Cannot upgrade!", "#ff0000");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Select Item to Upgrade", "#ffffff");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = this.itemManager.handleUse(data.itemId, player);
|
const result = this.itemManager.handleUse(data.itemId, player);
|
||||||
|
|
||||||
if (result.success && result.consumed) {
|
if (result.success && result.consumed) {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
private dragIcon: Phaser.GameObjects.Sprite | null = null;
|
private dragIcon: Phaser.GameObjects.Sprite | null = null;
|
||||||
private draggedItemIndex: number | null = null;
|
private draggedItemIndex: number | null = null;
|
||||||
private draggedEquipmentKey: string | null = null;
|
private draggedEquipmentKey: string | null = null;
|
||||||
|
private isDragging = false;
|
||||||
|
private dragPayload: any = null;
|
||||||
|
private cachedPlayer: CombatantActor | null = null; // Cache player for local methods
|
||||||
|
|
||||||
|
// Upgrade Mode
|
||||||
|
public isUpgradeMode = false;
|
||||||
|
private onUpgradeSelect?: (item: any) => void;
|
||||||
|
|
||||||
private tooltip: Phaser.GameObjects.Container | null = null;
|
private tooltip: Phaser.GameObjects.Container | null = null;
|
||||||
private tooltipName: Phaser.GameObjects.Text | null = null;
|
private tooltipName: Phaser.GameObjects.Text | null = null;
|
||||||
private tooltipStats: Phaser.GameObjects.Text | null = null;
|
private tooltipStats: Phaser.GameObjects.Text | null = null;
|
||||||
@@ -35,6 +43,23 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
this.createEquipmentPanel();
|
this.createEquipmentPanel();
|
||||||
this.createBackpackPanel();
|
this.createBackpackPanel();
|
||||||
this.createTooltip();
|
this.createTooltip();
|
||||||
|
|
||||||
|
// Global input listener to cancel upgrade mode on click outside
|
||||||
|
this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||||
|
// Only check if visible and in upgrade mode
|
||||||
|
if (this.container.visible && this.isUpgradeMode) {
|
||||||
|
this.handleUpgradeClick(pointer);
|
||||||
|
|
||||||
|
// If clicking outside both panels, cancel
|
||||||
|
const overBackpack = this.getBackpackSlotAt(pointer.x, pointer.y) !== null;
|
||||||
|
const overEquip = this.getEquipmentSlotAt(pointer.x, pointer.y) !== null;
|
||||||
|
|
||||||
|
if (!overBackpack && !overEquip) {
|
||||||
|
console.log("Clicked outside - cancelling (DEBUG: DISABLED to fix interaction)");
|
||||||
|
// this.cancelUpgradeMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTooltip() {
|
private createTooltip() {
|
||||||
@@ -309,6 +334,7 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(player: CombatantActor) {
|
update(player: CombatantActor) {
|
||||||
|
this.cachedPlayer = player;
|
||||||
if (!player.inventory) return;
|
if (!player.inventory) return;
|
||||||
|
|
||||||
// Clear existing items from backpack slots
|
// Clear existing items from backpack slots
|
||||||
@@ -361,8 +387,25 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
slot.setData("equipmentKey", undefined); // Explicitly clear to avoid confusion
|
slot.setData("equipmentKey", undefined); // Explicitly clear to avoid confusion
|
||||||
this.scene.input.setDraggable(slot);
|
this.scene.input.setDraggable(slot);
|
||||||
|
|
||||||
slot.on("pointerdown", () => {
|
// Clear previous listeners to avoid accumulation
|
||||||
console.log("Clicked item:", item);
|
slot.removeAllListeners("pointerdown");
|
||||||
|
slot.removeAllListeners("pointerover");
|
||||||
|
slot.removeAllListeners("pointerout");
|
||||||
|
|
||||||
|
slot.on("pointerdown", (pointer: Phaser.Input.Pointer) => {
|
||||||
|
if (this.isUpgradeMode && this.onUpgradeSelect) {
|
||||||
|
if (item && (item.type === "Weapon" || item.type === "BodyArmour" || item.type === "Helmet" || item.type === "Gloves" || item.type === "Boots")) {
|
||||||
|
this.onUpgradeSelect(item);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right click to use item
|
||||||
|
if (pointer.rightButtonDown()) {
|
||||||
|
const gameScene = this.scene.scene.get("GameScene");
|
||||||
|
gameScene.events.emit("use-item", { itemId: item.id });
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
slot.on("pointerover", (pointer: Phaser.Input.Pointer) => {
|
slot.on("pointerover", (pointer: Phaser.Input.Pointer) => {
|
||||||
@@ -391,6 +434,19 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
slot.setData("equipmentKey", key);
|
slot.setData("equipmentKey", key);
|
||||||
this.scene.input.setDraggable(slot);
|
this.scene.input.setDraggable(slot);
|
||||||
|
|
||||||
|
// Clear previous listeners
|
||||||
|
slot.removeAllListeners("pointerdown");
|
||||||
|
slot.removeAllListeners("pointerover");
|
||||||
|
slot.removeAllListeners("pointerout");
|
||||||
|
|
||||||
|
slot.on("pointerdown", () => {
|
||||||
|
if (this.isUpgradeMode && this.onUpgradeSelect) {
|
||||||
|
// All equipped items in valid slots are upgradeable by definition
|
||||||
|
this.onUpgradeSelect(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
slot.on("pointerover", (pointer: Phaser.Input.Pointer) => {
|
slot.on("pointerover", (pointer: Phaser.Input.Pointer) => {
|
||||||
this.showTooltip(item, pointer.x, pointer.y);
|
this.showTooltip(item, pointer.x, pointer.y);
|
||||||
});
|
});
|
||||||
@@ -440,6 +496,7 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private clearHighlights() {
|
private clearHighlights() {
|
||||||
|
// Reset Equipment Slots
|
||||||
this.equipmentSlots.forEach((container, key) => {
|
this.equipmentSlots.forEach((container, key) => {
|
||||||
const graphics = container.list[0] as Phaser.GameObjects.Graphics;
|
const graphics = container.list[0] as Phaser.GameObjects.Graphics;
|
||||||
if (graphics) {
|
if (graphics) {
|
||||||
@@ -456,12 +513,142 @@ export class InventoryOverlay extends OverlayComponent {
|
|||||||
|
|
||||||
graphics.fillStyle(slotBg, 1);
|
graphics.fillStyle(slotBg, 1);
|
||||||
graphics.fillRect(-size / 2 + 3, -size / 2 + 3, size - 6, size - 6);
|
graphics.fillRect(-size / 2 + 3, -size / 2 + 3, size - 6, size - 6);
|
||||||
|
|
||||||
|
// Allow interactions again if they were disabled (though we don't disable them currently)
|
||||||
|
container.setAlpha(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset Backpack Slots
|
||||||
|
this.backpackSlots.forEach(container => {
|
||||||
|
const graphics = container.list[0] as Phaser.GameObjects.Graphics;
|
||||||
|
if (graphics) {
|
||||||
|
graphics.clear();
|
||||||
|
const slotSize = 44;
|
||||||
|
const slotBorder = 0xd4af37;
|
||||||
|
const slotBg = 0x1a0f1a; // Darker bg for backpack
|
||||||
|
|
||||||
|
graphics.lineStyle(2, slotBorder, 1);
|
||||||
|
graphics.strokeRect(-slotSize / 2, -slotSize / 2, slotSize, slotSize);
|
||||||
|
|
||||||
|
graphics.lineStyle(1, 0x8b7355, 1);
|
||||||
|
graphics.strokeRect(-slotSize / 2 + 2, -slotSize / 2 + 2, slotSize - 4, slotSize - 4);
|
||||||
|
|
||||||
|
graphics.fillStyle(slotBg, 1);
|
||||||
|
graphics.fillRect(-slotSize / 2 + 3, -slotSize / 2 + 3, slotSize - 6, slotSize - 6);
|
||||||
|
|
||||||
|
container.setAlpha(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters upgrade mode, highlighting upgradeable items.
|
||||||
|
*/
|
||||||
|
enterUpgradeMode(onSelect: (item: any) => void) {
|
||||||
|
this.isUpgradeMode = true;
|
||||||
|
this.onUpgradeSelect = onSelect;
|
||||||
|
|
||||||
|
// Highlight all upgradeable items
|
||||||
|
this.highlightUpgradeableItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelUpgradeMode() {
|
||||||
|
this.isUpgradeMode = false;
|
||||||
|
this.onUpgradeSelect = undefined;
|
||||||
|
this.clearHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
private highlightUpgradeableItems() {
|
||||||
|
if (!this.cachedPlayer || !this.cachedPlayer.inventory) return;
|
||||||
|
|
||||||
|
// Green highlight for upgradeable items
|
||||||
|
this.backpackSlots.forEach((slot, index) => {
|
||||||
|
const item = this.cachedPlayer!.inventory!.items[index];
|
||||||
|
|
||||||
|
let bg = slot.list.find(c => c instanceof Phaser.GameObjects.Graphics) as Phaser.GameObjects.Graphics;
|
||||||
|
// In this complex container setup, the bg is the first child of the slot container's first child
|
||||||
|
// Actually looking at createBackpackPanel, the slot IS a container with [Graphics] in it.
|
||||||
|
// The Graphics draws border and bg.
|
||||||
|
// We should clear and redraw highlighted
|
||||||
|
|
||||||
|
if (item && (item.type === "Weapon" || item.type === "BodyArmour" || item.type === "Helmet" || item.type === "Gloves" || item.type === "Boots")) {
|
||||||
|
this.drawSlotHighlight(slot, true, 44);
|
||||||
|
} else {
|
||||||
|
this.drawSlotDim(slot, 44);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.equipmentSlots.forEach((slot, key) => {
|
||||||
|
const item = (this.cachedPlayer!.equipment as any)?.[key];
|
||||||
|
const size = (key === "bodyArmour") ? 58 : (key === "belt") ? 32 : (key === "boots") ? 46 : (key.startsWith("ring")) ? 38 : 46;
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
this.drawSlotHighlight(slot, true, size);
|
||||||
|
} else {
|
||||||
|
this.drawSlotDim(slot, size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawSlotHighlight(slot: Phaser.GameObjects.Container, active: boolean, size: number) {
|
||||||
|
const g = slot.list[0] as Phaser.GameObjects.Graphics;
|
||||||
|
if (g) {
|
||||||
|
g.clear();
|
||||||
|
// Highlight border
|
||||||
|
g.lineStyle(2, 0x00ff00, 1);
|
||||||
|
g.strokeRect(-size / 2, -size / 2, size, size);
|
||||||
|
|
||||||
|
g.lineStyle(1, 0x00aa00, 1);
|
||||||
|
g.strokeRect(-size / 2 + 2, -size / 2 + 2, size - 4, size - 4);
|
||||||
|
|
||||||
|
g.fillStyle(0x1a2f1a, 1);
|
||||||
|
g.fillRect(-size / 2 + 3, -size / 2 + 3, size - 6, size - 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawSlotDim(slot: Phaser.GameObjects.Container, size: number) {
|
||||||
|
const g = slot.list[0] as Phaser.GameObjects.Graphics;
|
||||||
|
if (g) {
|
||||||
|
g.setAlpha(0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle clicks for upgrade selection
|
||||||
|
private handleUpgradeClick(pointer: Phaser.Input.Pointer) {
|
||||||
|
if (!this.isUpgradeMode || !this.onUpgradeSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check backpack
|
||||||
|
const backpackIndex = this.getBackpackSlotAt(pointer.x, pointer.y);
|
||||||
|
if (backpackIndex !== null && this.cachedPlayer && this.cachedPlayer.inventory) {
|
||||||
|
const item = this.cachedPlayer.inventory.items[backpackIndex];
|
||||||
|
// Reuse eligibility check
|
||||||
|
if (item && (item.type === "Weapon" || item.type === "BodyArmour" || item.type === "Helmet" || item.type === "Gloves" || item.type === "Boots")) {
|
||||||
|
this.onUpgradeSelect(item);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check equipment
|
||||||
|
const equipSlot = this.getEquipmentSlotAt(pointer.x, pointer.y);
|
||||||
|
if (equipSlot !== null && this.cachedPlayer && this.cachedPlayer.equipment) {
|
||||||
|
const item = (this.cachedPlayer.equipment as any)[equipSlot];
|
||||||
|
if (item) { // All equipped items are upgradeable types
|
||||||
|
this.onUpgradeSelect(item);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupDragEvents() {
|
private setupDragEvents() {
|
||||||
this.scene.input.on("dragstart", (pointer: Phaser.Input.Pointer, gameObject: any) => {
|
this.scene.input.on("dragstart", (pointer: Phaser.Input.Pointer, gameObject: any) => {
|
||||||
|
// Handle Upgrade Mode clicks (prevent drag)
|
||||||
|
if (this.isUpgradeMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const gameScene = this.scene.scene.get("GameScene") as any;
|
const gameScene = this.scene.scene.get("GameScene") as any;
|
||||||
const player = gameScene.world.actors.get(gameScene.playerId);
|
const player = gameScene.world.actors.get(gameScene.playerId);
|
||||||
if (!player) return;
|
if (!player) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user