feat: restructure guardian progression system with dynamic element support
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s

This commit is contained in:
2026-05-29 17:18:13 +02:00
parent 644b76f16d
commit 71c68443c4
19 changed files with 757 additions and 446 deletions
+94
View File
@@ -0,0 +1,94 @@
// ─── 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);
}