51 lines
2.6 KiB
TypeScript
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;
|
|
}
|