287 lines
12 KiB
TypeScript
287 lines
12 KiB
TypeScript
import type { GameScene } from "../GameScene";
|
|
import { UpgradeManager } from "../../engine/systems/UpgradeManager";
|
|
import { InventoryOverlay } from "../../ui/components/InventoryOverlay";
|
|
import { equipItem, deEquipItem } from "../../engine/systems/EquipmentService";
|
|
import { inBounds, isBlocked } from "../../engine/world/world-logic";
|
|
import GameUI from "../../ui/GameUI";
|
|
|
|
export class GameEventHandler {
|
|
private scene: GameScene;
|
|
|
|
constructor(scene: GameScene) {
|
|
this.scene = scene;
|
|
}
|
|
|
|
public registerListeners() {
|
|
const events = this.scene.events;
|
|
|
|
events.on("menu-toggled", (isOpen: boolean) => {
|
|
this.scene.isMenuOpen = isOpen;
|
|
});
|
|
events.on("inventory-toggled", (isOpen: boolean) => {
|
|
this.scene.isInventoryOpen = isOpen;
|
|
});
|
|
events.on("character-toggled", (isOpen: boolean) => {
|
|
this.scene.isCharacterOpen = isOpen;
|
|
});
|
|
|
|
events.on("toggle-minimap", () => {
|
|
this.scene.dungeonRenderer.toggleMinimap();
|
|
});
|
|
|
|
events.on("request-ui-update", () => {
|
|
this.scene.emitUIUpdate();
|
|
});
|
|
|
|
events.on("restart-game", () => {
|
|
this.scene.restartGame();
|
|
});
|
|
|
|
events.on("allocate-stat", (statName: string) => {
|
|
const player = this.scene.entityAccessor.getPlayer();
|
|
if (player) {
|
|
this.scene.progressionManager.allocateStat(player, statName);
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
});
|
|
|
|
events.on("allocate-passive", (nodeId: string) => {
|
|
const player = this.scene.entityAccessor.getPlayer();
|
|
if (player) {
|
|
this.scene.progressionManager.allocatePassive(player, nodeId);
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
});
|
|
|
|
events.on("player-wait", () => {
|
|
if (!this.scene.awaitingPlayer) return;
|
|
if (this.scene.isMenuOpen || this.scene.isInventoryOpen || this.scene.dungeonRenderer.isMinimapVisible()) return;
|
|
this.scene.commitPlayerAction({ type: "wait" });
|
|
});
|
|
|
|
events.on("player-search", () => {
|
|
if (!this.scene.awaitingPlayer) return;
|
|
if (this.scene.isMenuOpen || this.scene.isInventoryOpen || this.scene.dungeonRenderer.isMinimapVisible()) return;
|
|
|
|
console.log("Player searching...");
|
|
this.scene.commitPlayerAction({ type: "wait" });
|
|
});
|
|
|
|
events.on("use-item", (data: { itemId: string }) => {
|
|
this.handleUseItem(data.itemId);
|
|
});
|
|
|
|
events.on("drop-item", (data: { itemId: string, pointerX: number, pointerY: number }) => {
|
|
this.handleDropItem(data);
|
|
});
|
|
|
|
events.on("equip-item", (data: { itemId: string, slotKey: string }) => {
|
|
this.handleEquipItem(data);
|
|
});
|
|
|
|
events.on("de-equip-item", (data: { slotKey: string }) => {
|
|
this.handleDeEquipItem(data);
|
|
});
|
|
}
|
|
|
|
private handleUseItem(itemId: string) {
|
|
if (!this.scene.awaitingPlayer) return;
|
|
|
|
const player = this.scene.entityAccessor.getPlayer();
|
|
if (!player || !player.inventory) return;
|
|
|
|
const itemIdx = player.inventory.items.findIndex(it => it.id === itemId);
|
|
if (itemIdx === -1) return;
|
|
const item = player.inventory.items[itemIdx];
|
|
|
|
// Ranged Weapon Logic
|
|
if (item.type === "Weapon" && (item.weaponType === "ranged" || item.weaponType === "ceramic_dragon_head")) {
|
|
if (item.weaponType === "ranged") {
|
|
// Check Ammo
|
|
if (item.currentAmmo <= 0) {
|
|
if (item.reloadingTurnsLeft > 0) {
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
|
return;
|
|
}
|
|
|
|
// Try Reload
|
|
this.scene.startReload(player, item as any);
|
|
return;
|
|
}
|
|
|
|
// Is it already reloading?
|
|
if (item.reloadingTurnsLeft > 0) {
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Reloading...", "#aaaaaa");
|
|
return;
|
|
}
|
|
} else if (item.weaponType === "ceramic_dragon_head") {
|
|
// Check Charges
|
|
if (item.charges <= 0) {
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "No charges!", "#ff6600");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Has ammo/charges, start targeting
|
|
if (this.scene.targetingSystem.isActive && this.scene.targetingSystem.itemId === item.id) {
|
|
// Already targeting - execute action
|
|
if (this.scene.targetingSystem.cursorPos) {
|
|
this.scene.executeThrow();
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
const { x: tx, y: ty } = this.scene.getPointerTilePos(this.scene.input.activePointer);
|
|
|
|
this.scene.targetingSystem.startTargeting(
|
|
item.id,
|
|
player.pos,
|
|
this.scene.world,
|
|
this.scene.entityAccessor,
|
|
this.scene.playerId,
|
|
this.scene.dungeonRenderer.seenArray,
|
|
this.scene.world.width,
|
|
{ x: tx, y: ty }
|
|
);
|
|
this.scene.emitUIUpdate();
|
|
return;
|
|
}
|
|
|
|
// Upgrade Scroll Logic
|
|
if (item.id === "upgrade_scroll") {
|
|
const uiScene = this.scene.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.scene.itemManager.removeFromInventory(player, "upgrade_scroll");
|
|
}
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Upgraded!", "#ffd700");
|
|
}
|
|
|
|
inventoryOverlay.cancelUpgradeMode();
|
|
this.scene.emitUIUpdate();
|
|
this.scene.commitPlayerAction({ type: "wait" });
|
|
} else {
|
|
// Should technically be prevented by UI highlights, but safety check
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Cannot upgrade!", "#ff0000");
|
|
inventoryOverlay.cancelUpgradeMode();
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
});
|
|
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, "Select Item to Upgrade", "#ffffff");
|
|
}
|
|
return;
|
|
}
|
|
|
|
const result = this.scene.itemManager.handleUse(itemId, player);
|
|
|
|
if (result.success && result.consumed) {
|
|
const healAmount = player.stats.maxHp - player.stats.hp; // Already healed by manager
|
|
const actualHeal = Math.min(healAmount, player.stats.hp);
|
|
this.scene.dungeonRenderer.showHeal(player.pos.x, player.pos.y, actualHeal);
|
|
this.scene.commitPlayerAction({ type: "wait" });
|
|
this.scene.emitUIUpdate();
|
|
} else if (result.success && !result.consumed) {
|
|
// Throwable item - start targeting
|
|
if (this.scene.targetingSystem.isActive && this.scene.targetingSystem.itemId === item.id) {
|
|
// Already targeting - execute throw
|
|
if (this.scene.targetingSystem.cursorPos) {
|
|
this.scene.executeThrow();
|
|
}
|
|
return;
|
|
}
|
|
|
|
const { x: tx, y: ty } = this.scene.getPointerTilePos(this.scene.input.activePointer);
|
|
|
|
this.scene.targetingSystem.startTargeting(
|
|
item.id,
|
|
player.pos,
|
|
this.scene.world,
|
|
this.scene.entityAccessor,
|
|
this.scene.playerId,
|
|
this.scene.dungeonRenderer.seenArray,
|
|
this.scene.world.width,
|
|
{ x: tx, y: ty }
|
|
);
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
}
|
|
|
|
private handleDropItem(data: { itemId: string, pointerX: number, pointerY: number }) {
|
|
if (!this.scene.awaitingPlayer) return;
|
|
|
|
const player = this.scene.entityAccessor.getPlayer();
|
|
if (!player || !player.inventory) return;
|
|
|
|
const item = this.scene.itemManager.getItem(player, data.itemId);
|
|
if (!item) return;
|
|
|
|
// Determine drop position based on pointer or player pos
|
|
let dropPos = { x: player.pos.x, y: player.pos.y };
|
|
if (data.pointerX !== undefined && data.pointerY !== undefined) {
|
|
const tilePos = this.scene.getPointerTilePos({ x: data.pointerX, y: data.pointerY } as Phaser.Input.Pointer);
|
|
|
|
// Limit drop distance to 1 tile from player for balance/fairness
|
|
const dx = Math.sign(tilePos.x - player.pos.x);
|
|
const dy = Math.sign(tilePos.y - player.pos.y);
|
|
const targetX = player.pos.x + dx;
|
|
const targetY = player.pos.y + dy;
|
|
|
|
if (inBounds(this.scene.world, targetX, targetY) && !isBlocked(this.scene.world, targetX, targetY, this.scene.entityAccessor)) {
|
|
dropPos = { x: targetX, y: targetY };
|
|
}
|
|
}
|
|
|
|
// Remove from inventory and spawn in world
|
|
if (this.scene.itemManager.removeFromInventory(player, data.itemId)) {
|
|
this.scene.itemManager.spawnItem(item, dropPos);
|
|
|
|
const quantityText = (item.quantity && item.quantity > 1) ? ` x${item.quantity}` : "";
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, `Dropped ${item.name}${quantityText}`, "#aaaaaa");
|
|
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
}
|
|
|
|
private handleEquipItem(data: { itemId: string, slotKey: string }) {
|
|
const player = this.scene.entityAccessor.getPlayer();
|
|
if (!player || !player.inventory) return;
|
|
|
|
const item = player.inventory.items.find(it => it.id === data.itemId);
|
|
if (!item) return;
|
|
|
|
const result = equipItem(player, item, data.slotKey as any);
|
|
if (!result.success) {
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, result.message ?? "Cannot equip!", "#ff0000");
|
|
return;
|
|
}
|
|
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, `Equipped ${item.name}`, "#d4af37");
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
|
|
private handleDeEquipItem(data: { slotKey: string }) {
|
|
const player = this.scene.entityAccessor.getPlayer();
|
|
if (!player || !player.equipment) return;
|
|
|
|
const removedItem = deEquipItem(player, data.slotKey as any);
|
|
if (removedItem) {
|
|
this.scene.dungeonRenderer.showFloatingText(player.pos.x, player.pos.y, `De-equipped ${removedItem.name}`, "#aaaaaa");
|
|
this.scene.emitUIUpdate();
|
|
}
|
|
}
|
|
}
|