Files
healer-man/src/game/dungeonRaceAffixAvoidance.ts
T
2026-09-27 14:31:15 -04:00

51 lines
2.6 KiB
TypeScript

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;
}