import type { Vec2 } from '@rpg/engine';
/**
* Линия видимости по сетке (чистая математика, без Pixi).
* Отрезок дискретизируется шагом 0.25 юнита; тайл с непрозрачным объектом
* (стена, дом, башня, дерево) перекрывает обзор. Не в движке: правила
* прозрачности зависят от игры, второй пользователь не виден.
*/
/** Шаг дискретизации отрезка, юнитов. */
const STEP = 0.25;
/** Виден ли `to` из `from` с учётом непрозрачных тайлов. */
export function hasLineOfSight(
from: Vec2,
to: Vec2,
isOpaque: (tx: number, ty: number) => boolean
): boolean {
const dx = to.x - from.x;
const dy = to.y - from.y;
const len = Math.hypot(dx, dy);
if (len < 1e-6) return true;
const n = Math.ceil(len / STEP);
const ux = dx / n;
const uy = dy / n;
// Концевые точки не проверяем: сами враг/герой стоят в своих тайлах.
for (let i = 1; i < n; i++) {
const x = from.x + ux * i;
const y = from.y + uy * i;
if (isOpaque(Math.floor(x), Math.floor(y))) return false;
}
return true;
}
/**
* Непрозрачен ли тайл карты: высокий объект (TallSpec) перекрывает взгляд.
* Вода и ровный пепел прозрачны — сгустки видят через низину.
*/
export function tileOpaque(map: {
data: { width: number; height: number; tiles: number[]; tall?: Record<number, unknown> };
}): (tx: number, ty: number) => boolean {
return (tx: number, ty: number): boolean => {
const { width, height, tiles, tall } = map.data;
if (tx < 0 || ty < 0 || tx >= width || ty >= height) return true;
return tall?.[tiles[ty * width + tx]] !== undefined;
};
}