From feca7549ad5fa494a0f032cc8ac2e928aa5e293d Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Sat, 23 May 2026 13:46:17 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20unify=20guardian=20system=20=E2=80=94?= =?UTF-8?q?=20merge=20static=20GUARDIANS=20with=20extended=20procedural=20?= =?UTF-8?q?guardians=20in=20Pacts=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - guardian-encounters.ts: add getGuardianForFloor() and getAllGuardianFloors() unified lookup functions that merge static GUARDIANS (floors 10-100) with extended system (compound 110, exotic 120-140, combo 150+) - GuardianPactsTab.tsx: use unified system, update tiers to cover all floors (Early 10-40, Mid 50-80, Late 90-100, Compound 110, Exotic 120-140, Transcendent 150+) - guardian-pacts-components.tsx: handle combo guardians with dual-element display (symbols + names + '✦ Combo' badge) - docs/circular-deps.txt, docs/dependency-graph.json: auto-generated updates - craftingStore.ts: extract initial equipment instances to crafting-initial-state.ts --- docs/circular-deps.txt | 4 +- docs/dependency-graph.json | 10 ++- src/components/game/tabs/GuardianPactsTab.tsx | 34 +++++++--- .../game/tabs/guardian-pacts-components.tsx | 47 +++++++++++--- src/lib/game/data/guardian-encounters.ts | 33 ++++++++-- src/lib/game/stores/crafting-initial-state.ts | 63 +++++++++++++++++++ src/lib/game/stores/craftingStore.ts | 58 ++--------------- 7 files changed, 171 insertions(+), 78 deletions(-) create mode 100644 src/lib/game/stores/crafting-initial-state.ts diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 3a6cc24..05ce645 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,8 +1,8 @@ # Circular Dependencies -Generated: 2026-05-22T07:19:25.482Z +Generated: 2026-05-22T16:18:31.266Z Found: 4 circular chain(s) — these MUST be fixed before modifying involved files. -1. Processed 128 files (1.6s) (3 warnings) +1. Processed 129 files (1.5s) (3 warnings) 2. 1) stores/gameStore.ts > stores/gameActions.ts 3. 2) stores/gameStore.ts > stores/gameLoopActions.ts 4. 3) stores/gameStore.ts > stores/tick-pipeline.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 0fc12fc..d7ea8a6 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-22T07:19:23.720Z", + "generated": "2026-05-22T16:18:29.615Z", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." }, @@ -426,13 +426,18 @@ "utils/room-utils.ts", "utils/safe-persist.ts" ], + "stores/crafting-initial-state.ts": [ + "crafting-utils.ts" + ], "stores/craftingStore.ts": [ "crafting-actions/application-actions.ts", + "crafting-actions/equipment-actions.ts", "crafting-actions/preparation-actions.ts", "crafting-design.ts", "crafting-equipment.ts", "crafting-utils.ts", "stores/combatStore.ts", + "stores/crafting-initial-state.ts", "stores/craftingStore.types.ts", "stores/manaStore.ts", "stores/uiStore.ts", @@ -442,7 +447,8 @@ "utils/safe-persist.ts" ], "stores/craftingStore.types.ts": [ - "types.ts" + "types.ts", + "types/equipmentSlot.ts" ], "stores/discipline-slice.ts": [ "data/disciplines/base.ts", diff --git a/src/components/game/tabs/GuardianPactsTab.tsx b/src/components/game/tabs/GuardianPactsTab.tsx index 499e081..bb0c53c 100644 --- a/src/components/game/tabs/GuardianPactsTab.tsx +++ b/src/components/game/tabs/GuardianPactsTab.tsx @@ -5,7 +5,8 @@ import { useShallow } from 'zustand/react/shallow'; import { usePrestigeStore } from '@/lib/game/stores/prestigeStore'; import { useManaStore } from '@/lib/game/stores/manaStore'; import { useUIStore } from '@/lib/game/stores/uiStore'; -import { GUARDIANS } from '@/lib/game/constants'; +import { getGuardianForFloor, getAllGuardianFloors } from '@/lib/game/data/guardian-encounters'; +import type { GuardianDef } from '@/lib/game/types'; import { DebugName } from '@/components/game/debug/debug-context'; import { ScrollArea } from '@/components/ui/scroll-area'; import { @@ -32,13 +33,19 @@ interface FloorTier { function groupFloorsByTier(floors: number[]): FloorTier[] { const tiers: FloorTier[] = [ { label: 'Early Spire (10–40)', floors: [] }, - { label: 'Mid Spire (50–60)', floors: [] }, - { label: 'Late Spire (80–100)', floors: [] }, + { label: 'Mid Spire (50–80)', floors: [] }, + { label: 'Late Spire (90–100)', floors: [] }, + { label: 'Compound (110)', floors: [] }, + { label: 'Exotic (120–140)', floors: [] }, + { label: 'Transcendent (150+)', floors: [] }, ]; for (const f of floors) { if (f <= 40) tiers[0].floors.push(f); - else if (f <= 60) tiers[1].floors.push(f); - else tiers[2].floors.push(f); + else if (f <= 80) tiers[1].floors.push(f); + else if (f <= 100) tiers[2].floors.push(f); + else if (f <= 110) tiers[3].floors.push(f); + else if (f <= 140) tiers[4].floors.push(f); + else tiers[5].floors.push(f); } return tiers.filter(t => t.floors.length > 0); } @@ -73,10 +80,19 @@ export const GuardianPactsTab: React.FC = () => { }, []); const guardianFloors = useMemo( - () => Object.keys(GUARDIANS).map(Number).sort((a, b) => a - b), + () => getAllGuardianFloors(), [], ); + const guardianMap = useMemo(() => { + const map: Record = {}; + for (const floor of guardianFloors) { + const g = getGuardianForFloor(floor); + if (g) map[floor] = g; + } + return map; + }, [guardianFloors]); + const tiers = useMemo(() => groupFloorsByTier(guardianFloors), [guardianFloors]); const filteredFloors = useMemo(() => { @@ -86,7 +102,7 @@ export const GuardianPactsTab: React.FC = () => { }, [activeTier, guardianFloors, tiers]); const handleStartRitual = useCallback((floor: number) => { - const guardian = GUARDIANS[floor]; + const guardian = getGuardianForFloor(floor); if (!guardian) return; const result = startPactRitual(floor, rawMana); @@ -100,7 +116,7 @@ export const GuardianPactsTab: React.FC = () => { const cumulativeBoons = useMemo(() => { const boonMap: Record = {}; for (const floor of signedPacts) { - const guardian = GUARDIANS[floor]; + const guardian = getGuardianForFloor(floor); if (!guardian) continue; for (const boon of guardian.boons) { boonMap[boon.type] = (boonMap[boon.type] || 0) + boon.value; @@ -137,7 +153,7 @@ export const GuardianPactsTab: React.FC = () => {
{filteredFloors.map((floor) => { - const guardian = GUARDIANS[floor]; + const guardian = guardianMap[floor]; if (!guardian) return null; return ( { + const def = ELEMENTS[el]; + return { + sym: def?.sym ?? '?', + name: def?.name ?? el, + color: def?.color ?? '#888', + }; + }); +} + // ─── Guardian Card ─────────────────────────────────────────────────────────── interface GuardianCardProps { @@ -40,9 +61,9 @@ export const GuardianCard: React.FC = React.memo(({ ritualProgress, onStartRitual, }) => { - const elemDef = ELEMENTS[guardian.element]; - const elemColor = elemDef?.color ?? '#888'; - const elemSym = elemDef?.sym ?? ''; + const elemDisplays = getElementDisplays(guardian.element); + const primaryColor = elemDisplays[0]?.color ?? '#888'; + const isCombo = elemDisplays.length > 1; const statusConfig: Record = { undefeated: { label: 'Undefeated', color: 'text-gray-400', bg: 'bg-gray-800/50' }, @@ -54,6 +75,11 @@ export const GuardianCard: React.FC = React.memo(({ const ritualTime = guardian.pactTime; const ritualComplete = ritualProgress >= ritualTime; + // Build element label: single element name, or "Fire + Water" for combos + const elementLabel = isCombo + ? elemDisplays.map(e => e.name).join(' + ') + : elemDisplays[0]?.name ?? guardian.element; + return ( = React.memo(({
- - {elemSym} + + + {elemDisplays.map((e, i) => ( + {e.sym} + ))} + {guardian.name} -
Floor {floor} · {elemDef?.name ?? guardian.element}
+
+ Floor {floor} · {elementLabel} + {isCombo && ✦ Combo} +
{sc.label} diff --git a/src/lib/game/data/guardian-encounters.ts b/src/lib/game/data/guardian-encounters.ts index 6405708..b8bc826 100644 --- a/src/lib/game/data/guardian-encounters.ts +++ b/src/lib/game/data/guardian-encounters.ts @@ -264,11 +264,36 @@ export function getExtendedGuardian(floor: number): GuardianDef | null { return null; } -// All guardian floors (extended) +// ─── Unified Guardian System ───────────────────────────────────────────────── +// Merges the static GUARDIANS constant (floors 10–100) with the extended +// procedural system (compound 110, exotic 120–140, combo 150+). +// For floors 90–100 the static definitions take precedence (canonical names). + +import { GUARDIANS } from '../constants/guardians'; + +/** Get the guardian for any floor, merging static and extended systems. */ +export function getGuardianForFloor(floor: number): GuardianDef | null { + // Static GUARDIANS take precedence for floors 10–100 (canonical definitions) + if (GUARDIANS[floor]) { + return { ...GUARDIANS[floor] }; + } + // Extended system for floors beyond 100 (and compound floors not in GUARDIANS) + return getExtendedGuardian(floor); +} + +/** All guardian floors — merged from static + extended. */ +export function getAllGuardianFloors(): number[] { + const staticFloors = Object.keys(GUARDIANS).map(Number); + const extendedFloors = [110, 120, 130, 140, ...Array.from({ length: 10 }, (_, i) => 150 + i * 10)]; + const all = new Set([...staticFloors, ...extendedFloors]); + return Array.from(all).sort((a, b) => a - b); +} + +// All guardian floors (extended — kept for backwards compatibility) export const ALL_GUARDIAN_FLOORS: number[] = [ - 10, 20, 30, 40, 50, 60, 80, 100, // Original - 90, 110, // Compound - 120, 130, 140, // Exotic + 10, 20, 30, 40, 50, 60, 80, 90, 100, // Original (all static floors) + 110, // Compound (90,100 already in static) + 120, 130, 140, // Exotic ...Array.from({ length: 10 }, (_, i) => 150 + i * 10), // Combo ].sort((a, b) => a - b); diff --git a/src/lib/game/stores/crafting-initial-state.ts b/src/lib/game/stores/crafting-initial-state.ts new file mode 100644 index 0000000..426e7cc --- /dev/null +++ b/src/lib/game/stores/crafting-initial-state.ts @@ -0,0 +1,63 @@ +// ─── Crafting Store Initial State ───────────────────────────────────────────── +// Helper to generate the starting equipment instances for new games. + +import * as CraftingUtils from '../crafting-utils'; + +export function createInitialEquipmentInstances() { + const staffId = CraftingUtils.generateInstanceId(); + const staffInstance = { + instanceId: staffId, + typeId: 'basicStaff', + name: 'Basic Staff', + enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }], + usedCapacity: 50, + totalCapacity: 50, + rarity: 'common', + quality: 100, + tags: [], + }; + + const shirtId = CraftingUtils.generateInstanceId(); + const shirtInstance = { + instanceId: shirtId, + typeId: 'civilianShirt', + name: 'Civilian Shirt', + enchantments: [], + usedCapacity: 0, + totalCapacity: 30, + rarity: 'common', + quality: 100, + tags: [], + }; + + const shoesId = CraftingUtils.generateInstanceId(); + const shoesInstance = { + instanceId: shoesId, + typeId: 'civilianShoes', + name: 'Civilian Shoes', + enchantments: [], + usedCapacity: 0, + totalCapacity: 15, + rarity: 'common', + quality: 100, + tags: [], + }; + + return { + instances: { + [staffId]: staffInstance, + [shirtId]: shirtInstance, + [shoesId]: shoesInstance, + } as Record, + equippedInstances: { + mainHand: staffId, + offHand: null, + head: null, + body: shirtId, + hands: null, + feet: shoesId, + accessory1: null, + accessory2: null, + } as Record, + }; +} diff --git a/src/lib/game/stores/craftingStore.ts b/src/lib/game/stores/craftingStore.ts index fcd2a15..cb9277f 100644 --- a/src/lib/game/stores/craftingStore.ts +++ b/src/lib/game/stores/craftingStore.ts @@ -17,49 +17,12 @@ import { equipItem as equipItemAction, unequipItem as unequipItemAction } from ' import { ErrorCode } from '../utils/result'; import { createSafeStorage } from '../utils/safe-persist'; import type { Result } from '../utils/result'; +import { createInitialEquipmentInstances } from './crafting-initial-state'; export const useCraftingStore = create()( persist( (set, get) => { - const staffId = CraftingUtils.generateInstanceId(); - const staffInstance = { - instanceId: staffId, - typeId: 'basicStaff', - name: 'Basic Staff', - enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }], - usedCapacity: 50, - totalCapacity: 50, - rarity: 'common', - quality: 100, - tags: [], - }; - - const shirtId = CraftingUtils.generateInstanceId(); - const shirtInstance = { - instanceId: shirtId, - typeId: 'civilianShirt', - name: 'Civilian Shirt', - enchantments: [], - usedCapacity: 0, - totalCapacity: 30, - rarity: 'common', - quality: 100, - tags: [], - }; - - const shoesId = CraftingUtils.generateInstanceId(); - const shoesInstance = { - instanceId: shoesId, - typeId: 'civilianShoes', - name: 'Civilian Shoes', - enchantments: [], - usedCapacity: 0, - totalCapacity: 15, - rarity: 'common', - quality: 100, - tags: [], - }; - + const initial = createInitialEquipmentInstances(); return { // Initial state designProgress: null, @@ -69,21 +32,8 @@ export const useCraftingStore = create()( equipmentCraftingProgress: null, enchantmentDesigns: [], unlockedEffects: [], - equippedInstances: { - mainHand: staffId, - offHand: null, - head: null, - body: shirtId, - hands: null, - feet: shoesId, - accessory1: null, - accessory2: null, - }, - equipmentInstances: { - [staffId]: staffInstance, - [shirtId]: shirtInstance, - [shoesId]: shoesInstance, - }, + equippedInstances: initial.equippedInstances, + equipmentInstances: initial.instances, lootInventory: { materials: {}, blueprints: [],