Release Healer Man 0.1.46
This commit is contained in:
1 parent
f82daf1c13
commit
9ff574ac0b
73 files changed
+33486
-174
No files matched your search
@@ -0,0 +1,108 @@
|
||||
import rawCatalog from "./generated/dungeonRouteCatalog.generated.json";
|
||||
import type {
|
||||
DungeonRouteApproval,
|
||||
DungeonRouteCatalogV1,
|
||||
DungeonRouteDefinitionV1,
|
||||
DungeonRouteLeg,
|
||||
} from "./dungeonSimulationTypes";
|
||||
import type { Vector3Tuple } from "./dungeonTypes";
|
||||
|
||||
export const DUNGEON_ROUTE_CATALOG = rawCatalog as unknown as DungeonRouteCatalogV1;
|
||||
export const DUNGEON_ROUTE_APPROVAL_CACHE_KEY = "healer-man.dungeon-route-approvals.v1";
|
||||
|
||||
const routesByDungeon = new Map(
|
||||
DUNGEON_ROUTE_CATALOG.routes.map((route) => [route.dungeonId, route] as const),
|
||||
);
|
||||
|
||||
export function dungeonRouteDefinition(dungeonId: string, routeId = "primary"): DungeonRouteDefinitionV1 | null {
|
||||
const route = routesByDungeon.get(dungeonId) ?? null;
|
||||
return route?.id === routeId ? route : null;
|
||||
}
|
||||
|
||||
export function dungeonRouteLeg(
|
||||
route: DungeonRouteDefinitionV1 | null,
|
||||
objectiveId: string | null,
|
||||
): DungeonRouteLeg | null {
|
||||
if (!route || !objectiveId) return null;
|
||||
return route.legs.find((leg) => leg.objectiveId === objectiveId) ?? null;
|
||||
}
|
||||
|
||||
export function routeDistance(points: readonly Vector3Tuple[]): number {
|
||||
let total = 0;
|
||||
for (let index = 1; index < points.length; index += 1) {
|
||||
total += Math.hypot(
|
||||
points[index][0] - points[index - 1][0],
|
||||
points[index][1] - points[index - 1][1],
|
||||
points[index][2] - points[index - 1][2],
|
||||
);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
export function routePointDistanceSquared(point: Vector3Tuple, start: Vector3Tuple, end: Vector3Tuple): {
|
||||
readonly planarSquared: number;
|
||||
readonly vertical: number;
|
||||
readonly progress: number;
|
||||
} {
|
||||
const dx = end[0] - start[0];
|
||||
const dz = end[2] - start[2];
|
||||
const lengthSquared = dx * dx + dz * dz;
|
||||
const progress = lengthSquared > 1e-9
|
||||
? Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[2] - start[2]) * dz) / lengthSquared))
|
||||
: 0;
|
||||
const projectedX = start[0] + dx * progress;
|
||||
const projectedY = start[1] + (end[1] - start[1]) * progress;
|
||||
const projectedZ = start[2] + dz * progress;
|
||||
return {
|
||||
planarSquared: (point[0] - projectedX) ** 2 + (point[2] - projectedZ) ** 2,
|
||||
vertical: Math.abs(point[1] - projectedY),
|
||||
progress,
|
||||
};
|
||||
}
|
||||
|
||||
export function positionAlongRouteCorridor(
|
||||
point: Vector3Tuple,
|
||||
checkpoints: readonly Vector3Tuple[],
|
||||
planarLimit = 10,
|
||||
verticalLimit = 4,
|
||||
): boolean {
|
||||
for (let index = 1; index < checkpoints.length; index += 1) {
|
||||
const distance = routePointDistanceSquared(point, checkpoints[index - 1], checkpoints[index]);
|
||||
if (distance.planarSquared <= planarLimit ** 2 && distance.vertical <= verticalLimit) return true;
|
||||
}
|
||||
return checkpoints.length === 1
|
||||
&& Math.hypot(point[0] - checkpoints[0][0], point[2] - checkpoints[0][2]) <= planarLimit
|
||||
&& Math.abs(point[1] - checkpoints[0][1]) <= verticalLimit;
|
||||
}
|
||||
|
||||
export function readCachedDungeonRouteApprovals(): readonly DungeonRouteApproval[] {
|
||||
if (typeof localStorage === "undefined") return [];
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(DUNGEON_ROUTE_APPROVAL_CACHE_KEY) ?? "null");
|
||||
if (!Array.isArray(parsed?.approvals)) return [];
|
||||
return parsed.approvals.filter((approval: unknown): approval is DungeonRouteApproval => {
|
||||
if (!approval || typeof approval !== "object") return false;
|
||||
const value = approval as Partial<DungeonRouteApproval>;
|
||||
return typeof value.dungeonId === "string" && typeof value.routeId === "string"
|
||||
&& /^[a-f0-9]{64}$/.test(value.routeHash ?? "") && Number.isFinite(value.reviewedAt);
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function cacheDungeonRouteApprovals(approvals: readonly DungeonRouteApproval[]): void {
|
||||
if (typeof localStorage === "undefined") return;
|
||||
localStorage.setItem(DUNGEON_ROUTE_APPROVAL_CACHE_KEY, JSON.stringify({ approvals }));
|
||||
}
|
||||
|
||||
export function routeHasExactApproval(
|
||||
route: DungeonRouteDefinitionV1 | null,
|
||||
approvals: readonly DungeonRouteApproval[],
|
||||
): boolean {
|
||||
return Boolean(route?.generationStatus === "Candidate" && approvals.some((approval) => (
|
||||
approval.dungeonId === route.dungeonId
|
||||
&& approval.routeId === route.id
|
||||
&& approval.routeHash === route.hash
|
||||
)));
|
||||
}
|
||||
Reference in new issue
Block a user