95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
// ─── Guardian Utilities ─────────────────────────────────────────────────────────
|
|
// Helper functions for guardian element resolution and name generation.
|
|
|
|
import { ELEMENTS } from '../constants/elements';
|
|
|
|
// ─── Element Chain Resolution ──────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Resolve the full component chain for an element.
|
|
* Walks the recipe tree from ELEMENTS to collect all base elements + the element itself.
|
|
*
|
|
* Examples:
|
|
* resolveUnlockChain('fire') → ['fire']
|
|
* resolveUnlockChain('metal') → ['fire', 'earth', 'metal']
|
|
* resolveUnlockChain('crystal') → ['earth', 'water', 'light', 'sand', 'crystal']
|
|
* resolveUnlockChain('stellar') → ['fire', 'light', 'stellar']
|
|
* resolveUnlockChain('void') → ['dark', 'death', 'void']
|
|
*/
|
|
export function resolveUnlockChain(element: string): string[] {
|
|
const result: string[] = [];
|
|
const visited = new Set<string>();
|
|
const queue: string[] = [element];
|
|
|
|
while (queue.length > 0) {
|
|
const current = queue.shift()!;
|
|
if (visited.has(current)) continue;
|
|
visited.add(current);
|
|
|
|
const def = ELEMENTS[current];
|
|
if (!def) continue;
|
|
|
|
if (def.recipe) {
|
|
for (const component of def.recipe) {
|
|
if (!visited.has(component)) {
|
|
queue.push(component);
|
|
}
|
|
}
|
|
} else {
|
|
// Base, utility, or raw element — no recipe, it's a leaf
|
|
result.push(current);
|
|
}
|
|
}
|
|
|
|
// Always include the element itself
|
|
if (!result.includes(element)) {
|
|
result.push(element);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Resolve unlock chains for multiple elements (union, deduped).
|
|
* Used when a guardian has multiple elements.
|
|
*
|
|
* Example:
|
|
* resolveMultiUnlockChain(['fire', 'earth']) → ['fire', 'earth']
|
|
* resolveMultiUnlockChain(['metal', 'sand']) → ['fire', 'earth', 'water', 'metal', 'sand']
|
|
*/
|
|
export function resolveMultiUnlockChain(elements: string[]): string[] {
|
|
const all = new Set<string>();
|
|
for (const el of elements) {
|
|
for (const resolved of resolveUnlockChain(el)) {
|
|
all.add(resolved);
|
|
}
|
|
}
|
|
return Array.from(all);
|
|
}
|
|
|
|
// ─── Dynamic Guardian Floor Computation ────────────────────────────────────────
|
|
|
|
const TIER_CONFIG = [
|
|
// [startFloor, endFloor, tiersPerFloor, description]
|
|
{ start: 10, end: 80, spacing: 10 }, // Base + utility: floors 10-80
|
|
{ start: 90, end: 120, spacing: 10 }, // Composite: floors 90-120
|
|
{ start: 130, end: 160, spacing: 10 }, // Composite + Components
|
|
{ start: 170, end: 200, spacing: 10 }, // Exotic
|
|
{ start: 210, end: 240, spacing: 10 }, // Dual Element
|
|
{ start: 250, end: 290, spacing: 10 }, // Dual Composite + Components
|
|
{ start: 300, end: 340, spacing: 10 }, // Exotic + Components
|
|
{ start: 350, end: 390, spacing: 10 }, // Exotic + Composite + Components
|
|
{ start: 400, end: 450, spacing: 10 }, // 1 Exotic + 2 Composite + All Components
|
|
] as const;
|
|
|
|
/** Get all guardian floors from a given start, dynamically computed. */
|
|
export function computeGuardianFloors(maxFloor: number = 450): number[] {
|
|
const floors: number[] = [];
|
|
for (const tier of TIER_CONFIG) {
|
|
for (let f = tier.start; f <= Math.min(tier.end, maxFloor); f += tier.spacing) {
|
|
floors.push(f);
|
|
}
|
|
}
|
|
return floors.sort((a, b) => a - b);
|
|
}
|