import type { EnemyAttackTelegraph } from "./enemyAttacks"; import type { CombatPosition } from "./mobRuntimeRegistry"; export interface EnemyTelegraphPlacement { readonly sourcePosition: CombatPosition; readonly targetPosition: CombatPosition; readonly sourceYaw: number; } export function enemyTelegraphYaw(placement: EnemyTelegraphPlacement): number { const dx = placement.targetPosition[0] - placement.sourcePosition[0]; const dz = placement.targetPosition[2] - placement.sourcePosition[2]; return dx * dx + dz * dz > 0.0001 ? Math.atan2(dx, dz) : placement.sourceYaw; } export function enemyTelegraphOrigin( telegraph: EnemyAttackTelegraph, placement: EnemyTelegraphPlacement, ): CombatPosition { return telegraph.anchor === "target" ? placement.targetPosition : placement.sourcePosition; } /** Matches the visible warning mesh in the X/Z combat plane. */ export function enemyTelegraphContainsPosition( telegraph: EnemyAttackTelegraph, placement: EnemyTelegraphPlacement, candidate: CombatPosition, ): boolean { const origin = enemyTelegraphOrigin(telegraph, placement); const dx = candidate[0] - origin[0]; const dz = candidate[2] - origin[2]; if (telegraph.shape === "circle") { const radius = telegraph.radius ?? 0; return dx * dx + dz * dz <= radius * radius; } const yaw = enemyTelegraphYaw(placement); const forwardX = Math.sin(yaw); const forwardZ = Math.cos(yaw); const forward = dx * forwardX + dz * forwardZ; const side = Math.abs(dx * forwardZ - dz * forwardX); const length = telegraph.length ?? 0; if (forward < 0 || forward > length) return false; if (telegraph.shape === "line") return side <= (telegraph.width ?? 0) / 2; const distance = Math.hypot(dx, dz); if (distance <= 0.0001) return true; const halfAngle = ((telegraph.angleDegrees ?? 0) * Math.PI) / 360; return Math.acos(Math.min(1, Math.max(-1, forward / distance))) <= halfAngle; }