Files
healer-man/src/game/mobRuntimeRegistry.ts
T
2026-08-18 16:22:19 -04:00

115 lines
4.0 KiB
TypeScript

export type CombatPosition = readonly [x: number, y: number, z: number];
export type MutableCombatPosition = [x: number, y: number, z: number];
interface RuntimeMobPosition {
readonly value: MutableCombatPosition;
yaw: number;
mounted: boolean;
}
const runtimePositions = new Map<string, RuntimeMobPosition>();
function finite(value: number): number {
return Number.isFinite(value) ? value : 0;
}
export function registerMobRuntime(id: string, initial: CombatPosition = [0, 0, 0], yaw = 0): () => void {
const record: RuntimeMobPosition = {
value: [finite(initial[0]), finite(initial[1]), finite(initial[2])],
yaw: finite(yaw),
mounted: true,
};
runtimePositions.set(id, record);
return () => {
if (runtimePositions.get(id) === record) runtimePositions.delete(id);
};
}
/** Allocation-free update intended for roaming-pack useFrame callbacks. */
export function updateMobRuntimePosition(id: string, x: number, y: number, z: number, yaw?: number): void {
const record = runtimePositions.get(id);
if (record) {
record.value[0] = finite(x);
record.value[1] = finite(y);
record.value[2] = finite(z);
if (yaw !== undefined) record.yaw = finite(yaw);
record.mounted = true;
return;
}
runtimePositions.set(id, { value: [finite(x), finite(y), finite(z)], yaw: finite(yaw ?? 0), mounted: true });
}
export function unregisterMobRuntime(id: string): void {
runtimePositions.delete(id);
}
/** Copies into caller-owned storage to avoid allocations in simulation ticks. */
export function readMobRuntimePosition(id: string, out: MutableCombatPosition): boolean {
const record = runtimePositions.get(id);
if (!record?.mounted) return false;
out[0] = record.value[0];
out[1] = record.value[1];
out[2] = record.value[2];
return true;
}
/** Snapshot helper for UI and occasional targeting operations. */
export function getMobRuntimePosition(id: string): CombatPosition | null {
const record = runtimePositions.get(id);
return record?.mounted ? [...record.value] as MutableCombatPosition : null;
}
export function getMobRuntimeTransform(id: string): { readonly position: CombatPosition; readonly yaw: number } | null {
const record = runtimePositions.get(id);
return record?.mounted ? { position: [...record.value] as MutableCombatPosition, yaw: record.yaw } : null;
}
export function mobRuntimeDistanceSquared(id: string, origin: CombatPosition): number {
const record = runtimePositions.get(id);
if (!record?.mounted) return Number.POSITIVE_INFINITY;
const dx = record.value[0] - origin[0];
const dy = record.value[1] - origin[1];
const dz = record.value[2] - origin[2];
return dx * dx + dy * dy + dz * dz;
}
export function nearbyMobRuntimeIds(origin: CombatPosition, maxDistance: number): string[] {
const limit = Math.max(0, maxDistance);
const limitSquared = limit * limit;
return Array.from(runtimePositions.entries())
.filter(([, record]) => {
if (!record.mounted) return false;
const dx = record.value[0] - origin[0];
const dy = record.value[1] - origin[1];
const dz = record.value[2] - origin[2];
return dx * dx + dy * dy + dz * dz <= limitSquared;
})
.sort((left, right) => {
const leftDistance = mobRuntimeDistanceSquared(left[0], origin);
const rightDistance = mobRuntimeDistanceSquared(right[0], origin);
return leftDistance - rightDistance || left[0].localeCompare(right[0]);
})
.map(([id]) => id);
}
export function hasMobRuntimePosition(id: string): boolean {
return runtimePositions.get(id)?.mounted ?? false;
}
export function clearMobRuntimeRegistry(): void {
runtimePositions.clear();
}
// Short integration aliases requested by the scene bridge.
export function setMobPosition(id: string, position: CombatPosition): void {
updateMobRuntimePosition(id, position[0], position[1], position[2]);
}
export function getMobPosition(id: string): CombatPosition | null {
return getMobRuntimePosition(id);
}
export function removeMobPosition(id: string): void {
unregisterMobRuntime(id);
}