Files
healer-man/src/game/mobMotion.ts
T
2026-08-14 15:56:39 -04:00

79 lines
2.6 KiB
TypeScript

import type { MobVector3, MutableMobVector3 } from "./mobPopulation";
const POSITION_EPSILON = 1e-6;
function finitePosition(position: MobVector3): boolean {
return Number.isFinite(position[0])
&& Number.isFinite(position[1])
&& Number.isFinite(position[2]);
}
/**
* Moves a feared mob away from its threat on the dungeon plane while keeping
* it inside its home leash. The caller may reuse `current` as `out`; no
* per-frame objects are allocated.
*/
export function stepFearedMobAway(
current: MobVector3,
threat: MobVector3,
home: MobVector3,
maximumStep: number,
leashRange: number,
out: MutableMobVector3,
): number {
if (!finitePosition(current) || !finitePosition(threat) || !finitePosition(home)) {
out[0] = Number.isFinite(current[0]) ? current[0] : 0;
out[1] = Number.isFinite(current[1]) ? current[1] : 0;
out[2] = Number.isFinite(current[2]) ? current[2] : 0;
return 0;
}
const safeStep = Number.isFinite(maximumStep) ? Math.max(0, maximumStep) : 0;
const safeLeash = Number.isFinite(leashRange) ? Math.max(0, leashRange) : 0;
if (safeStep <= 0 || safeLeash <= 0) {
out[0] = current[0];
out[1] = current[1];
out[2] = current[2];
return 0;
}
// Capture these before writing because high-frequency callers intentionally
// pass the same tuple for `current` and `out`.
const currentX = current[0];
const currentY = current[1];
const currentZ = current[2];
let awayX = currentX - threat[0];
let awayZ = currentZ - threat[2];
let awayLength = Math.hypot(awayX, awayZ);
if (awayLength <= POSITION_EPSILON) {
// Coincident threat positions need a stable escape direction. Prefer the
// direction already leading away from home; use +X only at home itself.
awayX = currentX - home[0];
awayZ = currentZ - home[2];
awayLength = Math.hypot(awayX, awayZ);
if (awayLength <= POSITION_EPSILON) {
awayX = 1;
awayZ = 0;
awayLength = 1;
}
}
const candidateX = currentX + awayX / awayLength * safeStep;
const candidateZ = currentZ + awayZ / awayLength * safeStep;
const homeX = candidateX - home[0];
const homeZ = candidateZ - home[2];
const candidateHomeDistance = Math.hypot(homeX, homeZ);
if (candidateHomeDistance > safeLeash) {
const leashScale = safeLeash / candidateHomeDistance;
out[0] = home[0] + homeX * leashScale;
out[2] = home[2] + homeZ * leashScale;
} else {
out[0] = candidateX;
out[2] = candidateZ;
}
// Dungeon floor height remains owned by the sampled spawn/path position.
out[1] = currentY;
return Math.hypot(out[0] - currentX, out[2] - currentZ);
}