forked from phenom/healer-man
Release Healer Man 0.1.6
This commit is contained in:
1 parent
55cfd43d66
commit
b12fb84859
96 files changed
+10874
-609
No files matched your search
@@ -0,0 +1,86 @@
|
||||
export type CombatSpatialPosition = readonly [x: number, y: number, z: number];
|
||||
|
||||
export interface CombatSpatialActorSnapshot {
|
||||
readonly id: string;
|
||||
readonly position: CombatSpatialPosition;
|
||||
/** World-space yaw in radians; +Z is forward. */
|
||||
readonly yaw: number;
|
||||
}
|
||||
|
||||
export interface CombatSpatialProvider {
|
||||
readonly actor: (actorId: string) => CombatSpatialActorSnapshot | null;
|
||||
readonly hasLineOfSight: (
|
||||
source: CombatSpatialPosition,
|
||||
target: CombatSpatialPosition,
|
||||
) => boolean;
|
||||
readonly projectGroundPoint: (
|
||||
requested: CombatSpatialPosition,
|
||||
maximumDistance: number,
|
||||
origin?: CombatSpatialPosition,
|
||||
) => CombatSpatialPosition | null;
|
||||
}
|
||||
|
||||
let provider: CombatSpatialProvider | null = null;
|
||||
|
||||
/** Registers the loaded dungeon's read-only spatial query seam. */
|
||||
export function registerCombatSpatialProvider(next: CombatSpatialProvider): () => void {
|
||||
provider = next;
|
||||
return () => {
|
||||
if (provider === next) provider = null;
|
||||
};
|
||||
}
|
||||
|
||||
export function resetCombatSpatialProvider(): void {
|
||||
provider = null;
|
||||
}
|
||||
|
||||
export function combatSpatialActor(actorId: string): CombatSpatialActorSnapshot | null {
|
||||
return provider?.actor(actorId) ?? null;
|
||||
}
|
||||
|
||||
export function combatHasLineOfSight(
|
||||
source: CombatSpatialPosition,
|
||||
target: CombatSpatialPosition,
|
||||
): boolean {
|
||||
return provider?.hasLineOfSight(source, target) ?? true;
|
||||
}
|
||||
|
||||
export function projectCombatGroundPoint(
|
||||
requested: CombatSpatialPosition,
|
||||
maximumDistance: number,
|
||||
origin?: CombatSpatialPosition,
|
||||
): CombatSpatialPosition | null {
|
||||
if (!requested.every(Number.isFinite) || !Number.isFinite(maximumDistance) || maximumDistance < 0) return null;
|
||||
return provider ? provider.projectGroundPoint(requested, maximumDistance, origin) : requested;
|
||||
}
|
||||
|
||||
function normalizedPlanarDirection(
|
||||
from: CombatSpatialPosition,
|
||||
to: CombatSpatialPosition,
|
||||
): readonly [number, number] | null {
|
||||
const dx = to[0] - from[0];
|
||||
const dz = to[2] - from[2];
|
||||
const length = Math.hypot(dx, dz);
|
||||
return length > 1e-5 ? [dx / length, dz / length] : null;
|
||||
}
|
||||
|
||||
export function isPositionInFacingArc(
|
||||
source: CombatSpatialActorSnapshot,
|
||||
target: CombatSpatialPosition,
|
||||
arcRadians = Math.PI,
|
||||
): boolean {
|
||||
const direction = normalizedPlanarDirection(source.position, target);
|
||||
if (!direction) return true;
|
||||
const forwardX = Math.sin(source.yaw);
|
||||
const forwardZ = Math.cos(source.yaw);
|
||||
const cosine = forwardX * direction[0] + forwardZ * direction[1];
|
||||
return cosine >= Math.cos(Math.max(0, Math.min(Math.PI * 2, arcRadians)) / 2);
|
||||
}
|
||||
|
||||
export function isSourceBehindTarget(
|
||||
source: CombatSpatialPosition,
|
||||
target: CombatSpatialActorSnapshot,
|
||||
rearArcRadians = Math.PI,
|
||||
): boolean {
|
||||
return !isPositionInFacingArc(target, source, Math.PI * 2 - rearArcRadians);
|
||||
}
|
||||
Reference in new issue
Block a user