import { type World, type Vec2, type EntityId } from "../../core/types"; import { isBlocked } from "../world/world-logic"; import { raycast } from "../../core/math"; import { EntityManager } from "../EntityManager"; export interface ProjectileResult { path: Vec2[]; blockedPos: Vec2; hitActorId?: EntityId; } /** * Calculates the path and impact of a projectile. */ export function traceProjectile( world: World, start: Vec2, target: Vec2, entityManager: EntityManager, shooterId?: EntityId ): ProjectileResult { const points = raycast(start.x, start.y, target.x, target.y); let blockedPos = target; let hitActorId: EntityId | undefined; // Iterate points (skip start) for (let i = 1; i < points.length; i++) { const p = points[i]; // Check for blocking if (isBlocked(world, p.x, p.y, entityManager)) { // Check if we hit a combatant const actors = entityManager.getActorsAt(p.x, p.y); const enemy = actors.find(a => a.category === "combatant" && a.id !== shooterId); if (enemy) { hitActorId = enemy.id; blockedPos = p; } else { // Hit wall or other obstacle blockedPos = p; } break; } blockedPos = p; } return { path: points, blockedPos, hitActorId }; } /** * Finds the closest visible enemy to a given position. */ export function getClosestVisibleEnemy( world: World, origin: Vec2, seenTiles: Set | boolean[] | Uint8Array, // Support various visibility structures width?: number // Required if seenTiles is a flat array ): Vec2 | null { let closestDistSq = Infinity; let closestPos: Vec2 | null = null; // Helper to check visibility const isVisible = (x: number, y: number) => { if (Array.isArray(seenTiles) || seenTiles instanceof Uint8Array || seenTiles instanceof Int8Array) { // Flat array if (!width) return false; return (seenTiles as any)[y * width + x]; } else { // Set return (seenTiles as Set).has(`${x},${y}`); } }; for (const actor of world.actors.values()) { if (actor.category !== "combatant" || actor.isPlayer) continue; // Check visibility if (!isVisible(actor.pos.x, actor.pos.y)) continue; const dx = actor.pos.x - origin.x; const dy = actor.pos.y - origin.y; const distSq = dx*dx + dy*dy; if (distSq < closestDistSq) { closestDistSq = distSq; closestPos = { x: actor.pos.x, y: actor.pos.y }; } } return closestPos; }