115 lines
4.0 KiB
TypeScript
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);
|
|
}
|