import { useCombatStore } from "./combatStore"; import { useDungeonRaceAffixStore } from "./dungeonRaceAffixRuntime"; import { getPartyRuntimePosition, type PartyWorldPosition } from "./partyRuntimeRegistry"; import { usePartyStore } from "./partyStore"; function away(position: PartyWorldPosition, origin: PartyWorldPosition, distance: number): PartyWorldPosition { const dx = position[0] - origin[0]; const dz = position[2] - origin[2]; const length = Math.hypot(dx, dz); const x = length > 0.01 ? dx / length : 1; const z = length > 0.01 ? dz / length : 0; return [position[0] + x * distance, position[1], position[2] + z * distance]; } /** Returns a temporary safe movement goal; the scene still validates and routes it on navmesh. */ export function raceAffixAvoidanceDestination( memberId: string, position: PartyWorldPosition, now: number, ): PartyWorldPosition | null { for (const effect of useDungeonRaceAffixStore.getState().effects) { if (now >= effect.expiresAt) continue; const center = effect.kind === "storm" && effect.targetId ? effect.targetId === "__player__" ? useCombatStore.getState().playerPosition : getPartyRuntimePosition(effect.targetId) ?? effect.position : effect.position; if (Math.abs(position[1] - center[1]) > 3) continue; const planar = Math.hypot(position[0] - center[0], position[2] - center[2]); if (effect.kind === "storm" && effect.targetId === memberId && now < effect.resolvesAt) { const others = [useCombatStore.getState().playerPosition, ...usePartyStore.getState().members.filter((member) => member.id !== memberId && member.health > 0) .map((member) => getPartyRuntimePosition(member.id)).filter((point): point is PartyWorldPosition => point !== null)]; const nearest = others.sort((left, right) => Math.hypot(left[0] - position[0], left[2] - position[2]) - Math.hypot(right[0] - position[0], right[2] - position[2]))[0]; if (nearest && Math.hypot(nearest[0] - position[0], nearest[2] - position[2]) < 4) return away(position, nearest, 4); continue; } if (effect.kind === "ring" && now < effect.resolvesAt && planar >= 3.5 && planar <= 7.5) { if (planar <= 5.5) return away(position, center, -(planar - 3)); return away(position, center, 8 - planar); } if (["volatile", "ash", "star", "storm", "predator"].includes(effect.kind) && (now < effect.resolvesAt || ["ash", "predator"].includes(effect.kind)) && planar <= effect.radius + 0.7) { if (effect.kind === "storm" && effect.targetId === memberId) continue; return away(position, center, effect.radius + 1 - planar); } } return null; }