Files
rogue/src/scenes/systems/TargetingSystem.ts
2026-01-07 09:19:38 +11:00

161 lines
4.2 KiB
TypeScript

import Phaser from "phaser";
import type { World, CombatantActor, Item, Vec2, EntityId } from "../../core/types";
import { TILE_SIZE } from "../../core/constants";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { traceProjectile, getClosestVisibleEnemy } from "../../engine/gameplay/CombatLogic";
import type { EntityManager } from "../../engine/EntityManager";
/**
* Manages targeting mode for thrown items.
* Extracted from GameScene to isolate targeting logic and reduce complexity.
*/
export class TargetingSystem {
private graphics: Phaser.GameObjects.Graphics;
private active: boolean = false;
private targetingItemId: string | null = null;
private cursor: Vec2 | null = null;
constructor(graphics: Phaser.GameObjects.Graphics) {
this.graphics = graphics;
}
/**
* Start targeting mode for a throwable item
*/
startTargeting(
itemId: string,
playerPos: Vec2,
world: World,
seenArray: Uint8Array,
worldWidth: number
): void {
this.targetingItemId = itemId;
this.active = true;
// Auto-target closest visible enemy
const closest = getClosestVisibleEnemy(world, playerPos, seenArray, worldWidth);
if (closest) {
this.cursor = closest;
} else {
this.cursor = null;
}
this.drawLine(playerPos);
console.log("Targeting Mode: ON");
}
/**
* Update the targeting cursor position
*/
updateCursor(worldPos: Vec2, playerPos: Vec2): void {
if (!this.active) return;
this.cursor = { x: worldPos.x, y: worldPos.y };
this.drawLine(playerPos);
}
/**
* Execute the throw action
*/
executeThrow(
world: World,
playerId: EntityId,
entityManager: EntityManager,
onProjectileComplete: (blockedPos: Vec2, hitActorId: EntityId | undefined, item: Item) => void
): boolean {
if (!this.active || !this.targetingItemId || !this.cursor) {
return false;
}
const player = world.actors.get(playerId) as CombatantActor;
if (!player || !player.inventory) return false;
const itemIdx = player.inventory.items.findIndex(it => it.id === this.targetingItemId);
if (itemIdx === -1) {
console.log("Item not found!");
this.cancel();
return false;
}
const item = player.inventory.items[itemIdx];
// Remove item from inventory before throw
player.inventory.items.splice(itemIdx, 1);
const start = player.pos;
const end = { x: this.cursor.x, y: this.cursor.y };
const result = traceProjectile(world, start, end, entityManager, playerId);
const { blockedPos, hitActorId } = result;
// Call the callback with throw results
onProjectileComplete(blockedPos, hitActorId, item);
return true;
}
/**
* Cancel targeting mode
*/
cancel(): void {
this.active = false;
this.targetingItemId = null;
this.cursor = null;
this.graphics.clear();
console.log("Targeting cancelled");
}
/**
* Check if targeting is currently active
*/
get isActive(): boolean {
return this.active;
}
/**
* Get the ID of the item being targeted
*/
get itemId(): string | null {
return this.targetingItemId;
}
/**
* Get the current cursor position
*/
get cursorPos(): Vec2 | null {
return this.cursor;
}
/**
* Draw targeting line from player to cursor
*/
private drawLine(playerPos: Vec2): void {
if (!this.cursor) {
this.graphics.clear();
return;
}
this.graphics.clear();
const startX = playerPos.x * TILE_SIZE + TILE_SIZE / 2;
const startY = playerPos.y * TILE_SIZE + TILE_SIZE / 2;
const endX = this.cursor.x * TILE_SIZE + TILE_SIZE / 2;
const endY = this.cursor.y * TILE_SIZE + TILE_SIZE / 2;
this.graphics.lineStyle(
GAME_CONFIG.ui.targetingLineWidth,
GAME_CONFIG.ui.targetingLineColor,
GAME_CONFIG.ui.targetingLineAlpha
);
this.graphics.lineBetween(startX, startY, endX, endY);
this.graphics.strokeRect(
this.cursor.x * TILE_SIZE,
this.cursor.y * TILE_SIZE,
TILE_SIZE,
TILE_SIZE
);
}
}