From fe0f2a079c1340872458412cb10635da045ed3b5 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Sat, 16 May 2026 11:20:11 +0200 Subject: [PATCH] Completely remove legacy skill system and tests --- docs/circular-deps.txt | 9 +- docs/dependency-graph.json | 99 +--- docs/project-structure.txt | 141 +---- src/components/game/SkillsTab.tsx | 57 -- src/components/game/debug/SkillDebug.tsx | 265 ---------- src/components/game/tabs/AchievementsTab.tsx | 35 -- src/components/game/tabs/ActivityLog.tsx | 69 --- src/components/game/tabs/AttunementsTab.tsx | 270 ---------- .../game/tabs/CategorySkillsList.tsx | 120 ----- src/components/game/tabs/CombatStatsPanel.tsx | 170 ------ src/components/game/tabs/CraftingTab.tsx | 269 ---------- src/components/game/tabs/DebugTab.tsx | 32 -- .../game/tabs/EnchantmentsPanel.tsx | 48 -- .../game/tabs/EquipmentControls.tsx | 74 --- .../game/tabs/EquipmentInventory.tsx | 185 ------- .../game/tabs/EquipmentSlotGrid.tsx | 237 --------- src/components/game/tabs/EquipmentTab.tsx | 386 -------------- src/components/game/tabs/FloorControls.tsx | 217 -------- src/components/game/tabs/GolemancyTab.tsx | 321 ------------ src/components/game/tabs/GuardianPanel.tsx | 53 -- src/components/game/tabs/LootTab.tsx | 29 -- .../game/tabs/MilestoneProgress.tsx | 22 - src/components/game/tabs/PrestigeTab.tsx | 115 ---- src/components/game/tabs/RoomDisplay.tsx | 194 ------- .../game/tabs/SkillCategoryHeader.tsx | 42 -- src/components/game/tabs/SkillMultipliers.tsx | 50 -- src/components/game/tabs/SkillRow.tsx | 231 -------- src/components/game/tabs/SpellsTab.tsx | 244 --------- .../game/tabs/SpireActiveSpells.tsx | 100 ---- src/components/game/tabs/SpireGolems.tsx | 67 --- src/components/game/tabs/SpireHeader.tsx | 109 ---- src/components/game/tabs/SpireTab.tsx | 376 ------------- src/components/game/tabs/StatsTab.tsx | 331 ------------ src/components/game/tabs/StudyProgress.tsx | 75 --- src/components/game/tabs/UpgradeDialog.tsx | 117 ----- src/components/game/tabs/index.ts | 23 - src/components/ui/skill-row.tsx | 220 -------- src/lib/game/__tests__/skill-system.test.ts | 347 ------------ .../skills-tests/ascension-skills.test.ts | 122 ----- .../integration-and-evolution.test.ts | 95 ---- .../skills-tests/mana-skills.test.ts | 222 -------- .../skills-tests/prestige-upgrades.test.ts | 120 ----- .../skills-tests/skill-prerequisites.test.ts | 30 -- .../skills-tests/specialized-skills.test.ts | 64 --- .../skills-tests/study-skills.test.ts | 120 ----- .../skills-tests/study-times.test.ts | 24 - src/lib/game/__tests__/skills.test.ts | 20 - src/lib/game/constants/skills-combat.ts | 46 -- src/lib/game/constants/skills-core.ts | 86 --- src/lib/game/constants/skills-crafting.ts | 16 - src/lib/game/constants/skills-element-caps.ts | 70 --- src/lib/game/constants/skills-enchant.ts | 42 -- src/lib/game/constants/skills-golemancy.ts | 25 - src/lib/game/constants/skills-hybrid.ts | 28 - src/lib/game/constants/skills-invocation.ts | 25 - src/lib/game/constants/skills-research.ts | 385 -------------- src/lib/game/constants/skills-v2-defs.ts | 3 - src/lib/game/constants/skills-v2-registry.ts | 292 ----------- src/lib/game/constants/skills-v2-types.ts | 142 ----- src/lib/game/constants/skills-v2.ts | 269 ---------- src/lib/game/constants/skills.ts | 348 ------------- .../game/hooks/useSkillUpgradeSelection.ts | 83 --- .../ascension-specialized-skills.test.ts | 170 ------ .../skills-split-tests/mana-skills.test.ts | 224 -------- ...es-studytimes-prestige-integration.test.ts | 212 -------- .../skills-split-tests/study-skills.test.ts | 115 ---- src/lib/game/skills.test.ts | 16 - .../store-tests/damage-calculation.test.ts | 69 --- .../game/store-tests/element-recipes.test.ts | 32 -- src/lib/game/store-tests/floor.test.ts | 58 --- src/lib/game/store-tests/formatting.test.ts | 50 -- .../game/store-tests/game-constants.test.ts | 115 ---- .../store-tests/individual-skills.test.ts | 272 ---------- .../insight-meditation-incursion.test.ts | 116 ----- src/lib/game/store-tests/integration.test.ts | 43 -- .../game/store-tests/mana-calculation.test.ts | 92 ---- .../game/store-tests/skill-evolution.test.ts | 40 -- .../store-tests/skill-requirements.test.ts | 28 - src/lib/game/store-tests/spell-cost.test.ts | 60 --- src/lib/game/store-tests/study-speed.test.ts | 30 -- src/lib/game/store-tests/test-utils.ts | 72 --- src/lib/game/store/skillSlice.ts | 346 ------------ .../stores-split-tests/combat-store.test.ts | 64 --- .../stores-split-tests/integration.test.ts | 122 ----- .../stores-split-tests/mana-store.test.ts | 137 ----- .../stores-split-tests/prestige-store.test.ts | 110 ---- .../stores-split-tests/skill-store.test.ts | 176 ------- .../game/stores-split-tests/ui-store.test.ts | 67 --- .../__tests__/archive/store-methods.test.ts | 17 - .../stores/__tests__/archive/stores.test.ts | 25 - .../stores/__tests__/computed-stats.test.ts | 492 ------------------ .../game/stores/__tests__/equipment.test.ts | 106 ---- .../index-tests/combat-calculations.test.ts | 147 ------ .../__tests__/index-tests/definitions.test.ts | 98 ---- .../index-tests/mana-calculations.test.ts | 164 ------ .../meditation-insight-incursion.test.ts | 180 ------- .../__tests__/index-tests/spell-cost.test.ts | 53 -- .../__tests__/index-tests/study-speed.test.ts | 34 -- .../index-tests/utility-functions.test.ts | 39 -- .../__tests__/mana-conversion-fix.test.ts | 59 --- src/lib/game/stores/__tests__/mana.test.ts | 35 -- src/lib/game/stores/__tests__/regen.test.ts | 51 -- src/lib/game/stores/__tests__/skill.test.ts | 65 --- .../game/stores/__tests__/spell-cost.test.ts | 68 --- .../__tests__/spire-exit-action.test.ts | 64 --- .../__tests__/spire-tab-refresh.test.ts | 94 ---- .../store-method-tests/combat-store.test.ts | 93 ---- .../store-method-tests/mana-store.test.ts | 140 ----- .../store-method-tests/prestige-store.test.ts | 150 ------ .../store-method-tests/skill-store.test.ts | 172 ------ .../store-method-tests/ui-store.test.ts | 65 --- .../stores-tests/damage-calculation.test.ts | 96 ---- .../__tests__/stores-tests/floor.test.ts | 40 -- .../__tests__/stores-tests/formatting.test.ts | 49 -- .../__tests__/stores-tests/guardians.test.ts | 26 - .../__tests__/stores-tests/incursion.test.ts | 26 - .../stores-tests/insight-calculation.test.ts | 96 ---- .../stores-tests/mana-calculation.test.ts | 188 ------- .../__tests__/stores-tests/meditation.test.ts | 36 -- .../stores-tests/prestige-upgrades.test.ts | 17 - .../stores-tests/skill-definitions.test.ts | 24 - .../__tests__/stores-tests/spell-cost.test.ts | 41 -- .../stores-tests/spell-definitions.test.ts | 22 - .../stores-tests/study-speed.test.ts | 40 -- src/lib/game/types/skills.ts | 92 ---- 125 files changed, 7 insertions(+), 14459 deletions(-) delete mode 100755 src/components/game/SkillsTab.tsx delete mode 100644 src/components/game/debug/SkillDebug.tsx delete mode 100755 src/components/game/tabs/AchievementsTab.tsx delete mode 100644 src/components/game/tabs/ActivityLog.tsx delete mode 100755 src/components/game/tabs/AttunementsTab.tsx delete mode 100644 src/components/game/tabs/CategorySkillsList.tsx delete mode 100644 src/components/game/tabs/CombatStatsPanel.tsx delete mode 100755 src/components/game/tabs/CraftingTab.tsx delete mode 100755 src/components/game/tabs/DebugTab.tsx delete mode 100644 src/components/game/tabs/EnchantmentsPanel.tsx delete mode 100644 src/components/game/tabs/EquipmentControls.tsx delete mode 100644 src/components/game/tabs/EquipmentInventory.tsx delete mode 100644 src/components/game/tabs/EquipmentSlotGrid.tsx delete mode 100755 src/components/game/tabs/EquipmentTab.tsx delete mode 100644 src/components/game/tabs/FloorControls.tsx delete mode 100755 src/components/game/tabs/GolemancyTab.tsx delete mode 100644 src/components/game/tabs/GuardianPanel.tsx delete mode 100755 src/components/game/tabs/LootTab.tsx delete mode 100644 src/components/game/tabs/MilestoneProgress.tsx delete mode 100644 src/components/game/tabs/PrestigeTab.tsx delete mode 100644 src/components/game/tabs/RoomDisplay.tsx delete mode 100644 src/components/game/tabs/SkillCategoryHeader.tsx delete mode 100644 src/components/game/tabs/SkillMultipliers.tsx delete mode 100644 src/components/game/tabs/SkillRow.tsx delete mode 100755 src/components/game/tabs/SpellsTab.tsx delete mode 100644 src/components/game/tabs/SpireActiveSpells.tsx delete mode 100644 src/components/game/tabs/SpireGolems.tsx delete mode 100644 src/components/game/tabs/SpireHeader.tsx delete mode 100755 src/components/game/tabs/SpireTab.tsx delete mode 100644 src/components/game/tabs/StatsTab.tsx delete mode 100755 src/components/game/tabs/StudyProgress.tsx delete mode 100755 src/components/game/tabs/UpgradeDialog.tsx delete mode 100755 src/components/game/tabs/index.ts delete mode 100644 src/components/ui/skill-row.tsx delete mode 100644 src/lib/game/__tests__/skill-system.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/ascension-skills.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/integration-and-evolution.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/mana-skills.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/prestige-upgrades.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/skill-prerequisites.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/specialized-skills.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/study-skills.test.ts delete mode 100644 src/lib/game/__tests__/skills-tests/study-times.test.ts delete mode 100644 src/lib/game/__tests__/skills.test.ts delete mode 100644 src/lib/game/constants/skills-combat.ts delete mode 100644 src/lib/game/constants/skills-core.ts delete mode 100644 src/lib/game/constants/skills-crafting.ts delete mode 100644 src/lib/game/constants/skills-element-caps.ts delete mode 100644 src/lib/game/constants/skills-enchant.ts delete mode 100644 src/lib/game/constants/skills-golemancy.ts delete mode 100644 src/lib/game/constants/skills-hybrid.ts delete mode 100644 src/lib/game/constants/skills-invocation.ts delete mode 100644 src/lib/game/constants/skills-research.ts delete mode 100644 src/lib/game/constants/skills-v2-defs.ts delete mode 100644 src/lib/game/constants/skills-v2-registry.ts delete mode 100644 src/lib/game/constants/skills-v2-types.ts delete mode 100644 src/lib/game/constants/skills-v2.ts delete mode 100644 src/lib/game/constants/skills.ts delete mode 100644 src/lib/game/hooks/useSkillUpgradeSelection.ts delete mode 100644 src/lib/game/skills-split-tests/ascension-specialized-skills.test.ts delete mode 100644 src/lib/game/skills-split-tests/mana-skills.test.ts delete mode 100644 src/lib/game/skills-split-tests/prerequisites-studytimes-prestige-integration.test.ts delete mode 100644 src/lib/game/skills-split-tests/study-skills.test.ts delete mode 100755 src/lib/game/skills.test.ts delete mode 100644 src/lib/game/store-tests/damage-calculation.test.ts delete mode 100644 src/lib/game/store-tests/element-recipes.test.ts delete mode 100644 src/lib/game/store-tests/floor.test.ts delete mode 100644 src/lib/game/store-tests/formatting.test.ts delete mode 100644 src/lib/game/store-tests/game-constants.test.ts delete mode 100644 src/lib/game/store-tests/individual-skills.test.ts delete mode 100644 src/lib/game/store-tests/insight-meditation-incursion.test.ts delete mode 100644 src/lib/game/store-tests/integration.test.ts delete mode 100644 src/lib/game/store-tests/mana-calculation.test.ts delete mode 100644 src/lib/game/store-tests/skill-evolution.test.ts delete mode 100644 src/lib/game/store-tests/skill-requirements.test.ts delete mode 100644 src/lib/game/store-tests/spell-cost.test.ts delete mode 100644 src/lib/game/store-tests/study-speed.test.ts delete mode 100644 src/lib/game/store-tests/test-utils.ts delete mode 100755 src/lib/game/store/skillSlice.ts delete mode 100644 src/lib/game/stores-split-tests/combat-store.test.ts delete mode 100644 src/lib/game/stores-split-tests/integration.test.ts delete mode 100644 src/lib/game/stores-split-tests/mana-store.test.ts delete mode 100644 src/lib/game/stores-split-tests/prestige-store.test.ts delete mode 100644 src/lib/game/stores-split-tests/skill-store.test.ts delete mode 100644 src/lib/game/stores-split-tests/ui-store.test.ts delete mode 100755 src/lib/game/stores/__tests__/archive/store-methods.test.ts delete mode 100755 src/lib/game/stores/__tests__/archive/stores.test.ts delete mode 100644 src/lib/game/stores/__tests__/computed-stats.test.ts delete mode 100644 src/lib/game/stores/__tests__/equipment.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/combat-calculations.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/definitions.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/mana-calculations.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/meditation-insight-incursion.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/spell-cost.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/study-speed.test.ts delete mode 100644 src/lib/game/stores/__tests__/index-tests/utility-functions.test.ts delete mode 100644 src/lib/game/stores/__tests__/mana-conversion-fix.test.ts delete mode 100644 src/lib/game/stores/__tests__/mana.test.ts delete mode 100644 src/lib/game/stores/__tests__/regen.test.ts delete mode 100644 src/lib/game/stores/__tests__/skill.test.ts delete mode 100644 src/lib/game/stores/__tests__/spell-cost.test.ts delete mode 100644 src/lib/game/stores/__tests__/spire-exit-action.test.ts delete mode 100644 src/lib/game/stores/__tests__/spire-tab-refresh.test.ts delete mode 100644 src/lib/game/stores/__tests__/store-method-tests/combat-store.test.ts delete mode 100644 src/lib/game/stores/__tests__/store-method-tests/mana-store.test.ts delete mode 100644 src/lib/game/stores/__tests__/store-method-tests/prestige-store.test.ts delete mode 100644 src/lib/game/stores/__tests__/store-method-tests/skill-store.test.ts delete mode 100644 src/lib/game/stores/__tests__/store-method-tests/ui-store.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/damage-calculation.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/floor.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/formatting.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/guardians.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/incursion.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/insight-calculation.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/mana-calculation.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/meditation.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/prestige-upgrades.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/skill-definitions.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/spell-cost.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/spell-definitions.test.ts delete mode 100644 src/lib/game/stores/__tests__/stores-tests/study-speed.test.ts delete mode 100644 src/lib/game/types/skills.ts diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index a0fb07a..e61a543 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,15 +1,14 @@ # Circular Dependencies -Generated: 2026-05-14T10:03:17.211Z -Found: 8 circular chain(s) — these MUST be fixed before modifying involved files. +Generated: 2026-05-15T16:50:47.281Z +Found: 7 circular chain(s) — these MUST be fixed before modifying involved files. -1. Processed 174 files (2.4s) (27 warnings) +1. Processed 156 files (1.8s) (29 warnings) 2. 1) data/equipment/index.ts > data/equipment/utils.ts 3. 2) data/golems/index.ts > data/golems/utils.ts 4. 3) stores/combat-actions.ts > stores/combatStore.ts 5. 4) stores/combatStore.ts > stores/gameStore.ts 6. 5) stores/combatStore.ts > stores/gameStore.ts > stores/gameActions.ts -7. 6) stores/combatStore.ts > stores/gameStore.ts > stores/gameActions.ts > stores/skillStore.ts -8. 7) stores/combatStore.ts > stores/gameStore.ts > stores/gameLoopActions.ts +7. 6) stores/combatStore.ts > stores/gameStore.ts > stores/gameLoopActions.ts ## How to fix 1. Identify which import in the chain can be extracted to a shared types/utils file. diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 97856ef..ca725c8 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-14T10:03:14.529Z", + "generated": "2026-05-15T16:50:45.254Z", "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." }, @@ -431,86 +431,6 @@ "computed-stats.ts", "types.ts" ], - "skill-evolution-modules/elemental-attunement.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/enchanting-skills.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/focused-mind.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/guardian-skills.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/hybrid-skills.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/index.ts": [ - "skill-evolution-modules/elemental-attunement.ts", - "skill-evolution-modules/enchanting-skills.ts", - "skill-evolution-modules/focused-mind.ts", - "skill-evolution-modules/guardian-skills.ts", - "skill-evolution-modules/hybrid-skills.ts", - "skill-evolution-modules/insight-harvest.ts", - "skill-evolution-modules/invocation-skills.ts", - "skill-evolution-modules/knowledge-retention.ts", - "skill-evolution-modules/mana-utility-skills.ts", - "skill-evolution-modules/mana-well-flow.ts", - "skill-evolution-modules/quick-learner.ts", - "skill-evolution-modules/types.ts", - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/insight-harvest.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/invocation-skills.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/knowledge-retention.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/learning-skills.ts": [ - "skill-evolution-modules/focused-mind.ts", - "skill-evolution-modules/insight-harvest.ts", - "skill-evolution-modules/knowledge-retention.ts", - "skill-evolution-modules/quick-learner.ts" - ], - "skill-evolution-modules/magic-skills.ts": [ - "skill-evolution-modules/elemental-attunement.ts", - "skill-evolution-modules/mana-utility-skills.ts", - "skill-evolution-modules/mana-well-flow.ts" - ], - "skill-evolution-modules/mana-utility-skills.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/mana-well-flow.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/quick-learner.ts": [ - "skill-evolution-modules/utils.ts", - "types.ts" - ], - "skill-evolution-modules/types.ts": [ - "types.ts" - ], - "skill-evolution-modules/utils.ts": [ - "types.ts" - ], - "skill-evolution.ts": [ - "skill-evolution-modules/index.ts" - ], "special-effects.ts": [ "upgrade-effects.types.ts" ], @@ -553,7 +473,6 @@ "data/equipment/index.ts", "data/golems/index.ts", "effects.ts", - "skill-evolution.ts", "special-effects.ts", "store-modules/activity-log.ts", "store-modules/computed-stats.ts", @@ -602,7 +521,6 @@ "store/computed.ts": [ "constants.ts", "effects.ts", - "skill-evolution.ts", "types.ts", "upgrade-effects.ts", "upgrade-effects.types.ts" @@ -676,7 +594,6 @@ ], "store/skillSlice.ts": [ "constants.ts", - "skill-evolution.ts", "types.ts", "upgrade-effects.ts" ], @@ -717,7 +634,6 @@ "stores/combatStore.ts", "stores/gameStore.ts", "stores/manaStore.ts", - "stores/skillStore.ts", "stores/uiStore.ts", "types.ts", "upgrade-effects.ts" @@ -726,7 +642,6 @@ "stores/combatStore.ts", "stores/manaStore.ts", "stores/prestigeStore.ts", - "stores/skillStore.ts", "stores/uiStore.ts", "upgrade-effects.ts", "utils/index.ts" @@ -739,7 +654,6 @@ "stores/gameStore.ts", "stores/manaStore.ts", "stores/prestigeStore.ts", - "stores/skillStore.ts", "stores/uiStore.ts", "utils/index.ts" ], @@ -748,7 +662,6 @@ "stores/combatStore.ts", "stores/manaStore.ts", "stores/prestigeStore.ts", - "stores/skillStore.ts", "stores/uiStore.ts", "utils/index.ts" ], @@ -762,7 +675,6 @@ "stores/gameLoopActions.ts", "stores/manaStore.ts", "stores/prestigeStore.ts", - "stores/skillStore.ts", "stores/uiStore.ts", "upgrade-effects.ts", "utils/index.ts" @@ -777,7 +689,6 @@ "stores/gameStore.ts", "stores/manaStore.ts", "stores/prestigeStore.ts", - "stores/skillStore.ts", "stores/uiStore.ts", "utils/index.ts" ], @@ -789,13 +700,6 @@ "constants.ts", "types.ts" ], - "stores/skillStore.ts": [ - "constants.ts", - "skill-evolution.ts", - "stores/combatStore.ts", - "stores/manaStore.ts", - "types.ts" - ], "stores/uiStore.ts": [], "study-slice.ts": [ "constants.ts", @@ -832,7 +736,6 @@ "types/spells.ts": [], "upgrade-effects.ts": [ "dynamic-compute.ts", - "skill-evolution.ts", "special-effects.ts", "types.ts", "upgrade-effects.types.ts" diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 9676a2e..22f5795 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -105,7 +105,6 @@ Mana-Loop/ │ │ │ │ ├── GameStateDebug.tsx │ │ │ │ ├── GolemDebug.tsx │ │ │ │ ├── PactDebug.tsx -│ │ │ │ ├── SkillDebug.tsx │ │ │ │ └── index.tsx │ │ │ ├── layout/ │ │ │ │ ├── Header.tsx @@ -121,38 +120,6 @@ Mana-Loop/ │ │ │ │ ├── StudyStatsSection.tsx │ │ │ │ ├── UpgradeEffectsSection.tsx │ │ │ │ └── index.tsx -│ │ │ ├── tabs/ -│ │ │ │ ├── AchievementsTab.tsx -│ │ │ │ ├── ActivityLog.tsx -│ │ │ │ ├── AttunementsTab.tsx -│ │ │ │ ├── CategorySkillsList.tsx -│ │ │ │ ├── CombatStatsPanel.tsx -│ │ │ │ ├── CraftingTab.tsx -│ │ │ │ ├── DebugTab.tsx -│ │ │ │ ├── EnchantmentsPanel.tsx -│ │ │ │ ├── EquipmentControls.tsx -│ │ │ │ ├── EquipmentInventory.tsx -│ │ │ │ ├── EquipmentSlotGrid.tsx -│ │ │ │ ├── EquipmentTab.tsx -│ │ │ │ ├── FloorControls.tsx -│ │ │ │ ├── GolemancyTab.tsx -│ │ │ │ ├── GuardianPanel.tsx -│ │ │ │ ├── LootTab.tsx -│ │ │ │ ├── MilestoneProgress.tsx -│ │ │ │ ├── PrestigeTab.tsx -│ │ │ │ ├── RoomDisplay.tsx -│ │ │ │ ├── SkillCategoryHeader.tsx -│ │ │ │ ├── SkillMultipliers.tsx -│ │ │ │ ├── SkillRow.tsx -│ │ │ │ ├── SpellsTab.tsx -│ │ │ │ ├── SpireActiveSpells.tsx -│ │ │ │ ├── SpireGolems.tsx -│ │ │ │ ├── SpireHeader.tsx -│ │ │ │ ├── SpireTab.tsx -│ │ │ │ ├── StatsTab.tsx -│ │ │ │ ├── StudyProgress.tsx -│ │ │ │ ├── UpgradeDialog.tsx -│ │ │ │ └── index.ts │ │ │ ├── AchievementsDisplay.tsx │ │ │ ├── ActionButtons.tsx │ │ │ ├── ActivityLogPanel.tsx @@ -163,7 +130,6 @@ Mana-Loop/ │ │ │ ├── GameContext.tsx │ │ │ ├── GameToast.tsx │ │ │ ├── ManaDisplay.tsx -│ │ │ ├── SkillsTab.tsx │ │ │ ├── SpellsTab.tsx │ │ │ ├── StatsTab.tsx │ │ │ ├── StudyProgress.tsx @@ -191,7 +157,6 @@ Mana-Loop/ │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx -│ │ │ ├── skill-row.tsx │ │ │ ├── stat-row.tsx │ │ │ ├── stepper.tsx │ │ │ ├── switch.tsx @@ -209,20 +174,9 @@ Mana-Loop/ │ └── lib/ │ ├── game/ │ │ ├── __tests__/ -│ │ │ ├── skills-tests/ -│ │ │ │ ├── ascension-skills.test.ts -│ │ │ │ ├── integration-and-evolution.test.ts -│ │ │ │ ├── mana-skills.test.ts -│ │ │ │ ├── prestige-upgrades.test.ts -│ │ │ │ ├── skill-prerequisites.test.ts -│ │ │ │ ├── specialized-skills.test.ts -│ │ │ │ ├── study-skills.test.ts -│ │ │ │ └── study-times.test.ts │ │ │ ├── store-method-tests/ │ │ │ ├── bug-fixes.test.ts -│ │ │ ├── computed-stats.test.ts -│ │ │ ├── skill-system.test.ts -│ │ │ └── skills.test.ts +│ │ │ └── computed-stats.test.ts │ │ ├── attunements/ │ │ │ ├── data.ts │ │ │ ├── index.ts @@ -246,20 +200,6 @@ Mana-Loop/ │ │ │ ├── index.ts │ │ │ ├── prestige.ts │ │ │ ├── rooms.ts -│ │ │ ├── skills-combat.ts -│ │ │ ├── skills-core.ts -│ │ │ ├── skills-crafting.ts -│ │ │ ├── skills-element-caps.ts -│ │ │ ├── skills-enchant.ts -│ │ │ ├── skills-golemancy.ts -│ │ │ ├── skills-hybrid.ts -│ │ │ ├── skills-invocation.ts -│ │ │ ├── skills-research.ts -│ │ │ ├── skills-v2-defs.ts -│ │ │ ├── skills-v2-registry.ts -│ │ │ ├── skills-v2-types.ts -│ │ │ ├── skills-v2.ts -│ │ │ ├── skills.ts │ │ │ └── spells.ts │ │ ├── crafting-actions/ │ │ │ ├── application-actions.ts @@ -315,13 +255,7 @@ Mana-Loop/ │ │ │ ├── enchantment-types.ts │ │ │ └── loot-drops.ts │ │ ├── hooks/ -│ │ │ ├── useGameDerived.ts -│ │ │ └── useSkillUpgradeSelection.ts -│ │ ├── skills-split-tests/ -│ │ │ ├── ascension-specialized-skills.test.ts -│ │ │ ├── mana-skills.test.ts -│ │ │ ├── prerequisites-studytimes-prestige-integration.test.ts -│ │ │ └── study-skills.test.ts +│ │ │ └── useGameDerived.ts │ │ ├── store/ │ │ │ ├── crafting-modules/ │ │ │ │ ├── initial-state.ts @@ -338,7 +272,6 @@ Mana-Loop/ │ │ │ ├── manaSlice.ts │ │ │ ├── pactSlice.ts │ │ │ ├── prestigeSlice.ts -│ │ │ ├── skillSlice.ts │ │ │ └── timeSlice.ts │ │ ├── store-modules/ │ │ │ ├── {room-utils,enemy-utils,initial-state,activity-log,store-actions}/ @@ -349,68 +282,7 @@ Mana-Loop/ │ │ │ ├── room-utils.ts │ │ │ ├── store-actions.ts │ │ │ └── tick-logic.ts -│ │ ├── store-tests/ -│ │ │ ├── damage-calculation.test.ts -│ │ │ ├── element-recipes.test.ts -│ │ │ ├── floor.test.ts -│ │ │ ├── formatting.test.ts -│ │ │ ├── game-constants.test.ts -│ │ │ ├── individual-skills.test.ts -│ │ │ ├── insight-meditation-incursion.test.ts -│ │ │ ├── integration.test.ts -│ │ │ ├── mana-calculation.test.ts -│ │ │ ├── skill-evolution.test.ts -│ │ │ ├── skill-requirements.test.ts -│ │ │ ├── spell-cost.test.ts -│ │ │ ├── study-speed.test.ts -│ │ │ └── test-utils.ts │ │ ├── stores/ -│ │ │ ├── __tests__/ -│ │ │ │ ├── archive/ -│ │ │ │ │ ├── store-methods.test.ts -│ │ │ │ │ └── stores.test.ts -│ │ │ │ ├── combat-store-tests/ -│ │ │ │ ├── index-tests/ -│ │ │ │ │ ├── combat-calculations.test.ts -│ │ │ │ │ ├── definitions.test.ts -│ │ │ │ │ ├── mana-calculations.test.ts -│ │ │ │ │ ├── meditation-insight-incursion.test.ts -│ │ │ │ │ ├── spell-cost.test.ts -│ │ │ │ │ ├── study-speed.test.ts -│ │ │ │ │ └── utility-functions.test.ts -│ │ │ │ ├── mana-store-tests/ -│ │ │ │ ├── prestige-store-tests/ -│ │ │ │ ├── store-method-tests/ -│ │ │ │ │ ├── combat-store.test.ts -│ │ │ │ │ ├── mana-store.test.ts -│ │ │ │ │ ├── prestige-store.test.ts -│ │ │ │ │ ├── skill-store.test.ts -│ │ │ │ │ └── ui-store.test.ts -│ │ │ │ ├── stores-split-tests/ -│ │ │ │ ├── stores-tests/ -│ │ │ │ │ ├── damage-calculation.test.ts -│ │ │ │ │ ├── floor.test.ts -│ │ │ │ │ ├── formatting.test.ts -│ │ │ │ │ ├── guardians.test.ts -│ │ │ │ │ ├── incursion.test.ts -│ │ │ │ │ ├── insight-calculation.test.ts -│ │ │ │ │ ├── mana-calculation.test.ts -│ │ │ │ │ ├── meditation.test.ts -│ │ │ │ │ ├── prestige-upgrades.test.ts -│ │ │ │ │ ├── skill-definitions.test.ts -│ │ │ │ │ ├── spell-cost.test.ts -│ │ │ │ │ ├── spell-definitions.test.ts -│ │ │ │ │ └── study-speed.test.ts -│ │ │ │ ├── ui-store-tests/ -│ │ │ │ ├── computed-stats.test.ts -│ │ │ │ ├── equipment.test.ts -│ │ │ │ ├── mana-conversion-fix.test.ts -│ │ │ │ ├── mana.test.ts -│ │ │ │ ├── regen.test.ts -│ │ │ │ ├── skill.test.ts -│ │ │ │ ├── spell-cost.test.ts -│ │ │ │ ├── spire-exit-action.test.ts -│ │ │ │ └── spire-tab-refresh.test.ts │ │ │ ├── attunementStore.ts │ │ │ ├── combat-actions.ts │ │ │ ├── combatStore.ts @@ -424,20 +296,12 @@ Mana-Loop/ │ │ │ ├── manaStore.ts │ │ │ ├── prestigeStore.ts │ │ │ └── uiStore.ts -│ │ ├── stores-split-tests/ -│ │ │ ├── combat-store.test.ts -│ │ │ ├── integration.test.ts -│ │ │ ├── mana-store.test.ts -│ │ │ ├── prestige-store.test.ts -│ │ │ ├── skill-store.test.ts -│ │ │ └── ui-store.test.ts │ │ ├── types/ │ │ │ ├── attunements.ts │ │ │ ├── elements.ts │ │ │ ├── equipment.ts │ │ │ ├── game.ts │ │ │ ├── index.ts -│ │ │ ├── skills.ts │ │ │ └── spells.ts │ │ ├── utils/ │ │ │ ├── activity-log.ts @@ -464,7 +328,6 @@ Mana-Loop/ │ │ ├── effects.ts.fix │ │ ├── formatting.ts │ │ ├── navigation-slice.ts -│ │ ├── skills.test.ts │ │ ├── special-effects.ts │ │ ├── store.test.ts │ │ ├── store.ts diff --git a/src/components/game/SkillsTab.tsx b/src/components/game/SkillsTab.tsx deleted file mode 100755 index a74bfaa..0000000 --- a/src/components/game/SkillsTab.tsx +++ /dev/null @@ -1,57 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useSkillStore } from '@/lib/game/stores'; -import { SKILL_CATEGORIES } from '@/lib/game/constants'; -import { Card, CardContent } from '@/components/ui/card'; -import { SkillUpgradeDialog } from './SkillsTab/SkillUpgradeDialog'; -import { SkillStudyProgress } from './SkillsTab/SkillStudyProgress'; -import { SkillCategory } from './SkillsTab/SkillCategory'; -import { DebugName } from '@/lib/game/debug-context'; - -export function SkillsTab() { - const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget); - const [upgradeDialogSkill, setUpgradeDialogSkill] = useState(null); - const [upgradeDialogMilestone, setUpgradeDialogMilestone] = useState<5 | 10>(5); - - const handleUpgradeClick = (skillId: string, milestone: 5 | 10) => { - setUpgradeDialogSkill(skillId); - setUpgradeDialogMilestone(milestone); - }; - - const handleUpgradeClose = () => { - setUpgradeDialogSkill(null); - }; - - return ( - -
- {/* Upgrade Selection Dialog */} - - - {/* Current Study Progress */} - {currentStudyTarget && currentStudyTarget.type === 'skill' && ( - - - - - - )} - - {SKILL_CATEGORIES.map((cat) => ( - - ))} -
-
- ); -} - -SkillsTab.displayName = "SkillsTab"; diff --git a/src/components/game/debug/SkillDebug.tsx b/src/components/game/debug/SkillDebug.tsx deleted file mode 100644 index 2aa1641..0000000 --- a/src/components/game/debug/SkillDebug.tsx +++ /dev/null @@ -1,265 +0,0 @@ -'use client'; - -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Separator } from '@/components/ui/separator'; -import { BookOpen } from 'lucide-react'; -import { useSkillStore } from '@/lib/game/stores'; -import { useManaStore } from '@/lib/game/stores'; - -export function SkillDebug() { - const skills = useSkillStore((s) => s.skills); - - const incrementSkillLevel = (skillId: string) => { - useSkillStore.getState().incrementSkillLevel(skillId); - }; - - const setSkillLevel = (skillId: string, level: number) => { - useSkillStore.getState().setSkillLevel(skillId, level); - }; - - const unlockAllEffects = () => { - const effectIds = [ - 'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot', - 'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash', - 'spell_stoneBullet', 'spell_rockSpike', 'spell_lightLance', 'spell_radiance', - 'spell_shadowBolt', 'spell_darkPulse', 'spell_drain', - 'spell_inferno', 'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm', - 'spell_hurricane', 'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage', - 'spell_solarFlare', 'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm', - 'spell_pyroclasm', 'spell_tsunami', 'spell_meteorStrike', - 'spell_spark', 'spell_lightningBolt', 'spell_chainLightning', - 'spell_stormCall', 'spell_thunderStrike', - 'spell_metalShard', 'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast', - 'spell_sandBlast', 'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse', - 'mana_cap_50', 'mana_cap_100', 'mana_regen_1', 'mana_regen_2', 'mana_regen_5', - 'click_mana_1', 'click_mana_3', - 'damage_5', 'damage_10', 'damage_pct_10', 'crit_5', 'attack_speed_10', - 'meditate_10', 'study_10', 'insight_5', - 'spell_echo_10', 'guardian_dmg_10', 'overpower_80', - 'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100', - 'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5', - 'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void' - ]; - useManaStore.setState((prev: any) => { - const currentEffects = prev.unlockedEffects || []; - const newEffects = [...currentEffects]; - effectIds.forEach(id => { - if (!newEffects.includes(id)) { - newEffects.push(id); - } - }); - return { ...prev, unlockedEffects: newEffects }; - }); - }; - - return ( - - - - - Skill Research Debug - - - -
- {/* Enchanting Skills */} -
-
Enchanting Skills:
-
- - -
-
- - {/* Mana Skills */} -
-
Mana Skills:
-
- - -
-
- - {/* Study Skills */} -
-
Study Skills:
-
- - -
-
- - {/* Crafting Skills */} -
-
Crafting Skills:
-
- - -
-
- - {/* Research Effects */} -
-
Research Effects:
-
- - -
-
- - {/* Max All */} - -
- -
-
-
-
- ); -} - -SkillDebug.displayName = "SkillDebug"; diff --git a/src/components/game/tabs/AchievementsTab.tsx b/src/components/game/tabs/AchievementsTab.tsx deleted file mode 100755 index 791464a..0000000 --- a/src/components/game/tabs/AchievementsTab.tsx +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; - -import { AchievementsDisplay } from '@/components/game/AchievementsDisplay'; -import { useCombatStore, useManaStore, usePrestigeStore } from '@/lib/game/stores'; -import { DebugName } from '@/lib/game/debug-context'; - -export function AchievementsTab() { - const achievements = useCombatStore((s) => s.achievements); - const maxFloorReached = useCombatStore((s) => s.maxFloorReached); - const totalManaGathered = useManaStore((s) => s.totalManaGathered); - const signedPacts = usePrestigeStore((s) => s.signedPacts); - const totalSpellsCast = useCombatStore((s) => s.totalSpellsCast); - const totalDamageDealt = useCombatStore((s) => s.totalDamageDealt); - const totalCraftsCompleted = useCombatStore((s) => s.totalCraftsCompleted); - - return ( - -
- -
-
- ); -} - -AchievementsTab.displayName = "AchievementsTab"; diff --git a/src/components/game/tabs/ActivityLog.tsx b/src/components/game/tabs/ActivityLog.tsx deleted file mode 100644 index d1f7970..0000000 --- a/src/components/game/tabs/ActivityLog.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import type { ActivityLogEntry } from '@/lib/game/types'; - -interface ActivityLogProps { - activityLog?: ActivityLogEntry[]; - maxEntries?: number; -} - -export function ActivityLog({ activityLog, maxEntries = 50 }: ActivityLogProps) { - const entries = activityLog || []; - - return ( - - - Activity Log - - - -
- {entries.slice(0, maxEntries).map((entry, i) => { - const isLatest = i === 0; - const color = getEventStyle(entry.eventType); - return ( -
- {entry.message} -
- ); - })} - {entries.length === 0 && ( -
No activity yet...
- )} -
-
-
-
- ); -} - -function getEventStyle(eventType: string): string { - switch (eventType) { - case 'enemy_defeated': - case 'floor_cleared': - return 'text-green-400'; - case 'damage_dealt': - return 'text-red-400'; - case 'dodge': - return 'text-yellow-400'; - case 'armor_proc': - return 'text-blue-400'; - case 'special_effect': - return 'text-purple-400'; - case 'floor_transition': - return 'text-cyan-400'; - case 'spell_cast': - return 'text-amber-400'; - case 'golem_attack': - return 'text-orange-400'; - case 'puzzle_solved': - return 'text-pink-400'; - default: - return 'text-gray-300'; - } -} diff --git a/src/components/game/tabs/AttunementsTab.tsx b/src/components/game/tabs/AttunementsTab.tsx deleted file mode 100755 index aa69b63..0000000 --- a/src/components/game/tabs/AttunementsTab.tsx +++ /dev/null @@ -1,270 +0,0 @@ -'use client'; - -import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getAttunementConversionRate } from '@/lib/game/data/attunements'; -import { ELEMENTS } from '@/lib/game/constants'; -import type { AttunementState } from '@/lib/game/types'; -import { usePrestigeStore, useManaStore } from '@/lib/game/stores'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { Progress } from '@/components/ui/progress'; -import { Lock, TrendingUp } from 'lucide-react'; -import { DebugName } from '@/lib/game/debug-context'; - -export function AttunementsTab() { - const attunements = usePrestigeStore((s) => s.attunements) || {}; - const elements = useManaStore((s) => s.elements); - - // Get active attunements - const activeAttunements = Object.entries(attunements) - .filter(([, state]) => state.active) - .map(([id]) => ATTUNEMENTS_DEF[id]) - .filter(Boolean); - - // Calculate total regen from attunements - const totalAttunementRegen = getTotalAttunementRegen(attunements); - - // Get available skill categories - const availableCategories = getAvailableSkillCategories(attunements); - - return ( - -
- {/* Overview Card */} - - - Your Attunements - - -

- Attunements are magical bonds tied to specific body locations. Each attunement grants unique capabilities, - mana regeneration, and access to specialized skills. Level them up to increase their power. -

-
- - +{totalAttunementRegen.toFixed(1)} raw mana/hr - - - {activeAttunements.length} active attunement{activeAttunements.length !== 1 ? 's' : ''} - -
-
-
- - {/* Attunement Slots */} -
- {Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => { - const state = attunements[id]; - const isActive = state?.active; - const isUnlocked = state?.active || def.unlocked; - const level = state?.level || 1; - const xp = state?.experience || 0; - const xpNeeded = getAttunementXPForLevel(level + 1); - const xpProgress = xpNeeded > 0 ? (xp / xpNeeded) * 100 : 100; - const isMaxLevel = level >= MAX_ATTUNEMENT_LEVEL; - - // Get primary mana element info - const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null; - - // Get current mana for this attunement's type - const currentMana = def.primaryManaType ? elements[def.primaryManaType]?.current || 0 : 0; - const maxMana = def.primaryManaType ? elements[def.primaryManaType]?.max || 50 : 50; - - // Calculate level-scaled stats - const levelMult = Math.pow(1.5, level - 1); - const scaledRegen = def.rawManaRegen * levelMult; - const scaledConversion = getAttunementConversionRate(id, level); - - return ( - - -
-
- {def.icon} -
- - {def.name} - -
- {ATTUNEMENT_SLOT_NAMES[def.slot]} -
-
-
- {!isUnlocked && ( - - )} - {isActive && ( - - Lv.{level} - - )} -
-
- -

{def.desc}

- - {/* Mana Type */} -
-
- Primary Mana - {primaryElem ? ( - - {primaryElem.sym} {primaryElem.name} - - ) : ( - From Pacts - )} -
- - {/* Mana bar (only for attunements with primary type) */} - {primaryElem && isActive && ( -
- -
- {currentMana.toFixed(1)} - /{maxMana} -
-
- )} -
- - {/* Stats with level scaling */} -
-
-
Raw Regen
-
- +{scaledRegen.toFixed(2)}/hr - {level > 1 && ({((levelMult - 1) * 100).toFixed(0)}% bonus)} -
-
-
-
Conversion
-
- {scaledConversion > 0 ? `${scaledConversion.toFixed(2)}/hr` : '—'} - {level > 1 && scaledConversion > 0 && ({((levelMult - 1) * 100).toFixed(0)}% bonus)} -
-
-
- - {/* XP Progress Bar */} - {isUnlocked && state && !isMaxLevel && ( -
-
- - - XP Progress - - {xp} / {xpNeeded} -
- -
- {isMaxLevel ? 'Max Level' : `${xpNeeded - xp} XP to Level ${level + 1}`} -
-
- )} - - {/* Max Level Indicator */} - {isMaxLevel && ( -
- ✨ MAX LEVEL ✨ -
- )} - - {/* Capabilities */} -
-
Capabilities
-
- {def.capabilities.map(cap => ( - - {cap === 'enchanting' && '✨ Enchanting'} - {cap === 'disenchanting' && '🔄 Disenchant'} {/* TODO: Remove after bug 13 complete */} - {cap === 'pacts' && '🤝 Pacts'} - {cap === 'guardianPowers' && '💜 Guardian Powers'} - {cap === 'elementalMastery' && '🌟 Elem. Mastery'} - {cap === 'golemCrafting' && '🗿 Golems'} - {cap === 'gearCrafting' && '⚒️ Gear'} - {cap === 'earthShaping' && '⛰️ Earth Shaping'} - {!['enchanting', 'pacts', 'guardianPowers', - 'elementalMastery', 'golemCrafting', 'gearCrafting', 'earthShaping'].includes(cap) && cap} - - ))} -
-
- - {/* Unlock condition for locked attunements */} - {!isUnlocked && def.unlockCondition && ( -
- 🔒 {def.unlockCondition} -
- )} -
-
- ); - })} -
- - {/* Available Skills Summary */} - - - Available Skill Categories - - -

- Your attunements grant access to specialized skill categories: -

-
- {availableCategories.map(cat => { - const attunement = Object.values(ATTUNEMENTS_DEF || {}).find(a => - a.skillCategories.includes(cat) && attunements[a.id]?.active - ); - return ( - - {cat === 'mana' && '💧 Mana'} - {cat === 'study' && '📚 Study'} - {cat === 'research' && '🔮 Research'} {/* TODO: Remove after Bug 12 - research moved to mana */} - {cat === 'ascension' && '⭐ Ascension'} - {cat === 'enchant' && '✨ Enchanting'} - {cat === 'effectResearch' && '🔬 Effect Research'} - {cat === 'invocation' && '💜 Invocation'} - {cat === 'pact' && '🤝 Pact Mastery'} - {cat === 'fabrication' && '⚒️ Fabrication'} - {cat === 'golemancy' && '🗿 Golemancy'} - {!['mana', 'study', 'research', 'ascension', 'enchant', 'effectResearch', - 'invocation', 'pact', 'fabrication', 'golemancy'].includes(cat) && cat} - - ); - })} -
-
-
-
-
- ); -} - -AttunementsTab.displayName = "AttunementsTab"; diff --git a/src/components/game/tabs/CategorySkillsList.tsx b/src/components/game/tabs/CategorySkillsList.tsx deleted file mode 100644 index 5aa6da8..0000000 --- a/src/components/game/tabs/CategorySkillsList.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// CategorySkillsList - Displays skills for a specific category -// Migrated to use hooks directly (removed GameStore prop) - -'use client'; - -import { useState } from 'react'; -import type { SkillUpgradeChoice } from '@/lib/game/types'; -import { SKILLS_DEF } from '@/lib/game/constants'; -import { getTierMultiplier } from '@/lib/game/skill-evolution'; -import { getUnifiedEffects } from '@/lib/game/effects'; -import { useSkillStore, useGameStore, usePrestigeStore } from '@/lib/game/stores'; -import { SkillRow } from './SkillRow'; -import type { GameStore } from '@/lib/game/stores'; // Keep type import for backward compatibility - -interface CategorySkillsListProps { - categoryId: string; - categoryName: string; - skills: Record; - skillUpgrades: Record; - skillTiers: Record; - prestigeUpgrades: Record; - studySpeedMult: number; - upgradeEffects: any; - currentStudyTarget: any; - onStartStudying: (skillId: string) => void; - onParallelStudy: (skillId: string) => void; - onCancelStudy: () => void; - onOpenUpgradeDialog: (skillId: string, milestone: 5 | 10) => void; - onTierUp: (skillId: string) => void; - pendingSelections: string[]; - setPendingSelections: (selections: string[]) => void; -} - -export function CategorySkillsList({ - categoryId, - categoryName, - skills, - skillUpgrades, - skillTiers, - prestigeUpgrades, - studySpeedMult, - upgradeEffects, - currentStudyTarget, - onStartStudying, - onParallelStudy, - onCancelStudy, - onOpenUpgradeDialog, - onTierUp, - pendingSelections, - setPendingSelections, -}: CategorySkillsListProps) { - const [collapsed, setCollapsed] = useState(false); - - const categorySkills = Object.entries(SKILLS_DEF || {}) - .filter(([, def]) => def.category === categoryId) - .sort((a, b) => (a[1].tier || 0) - (b[1].tier || 0)); - - const toggleCollapse = () => setCollapsed(!collapsed); - - return ( -
-
- - {categoryName} ({categorySkills.length}) - - - {collapsed ? '▼' : '▶'} - -
- - {!collapsed && ( -
- {categorySkills.map(([skillId, def]) => { - const skillLevel = skills[skillId] || 0; - const tier = skillTiers[skillId] || 0; - const tierMult = getTierMultiplier(skillId)(tier); - const isStudying = currentStudyTarget?.id === skillId; - const isParallel = currentStudyTarget?.type === 'parallel' && currentStudyTarget?.id === skillId; - - // Get upgrade choices for this skill - const store = useGameStore.getState(); - const { available, selected } = store.getSkillUpgradeChoices(skillId, tier as 5 | 10); - - return ( - { - if (pendingSelections.includes(upgradeId)) { - setPendingSelections(pendingSelections.filter(id => id !== upgradeId)); - } else { - setPendingSelections([...pendingSelections, upgradeId]); - } - }} - onStartStudying={() => onStartStudying(skillId)} - onParallelStudy={() => onParallelStudy(skillId)} - onTierUp={() => onTierUp(skillId)} - onOpenUpgradeDialog={(milestone) => onOpenUpgradeDialog(skillId, milestone)} - /> - ); - })} -
- )} -
- ); -} diff --git a/src/components/game/tabs/CombatStatsPanel.tsx b/src/components/game/tabs/CombatStatsPanel.tsx deleted file mode 100644 index 892d09c..0000000 --- a/src/components/game/tabs/CombatStatsPanel.tsx +++ /dev/null @@ -1,170 +0,0 @@ -'use client'; - -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'; -import { Zap, Shield, ShieldCheck, Wind, Heart, Mountain, BookOpen } from 'lucide-react'; -import { ELEMENTS } from '@/lib/game/constants'; -import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems'; -import type { CombatStatsPanelProps } from '@/lib/game/types'; -import { useCombatStore } from '@/lib/game/stores'; -import { useSkillStore } from '@/lib/game/stores'; -import { usePrestigeStore } from '@/lib/game/stores'; - -export function CombatStatsPanel({ - activeEquipmentSpells, - totalDPS, - calcDamage, - formatSpellCost, - getSpellCostColor, - SPELLS_DEF, - upgradeEffects, - canCastSpell, - studySpeedMult, - storeCurrentAction, -}: CombatStatsPanelProps) { - const golemancy = useCombatStore((s) => s.golemancy); - const equipmentSpellStates = useCombatStore((s) => s.equipmentSpellStates); - const skills = useSkillStore((s) => s.skills); - const signedPacts = usePrestigeStore((s) => s.signedPacts); - const activeGolems = golemancy.summonedGolems; - - return ( - - - Combat Stats - - -
- Total DPS: {storeCurrentAction === 'climb' && activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'} -
- - {activeEquipmentSpells.length > 0 && ( -
-
Active Spells
- {activeEquipmentSpells.map(({ spellId, equipmentId }) => { - const spellDef = SPELLS_DEF[spellId]; - if (!spellDef) return null; - const spellState = equipmentSpellStates?.find( - s => s.spellId === spellId && s.sourceEquipment === equipmentId - ); - const progress = spellState?.castProgress || 0; - const canCast = canCastSpell(spellId); - - return ( -
-
- - {spellDef.name} - {spellDef.tier === 0 && Basic} - {spellDef.tier >= 4 && Legendary} - - - {canCast ? '✓' : '✗'} - -
-
- ⚔️ {fmt(calcDamage({ skills, signedPacts }, spellId))} dmg • {' '} - - {formatSpellCost(spellDef.cost)} - - {' '}• ⚡ {fmt(Math.floor(calcDamage({ skills, signedPacts }, spellId) * (spellDef.castSpeed || 1)))} dmg/hr -
- {storeCurrentAction === 'climb' && ( -
-
- Cast - {(progress * 100).toFixed(0)}% -
-
-
-
-
- )} -
- ); - })} -
- )} - - {activeGolems.length > 0 && ( -
-
- - Active Golems -
- {activeGolems.map((summoned) => { - const golemDef = GOLEMS_DEF[summoned.golemId]; - if (!golemDef) return null; - const elemColor = ELEMENTS[golemDef.baseManaType]?.color || '#888'; - const damage = getGolemDamage(summoned.golemId, skills); - const attackSpeed = getGolemAttackSpeed(summoned.golemId, skills); - - return ( -
-
-
- - - {golemDef.name} - -
- {golemDef.isAoe && ( - AOE {golemDef.aoeTargets} - )} -
-
- ⚔️ {damage} DMG • ⚡ {attackSpeed.toFixed(1)}/hr -
- {storeCurrentAction === 'climb' && summoned.attackProgress > 0 && ( -
-
- Attack - {Math.min(100, (summoned.attackProgress * 100)).toFixed(0)}% -
-
-
-
-
- )} -
- ); - })} -
- )} - -
-
Study Speed: {Math.round(studySpeedMult * 100)}%
-
- - - ); -} - -function fmt(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} - -function fmtDec(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} diff --git a/src/components/game/tabs/CraftingTab.tsx b/src/components/game/tabs/CraftingTab.tsx deleted file mode 100755 index 9ddf03a..0000000 --- a/src/components/game/tabs/CraftingTab.tsx +++ /dev/null @@ -1,269 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Progress } from '@/components/ui/progress'; -import { GameCard } from '@/components/ui/game-card'; -import { SectionHeader } from '@/components/ui/section-header'; -import { ActionButton } from '@/components/ui/action-button'; -import { Scroll, Hammer, Sparkles, Anvil } from 'lucide-react'; -import { fmt } from '@/lib/game/stores'; -import { - EnchantmentDesigner, - EnchantmentPreparer, - EnchantmentApplier, - EquipmentCrafter, -} from '@/components/game/crafting'; -import { useCombatStore, useCraftingStore } from '@/lib/game/stores'; -import { DebugName } from '@/lib/game/debug-context'; -import { useGameToast } from '@/components/game/GameToast'; -import type { DesignEffect } from '@/lib/game/types'; - -export function CraftingTab() { - const showToast = useGameToast(); - const currentAction = useCombatStore((s) => s.currentAction); - const designProgress = useCraftingStore((s) => s.designProgress); - const preparationProgress = useCraftingStore((s) => s.preparationProgress); - const applicationProgress = useCraftingStore((s) => s.applicationProgress); - const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress); - const pauseApplication = useCraftingStore((s) => s.pauseApplication); - const resumeApplication = useCraftingStore((s) => s.resumeApplication); - - const [activeTab, setActiveTab] = useState<'fabricate' | 'enchant'>('fabricate'); - const [enchantStage, setEnchantStage] = useState<'design' | 'prepare' | 'apply'>('design'); - - // Enchant state - const [selectedEquipmentType, setSelectedEquipmentType] = useState(null); - const [selectedEffects, setSelectedEffects] = useState([]); - const [designName, setDesignName] = useState(''); - const [selectedDesign, setSelectedDesign] = useState(null); - const [selectedEquipmentInstance, setSelectedEquipmentInstance] = useState(null); - - // Safe toFixed helper - const safeToFixed = (value: number | undefined, decimals: number = 0): string => { - if (value === undefined || isNaN(value)) return '0'; - return value.toFixed(decimals); - }; - - // Safe percentage calculation - const calcPercent = (progress: number, required: number): number => { - if (!required || required === 0) return 0; - return (progress / required) * 100; - }; - - // Handle enchantment application with toast - const handleEnchantmentApplied = () => { - showToast('success', 'Enchantment Applied', 'The enchantment has been successfully applied!'); - }; - - // Handle enchantment capacity exceeded - const handleCapacityExceeded = (itemName: string, used: number, total: number) => { - showToast('error', 'Enchantment Capacity Exceeded', `${itemName} can only hold ${total} enchantments (${used}/${total} used). Remove some enchantments first.`); - }; - - return ( - -
- {/* Top Sub-Tabs: Fabricate / Enchant */} - -
- setActiveTab('fabricate')} - className={activeTab === 'fabricate' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} - > - - Fabricate - - setActiveTab('enchant')} - className={activeTab === 'enchant' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} - > - - Enchant - -
-
- - {/* Fabricate Content: EquipmentCrafter */} - {activeTab === 'fabricate' && ( - - )} - - {/* Enchant Content: Design → Prepare → Apply workflow */} - {activeTab === 'enchant' && ( -
- {/* Enchant Sub-Navigation (no numbered stepper) */} - -
- setEnchantStage('design')} - className={enchantStage === 'design' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} - > - - Design - - setEnchantStage('prepare')} - className={enchantStage === 'prepare' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} - > - - Prepare - - setEnchantStage('apply')} - className={enchantStage === 'apply' ? 'ring-2 ring-[var(--interactive-primary)]' : ''} - > - - Apply - -
-
- - {/* Enchant Stage Content */} - {enchantStage === 'design' && ( - - )} - {enchantStage === 'prepare' && ( - - )} - {enchantStage === 'apply' && ( - - )} -
- )} - - {/* Current Activity Indicator: Crafting */} - {currentAction === 'craft' && equipmentCraftingProgress && ( - - - {safeToFixed(calcPercent(equipmentCraftingProgress.progress, equipmentCraftingProgress.required), 0)}% - - } - /> - -
- - Crafting equipment... -
-
- )} - - {/* Current Activity Indicator: Designing */} - {currentAction === 'design' && designProgress && ( - - useCraftingStore.getState().cancelDesign()}> - Cancel - - } - /> - -
- - Designing: {designProgress.name} -
-
- )} - - {/* Current Activity Indicator: Preparing */} - {currentAction === 'prepare' && preparationProgress && ( - - useCraftingStore.getState().cancelPreparation()}> - Cancel - - } - /> - -
- - Preparing equipment... - - Mana paid: {fmt(preparationProgress.manaCostPaid)} - -
-
- )} - - {/* Current Activity Indicator: Enchanting */} - {currentAction === 'enchant' && applicationProgress && ( - - - {applicationProgress.paused ? ( - Resume - ) : ( - <> - Pause - { - useCraftingStore.getState().cancelApplication(); - showToast('warning', 'Enchantment Cancelled', 'The enchantment application was cancelled.'); - }}>Cancel - - )} -
- } - /> - -
- - {applicationProgress.paused ? 'Enchantment paused' : 'Applying enchantment...'} - - {safeToFixed(calcPercent(applicationProgress.progress, applicationProgress.required), 0)}% - -
- - )} -
- - ); -} - -CraftingTab.displayName = 'CraftingTab'; diff --git a/src/components/game/tabs/DebugTab.tsx b/src/components/game/tabs/DebugTab.tsx deleted file mode 100755 index 7668320..0000000 --- a/src/components/game/tabs/DebugTab.tsx +++ /dev/null @@ -1,32 +0,0 @@ -'use client'; - -import { GameStateDebug } from '@/components/game/debug/GameStateDebug'; -import { DebugName } from '@/lib/game/debug-context'; -import { - SkillDebug, - AttunementDebug, - ElementDebug, - GolemDebug, - PactDebug -} from '@/components/game/debug'; - -export function DebugTab() { - return ( - -
- - -
- - -
- - - - -
-
- ); -} - -DebugTab.displayName = "DebugTab"; diff --git a/src/components/game/tabs/EnchantmentsPanel.tsx b/src/components/game/tabs/EnchantmentsPanel.tsx deleted file mode 100644 index b20e234..0000000 --- a/src/components/game/tabs/EnchantmentsPanel.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { Badge } from '@/components/ui/badge'; - -interface EnchantmentsPanelProps { - enchantments: Array<{ effectId: string; stacks: number }>; - compact?: boolean; -} - -export function EnchantmentsPanel({ - enchantments, - compact = false, -}: EnchantmentsPanelProps) { - if (enchantments.length === 0) { - return null; - } - - return ( -
- {enchantments.map((ench, i) => { - const effect = ENCHANTMENT_EFFECTS[ench.effectId]; - return ( - - - - - {effect?.name || ench.effectId} - {ench.stacks > 1 && ` x${ench.stacks}`} - - - -

{effect?.description || 'Unknown effect'}

-

- Category: {effect?.category || 'unknown'} -

-
-
-
- ); - })} -
- ); -} diff --git a/src/components/game/tabs/EquipmentControls.tsx b/src/components/game/tabs/EquipmentControls.tsx deleted file mode 100644 index cbe8e2a..0000000 --- a/src/components/game/tabs/EquipmentControls.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; - -import { EquipmentSlot } from '@/lib/game/data/equipment'; -// GameStore import removed - not used -import { ActionButton } from '@/components/ui/action-button'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { X } from 'lucide-react'; - -interface EquipmentControlsProps { - onUnequip: (slot: EquipmentSlot) => void; - onDelete: (instanceId: string, name: string) => void; - selectedSlot: EquipmentSlot | null; - isSlotBlocked: (slot: EquipmentSlot) => boolean; - isEquipped: (instanceId: string) => boolean; - getEquippableSlots: (typeId: string) => EquipmentSlot[]; -} - -export function EquipmentControls({ - onUnequip, - onDelete, - selectedSlot, - isSlotBlocked, - isEquipped, - getEquippableSlots, -}: EquipmentControlsProps) { - const SLOT_NAMES = { - mainHand: 'Main Hand', - offHand: 'Off Hand', - head: 'Head', - body: 'Body', - hands: 'Hands', - feet: 'Feet', - accessory1: 'Accessory 1', - accessory2: 'Accessory 2', - } as const; - - return { - renderUnequipButton: (slot: EquipmentSlot, instanceName: string) => ( - { - e.stopPropagation(); - onUnequip(slot); - }} - aria-label={`Unequip ${instanceName}`} - > - - - ), - - renderDeleteButton: (instanceId: string, name: string) => ( - - - - onDelete(instanceId, name)} - aria-label={`Delete ${name}`} - > - - - - -

Delete this item

-
-
-
- ), - }; -} diff --git a/src/components/game/tabs/EquipmentInventory.tsx b/src/components/game/tabs/EquipmentInventory.tsx deleted file mode 100644 index 72dcdbe..0000000 --- a/src/components/game/tabs/EquipmentInventory.tsx +++ /dev/null @@ -1,185 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { EquipmentSlot } from '@/lib/game/data/equipment'; -import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment'; -import type { EquipmentInstance } from '@/lib/game/types'; -import { GameCard } from '@/components/ui/game-card'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { EnchantmentsPanel } from './EnchantmentsPanel'; - -interface EquipmentInventoryProps { - unequippedItems: EquipmentInstance[]; - onEquip: (instanceId: string, slot: EquipmentSlot) => void; - onDelete: (instanceId: string, name: string) => void; - getEquippableSlots: (typeId: string) => EquipmentSlot[]; - SLOT_NAMES: Record; - SLOT_ICONS: Record; -} - -export function EquipmentInventory({ - unequippedItems, - onEquip, - onDelete, - getEquippableSlots, - SLOT_NAMES, - SLOT_ICONS, -}: EquipmentInventoryProps) { - return ( -
- {unequippedItems.map((instance) => { - const equipmentType = EQUIPMENT_TYPES[instance.typeId]; - const validSlots = getEquippableSlots(instance.typeId); - - return ( - -
-
-
- {instance.name} -
-
- {equipmentType?.description} -
-
- - {equipmentType?.category || 'unknown'} - -
- -
-
- Capacity: {instance.usedCapacity}/{instance.totalCapacity} - {instance.quality < 100 && ( - - (Quality: {instance.quality}%) - - )} -
- {instance.enchantments.length > 0 && ( - - )} -
- - {validSlots.length > 0 && ( - - )} -
- ); - })} -
- ); -} - -function getRarityBorderColor(rarity: string) { - const colors: Record = { - common: 'border-[var(--text-muted)]', - uncommon: 'border-[var(--color-success)]', - rare: 'border-[var(--mana-water)]', - epic: 'border-[var(--mana-stellar)]', - legendary: 'border-[var(--mana-light)]', - mythic: 'border-[var(--mana-dark)]', - }; - return colors[rarity] || 'border-[var(--border-default)]'; -} - -function getRarityBgColor(rarity: string) { - const colors: Record = { - common: 'bg-[var(--bg-sunken)]/30', - uncommon: 'bg-[var(--color-success)]/10', - rare: 'bg-[var(--mana-water)]/10', - epic: 'bg-[var(--mana-stellar)]/10', - legendary: 'bg-[var(--mana-light)]/10', - mythic: 'bg-[var(--mana-dark)]/10', - }; - return colors[rarity] || ''; -} - -function getRarityTextColor(rarity: string) { - const colors: Record = { - common: 'text-[var(--text-secondary)]', - uncommon: 'text-[var(--color-success)]', - rare: 'text-[var(--mana-water)]', - epic: 'text-[var(--mana-stellar)]', - legendary: 'text-[var(--mana-light)]', - mythic: 'text-[var(--mana-dark)]', - }; - return colors[rarity] || 'text-[var(--text-primary)]'; -} - -interface EquipControlsProps { - instance: EquipmentInstance; - validSlots: EquipmentSlot[]; - onEquip: (instanceId: string, slot: EquipmentSlot) => void; - onDelete: (instanceId: string, name: string) => void; - SLOT_NAMES: Record; - SLOT_ICONS: Record; -} - -function EquipControls({ - instance, - validSlots, - onEquip, - onDelete, - SLOT_NAMES, - SLOT_ICONS, -}: EquipControlsProps) { - return ( -
- - - -
- ); -} diff --git a/src/components/game/tabs/EquipmentSlotGrid.tsx b/src/components/game/tabs/EquipmentSlotGrid.tsx deleted file mode 100644 index c3cf264..0000000 --- a/src/components/game/tabs/EquipmentSlotGrid.tsx +++ /dev/null @@ -1,237 +0,0 @@ -'use client'; - -import { EquipmentSlot } from '@/lib/game/data/equipment'; -import { SLOT_NAMES, EQUIPMENT_TYPES } from '@/lib/game/data/equipment'; -import type { EquipmentInstance } from '@/lib/game/types'; -import { GameCard } from '@/components/ui/game-card'; -import { Badge } from '@/components/ui/badge'; -import { AlertCircle, Sword, Shield, HardHat, Shirt, Hand, Footprints, Gem } from 'lucide-react'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { RARITY_BORDER_COLORS, RARITY_BG_COLORS, RARITY_TEXT_COLORS } from './EquipmentTab'; -import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects'; -import type { EquipmentType } from '@/lib/game/data/equipment'; - -const SLOT_ICONS: Record = { - mainHand: Sword, - offHand: Shield, - head: HardHat, - body: Shirt, - hands: Hand, - feet: Footprints, - accessory1: Gem, - accessory2: Gem, -}; - -interface EquipmentSlotGridProps { - equippedInstances: Record; - equipmentInstances: Record; - selectedSlot: EquipmentSlot | null; - onSlotClick: (slot: EquipmentSlot) => void; - onUnequip: (slot: EquipmentSlot) => void; - isSlotBlocked: (slot: EquipmentSlot) => boolean; - SLOT_GROUPS: Array<{ label: string; slots: EquipmentSlot[] }>; -} - -export function EquipmentSlotGrid({ - equippedInstances, - equipmentInstances, - selectedSlot, - onSlotClick, - onUnequip, - isSlotBlocked, - SLOT_GROUPS, -}: EquipmentSlotGridProps) { - const renderSlot = (slot: EquipmentSlot) => { - const instanceId = equippedInstances[slot]; - const instance = instanceId ? equipmentInstances[instanceId] : null; - const equipmentType = instance ? EQUIPMENT_TYPES?.[instance.typeId] : null; - const blocked = isSlotBlocked(slot); - const isEmpty = !instance; - const SlotIcon = SLOT_ICONS[slot]; - - const slotContent = ( - !blocked && onSlotClick(slot)} - onKeyDown={(e) => { - if (!blocked && (e.key === 'Enter' || e.key === ' ')) { - onSlotClick(slot); - } - }} - > -
-
- - - {SLOT_NAMES[slot]} - - {blocked && ( - - - Occupied — 2H Weapon - - )} -
- {instance && !blocked && ( - - )} -
- - {instance ? ( - - ) : blocked ? ( -
- - Blocked by 2-handed weapon -
- ) : ( -
- {SLOT_NAMES[slot]} -
- )} -
- ); - - if (blocked) { - return ( - - - - {slotContent} - - -

The offhand slot is blocked because a 2-handed weapon is equipped in the main hand.

-

Unequip the 2-handed weapon to use this slot.

-
-
-
- ); - } - - return
{slotContent}
; - }; - - return ( -
- {SLOT_GROUPS.map((group) => ( -
-

- {group.label} -

-
- {group.slots.map((slot) => renderSlot(slot))} -
-
- ))} -
- ); -} - -interface EquipmentItemDisplayProps { - instance: EquipmentInstance; - equipmentType: EquipmentType | undefined; - isTwoHanded: boolean; - isCompact?: boolean; -} - -function EquipmentItemDisplay({ - instance, - equipmentType, - isTwoHanded, - isCompact = false, -}: EquipmentItemDisplayProps) { - return ( -
-
- {instance.name} - {isTwoHanded && ( - - 2-Handed - - )} -
-
- Enchantments: {instance.enchantments.length}/{instance.totalCapacity} -
- {instance.enchantments.length > 0 && ( - - )} -
- ); -} - -interface EnchantmentsDisplayProps { - enchantments: Array<{ effectId: string; stacks: number }>; - compact?: boolean; -} - -function EnchantmentsDisplay({ enchantments, compact = false }: EnchantmentsDisplayProps) { - return ( -
- {enchantments.map((ench, i) => { - const effect = ENCHANTMENT_EFFECTS[ench.effectId]; - return ( - - - - - {effect?.name || ench.effectId} - {ench.stacks > 1 && ` x${ench.stacks}`} - - - -

{effect?.description || 'Unknown effect'}

-

- Category: {effect?.category || 'unknown'} -

-
-
-
- ); - })} -
- ); -} diff --git a/src/components/game/tabs/EquipmentTab.tsx b/src/components/game/tabs/EquipmentTab.tsx deleted file mode 100755 index f08536b..0000000 --- a/src/components/game/tabs/EquipmentTab.tsx +++ /dev/null @@ -1,386 +0,0 @@ -'use client'; - -import { useState, useMemo } from 'react'; -import { - EQUIPMENT_TYPES, - EQUIPMENT_SLOTS, - SLOT_NAMES, - getEquipmentBySlot, - type EquipmentSlot, - type EquipmentType, -} from '@/lib/game/data/equipment'; -import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects'; -import { getUnifiedEffects } from '@/lib/game/effects'; -import { fmt } from '@/lib/game/stores'; -import { Button } from '@/components/ui/button'; -import { GameCard } from '@/components/ui/game-card'; -import { SectionHeader } from '@/components/ui/section-header'; -import { StatRow } from '@/components/ui/stat-row'; -import { Badge } from '@/components/ui/badge'; -import type { EquipmentInstance } from '@/lib/game/types'; -import { EquipmentSlotGrid } from './EquipmentSlotGrid'; -import { EquipmentInventory } from './EquipmentInventory'; -import { EnchantmentsPanel } from './EnchantmentsPanel'; -import { useGameToast } from '@/components/game/GameToast'; -import { ConfirmDialog } from '@/components/game/ConfirmDialog'; -import { DebugName } from '@/lib/game/debug-context'; -import { equipItem, unequipItem, deleteEquipmentInstance } from '@/lib/game/crafting-actions'; -import { useCombatStore, useCraftingStore } from '@/lib/game/stores'; -import type { GameState } from '@/lib/game/types'; - -// Rarity color mappings using design system tokens -export const RARITY_BORDER_COLORS: Record = { - common: 'border-[var(--text-muted)]', - uncommon: 'border-[var(--color-success)]', - rare: 'border-[var(--mana-water)]', - epic: 'border-[var(--mana-stellar)]', - legendary: 'border-[var(--mana-light)]', - mythic: 'border-[var(--mana-dark)]', -}; -export const RARITY_BG_COLORS: Record = { - common: 'bg-[var(--bg-sunken)]/30', - uncommon: 'bg-[var(--color-success)]/10', - rare: 'bg-[var(--mana-water)]/10', - epic: 'bg-[var(--mana-stellar)]/10', - legendary: 'bg-[var(--mana-light)]/10', - mythic: 'bg-[var(--mana-dark)]/10', -}; -export const RARITY_TEXT_COLORS: Record = { - common: 'text-[var(--text-secondary)]', - uncommon: 'text-[var(--color-success)]', - rare: 'text-[var(--mana-water)]', - epic: 'text-[var(--mana-stellar)]', - legendary: 'text-[var(--mana-light)]', - mythic: 'text-[var(--mana-dark)]', -}; - -const SLOT_ICONS: Record = { - mainHand: () => ( - - - - - ), - offHand: () => ( - - - - ), - head: () => ( - - - - - ), - body: () => ( - - - - - ), - hands: () => ( - - - - - ), - feet: () => ( - - - - - ), - accessory1: () => ( - - - - - ), - accessory2: () => ( - - - - - ), -}; - -// Slot grouping for visual layout -type SlotGroup = { - label: string; - slots: EquipmentSlot[]; -}; - -const SLOT_GROUPS: SlotGroup[] = [ - { label: 'Weapon & Shield', slots: ['mainHand', 'offHand'] }, - { label: 'Armor', slots: ['head', 'body', 'hands', 'feet'] }, - { label: 'Accessories', slots: ['accessory1', 'accessory2'] }, -]; - -export function EquipmentTab() { - const showToast = useGameToast(); - const [selectedSlot, setSelectedSlot] = useState(null); - const [deleteConfirm, setDeleteConfirm] = useState<{ instanceId: string; name: string } | null>(null); - - // Use modular store directly - MUST be called before any conditional returns - const equippedInstances = useCraftingStore((s) => s.equippedInstances); - const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); - - // Get unequipped items - hooks must be called before conditional returns - const equippedIds = useMemo(() => - new Set(Object.values(equippedInstances || {}).filter(Boolean)), - [equippedInstances] - ); - - const unequippedItems = useMemo(() => - Object.values(equipmentInstances || {}).filter( - (inst) => !equippedIds.has(inst.instanceId) - ), - [equipmentInstances, equippedIds] - ); - - // Guard against undefined during initialization - AFTER all hooks - if (!equippedInstances || !equipmentInstances) { - return ( - -
- Loading equipment data... -
- -
); - } - - // Equip an item to a slot - const handleEquip = (instanceId: string, slot: EquipmentSlot) => { - const instance = equipmentInstances[instanceId]; - equipItem(instanceId, slot, useCraftingStore.getState as () => GameState, (fn) => useCraftingStore.setState(fn as any)); - setSelectedSlot(null); - showToast('success', 'Item Equipped', `${instance?.name || 'Item'} equipped to ${SLOT_NAMES[slot]}`); - }; - - // Unequip from a slot - const handleUnequip = (slot: EquipmentSlot) => { - const instanceId = equippedInstances[slot]; - const instance = instanceId ? equipmentInstances[instanceId] : null; - unequipItem(slot, (fn) => useCraftingStore.setState(fn as any)); - showToast('success', 'Item Unequipped', `${instance?.name || 'Item'} removed from ${SLOT_NAMES[slot]}`); - }; - - // Check if a slot is blocked by a 2-handed weapon - const isSlotBlocked = (slot: EquipmentSlot): boolean => { - if (slot === 'offHand' && equippedInstances.mainHand) { - const mainHandInstance = equipmentInstances[equippedInstances.mainHand]; - if (!mainHandInstance) return false; - const mainHandType = EQUIPMENT_TYPES[mainHandInstance.typeId]; - return mainHandType?.twoHanded === true; - } - return false; - }; - - // Get items that can be equipped in a slot - const getEquippableItems = (slot: EquipmentSlot): EquipmentInstance[] => { - const equipmentTypes = getEquipmentBySlot(slot); - const typeIds = new Set(equipmentTypes.map((t) => t.id)); - return unequippedItems.filter((inst) => typeIds.has(inst.typeId)); - }; - - // Get all items that can go in a slot - const getItemsForSlot = (slot: EquipmentSlot): EquipmentInstance[] => { - if (isSlotBlocked(slot)) return []; - - if (slot === 'accessory1' || slot === 'accessory2') { - const accessoryTypeIds = Object.values(EQUIPMENT_TYPES) - .filter((t) => t.category === 'accessory') - .map((t) => t.id); - return unequippedItems.filter((inst) => accessoryTypeIds.includes(inst.typeId)); - } - - if (slot === 'offHand') { - return getEquippableItems(slot).filter((inst) => { - const type = EQUIPMENT_TYPES[inst.typeId]; - return !type?.twoHanded; - }); - } - - return getEquippableItems(slot); - }; - - // Check if an instance is currently equipped - const isEquipped = (instanceId: string): boolean => - Object.values(equippedInstances || {}).includes(instanceId); - - // Get all slots an item type can be equipped to - const getEquippableSlots = (typeId: string): EquipmentSlot[] => { - const equipmentType = EQUIPMENT_TYPES[typeId]; - if (!equipmentType) return []; - if (equipmentType.category === 'accessory') { - return ['accessory1', 'accessory2']; - } - return [equipmentType.slot]; - }; - - // Handle item deletion - const handleDelete = (instanceId: string, name: string) => { - setDeleteConfirm({ instanceId, name }); - }; - - const confirmDelete = () => { - if (deleteConfirm) { - deleteEquipmentInstance(deleteConfirm.instanceId, useCraftingStore.getState, (fn) => useCraftingStore.setState(fn)); - showToast('success', 'Item Discarded', `${deleteConfirm.name} has been removed from inventory`); - setDeleteConfirm(null); - } - }; - - // Use already-fetched values for unified effects - const unifiedEffects = getUnifiedEffects({ equipmentInstances, equippedInstances }); - - return ( - -
- {/* Equipment Slots */} - - - {Object.values(equippedInstances || {}).filter(Boolean).length} / {EQUIPMENT_SLOTS.length} slots filled - - } - /> - - - - {/* Equipment Inventory */} - - - {unequippedItems.length === 0 ? ( -
- No unequipped items. Craft new gear in the Crafting tab. -
- ) : ( - - )} -
- - {/* Equipment Stats Summary */} - - -
-
-
- {Object.values(equipmentInstances || {}).length} -
-
Total Items
-
-
-
- {equippedIds.size} -
-
Equipped
-
-
-
- {unequippedItems.length} -
-
In Inventory
-
-
-
- {Object.values(equipmentInstances || {}).reduce( - (sum, inst) => sum + inst.enchantments.length, - 0 - )} -
-
Total Enchantments
-
-
- - {/* Enchantment Power */} - -
-

- ✨ Enchantment Power -

-
-
- {(() => { - const effects = unifiedEffects; - if (!effects) return null; - const enchantPower = effects.enchantmentPowerMultiplier || 1; - return ( - <> - 1 ? 'success' : 'default'} - /> -

- Increases the power of all enchantments by {(enchantPower - 1) * 100}%. Multiplier applied to all enchantment effects. -

- - ); - })()} -
-
- - {/* Active Effects from Equipment */} -
-
Active Effects from Equipment:
-
- {(() => { - const effects = unifiedEffects; - if (!effects?.equipmentEffects) { - return No active effects; - } - const effectEntries = Object.entries(effects.equipmentEffects).filter(([, v]) => v > 0); - - if (effectEntries.length === 0) { - return No active effects; - } - - return effectEntries.map(([stat, value]) => ( - - {stat}: +{fmt(value)} - - )); - })()} -
-
-
- - {/* Delete Confirmation Dialog */} - setDeleteConfirm(null)} - title="Discard Item?" - description={`Discard ${deleteConfirm?.name}? This cannot be undone.`} - variant="danger" - confirmText="Discard" - onConfirm={confirmDelete} - /> -
- -
); -} - -EquipmentTab.displayName = 'EquipmentTab'; diff --git a/src/components/game/tabs/FloorControls.tsx b/src/components/game/tabs/FloorControls.tsx deleted file mode 100644 index f77cf85..0000000 --- a/src/components/game/tabs/FloorControls.tsx +++ /dev/null @@ -1,217 +0,0 @@ -'use client'; - -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { ChevronUp, ChevronDown, Mountain, Skull } from 'lucide-react'; -import { ELEMENTS } from '@/lib/game/constants'; - -interface FloorControlsProps { - // Store values passed as individual props - currentFloor: number; - floorHP: number; - floorMaxHP: number; - maxFloorReached: number; - equipmentSpellStates: any[]; - - // Other props - climbDirection: 'up' | 'down' | null; - isGuardianFloor: boolean; - currentRoom: any; - currentGuardian: any; - isFloorCleared: boolean; - floorElemDef: any; - roomType: string; - roomConfig: { label: string; icon: string; color: string }; - activeEquipmentSpells: any[]; - floorElem: string; - totalDPS: number; - calcDamage: (state: { skills: Record; signedPacts: number[] }, spellId: string, floorElem?: string) => number; - SPELLS_DEF: Record; - canCastSpell: (spellId: string) => boolean; - storeCurrentAction: string; - handleClimb: (direction: 'up' | 'down') => void; - formatSpellCost: (cost: any) => string; - getSpellCostColor: (cost: any) => string; - // Skills and pacts needed for calcDamage - skills: Record; - signedPacts: number[]; -} - -export function FloorControls({ - currentFloor, - floorHP, - floorMaxHP, - maxFloorReached, - equipmentSpellStates, - climbDirection, - isGuardianFloor, - currentRoom, - currentGuardian, - isFloorCleared, - floorElemDef, - roomType, - roomConfig, - activeEquipmentSpells, - floorElem, - totalDPS, - calcDamage, - SPELLS_DEF, - canCastSpell, - storeCurrentAction, - handleClimb, - formatSpellCost, - getSpellCostColor, - skills, - signedPacts, -}: FloorControlsProps) { - return ( - - - Floor Navigation - - -
- - -
- - {storeCurrentAction === 'climb' && ( -
-
- - - Climbing {climbDirection === 'up' ? 'Up' : 'Down'} - - {isGuardianFloor && ( - GUARDIAN - )} -
- - {currentGuardian && ( -
-
- - - {currentGuardian.name} - -
-
- )} - - {!(roomType === 'swarm' || roomType === 'puzzle') && ( -
-
-
-
-
- {fmt(floorHP)} / {fmt(floorMaxHP)} HP - - DPS: {activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'} - -
-
- )} - - {activeEquipmentSpells.length > 0 ? ( -
- {activeEquipmentSpells.map(({ spellId, equipmentId }) => { - const spellDef = SPELLS_DEF[spellId]; - if (!spellDef) return null; - const spellState = equipmentSpellStates?.find( - s => s.spellId === spellId && s.sourceEquipment === equipmentId - ); - const progress = spellState?.castProgress || 0; - const canCast = canCastSpell(spellId); - - return ( -
-
- - {spellDef.name} - - - {canCast ? '✓' : '✗'} - -
-
- ⚔️ {fmt(calcDamage({ skills, signedPacts }, spellId, floorElem))} dmg • {' '} - - {formatSpellCost(spellDef.cost)} - -
-
-
- Cast - {(progress * 100).toFixed(0)}% -
-
-
-
-
-
- ); - })} -
- ) : ( -
No active spells. Equip staves with spell effects.
- )} -
- )} - - {storeCurrentAction !== 'climb' && ( -
- Click Climb Up/Down to begin climbing -
- )} - - - ); -} - -function fmt(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} - -function fmtDec(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} diff --git a/src/components/game/tabs/GolemancyTab.tsx b/src/components/game/tabs/GolemancyTab.tsx deleted file mode 100755 index 40a0c29..0000000 --- a/src/components/game/tabs/GolemancyTab.tsx +++ /dev/null @@ -1,321 +0,0 @@ -'use client'; - -import { GameCard, StatRow, ElementBadge, ActionButton } from '@/components/ui'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Separator } from '@/components/ui/separator'; -import { - Mountain, Zap, Clock, Swords, Sparkles, Lock, Check, X, - Info, HelpCircle -} from 'lucide-react'; -import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems'; -import { ELEMENTS } from '@/lib/game/constants'; -import { useManaStore, useSkillStore, useCombatStore, useAttunementStore } from '@/lib/game/stores'; - -export function GolemancyTab() { - const attunements = useAttunementStore((s) => s.attunements); - const elements = useManaStore((s) => s.elements); - const skills = useSkillStore((s) => s.skills); - const golemancy = useCombatStore((s) => s.golemancy); - const currentFloor = useCombatStore((s) => s.currentFloor); - const currentRoom = useCombatStore((s) => s.currentRoom); - const toggleGolem = useCombatStore((s) => s.toggleGolem); - const rawMana = useManaStore((s) => s.rawMana); - - // Get Fabricator level and golem slots - const fabricatorLevel = attunements.fabricator?.level || 0; - const fabricatorActive = attunements.fabricator?.active || false; - const maxSlots = getGolemSlots(fabricatorLevel); - - // Get unlocked elements - const unlockedElements = Object.entries(elements) - .filter(([, e]) => e.unlocked) - .map(([id]) => id); - - // Get all unlocked golems - const unlockedGolems = Object.values(GOLEMS_DEF || {}).filter(golem => - isGolemUnlocked(golem.id, attunements, unlockedElements) - ); - - // Check if golemancy is available - const hasGolemancy = fabricatorActive && fabricatorLevel >= 2; - - // Check if currently in combat (not puzzle) - const inCombat = currentRoom?.roomType !== 'puzzle'; - - // Get element info helper - const getElementInfo = (elementId: string) => { - return ELEMENTS[elementId]; - }; - - // Render a golem card - const renderGolemCard = (golemId: string, isUnlocked: boolean) => { - const golem = GOLEMS_DEF[golemId]; - if (!golem) return null; - - const isEnabled = golemancy.enabledGolems.includes(golemId); - const isSelected = golemancy.summonedGolems.some(g => g.golemId === golemId); - - // Calculate effective stats - const damage = getGolemDamage(golemId, skills); - const attackSpeed = getGolemAttackSpeed(golemId, skills); - const floorDuration = getGolemFloorDuration(skills); - - // Get element color - const primaryElement = getElementInfo(golem.baseManaType); - const elementId = golem.baseManaType; - - if (!isUnlocked) { - // Locked golem card - return ( - - - -
-

- - ??? -

-
-
- {golem.unlockCondition.type === 'attunement_level' && ( -
Requires Fabricator Level {golem.unlockCondition.level}
- )} - {golem.unlockCondition.type === 'mana_unlocked' && ( -
Requires {ELEMENTS[golem.unlockCondition.manaType || '']?.name || golem.unlockCondition.manaType} Mana
- )} - {golem.unlockCondition.type === 'dual_attunement' && ( -
Requires Enchanter & Fabricator Level 5
- )} -
-
- -
); - } - - return ( - toggleGolem(golemId)} - aria-label={`${isEnabled ? 'Disable' : 'Enable'} ${golem.name}`} - role="button" - tabIndex={0} - > -
-

-
- - {golem.name} -
-
- {golem.isAoe && ( - - AOE {golem.aoeTargets} - - )} - - {golem.tier} - - {isEnabled ? ( - - ) : ( - - )} -
-

-
-
-

{golem.description}

- - - -
- - - - -
- - - - {/* Summon Cost */} -
-
Summon Cost:
-
- {golem.summonCost.map((cost, idx) => { - const elem = getElementInfo(cost.element || ''); - const available = cost.type === 'raw' - ? rawMana - : elements[cost.element || '']?.current || 0; - const canAfford = available >= cost.amount; - - return ( - - {cost.element && } - {' '}{cost.amount} - - ); - })} -
-
- - {/* Maintenance Cost */} -
-
Maintenance/hr:
-
- {golem.maintenanceCost.map((cost, idx) => { - return ( - - {cost.element && } - {' '}{cost.amount}/hr - - ); - })} -
-
- - {/* Status */} - {isSelected && ( -
- - Active on Floor {currentFloor} -
- )} -
-
- ); - }; - - return ( -
- {/* Header */} - -
-

- - Golemancy -

-
-
- {!hasGolemancy ? ( -
- -

Unlock the Fabricator attunement and reach Level 2 to summon golems.

-
- ) : ( - <> - 0 ? 'success' : undefined} - /> - - - - -

- Golems are automatically summoned at the start of each combat floor. - They cost mana to maintain and will be dismissed if you run out. -

- - )} -
-
- - {/* Active Golems - Empty State */} - {hasGolemancy && golemancy.summonedGolems.length === 0 && ( - -
- -

No golems summoned

-

Enable golems below to summon them at the start of combat

-
-
- )} - - {/* Active Golems */} - {hasGolemancy && golemancy.summonedGolems.length > 0 && ( - -
-

- - Active Golems ({golemancy.summonedGolems.length}) -

-
-
- {golemancy.summonedGolems.map(sg => { - const golem = GOLEMS_DEF[sg.golemId]; - if (!golem) return null; - - return ( - - - {golem.name} - - ); - })} -
-
- )} - - {/* Golem Selection */} - {hasGolemancy && ( - -
-

Select Golems to Summon

-
- -
- {/* Unlocked Golems */} - {unlockedGolems.map(golem => renderGolemCard(golem.id, true))} - - {/* Locked Golems */} - {Object.values(GOLEMS_DEF || {}) - .filter(g => !isGolemUnlocked(g.id, attunements, unlockedElements)) - .map(golem => renderGolemCard(golem.id, false))} -
-
-
- )} - - {/* Golemancy Skills Info */} - -
-

Golemancy Skills

-
-
- - - - -
-
-
- ); -} - -GolemancyTab.displayName = "GolemancyTab"; -import { DebugName } from '@/lib/game/debug-context'; diff --git a/src/components/game/tabs/GuardianPanel.tsx b/src/components/game/tabs/GuardianPanel.tsx deleted file mode 100644 index 993777d..0000000 --- a/src/components/game/tabs/GuardianPanel.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client'; - -import { Badge } from '@/components/ui/badge'; -import { ELEMENTS } from '@/lib/game/constants'; -import { GUARDIANS } from '@/lib/game/constants'; - -interface GuardianPanelProps { - currentFloor: number; - floorElemDef: any; -} - -export function GuardianPanel({ currentFloor, floorElemDef }: GuardianPanelProps) { - const guardian = GUARDIANS[currentFloor]; - if (!guardian) return null; - - return ( -
-
- ⚔️ - - {guardian.name} - -
- -
-
Power: {fmt(guardian.power)}
- - {guardian.armor > 0 && ( -
Armor: {(guardian.armor * 100).toFixed(0)}%
- )} - - {guardian.effects && guardian.effects.length > 0 && ( -
- {guardian.effects.map((eff: any, i: number) => ( - - {eff.type === 'burn' && `🔥 Burn ${(eff.value * 100).toFixed(0)}%`} - {eff.type === 'armor_pierce' && `🗡️ Pierce ${(eff.value * 100).toFixed(0)}%`} - - ))} -
- )} -
-
- ); -} - -function fmt(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} diff --git a/src/components/game/tabs/LootTab.tsx b/src/components/game/tabs/LootTab.tsx deleted file mode 100755 index 0380c9d..0000000 --- a/src/components/game/tabs/LootTab.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { useCraftingStore, useManaStore } from '@/lib/game/stores'; -import { LootInventoryDisplay } from '@/components/game/LootInventory'; -import { DebugName } from '@/lib/game/debug-context'; - -export function LootTab() { - const lootInventory = useCraftingStore((s) => s.lootInventory); - const elements = useManaStore((s) => s.elements); - const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); - const deleteMaterial = useCraftingStore((s) => s.deleteMaterial); - const deleteEquipmentInstance = useCraftingStore((s) => s.deleteEquipmentInstance); - - return ( - -
- -
-
- ); -} - -LootTab.displayName = "LootTab"; diff --git a/src/components/game/tabs/MilestoneProgress.tsx b/src/components/game/tabs/MilestoneProgress.tsx deleted file mode 100644 index 3b2d34e..0000000 --- a/src/components/game/tabs/MilestoneProgress.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// ─── Milestone Progress ─────────────────────────────────────────── -// Milestone upgrade progress indicator for skill rows - -import { Badge } from '@/components/ui/badge'; - -interface MilestoneProgressProps { - milestoneInfo: { - milestone: 5 | 10; - hasUpgrades: boolean; - selectedCount: number; - } | null; -} - -export function MilestoneProgress({ milestoneInfo }: MilestoneProgressProps) { - if (!milestoneInfo) return null; - - return ( -
- ⭐ Level {milestoneInfo.milestone} milestone: {milestoneInfo.selectedCount}/2 upgrades selected -
- ); -} diff --git a/src/components/game/tabs/PrestigeTab.tsx b/src/components/game/tabs/PrestigeTab.tsx deleted file mode 100644 index c4fa6c7..0000000 --- a/src/components/game/tabs/PrestigeTab.tsx +++ /dev/null @@ -1,115 +0,0 @@ -// ─── Prestige/Grimoire Tab ────────────────────────── - -'use client'; - -import { useState } from 'react'; -import { usePrestigeStore, useSkillStore, useManaStore, useCraftingStore } from '@/lib/game/stores'; -import { useGameLoop } from '@/lib/game/stores/gameHooks'; -import { getUnifiedEffects } from '@/lib/game/effects'; -import { - ELEMENTS, - GUARDIANS, - PRESTIGE_DEF, - getStudySpeedMultiplier, -} from '@/lib/game/constants'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { fmt } from '@/lib/game/stores'; - -export function PrestigeTab() { - const [selectedManaType, setSelectedManaType] = useState(''); - - useGameLoop(); - - const skills = useSkillStore((s) => s.skills); - const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades); - const rawMana = useManaStore((s) => s.rawMana); - const elements = useManaStore((s) => s.elements); - const skillUpgrades = useSkillStore((s) => s.skillUpgrades); - const skillTiers = useSkillStore((s) => s.skillTiers); - const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); - const equippedInstances = useCraftingStore((s) => s.equippedInstances); - const doPrestige = usePrestigeStore((s) => s.doPrestige); - - const upgradeEffects = getUnifiedEffects({ - skillUpgrades, - skillTiers, - equipmentInstances, - equippedInstances, - }); - - // Get unlocked elements for mana type selector - const unlockedElements = Object.entries(ELEMENTS) - .filter(([id]) => elements[id]?.unlocked) - .map(([id, elem]) => ({ - id, - name: elem.name, - sym: elem.sym, - color: elem.color, - })); - - return ( - -
-
- {/* Prestige Upgrades */} - {Object.entries(PRESTIGE_DEF || {}).map(([id, def]) => { - const level = prestigeUpgrades[id] || 0; - const canAfford = rawMana >= def.cost; - const effect = upgradeEffects ? upgradeEffects.specials.has(id) : false; - - return ( - - - - {def.name} - {effect && Active} - - - -

{def.description}

-
- Level: {level}/{def.maxLevel} - -
-
-
- ); - })} - - {/* Mana Type Selection for Attunements */} - - - Select Mana Type for Attunement - - -
- {unlockedElements.map(elem => ( - - ))} -
-
-
-
-
-
- ); -} - -PrestigeTab.displayName = "PrestigeTab"; diff --git a/src/components/game/tabs/RoomDisplay.tsx b/src/components/game/tabs/RoomDisplay.tsx deleted file mode 100644 index 292b16d..0000000 --- a/src/components/game/tabs/RoomDisplay.tsx +++ /dev/null @@ -1,194 +0,0 @@ -'use client'; - -import { Progress } from '@/components/ui/progress'; -import { Badge } from '@/components/ui/badge'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { Shield, Wind, Heart, ShieldCheck, Skull } from 'lucide-react'; -import type { RoomDisplayProps } from '@/lib/game/types'; -import { ELEMENTS } from '@/lib/game/constants'; - -const ROOM_TYPE_CONFIG: Record = { - combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' }, - swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' }, - speed: { label: 'Speed', icon: '💨', color: '#3B82F6' }, - guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' }, - puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' }, -} as const; - -export function RoomDisplay({ - roomType, - roomConfig, - primaryEnemy, - swarmEnemies, - puzzleId, - puzzleProgress, - simpleMode, - floorElemDef, - floorHP, - floorMaxHP, - totalDPS, - currentAction, - activeEquipmentSpells, -}: RoomDisplayProps) { - // Puzzle Room Display - if (roomType === 'puzzle') { - return ( -
-
- 🧩 - - {puzzleId ? puzzleId.replace(/_/g, ' ').toUpperCase() : 'Puzzle Room'} - -
-
-
- Progress - {((puzzleProgress || 0) * 100).toFixed(0)}% -
- -
-
- ); - } - - // Single Enemy Display (Combat/Speed/Guardian - non-swarm) - if ((roomType === 'combat' || roomType === 'speed' || roomType === 'guardian') && primaryEnemy) { - return ( -
-
-
- - - {primaryEnemy.name || 'Unknown Enemy'} - -
- - {ELEMENTS[primaryEnemy.element]?.sym} {ELEMENTS[primaryEnemy.element]?.name} - -
- - {/* Enemy HP Bar */} -
-
-
-
-
- {fmt(primaryEnemy.hp)} / {fmt(primaryEnemy.maxHP)} HP -
-
- - {/* Enemy Properties */} - -
- ); - } - - // Swarm Enemies Display - if (roomType === 'swarm' && swarmEnemies && swarmEnemies.length > 0) { - return ( -
-
- Swarm Enemies ({swarmEnemies.length}) -
- {swarmEnemies.map((enemy: any, index: number) => ( -
-
-
- - - {enemy.name || `Enemy ${index + 1}`} - -
- - {ELEMENTS[enemy.element]?.sym} {fmt(enemy.hp)}/{fmt(enemy.maxHP)} HP - -
-
-
-
- -
- ))} -
- ); - } - - // Floor HP Bar (non-swarm, non-puzzle) - return ( -
-
-
-
-
- {fmt(floorHP)} / {fmt(floorMaxHP)} HP - DPS: {currentAction === 'climb' && activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'} -
-
- ); -} - -function EnemyProperties({ enemy, small }: { enemy: any; small?: boolean }) { - const props = []; - if (enemy.armor > 0) props.push({ type: 'armor', label: `${(enemy.armor * 100).toFixed(0)}% Armor`, icon: Shield }); - if (enemy.dodgeChance > 0) props.push({ type: 'dodge', label: `${(enemy.dodgeChance * 100).toFixed(0)}% Dodge`, icon: Wind }); - if (enemy.healthRegen > 0) props.push({ type: 'regen', label: `${(enemy.healthRegen * 100).toFixed(1)}% Regen`, icon: Heart }); - if (enemy.barrier > 0) props.push({ type: 'barrier', label: `${(enemy.barrier * 100).toFixed(0)}% Barrier`, icon: ShieldCheck }); - - if (props.length === 0) return null; - - return ( -
- {props.map((p, i) => { - const Icon = p.icon; - return ( - - - - - {p.label} - - - -

Reduces incoming damage by {(enemy.armor * 100).toFixed(0)}%

-
-
- ); - })} -
- ); -} - -function fmt(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} - -function fmtDec(value: number): string { - if (value >= 1e12) return (value / 1e12).toFixed(2) + 't'; - if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b'; - if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm'; - if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k'; - return value.toFixed(0); -} diff --git a/src/components/game/tabs/SkillCategoryHeader.tsx b/src/components/game/tabs/SkillCategoryHeader.tsx deleted file mode 100644 index f39ce2b..0000000 --- a/src/components/game/tabs/SkillCategoryHeader.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// ─── Skill Category Header ─────────────────────────────────────────── -// Header for a skill category with collapse/expand toggle - -import { Badge } from '@/components/ui/badge'; -import { ChevronDown, ChevronRight } from 'lucide-react'; - -interface SkillCategoryHeaderProps { - category: { - id: string; - name: string; - icon: string; - }; - skillCount: number; - isCollapsed: boolean; - onToggle: () => void; -} - -export function SkillCategoryHeader({ - category, - skillCount, - isCollapsed, - onToggle, -}: SkillCategoryHeaderProps) { - return ( - - - - {category.icon} {category.name} - -
- - {skillCount} skills - - {isCollapsed ? : } -
-
-
- ); -} - -// Local import of CardHeader and CardTitle to avoid circular deps -import { CardHeader, CardTitle } from '@/components/ui/card'; diff --git a/src/components/game/tabs/SkillMultipliers.tsx b/src/components/game/tabs/SkillMultipliers.tsx deleted file mode 100644 index 6c76bd6..0000000 --- a/src/components/game/tabs/SkillMultipliers.tsx +++ /dev/null @@ -1,50 +0,0 @@ -// ─── Skill Multipliers ─────────────────────────────────────────── -// Study speed and cost multiplier display for skill rows - -import { ELEMENTS } from '@/lib/game/constants'; - -type AdditionalCost = { type: 'element'; element: string; amount: number }; - -interface SkillMultipliersProps { - effectiveStudyTime: number; - speedMult: number; - costMult: number; - cost: number; - additionalCost?: AdditionalCost; -} - -export function SkillMultipliers({ - effectiveStudyTime, - speedMult, - costMult, - cost, - additionalCost, -}: SkillMultipliersProps) { - return ( -
- 1 ? 'text-green-400' : ''}> - Study: {formatStudyTime(effectiveStudyTime)}{speedMult > 1 && ( - ({Math.round(speedMult * 100)}% speed) - )} - - {' • '} - - Cost: {cost} mana{costMult < 1 && ( - ({Math.round(costMult * 100)}% cost) - )} - {additionalCost && additionalCost.type === 'element' && ( - - + {additionalCost.amount} {ELEMENTS[additionalCost.element]?.sym} {additionalCost.element} - - )} - -
- ); -} - -function formatStudyTime(ms: number): string { - if (ms < 60_000) return `${Math.floor(ms / 1000)}s`; - if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`; - if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ${Math.floor((ms % 3_600_000) / 60_000)}m`; - return `${Math.floor(ms / 86_400_000)}d ${Math.floor((ms % 86_400_000) / 3_600_000)}h`; -} diff --git a/src/components/game/tabs/SkillRow.tsx b/src/components/game/tabs/SkillRow.tsx deleted file mode 100644 index fd30403..0000000 --- a/src/components/game/tabs/SkillRow.tsx +++ /dev/null @@ -1,231 +0,0 @@ -// ─── Skill Row Component ─────────────────────────────────────────────────── -// Individual skill row for the Skills tab - extracted from SkillsTab for modularity - -'use client'; - -import { useState } from 'react'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { ChevronRight } from 'lucide-react'; -import type { SkillUpgradeChoice } from '@/lib/game/types'; -import { ELEMENTS } from '@/lib/game/constants'; -import { formatStudyTime } from '@/lib/game/formatting'; -import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier, SKILL_EVOLUTION_PATHS } from '@/lib/game/skill-evolution'; -import type { ComputedEffects } from '@/lib/game/upgrade-effects.types'; -import { SPECIAL_EFFECTS, hasSpecial } from '@/lib/game/special-effects'; -import { MilestoneProgress } from './MilestoneProgress'; -import { SkillMultipliers } from './SkillMultipliers'; - -type StudyTarget = { type: 'skill' | 'spell'; id: string; progress: number; required: number; } | null; - -interface SkillRowProps { - skillId: string; - def: { - name: string; - desc: string; - cat: string; - max: number; - studyTime: number; - base: number; - cost?: { type: 'element'; element: string; amount: number } | { type: 'raw'; amount: number }; - req?: Record; - }; - level: number; - maxed: boolean; - isStudying: boolean; - tierMultiplier: number; - skillDisplayName: string; - selectedUpgrades: string[]; - selectedL5: string[]; - selectedL10: string[]; - prereqMet: boolean; - canStudy: boolean; - isParallelStudy: boolean; - canParallelStudy: boolean; - canTierUp: boolean; - hasInsufficientMana: boolean; - currentStudyTarget: StudyTarget; - milestoneInfo: { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null; - upgradeEffects: ComputedEffects; - // Costs and times - cost: number; - additionalCost?: { type: 'element'; element: string; amount: number }; - effectiveStudyTime: number; - costMult: number; - speedMult: number; - // Callbacks - onStudy: (skillId: string) => void; - onParallelStudy: (skillId: string) => void; - onCancelStudy: (skillId: string) => void; - onUpgradeDialogOpen: (skillId: string, milestone: 5 | 10) => void; - onTierUp: (skillId: string) => void; - onShowToast: (type: 'info' | 'error', title: string, description: string) => void; - tierUpLabel?: string; -} - -export function SkillRow(props: SkillRowProps) { - const { - skillId, - def, - level, - maxed, - isStudying, - tierMultiplier, - skillDisplayName, - selectedUpgrades, - selectedL5, - selectedL10, - prereqMet, - canStudy, - isParallelStudy, - canParallelStudy, - canTierUp, - hasInsufficientMana, - currentStudyTarget, - milestoneInfo, - upgradeEffects, - cost, - additionalCost, - effectiveStudyTime, - costMult, - speedMult, - onStudy, - onParallelStudy, - onCancelStudy, - onUpgradeDialogOpen, - onTierUp, - } = props; - - return ( -
-
-
- {skillDisplayName} - {level > 0 && Lv.{level}} - {selectedUpgrades.length > 0 && ( -
- {selectedL5.length > 0 && ( - L5: {selectedL5.length} - )} - {selectedL10.length > 0 && ( - L10: {selectedL10.length} - )} -
- )} -
-
{def.desc}{level > 0 && tierMultiplier !== 1 && ` (Tier ${tierMultiplier}x effect)`}
- {!prereqMet && def.req && ( -
- Requires: {Object.entries(def.req).map(([r, rl]) => `${r} Lv.${rl}`).join(', ')} -
- )} - - - {hasInsufficientMana && ( -
- Insufficient mana! Need {cost} mana to study. -
- )} - - -
- -
- {/* Level dots */} -
- {Array.from({ length: def.max }).map((_, i) => ( -
- ))} -
- - {isStudying ? ( -
- {formatStudyTime(currentStudyTarget?.progress || 0)}/{formatStudyTime(def.studyTime * (level > 1 ? level : 1))} -
- ) : milestoneInfo ? ( - - ) : canTierUp ? ( - - ) : maxed ? ( - Maxed - ) : ( -
- - {hasSpecial(upgradeEffects, SPECIAL_EFFECTS.PARALLEL_STUDY) && - currentStudyTarget && - !isParallelStudy && - canParallelStudy && - canStudy && ( - - - - - - -

Study in parallel (50% speed)

-
-
-
- )} -
- )} -
-
- ); -} diff --git a/src/components/game/tabs/SpellsTab.tsx b/src/components/game/tabs/SpellsTab.tsx deleted file mode 100755 index ca259e2..0000000 --- a/src/components/game/tabs/SpellsTab.tsx +++ /dev/null @@ -1,244 +0,0 @@ -'use client'; - -import { useCombatStore, useCraftingStore, useManaStore, usePrestigeStore } from '@/lib/game/stores'; -import { GameCard, ElementBadge } from '@/components/ui'; -import { Badge } from '@/components/ui/badge'; -import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants'; -import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects'; -import { canAffordSpellCost } from '@/lib/game/utils'; -import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting'; - -export function SpellsTab() { - // Use modular stores directly - const spells = useCombatStore((s) => s.spells); - const activeSpell = useCombatStore((s) => s.activeSpell); - const setSpell = useCombatStore((s) => s.setSpell); - - const equippedInstances = useCraftingStore((s) => s.equippedInstances); - const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); - - const rawMana = useManaStore((s) => s.rawMana); - const elements = useManaStore((s) => s.elements); - - const signedPacts = usePrestigeStore((s) => s.signedPacts); - const unlockedEffects = useCraftingStore((s) => s.unlockedEffects); - - // Get spells from equipment - const equipmentSpellIds: string[] = []; - const spellSources: Record = {}; - - // Guard against undefined stores during initialization - if (!equippedInstances || !equipmentInstances) { - return ( - - -
- Loading spell data... -
- -
); - } - - for (const instanceId of Object.values(equippedInstances || {})) { - if (!instanceId) continue; - const instance = equipmentInstances[instanceId]; - if (!instance) continue; - - for (const ench of instance.enchantments) { - const effectDef = ENCHANTMENT_EFFECTS?.[ench.effectId]; - if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) { - const spellId = effectDef.effect.spellId; - if (!equipmentSpellIds.includes(spellId)) { - equipmentSpellIds.push(spellId); - } - if (!spellSources[spellId]) { - spellSources[spellId] = []; - } - spellSources[spellId].push(instance.name); - } - } - } - - const canCastSpell = (spellId: string): boolean => { - const spell = SPELLS_DEF[spellId]; - if (!spell || !spell.cost) return false; - return canAffordSpellCost(spell.cost, rawMana, elements); - }; - - const hasPactSpells = signedPacts && signedPacts.length > 0; - - return ( -
- {/* Equipment-Granted Spells */} -
-

- Known Spells -

-

- Spells are obtained by enchanting equipment with spell effects. - Visit the Crafting tab to design and apply enchantments. -

- - {equipmentSpellIds.length > 0 ? ( -
- {equipmentSpellIds.map(id => { - const def = SPELLS_DEF[id]; - if (!def) return null; - - const isActive = activeSpell === id; - const canCast = canCastSpell(id); - const elemDef = def.elem === 'raw' ? null : ELEMENTS[def.elem]; - const sources = spellSources[id] || []; - - return ( - -
-
-

- {def.name} -

- - Equipment - -
-
-
-
- {def.elem !== 'raw' && ( - - {elemDef?.name} - - )} - ⚔️ {def.dmg} dmg -
-
- Cost: {formatSpellCost(def.cost)} -
-
From: {sources.join(', ')}
-
- {isActive ? ( - - Active - - ) : ( - - )} -
-
-
- ); - })} -
- ) : ( -
-
No spells known yet
-
Enchant a staff with a spell effect to gain spells
-
- )} -
- - {/* Pact Spells (from guardian defeats) - Empty State */} - {!hasPactSpells && ( -
-

- Pact Spells -

-
-

Defeat guardians and sign pacts to unlock powerful spells

-
-
- )} - - {hasPactSpells && ( -
-

- Pact Spells -

-

Spells earned through guardian pacts appear here.

-
- )} - - {/* Spell Reference - show all available spells for enchanting */} -
-

- Spell Reference -

-

- These spells can be applied to equipment through the enchanting system. - Research enchantment effects in the Skills tab to unlock them for designing. -

- -
- {Object.entries(SPELLS_DEF).map(([id, def]) => { - const elemDef = def.elem === 'raw' ? null : ELEMENTS[def.elem]; - const isUnlocked = unlockedEffects?.includes(`spell_${id}`); - - return ( - -
-
-

- {def.name} -

-
- {def.tier > 0 && ( - - T{def.tier} - - )} - {isUnlocked && ( - - Unlocked - - )} -
-
-
-
-
- {def.elem !== 'raw' && ( - - {elemDef?.name} - - )} - ⚔️ {def.dmg} dmg -
-
- Cost: {formatSpellCost(def.cost)} -
- {def.desc && ( -
{def.desc}
- )} - {!isUnlocked && ( -
Research to unlock for enchanting
- )} -
-
- ); - })} -
-
-
- ); -} - -SpellsTab.displayName = "SpellsTab"; -import { DebugName } from '@/lib/game/debug-context'; diff --git a/src/components/game/tabs/SpireActiveSpells.tsx b/src/components/game/tabs/SpireActiveSpells.tsx deleted file mode 100644 index d6282ef..0000000 --- a/src/components/game/tabs/SpireActiveSpells.tsx +++ /dev/null @@ -1,100 +0,0 @@ -'use client'; - -import { Card, CardContent } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants'; -import { calcDamage } from '@/lib/game/stores'; -import { getUnifiedEffects } from '@/lib/game/effects'; -import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting'; -import { canAffordSpellCost } from '@/lib/game/stores'; -import type { EquipmentSpellState } from '@/lib/game/types'; - -interface ActiveSpellsProps { - activeEquipmentSpells: { spellId: string; equipmentId: string }[]; - spells: Record; - equipmentSpellStates: EquipmentSpellState[]; - skills: Record; - skillUpgrades: Record; - skillTiers: Record; - signedPacts: number[]; - currentAction: string; - floorElem: string; - canCastSpell: (spellId: string) => boolean; -} - -export function SpireActiveSpells({ - activeEquipmentSpells, - spells, - equipmentSpellStates, - skills, - skillUpgrades, - skillTiers, - signedPacts, - currentAction, - floorElem, - canCastSpell, -}: ActiveSpellsProps) { - return ( - - -
- Active Spells ({activeEquipmentSpells.length}) -
- {activeEquipmentSpells.length > 0 ? ( -
- {activeEquipmentSpells.map(({ spellId, equipmentId }) => { - const spellDef = SPELLS_DEF[spellId]; - if (!spellDef) return null; - const spellState = equipmentSpellStates?.find( - s => s.spellId === spellId && s.sourceEquipment === equipmentId - ); - const progress = spellState?.castProgress || 0; - const canCast = canCastSpell(spellId); - - // Compute progress bar JSX - let progressBar = null; - if (currentAction === 'climb') { - const widthPercent = Math.min(100, progress * 100); - const elemColor = spellDef.elem === 'raw' ? '#60A5FA' : ELEMENTS[spellDef.elem]?.color || '#60A5FA'; - const backgroundGradient = 'linear-gradient(90deg, ' + elemColor + '99, ' + elemColor + ')'; - progressBar = ( -
-
- Cast - {widthPercent.toFixed(0)}% -
-
-
-
-
- ); - } - - return ( -
-
- - {spellDef.name} - {spellDef.tier === 0 && Basic} - - {canCast ? '✓' : '✗'} -
-
- ⚔️ {calcDamage({ skills, signedPacts, skillUpgrades, skillTiers }, spellId, floorElem)} dmg • {formatSpellCost(spellDef.cost)} {' '}• ⚡ {Math.floor(calcDamage({ skills, signedPacts, skillUpgrades, skillTiers }, spellId, floorElem) * (spellDef.castSpeed || 1))} dmg/hr -
- {progressBar} -
- ); - })} -
- ) : ( -
No spells on equipped weapons. Enchant a staff with spell effects in the Crafting tab.
- )} - - - ); -} diff --git a/src/components/game/tabs/SpireGolems.tsx b/src/components/game/tabs/SpireGolems.tsx deleted file mode 100644 index 951f417..0000000 --- a/src/components/game/tabs/SpireGolems.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client'; - -import { Card, CardContent } from '@/components/ui/card'; -import { Mountain } from 'lucide-react'; -import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems'; -import { ELEMENTS } from '@/lib/game/constants'; - -interface SpireGolemsProps { - golemancy: { - summonedGolems: string[]; - enabledGolems: string[]; - }; - skills: Record; - floorElem: string; - currentAction: string; -} - -export function SpireGolems({ golemancy, skills, floorElem }: SpireGolemsProps) { - if (golemancy.summonedGolems.length === 0) return null; - - return ( - - -
- - Active Golems ({golemancy.summonedGolems.length}) -
-
- {golemancy.summonedGolems.map((summoned) => { - const golemDef = GOLEMS_DEF[summoned.golemId]; - if (!golemDef) return null; - const elemColor = ELEMENTS[golemDef.baseManaType]?.color || '#888'; - const damage = getGolemDamage(summoned.golemId, skills); - const attackSpeed = getGolemAttackSpeed(summoned.golemId, skills); - - return ( -
-
-
- - {golemDef.name} -
- {golemDef.isAoe && AOE {golemDef.aoeTargets}} -
-
⚔️ {damage} DMG • ⚡ {attackSpeed.toFixed(1)}/hr
- {currentAction === 'climb' && summoned.attackProgress > 0 && ( -
-
- Attack - {(summoned.attackProgress * 100).toFixed(0)}% -
-
-
-
-
- )} -
- ); - })} -
- - - ); -} diff --git a/src/components/game/tabs/SpireHeader.tsx b/src/components/game/tabs/SpireHeader.tsx deleted file mode 100644 index 9bbfa16..0000000 --- a/src/components/game/tabs/SpireHeader.tsx +++ /dev/null @@ -1,109 +0,0 @@ -'use client'; - -import { Badge } from '@/components/ui/badge'; -import { Separator } from '@/components/ui/separator'; -import { Mountain } from 'lucide-react'; -import { ELEMENTS } from '@/lib/game/constants'; - -interface SpireHeaderProps { - currentFloor: number; - maxFloorReached: number; - signedPacts: number; - isGuardianFloor: boolean; - roomType: string; - roomLabel: string; - roomIcon: string; - roomColor: string; - floorElem: string; - floorElemDef: any; - simpleMode: boolean; -} - -export function SpireHeader({ - currentFloor, - maxFloorReached, - signedPacts, - isGuardianFloor, - roomType, - roomLabel, - roomIcon, - roomColor, - floorElem, - floorElemDef, - simpleMode, -}: SpireHeaderProps) { - return ( - <> - {/* Current Floor Card - Only show in Spire Mode (simpleMode) */} - {simpleMode && ( -
-
-

- Current Floor - - {roomIcon} {roomLabel} - -

-
- -
-
- - {currentFloor} - - / 100 - - {floorElemDef?.sym} {floorElemDef?.name} - - {isGuardianFloor && ( - GUARDIAN - )} -
- - {isGuardianFloor && ( -
- ⚔️ Guardian Floor -
- )} - -
- Best: Floor {maxFloorReached} • - Pacts: {signedPacts} -
-
-
- )} - - {/* Spire Stats Card - Only show in normal mode */} - {!simpleMode && ( -
-
-

Spire Stats

-
- -
-
-
{maxFloorReached}
-
Best Floor
-
-
-
{signedPacts}
-
Pacts Signed
-
-
- -
- Current Floor: {currentFloor} -
-
- )} - - ); -} diff --git a/src/components/game/tabs/SpireTab.tsx b/src/components/game/tabs/SpireTab.tsx deleted file mode 100755 index 5ddb883..0000000 --- a/src/components/game/tabs/SpireTab.tsx +++ /dev/null @@ -1,376 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; -import { Mountain, ChevronDown } from 'lucide-react'; -import type { ActivityLogEntry } from '@/lib/game/types'; -import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constants'; -import { calcDamage, getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/utils'; -import { getUnifiedEffects } from '@/lib/game/effects'; -import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting'; -import { canAffordSpellCost, getFloorElement } from '@/lib/game/utils'; -import { useManaStore, useSkillStore, useCombatStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores'; -import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems'; - -// Extracted components -import { SpireHeader } from './SpireHeader'; -import { GuardianPanel } from './GuardianPanel'; -import { RoomDisplay } from './RoomDisplay'; -import { FloorControls } from './FloorControls'; -import { CombatStatsPanel } from './CombatStatsPanel'; -import { ActivityLog } from './ActivityLog'; -import { SpireActiveSpells } from './SpireActiveSpells'; -import { SpireGolems } from './SpireGolems'; -import { DebugName } from '@/lib/game/debug-context'; - -// Room type configurations -const ROOM_TYPE_CONFIG: Record = { - combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' }, - swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' }, - speed: { label: 'Speed', icon: '💨', color: '#3B82F6' }, - guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' }, - puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' }, -}; - -interface SpireTabProps { - simpleMode?: boolean; -} - -// Check if player can enter spire mode -const canEnterSpireMode = (spireMode: boolean): boolean => { - return !spireMode; -}; - -export function SpireTab({ simpleMode = false }: SpireTabProps) { - // Get state from modular stores - const currentFloor = useCombatStore((s) => s.currentFloor); - const floorHP = useCombatStore((s) => s.floorHP); - const floorMaxHP = useCombatStore((s) => s.floorMaxHP); - const maxFloorReached = useCombatStore((s) => s.maxFloorReached); - const currentAction = useCombatStore((s) => s.currentAction); - const castProgress = useCombatStore((s) => s.castProgress); - const activeSpell = useCombatStore((s) => s.activeSpell); - const spireMode = useCombatStore((s) => s.spireMode); - const climbDirection = useCombatStore((s) => s.climbDirection) || 'up'; - const clearedFloors = useCombatStore((s) => s.clearedFloors || {}); - const currentRoom = useCombatStore((s) => s.currentRoom); - const equipmentSpellStates = useCombatStore((s) => s.equipmentSpellStates); - const golemancy = useCombatStore((s) => s.golemancy); - const activityLog = useCombatStore((s) => s.activityLog); - - const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget); - const parallelStudyTarget = useSkillStore((s) => s.parallelStudyTarget); - const signedPacts = usePrestigeStore((s) => s.signedPacts); - const skills = useSkillStore((s) => s.skills); - const skillUpgrades = useSkillStore((s) => s.skillUpgrades); - const skillTiers = useSkillStore((s) => s.skillTiers); - - const rawMana = useManaStore((s) => s.rawMana); - const elements = useManaStore((s) => s.elements); - const equippedInstances = useCraftingStore((s) => s.equippedInstances); - const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); - const designProgress = useCraftingStore((s) => s.designProgress); - const designProgress2 = useCraftingStore((s) => s.designProgress2); - const preparationProgress = useCraftingStore((s) => s.preparationProgress); - const applicationProgress = useCraftingStore((s) => s.applicationProgress); - const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress); - - // Derived data - const floorElem = getFloorElement(currentFloor); - const floorElemDef = ELEMENTS[floorElem]; - const isGuardianFloor = !!GUARDIANS[currentFloor]; - const currentGuardian = GUARDIANS[currentFloor]; - const isFloorCleared = clearedFloors[currentFloor]; - const roomType = currentRoom?.roomType || 'combat'; - const roomConfig = ROOM_TYPE_CONFIG[roomType] || ROOM_TYPE_CONFIG.combat; - - const activeEquipmentSpells = useMemo( - () => getActiveEquipmentSpells(equippedInstances, equipmentInstances), - [equippedInstances, equipmentInstances] - ); - - const upgradeEffects = useMemo( - () => getUnifiedEffects({ - skillUpgrades, - skillTiers, - equippedInstances, - equipmentInstances, - }), - [skillUpgrades, skillTiers, equippedInstances, equipmentInstances] - ); - - const totalDPS = useMemo( - () => getTotalDPS({ skills, signedPacts, skillUpgrades, skillTiers }, upgradeEffects, floorElem), - [skills, signedPacts, skillUpgrades, skillTiers, upgradeEffects, floorElem] - ); - - // Spell casting check - const canCastSpell = (spellId: string): boolean => { - const spell = SPELLS_DEF[spellId]; - if (!spell || !spell.cost) return false; - return canAffordSpellCost(spell.cost, rawMana, elements); - }; - - // Climb handlers - defined OUTSIDE of JSX - const handleClimbUp = () => { - useCombatStore.getState().startClimbUp(); - }; - - const handleClimbDown = () => { - useCombatStore.getState().startClimbDown(); - }; - - const handleClimb = (direction: 'up' | 'down') => { - if (direction === 'up') { - handleClimbUp(); - } else { - handleClimbDown(); - } - }; - - const getSkillName = (skillId: string): string => { - return SKILLS_DEF[skillId]?.name || skillId; - }; - - // Handle exit spire mode - const exitSpireMode = () => { - useCombatStore.getState().exitSpireMode(); - }; - - // Handle enter spire mode - const enterSpireMode = () => { - useCombatStore.getState().enterSpireMode(); - }; - - return ( - -
- {/* Enter Spire Mode - Normal mode only */} - {!simpleMode && ( - - - -
- Climb the Spire to face guardians and earn pacts -
-
-
- )} - - {/* Exit Spire Mode - Spire mode only */} - {simpleMode && ( - - - -
- Climb down to floor 1 to return to the main game -
-
-
- )} - - {/* Spire Header */} - - - {/* Active Spells Card - Spire Mode only */} - {simpleMode && ( - - )} - - {/* Summoned Golems */} - {simpleMode && golemancy.summonedGolems.length > 0 && ( - - )} - - {/* Guardian Panel */} - {isGuardianFloor && simpleMode && ( - - )} - - {/* Room Display */} - {simpleMode && ( - - )} - - {/* Floor Controls */} - {simpleMode && ( - - )} - - {/* Combat Stats Panel */} - {simpleMode && ( - - )} - - {/* Activity Log - Spire Mode only */} - {simpleMode && } - - {/* Study Progress - Normal mode only */} - {!simpleMode && currentStudyTarget && ( - - -
Study: {getSkillName(currentStudyTarget.id)}
-
-
-
- {parallelStudyTarget && ( -
-
Parallel: {getSkillName(parallelStudyTarget.id)} (50% speed)
-
-
-
-
- )} - - - )} - - {/* Crafting Progress - Normal mode only */} - {!simpleMode && (designProgress || preparationProgress || applicationProgress) && ( - - - {designProgress && ( -
-
Design Progress
-
-
-
-
- )} - {preparationProgress && ( -
-
Preparation Progress
-
-
-
-
- )} - {applicationProgress && ( -
-
Application Progress
-
-
-
-
- )} - - - )} -
- - ); -} - -SpireTab.displayName = "SpireTab"; diff --git a/src/components/game/tabs/StatsTab.tsx b/src/components/game/tabs/StatsTab.tsx deleted file mode 100644 index 4427e7d..0000000 --- a/src/components/game/tabs/StatsTab.tsx +++ /dev/null @@ -1,331 +0,0 @@ -'use client'; - -import { ELEMENTS, GUARDIANS } from '@/lib/game/constants'; -import { getTierMultiplier } from '@/lib/game/skill-evolution'; -import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects'; -import { getUnifiedEffects } from '@/lib/game/effects'; -import { fmt, fmtDec, computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/stores'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Badge } from '@/components/ui/badge'; -import { Separator } from '@/components/ui/separator'; -import { FlaskConical, Trophy, RotateCcw } from 'lucide-react'; -import { ManaStatsSection } from '../stats/ManaStatsSection'; -import { ManaTypeBreakdown } from '../stats/ManaTypeBreakdown'; -import { CombatStatsSection } from '../stats/CombatStatsSection'; -import { StudyStatsSection } from '../stats/StudyStatsSection'; -import { UpgradeEffectsSection } from '../stats/UpgradeEffectsSection'; - -// Modular stores -import { useCombatStore, useManaStore, useSkillStore, usePrestigeStore, useGameStore } from '@/lib/game/stores'; -import { useCraftingStore } from '@/lib/game/stores/craftingStore'; -import { DebugName } from '@/lib/game/debug-context'; - -export function StatsTab() { - // Get state from modular stores - const skills = useSkillStore((s) => s.skills); - const skillTiers = useSkillStore((s) => s.skillTiers); - const skillUpgrades = useSkillStore((s) => s.skillUpgrades); - - const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades); - const loopCount = usePrestigeStore((s) => s.loopCount); - const insight = usePrestigeStore((s) => s.insight); - const totalInsight = usePrestigeStore((s) => s.totalInsight); - const memorySlots = usePrestigeStore((s) => s.memorySlots); - const signedPacts = usePrestigeStore((s) => s.signedPacts); - - const elements = useManaStore((s) => s.elements); - const totalManaGathered = useManaStore((s) => s.totalManaGathered); - const rawMana = useManaStore((s) => s.rawMana); - const meditateTicks = useManaStore((s) => s.meditateTicks); - - const maxFloorReached = useCombatStore((s) => s.maxFloorReached); - const spells = useCombatStore((s) => s.spells); - - // Get equipment state from crafting store - const equippedInstances = useCraftingStore(s => s.equippedInstances); - const equipmentInstances = useCraftingStore(s => s.equipmentInstances); - - // Compute unified effects - const upgradeEffects = getUnifiedEffects({ - skillUpgrades, - skillTiers, - equippedInstances, - equipmentInstances - }); - - // Compute derived stats - const maxMana = computeMaxMana({ - skills, - prestigeUpgrades, - skillUpgrades, - skillTiers - }, upgradeEffects); - - const baseRegen = computeRegen({ - skills, - prestigeUpgrades, - skillUpgrades, - skillTiers - }, upgradeEffects); - - const clickMana = computeClickMana({ - skills, - prestigeUpgrades, - skillUpgrades, - skillTiers - }); - - const meditationMultiplier = getMeditationBonus(meditateTicks, skills, upgradeEffects.meditationEfficiency); - - const day = useGameStore((s) => s.day); - const hour = useGameStore((s) => s.hour); - const incursionStrength = getIncursionStrength(day, hour); - - // Effective regen with incursion penalty - const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength); - - // Mana Cascade bonus - const manaCascadeBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_CASCADE) - ? Math.floor(maxMana / 100) * 0.1 - : 0; - - // Mana Waterfall bonus - const manaWaterfallBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_WATERFALL) - ? Math.floor(maxMana / 100) * 0.25 - : 0; - - // Effective regen - const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier; - - // Get study speed/cost multipliers - const studySpeedMult = getStudySpeedMultiplier(skills); - const studyCostMult = getStudyCostMultiplier(skills); - - // Check special effects - const hasManaWaterfall = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_WATERFALL); - const hasFlowSurge = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.FLOW_SURGE); - const hasManaOverflow = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_OVERFLOW); - const hasEternalFlow = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.ETERNAL_FLOW); - - // Compute element max - const elemMax = (() => { - const ea = skillTiers?.elemAttune || 1; - const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune'; - const level = skills[tieredSkillId] || skills.elemAttune || 0; - const tierMult = getTierMultiplier(tieredSkillId); - return 10 + level * 50 * tierMult + (prestigeUpgrades.elementalAttune || 0) * 25; - })(); - - return ( - -
- {/* Mana Stats */} - - - {/* Mana Type Breakdown */} - - - {/* Combat Stats */} - - - {/* Study Stats */} - - - {/* Element Stats */} - - - - - Element Stats - - - -
-
-
- Element Capacity: - {elemMax} -
-
- Elem. Attunement Bonus: - - {(() => { - const ea = skillTiers?.elemAttune || 1; - const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune'; - const level = skills[tieredSkillId] || skills.elemAttune || 0; - const tierMult = getTierMultiplier(tieredSkillId); - return `+${level * 50 * tierMult}`; - })()} - -
-
- Prestige Attunement: - +{(prestigeUpgrades.elementalAttune || 0) * 25} -
-
-
-
- Unlocked Elements: - {Object.values(elements || {}).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length} -
-
- Elem. Crafting Bonus: - ×{fmtDec(1 + (skills.elemCrafting || 0) * 0.25, 2)} -
-
-
- -
Elemental Mana Pools:
-
- {Object.entries(elements) - .filter(([, state]) => state.unlocked) - .map(([id, state]) => { - const def = ELEMENTS[id]; - return ( -
-
{def?.sym}
-
{state.current}/{state.max}
-
- ); - })} -
-
-
- - {/* Active Upgrades */} - - - {/* Enchantment Power */} - - - - ✨ Enchantment Power - - - -
- Enchantment Power: - - {upgradeEffects?.enchantmentPowerMultiplier?.toFixed(2) || '1.0'}× - -
-

- Increases the power of all enchantments by {((upgradeEffects?.enchantmentPowerMultiplier || 1) - 1) * 100}%. Multiplier applied to all enchantment effects. -

-
-
- - {/* Pact Bonuses */} - - - - - Signed Pacts ({signedPacts.length}/10) - - - - {signedPacts.length === 0 ? ( -
No pacts signed yet. Defeat guardians to earn pacts.
- ) : ( -
- {signedPacts.map((floor) => { - const guardian = GUARDIANS[floor]; - if (!guardian) return null; - return ( -
-
-
- {guardian.name} -
-
Floor {floor}
-
- - {guardian.pact}x multiplier - -
- ); - })} -
- Combined Pact Multiplier: - ×{fmtDec(signedPacts.reduce((m, f) => m * (GUARDIANS[f]?.pact || 1), 1), 2)} -
-
- )} -
-
- - {/* Loop Stats */} - - - - - Loop Stats - - - -
-
-
{loopCount}
-
Loops Completed
-
-
-
{fmt(insight)}
-
Current Insight
-
-
-
{fmt(totalInsight)}
-
Total Insight
-
-
-
{maxFloorReached}
-
Max Floor
-
-
- -
-
-
{Object.values(spells || {}).filter((s: any) => s.learned).length}
-
Spells Learned
-
-
-
{Object.values(skills).reduce((a, b) => a + b, 0)}
-
Total Skill Levels
-
-
-
{fmt(totalManaGathered)}
-
Total Mana Gathered
-
-
-
{memorySlots}
-
Memory Slots
-
-
-
-
-
-
- ); -} - -StatsTab.displayName = "StatsTab"; diff --git a/src/components/game/tabs/StudyProgress.tsx b/src/components/game/tabs/StudyProgress.tsx deleted file mode 100755 index 42bfa63..0000000 --- a/src/components/game/tabs/StudyProgress.tsx +++ /dev/null @@ -1,75 +0,0 @@ -'use client'; - -import { SKILLS_DEF } from '@/lib/game/constants'; -import { formatStudyTime } from '@/lib/game/formatting'; -import { Button } from '@/components/ui/button'; -import { Progress } from '@/components/ui/progress'; -import type { StudyTarget } from '@/lib/game/types'; - -export interface StudyProgressProps { - currentStudyTarget: StudyTarget; - skills: Record; - studySpeedMult: number; - cancelStudy: () => void; -} - -export function StudyProgress({ - currentStudyTarget, - skills, - studySpeedMult, - cancelStudy -}: StudyProgressProps) { - const { id, progress, required } = currentStudyTarget; - - // Get skill name - const baseId = id.includes('_t') ? id.split('_t')[0] : id; - const skillDef = SKILLS_DEF[baseId]; - const skillName = skillDef?.name || id; - - // Get current level - const currentLevel = skills[id] || skills[baseId] || 0; - - // Calculate progress percentage - const progressPercent = Math.min((progress / required) * 100, 100); - - // Estimated completion - const remainingHours = required - progress; - const effectiveSpeed = studySpeedMult; - const realTimeRemaining = remainingHours / effectiveSpeed; - - return ( -
-
-
- {skillName} - - Level {currentLevel} → {currentLevel + 1} - -
- -
- -
-
- {formatStudyTime(progress)} / {formatStudyTime(required)} - {progressPercent.toFixed(1)}% -
- - {studySpeedMult > 1 && ( -
- Speed: {(studySpeedMult * 100).toFixed(0)}% • ETA: {formatStudyTime(realTimeRemaining)} -
- )} -
-
- ); -} - -StudyProgress.displayName = "StudyProgress"; diff --git a/src/components/game/tabs/UpgradeDialog.tsx b/src/components/game/tabs/UpgradeDialog.tsx deleted file mode 100755 index 051fd81..0000000 --- a/src/components/game/tabs/UpgradeDialog.tsx +++ /dev/null @@ -1,117 +0,0 @@ -'use client'; - -import { SKILLS_DEF } from '@/lib/game/constants'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import type { SkillUpgradeChoice } from '@/lib/game/types'; - -export interface UpgradeDialogProps { - open: boolean; - skillId: string | null; - milestone: 5 | 10; - pendingSelections: string[]; - available: SkillUpgradeChoice[]; - alreadySelected: string[]; - onToggle: (upgradeId: string) => void; - onConfirm: () => void; - onCancel: () => void; - onOpenChange: (open: boolean) => void; -} - -export function UpgradeDialog({ - open, - skillId, - milestone, - pendingSelections, - available, - alreadySelected, - onToggle, - onConfirm, - onCancel, - onOpenChange, -}: UpgradeDialogProps) { - if (!skillId) return null; - - // Get skill name - const baseId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; - const skillDef = SKILLS_DEF[baseId]; - const skillName = skillDef?.name || skillId; - - const currentSelections = pendingSelections.length > 0 ? pendingSelections : alreadySelected; - const canConfirm = currentSelections.length === 2; - - return ( - - - - - Level {milestone} Milestone: {skillName} - - - Choose 2 upgrades for this skill. These choices are permanent. - - - -
- {available.map((upgrade) => { - const isSelected = currentSelections.includes(upgrade.id); - const canSelect = isSelected || currentSelections.length < 2; - - return ( -
canSelect && onToggle(upgrade.id)} - className={`p-3 rounded border cursor-pointer transition-all ${ - isSelected - ? 'border-amber-500 bg-amber-900/30' - : canSelect - ? 'border-gray-700 hover:border-gray-500 bg-gray-800/30' - : 'border-gray-800 bg-gray-900/30 opacity-50 cursor-not-allowed' - }`} - > -
- - {upgrade.name} - - {isSelected && ( - Selected - )} -
-

{upgrade.desc}

-
- ); - })} - - {available.length === 0 && ( -
- No upgrades available at this milestone. -
- )} -
- - - - - -
-
- ); -} - -UpgradeDialog.displayName = "UpgradeDialog"; diff --git a/src/components/game/tabs/index.ts b/src/components/game/tabs/index.ts deleted file mode 100755 index dc78f37..0000000 --- a/src/components/game/tabs/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -// ─── Tab Components Index ────────────────────────────────────────────────────── -// Re-exports all tab components for cleaner imports - -export { CraftingTab } from './CraftingTab'; -export { SpireTab } from './SpireTab'; -export { SpellsTab } from './SpellsTab'; -// SkillsTab is now exported from src/components/game/index.ts -export { SkillsTab } from '../SkillsTab'; -export { StatsTab } from './StatsTab'; -export { EquipmentTab } from './EquipmentTab'; -export { AttunementsTab } from './AttunementsTab'; -export { DebugTab } from './DebugTab'; -export { LootTab } from './LootTab'; -export { AchievementsTab } from './AchievementsTab'; -export { GolemancyTab } from './GolemancyTab'; - -// Spire sub-components -export { SpireHeader } from './SpireHeader'; -export { GuardianPanel } from './GuardianPanel'; -export { RoomDisplay } from './RoomDisplay'; -export { FloorControls } from './FloorControls'; -export { CombatStatsPanel } from './CombatStatsPanel'; -export { ActivityLog } from './ActivityLog'; diff --git a/src/components/ui/skill-row.tsx b/src/components/ui/skill-row.tsx deleted file mode 100644 index c130c0a..0000000 --- a/src/components/ui/skill-row.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import * as React from "react"; -import { cn } from "@/lib/utils"; -import { ActionButton } from "./action-button"; -import { TooltipInfo } from "./tooltip-info"; -import { Progress } from "./progress"; -import { Lock } from "lucide-react"; - -interface SkillRowProps extends React.HTMLAttributes { - name: string; - description: string; - level: number; - maxLevel: number; - manaType?: string; // For coloring level dots - tier?: number; - studying?: boolean; - maxed?: boolean; - canTierUp?: boolean; - hasMilestone?: boolean; - milestoneLevel?: 5 | 10; - onStudy?: () => void; - onUpgrade?: () => void; - onTierUp?: () => void; - onMilestoneClick?: () => void; - cost?: number | string | React.ReactNode; - time?: string; - prereqMet?: boolean; - prereqText?: string; - showParallelStudy?: boolean; - onParallelStudy?: () => void; - selectedL5?: number; - selectedL10?: number; -} - -export function SkillRow({ - name, - description, - level, - maxLevel, - manaType = 'light', - tier, - studying = false, - maxed = false, - canTierUp = false, - hasMilestone = false, - milestoneLevel, - onStudy, - onUpgrade, - onTierUp, - onMilestoneClick, - cost, - time, - prereqMet = true, - prereqText, - showParallelStudy = false, - onParallelStudy, - selectedL5 = 0, - selectedL10 = 0, - className, - ...props -}: SkillRowProps) { - const manaColor = `var(--mana-${manaType})`; - - return ( -
-
- {/* Skill Header: Name + Tier Badge + Milestone Indicator + Prereq Lock */} -
-

- {name} -

- {tier !== undefined && tier > 1 && ( - - T{tier} - - )} - {hasMilestone && onMilestoneClick && ( - - )} - {!prereqMet && prereqText && ( - - - - )} - {(selectedL5 > 0 || selectedL10 > 0) && ( -
- {selectedL5 > 0 && ( - - L5: {selectedL5} - - )} - {selectedL10 > 0 && ( - - L10: {selectedL10} - - )} -
- )} -
- - {/* Description */} -

- {description} -

- - {/* Level Dots - colored by mana type */} -
- {Array.from({ length: maxLevel }).map((_, i) => ( -
- ))} - - {level}/{maxLevel} - -
- - {/* Cost and Time */} - {(cost !== undefined || time) && ( -
- {time && Time: {time}} - {cost !== undefined && Cost: {cost}} -
- )} - - {/* Study Progress */} - {studying && ( -
- -
- )} -
- - {/* Action Buttons */} -
- {studying ? ( - - Studying... - - ) : hasMilestone && onMilestoneClick ? ( - - Choose Upgrades - - ) : canTierUp && onTierUp ? ( - - ⬆️ Tier Up - - ) : maxed ? ( - - Maxed - - ) : ( -
- {onStudy && ( - - Study - {cost !== undefined && ` (${cost})`} - - )} - {/* Parallel Study button */} - {showParallelStudy && onParallelStudy && ( - - - ⚡ - - - )} -
- )} -
-
- ); -} diff --git a/src/lib/game/__tests__/skill-system.test.ts b/src/lib/game/__tests__/skill-system.test.ts deleted file mode 100644 index 6e620e2..0000000 --- a/src/lib/game/__tests__/skill-system.test.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - SKILL_EVOLUTION_PATHS, - getBaseSkillId, - generateTierSkillDef, - getUpgradesForSkillAtMilestone, - getNextTierSkill, - getTierMultiplier, - canTierUp, - getAvailableUpgrades, -} from '../skill-evolution'; -import { SKILLS_DEF } from '../constants'; - -describe('Skill Evolution Paths', () => { - it('should have evolution paths for all skills with max > 1', () => { - const skillsWithMaxGt1 = Object.entries(SKILLS_DEF) - .filter(([_, def]) => def.max > 1) - .map(([id]) => id); - - for (const skillId of skillsWithMaxGt1) { - expect(SKILL_EVOLUTION_PATHS[skillId], `Missing evolution path for ${skillId}`).toBeDefined(); - } - }); - - it('should have at least one tier for each evolution path', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - expect(path.tiers.length, `${skillId} should have at least one tier`).toBeGreaterThanOrEqual(1); - expect(path.baseSkillId, `${skillId} baseSkillId should match`).toBe(skillId); - } - }); - - it('should have correct tier multipliers (10x per tier)', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - for (const tier of path.tiers) { - const expectedMultiplier = Math.pow(10, tier.tier - 1); - expect(tier.multiplier, `${skillId} tier ${tier.tier} multiplier`).toBe(expectedMultiplier); - } - } - }); - - it('should have valid skill IDs for each tier', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - for (const tier of path.tiers) { - if (tier.tier === 1) { - expect(tier.skillId, `${skillId} tier 1 skillId`).toBe(skillId); - } else { - expect(tier.skillId, `${skillId} tier ${tier.tier} skillId`).toContain('_t'); - } - } - } - }); -}); - -describe('getBaseSkillId', () => { - it('should return the same ID for base skills', () => { - expect(getBaseSkillId('manaWell')).toBe('manaWell'); - expect(getBaseSkillId('manaFlow')).toBe('manaFlow'); - expect(getBaseSkillId('enchanting')).toBe('enchanting'); - }); - - it('should extract base ID from tiered skills', () => { - expect(getBaseSkillId('manaWell_t2')).toBe('manaWell'); - expect(getBaseSkillId('manaFlow_t3')).toBe('manaFlow'); - expect(getBaseSkillId('enchanting_t5')).toBe('enchanting'); - }); -}); - -describe('generateTierSkillDef', () => { - it('should return null for non-existent skills', () => { - expect(generateTierSkillDef('nonexistent', 1)).toBeNull(); - }); - - it('should return null for non-existent tiers', () => { - // Most skills don't have tier 10 - expect(generateTierSkillDef('manaWell', 10)).toBeNull(); - }); - - it('should return correct tier definition', () => { - const tier1 = generateTierSkillDef('manaWell', 1); - expect(tier1).not.toBeNull(); - expect(tier1?.name).toBe('Mana Well'); - expect(tier1?.tier).toBe(1); - expect(tier1?.multiplier).toBe(1); - - const tier2 = generateTierSkillDef('manaWell', 2); - expect(tier2).not.toBeNull(); - expect(tier2?.name).toBe('Deep Reservoir'); - expect(tier2?.tier).toBe(2); - expect(tier2?.multiplier).toBe(10); - }); -}); - -describe('getUpgradesForSkillAtMilestone', () => { - it('should return empty array for non-existent skills', () => { - const upgrades = getUpgradesForSkillAtMilestone('nonexistent', 5, {}); - expect(upgrades).toEqual([]); - }); - - it('should return upgrades for manaWell at milestone 5', () => { - const upgrades = getUpgradesForSkillAtMilestone('manaWell', 5, { manaWell: 1 }); - expect(upgrades.length).toBeGreaterThan(0); - - // All should be milestone 5 - for (const upgrade of upgrades) { - expect(upgrade.milestone).toBe(5); - } - }); - - it('should return upgrades for manaWell at milestone 10', () => { - const upgrades = getUpgradesForSkillAtMilestone('manaWell', 10, { manaWell: 1 }); - expect(upgrades.length).toBeGreaterThan(0); - - // All should be milestone 10 - for (const upgrade of upgrades) { - expect(upgrade.milestone).toBe(10); - } - }); - - it('should return tier 2 upgrades when at tier 2', () => { - const upgrades = getUpgradesForSkillAtMilestone('manaWell', 5, { manaWell: 2 }); - expect(upgrades.length).toBeGreaterThan(0); - - // Should have tier 2 specific upgrades - const upgradeIds = upgrades.map(u => u.id); - expect(upgradeIds.some(id => id.startsWith('mw_t2'))).toBe(true); - }); -}); - -describe('getNextTierSkill', () => { - it('should return null for non-existent skills', () => { - expect(getNextTierSkill('nonexistent')).toBeNull(); - }); - - it('should return next tier for tier 1 skills', () => { - expect(getNextTierSkill('manaWell')).toBe('manaWell_t2'); - expect(getNextTierSkill('manaFlow')).toBe('manaFlow_t2'); - }); - - it('should return next tier for higher tier skills', () => { - expect(getNextTierSkill('manaWell_t2')).toBe('manaWell_t3'); - expect(getNextTierSkill('manaWell_t3')).toBe('manaWell_t4'); - }); - - it('should return null for max tier skills', () => { - // manaWell has 5 tiers - expect(getNextTierSkill('manaWell_t5')).toBeNull(); - }); -}); - -describe('getTierMultiplier', () => { - it('should return 1 for tier 1 skills', () => { - expect(getTierMultiplier('manaWell')).toBe(1); - expect(getTierMultiplier('manaFlow')).toBe(1); - }); - - it('should return 10 for tier 2 skills', () => { - expect(getTierMultiplier('manaWell_t2')).toBe(10); - expect(getTierMultiplier('manaFlow_t2')).toBe(10); - }); - - it('should return correct multiplier for higher tiers', () => { - expect(getTierMultiplier('manaWell_t3')).toBe(100); - expect(getTierMultiplier('manaWell_t4')).toBe(1000); - expect(getTierMultiplier('manaWell_t5')).toBe(10000); - }); -}); - -describe('canTierUp', () => { - const mockAttunements = { - enchanter: { level: 10, active: true }, - fabricator: { level: 10, active: true }, - invoker: { level: 10, active: true }, - }; - - it('should return false for non-existent skills', () => { - const result = canTierUp('nonexistent', 10, {}, mockAttunements); - expect(result.canTierUp).toBe(false); - expect(result.reason).toBe('No evolution path'); - }); - - it('should return false if not at max level', () => { - const result = canTierUp('manaWell', 5, { manaWell: 1 }, mockAttunements); - expect(result.canTierUp).toBe(false); - expect(result.reason).toBe('Need level 10 to tier up'); - }); - - it('should return true when at max level with attunement', () => { - const result = canTierUp('manaWell', 10, { manaWell: 1 }, mockAttunements); - expect(result.canTierUp).toBe(true); - }); - - it('should return false if already at max tier', () => { - const result = canTierUp('manaWell_t5', 10, { manaWell: 5 }, mockAttunements); - expect(result.canTierUp).toBe(false); - expect(result.reason).toBe('Already at max tier'); - }); -}); - -describe('getAvailableUpgrades', () => { - const manaWellTier1Tree = SKILL_EVOLUTION_PATHS.manaWell.tiers[0].upgrades; - - it('should return only upgrades for specified milestone', () => { - const available = getAvailableUpgrades( - manaWellTier1Tree as any[], - [], - 5, - [] - ); - - for (const upgrade of available) { - expect(upgrade.milestone).toBe(5); - } - }); - - it('should exclude already chosen upgrades', () => { - const available = getAvailableUpgrades( - manaWellTier1Tree as any[], - ['mw_t1_l5_capacity'], - 5, - ['mw_t1_l5_capacity'] - ); - - const ids = available.map(u => u.id); - expect(ids).not.toContain('mw_t1_l5_capacity'); - }); -}); - -describe('Skill Definitions', () => { - it('should have valid max levels for all skills', () => { - for (const [skillId, def] of Object.entries(SKILLS_DEF)) { - expect(def.max, `${skillId} max level`).toBeGreaterThanOrEqual(1); - expect(def.max, `${skillId} max level`).toBeLessThanOrEqual(10); - } - }); - - it('should have study time defined for all skills', () => { - for (const [skillId, def] of Object.entries(SKILLS_DEF)) { - expect(def.studyTime, `${skillId} study time`).toBeGreaterThanOrEqual(0); - } - }); - - it('should have base cost defined for all skills', () => { - for (const [skillId, def] of Object.entries(SKILLS_DEF)) { - expect(def.base, `${skillId} base cost`).toBeGreaterThan(0); - } - }); - - it('should have correct categories for skills', () => { - const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant', - 'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'craft', 'hybrid']; - - for (const [skillId, def] of Object.entries(SKILLS_DEF)) { - expect(validCategories, `${skillId} category`).toContain(def.cat); - } - }); -}); - -describe('Upgrade Tree Structure', () => { - it('should have valid effect types for all upgrades', () => { - const validTypes = ['multiplier', 'bonus', 'special']; - - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - for (const tier of path.tiers) { - for (const upgrade of tier.upgrades) { - expect(validTypes, `${upgrade.id} effect type`).toContain(upgrade.effect.type); - } - } - } - }); - - it('should have milestone 5 or 10 for all upgrades', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - for (const tier of path.tiers) { - for (const upgrade of tier.upgrades) { - expect([5, 10], `${upgrade.id} milestone`).toContain(upgrade.milestone); - } - } - } - }); - - it('should have unique upgrade IDs within each skill', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - const allIds: string[] = []; - for (const tier of path.tiers) { - for (const upgrade of tier.upgrades) { - expect(allIds, `${upgrade.id} should be unique in ${skillId}`).not.toContain(upgrade.id); - allIds.push(upgrade.id); - } - } - } - }); - - it('should have descriptions for all upgrades', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - for (const tier of path.tiers) { - for (const upgrade of tier.upgrades) { - expect(upgrade.desc, `${upgrade.id} should have description`).toBeTruthy(); - expect(upgrade.desc.length, `${upgrade.id} description length`).toBeGreaterThan(0); - } - } - } - }); - - it('should have special descriptions for special effects', () => { - for (const [skillId, path] of Object.entries(SKILL_EVOLUTION_PATHS)) { - for (const tier of path.tiers) { - for (const upgrade of tier.upgrades) { - if (upgrade.effect.type === 'special') { - expect(upgrade.effect.specialDesc, `${upgrade.id} should have special description`).toBeTruthy(); - expect(upgrade.effect.specialId, `${upgrade.id} should have special ID`).toBeTruthy(); - } - } - } - } - }); -}); - -describe('Skill Level and Tier Calculations', () => { - it('should calculate tier 2 level 1 as equivalent to tier 1 level 10', () => { - // Base skill gives +100 max mana per level - // At tier 1 level 10: 10 * 100 = 1000 max mana - // At tier 2 level 1 with 10x multiplier: 1 * 100 * 10 = 1000 max mana - const tier1Level10Effect = 10 * 100; // level * base - const tier2Level1Effect = 1 * 100 * 10; // level * base * tierMultiplier - - expect(tier2Level1Effect).toBe(tier1Level10Effect); - }); - - it('should have increasing power with tiers', () => { - // Tier 3 level 1 should be stronger than tier 2 level 10 - const tier2Level10 = 10 * 100 * 10; // level * base * tierMultiplier - const tier3Level1 = 1 * 100 * 100; // level * base * tierMultiplier - - expect(tier3Level1).toBe(tier2Level10); - }); - - it('should have correct max tier for skills', () => { - // Skills with max 5 should typically have 2-3 tiers - // Skills with max 10 should typically have 3-5 tiers - - const manaWellPath = SKILL_EVOLUTION_PATHS.manaWell; - expect(manaWellPath.tiers.length).toBe(5); // manaWell has 5 tiers - - const manaFlowPath = SKILL_EVOLUTION_PATHS.manaFlow; - expect(manaFlowPath.tiers.length).toBe(5); // manaFlow has 5 tiers - }); -}); diff --git a/src/lib/game/__tests__/skills-tests/ascension-skills.test.ts b/src/lib/game/__tests__/skills-tests/ascension-skills.test.ts deleted file mode 100644 index 3c74975..0000000 --- a/src/lib/game/__tests__/skills-tests/ascension-skills.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Ascension Skills Tests - * - * Tests for ascension-related skills: Insight Harvest, Guardian Bane - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '@/lib/game/constants'; -import { calcInsight } from '@/lib/game/computed-stats'; -import type { GameState } from '../types'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - const baseElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning']; - baseElements.forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: baseElements.slice(0, 4).includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - lootInventory: { materials: {}, blueprints: [] }, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - ...overrides, - } as GameState; -} - -describe('Ascension Skills', () => { - describe('Insight Harvest (+10% insight gain)', () => { - it('should multiply insight gain by 10% per level', () => { - const state0 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 0 } }); - const state1 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 1 } }); - const state5 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 5 } }); - - const insight0 = calcInsight(state0); - const insight1 = calcInsight(state1); - const insight5 = calcInsight(state5); - - expect(insight1).toBeGreaterThan(insight0); - expect(insight5).toBeGreaterThan(insight1); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.insightHarvest.desc).toBe("+10% insight gain"); - expect(SKILLS_DEF.insightHarvest.max).toBe(5); - }); - }); - - describe('Guardian Bane (+20% dmg vs guardians)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.guardianBane.desc).toBe("+20% dmg vs guardians"); - expect(SKILLS_DEF.guardianBane.max).toBe(3); - }); - }); -}); - -console.log('✅ Ascension skills tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/integration-and-evolution.test.ts b/src/lib/game/__tests__/skills-tests/integration-and-evolution.test.ts deleted file mode 100644 index e63fda6..0000000 --- a/src/lib/game/__tests__/skills-tests/integration-and-evolution.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Skill Integration Tests - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF, SKILL_EVOLUTION_PATHS, getTierMultiplier, getNextTierSkill, generateTierSkillDef } from '@/lib/game/constants'; -import { SKILL_EVOLUTION_PATHS as EVOLUTION_PATHS, getUpgradesForSkillAtMilestone, getNextTierSkill as NextTier, getTierMultiplier as TierMultiplier, generateTierSkillDef as GenerateTier } from '../skill-evolution'; - -describe('Integration Tests', () => { - it('skill costs should scale with level', () => { - const skill = SKILLS_DEF.manaWell; - for (let level = 0; level < skill.max; level++) { - const cost = skill.base * (level + 1); - expect(cost).toBeGreaterThan(0); - } - }); - - it('all skills should have valid categories', () => { - const validCategories = ['mana', 'study', 'ascension', 'enchant', 'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'research', 'craft', 'hybrid']; - Object.values(SKILLS_DEF).forEach(skill => { - expect(validCategories).toContain(skill.cat); - }); - }); - - it('all prerequisite skills should exist', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.keys(skill.req).forEach(reqId => { - expect(SKILLS_DEF[reqId]).toBeDefined(); - }); - } - }); - }); - - it('all prerequisite levels should be within skill max', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.entries(skill.req).forEach(([reqId, reqLevel]) => { - expect(reqLevel).toBeLessThanOrEqual(SKILLS_DEF[reqId].max); - }); - } - }); - }); - - it('all attunement-requiring skills should have valid attunement', () => { - const validAttunements = ['enchanter', 'invoker', 'fabricator']; - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.attunement) { - expect(validAttunements).toContain(skill.attunement); - } - }); - }); -}); - -// ─── Skill Evolution Tests ────────────────────────────────────────────────────── - -describe('Skill Evolution', () => { - it('skills with max > 1 should have evolution paths', () => { - const skillsWithMaxGt1 = Object.entries(SKILLS_DEF) - .filter(([_, def]) => def.max > 1) - .map(([id]) => id); - - for (const skillId of skillsWithMaxGt1) { - expect(SKILL_EVOLUTION_PATHS[skillId], `Missing evolution path for ${skillId}`).toBeDefined(); - } - }); - - it('tier multiplier should be 10^(tier-1)', () => { - expect(getTierMultiplier('manaWell')).toBe(1); - expect(getTierMultiplier('manaWell_t2')).toBe(10); - expect(getTierMultiplier('manaWell_t3')).toBe(100); - expect(getTierMultiplier('manaWell_t4')).toBe(1000); - expect(getTierMultiplier('manaWell_t5')).toBe(10000); - }); - - it('getNextTierSkill should return correct next tier', () => { - expect(getNextTierSkill('manaWell')).toBe('manaWell_t2'); - expect(getNextTierSkill('manaWell_t2')).toBe('manaWell_t3'); - expect(getNextTierSkill('manaWell_t5')).toBeNull(); - }); - - it('generateTierSkillDef should return valid definitions', () => { - const tier1 = generateTierSkillDef('manaWell', 1); - expect(tier1).not.toBeNull(); - expect(tier1?.name).toBe('Mana Well'); - expect(tier1?.multiplier).toBe(1); - - const tier2 = generateTierSkillDef('manaWell', 2); - expect(tier2).not.toBeNull(); - expect(tier2?.name).toBe('Deep Reservoir'); - expect(tier2?.multiplier).toBe(10); - }); -}); - -console.log('✅ Integration and skill evolution tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/mana-skills.test.ts b/src/lib/game/__tests__/skills-tests/mana-skills.test.ts deleted file mode 100644 index 3db6e32..0000000 --- a/src/lib/game/__tests__/skills-tests/mana-skills.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Mana Skills Tests - * - * Tests for mana-related skills: Mana Well, Mana Flow, Mana Spring, - * Elemental Attunement, Mana Overflow, Mana Tap, Mana Surge - */ - -import { describe, it, expect } from 'vitest'; -import { - computeMaxMana, - computeElementMax, - computeRegen, - computeClickMana, -} from '@/lib/game/computed-stats'; -import { SKILLS_DEF } from '@/lib/game/constants'; -import type { GameState } from '../types'; - -// ─── Test Helpers ─────────────────────────────────────────────────────────── - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - const baseElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning']; - baseElements.forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: baseElements.slice(0, 4).includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - lootInventory: { materials: {}, blueprints: [] }, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - ...overrides, - } as GameState; -} - -// ─── Mana Skills Tests ───────────────────────────────────────────────────────── - -describe('Mana Skills', () => { - describe('Mana Well (+100 max mana)', () => { - it('should add 100 max mana per level', () => { - const state0 = createMockState({ skills: { manaWell: 0 } }); - const state1 = createMockState({ skills: { manaWell: 1 } }); - const state5 = createMockState({ skills: { manaWell: 5 } }); - const state10 = createMockState({ skills: { manaWell: 10 } }); - - expect(computeMaxMana(state0, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100); - expect(computeMaxMana(state1, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100 + 100); - expect(computeMaxMana(state5, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100 + 500); - expect(computeMaxMana(state10, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100 + 1000); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaWell.desc).toBe("+100 max mana"); - expect(SKILLS_DEF.manaWell.max).toBe(10); - }); - - it('should have upgrade tree', () => { - expect(SKILLS_DEF.manaWell).toBeDefined(); - expect(SKILLS_DEF.manaWell.max).toBe(10); - }); - }); - - describe('Mana Flow (+1 regen/hr)', () => { - it('should add 1 regen per hour per level', () => { - const state0 = createMockState({ skills: { manaFlow: 0 } }); - const state1 = createMockState({ skills: { manaFlow: 1 } }); - const state5 = createMockState({ skills: { manaFlow: 5 } }); - - const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 }; - expect(computeRegen(state0, effects)).toBe(2); - expect(computeRegen(state1, effects)).toBe(2 + 1); - expect(computeRegen(state5, effects)).toBe(2 + 5); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaFlow.desc).toBe("+1 regen/hr"); - expect(SKILLS_DEF.manaFlow.max).toBe(10); - }); - }); - - describe('Mana Spring (+2 mana regen)', () => { - it('should add 2 mana regen', () => { - const state0 = createMockState({ skills: { manaSpring: 0 } }); - const state1 = createMockState({ skills: { manaSpring: 1 } }); - - const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 }; - expect(computeRegen(state0, effects)).toBe(2); - expect(computeRegen(state1, effects)).toBe(2 + 2); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaSpring.desc).toBe("+2 mana regen"); - expect(SKILLS_DEF.manaSpring.max).toBe(1); - }); - }); - - describe('Elemental Attunement (+50 elem mana cap)', () => { - it('should add 50 element mana capacity per level', () => { - const state0 = createMockState({ skills: { elemAttune: 0 } }); - const state1 = createMockState({ skills: { elemAttune: 1 } }); - const state5 = createMockState({ skills: { elemAttune: 5 } }); - - expect(computeElementMax(state0, { elementCapBonus: 0, elementCapMultiplier: 1 })).toBe(10); - expect(computeElementMax(state1, { elementCapBonus: 0, elementCapMultiplier: 1 })).toBe(10 + 50); - expect(computeElementMax(state5, { elementCapBonus: 0, elementCapMultiplier: 1 })).toBe(10 + 250); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.elemAttune.desc).toBe("+50 elem mana cap"); - expect(SKILLS_DEF.elemAttune.max).toBe(10); - }); - }); - - describe('Mana Overflow (+25% mana from clicks)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaOverflow.desc).toBe("+25% mana from clicks"); - expect(SKILLS_DEF.manaOverflow.max).toBe(5); - }); - - it('should require Mana Well 3', () => { - expect(SKILLS_DEF.manaOverflow.req).toEqual({ manaWell: 3 }); - }); - }); - - describe('Mana Tap (+1 mana/click)', () => { - it('should add 1 mana per click', () => { - const state0 = createMockState({ skills: { manaTap: 0 } }); - const state1 = createMockState({ skills: { manaTap: 1 } }); - - expect(computeClickMana(state0, { clickManaBonus: 0, clickManaMultiplier: 1 })).toBe(1); - expect(computeClickMana(state1, { clickManaBonus: 0, clickManaMultiplier: 1 })).toBe(2); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaTap.desc).toBe("+1 mana/click"); - expect(SKILLS_DEF.manaTap.max).toBe(1); - }); - }); - - describe('Mana Surge (+3 mana/click)', () => { - it('should add 3 mana per click', () => { - const state1 = createMockState({ skills: { manaSurge: 1 } }); - expect(computeClickMana(state1, { clickManaBonus: 0, clickManaMultiplier: 1 })).toBe(1 + 3); - }); - - it('should stack with Mana Tap', () => { - const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } }); - expect(computeClickMana(state, { clickManaBonus: 0, clickManaMultiplier: 1 })).toBe(1 + 1 + 3); - }); - - it('should require Mana Tap 1', () => { - expect(SKILLS_DEF.manaSurge.req).toEqual({ manaTap: 1 }); - }); - }); -}); - -console.log('✅ Mana skills tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/prestige-upgrades.test.ts b/src/lib/game/__tests__/skills-tests/prestige-upgrades.test.ts deleted file mode 100644 index c717f2e..0000000 --- a/src/lib/game/__tests__/skills-tests/prestige-upgrades.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Prestige Upgrade Tests for Skills - */ - -import { describe, it, expect } from 'vitest'; -import { PRESTIGE_DEF } from '@/lib/game/constants'; -import { computeMaxMana, computeElementMax } from '@/lib/game/computed-stats'; -import type { GameState } from '../types'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - const baseElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning']; - baseElements.forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: baseElements.slice(0, 4).includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - lootInventory: { materials: {}, blueprints: [] }, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - ...overrides, - } as GameState; -} - -describe('Prestige Upgrades', () => { - it('all prestige upgrades should have valid costs', () => { - Object.entries(PRESTIGE_DEF).forEach(([id, upgrade]) => { - expect(upgrade.cost).toBeGreaterThan(0); - expect(upgrade.max).toBeGreaterThan(0); - }); - }); - - it('Mana Well prestige should add 500 starting max mana', () => { - const state0 = createMockState({ prestigeUpgrades: { manaWell: 0 } }); - const state1 = createMockState({ prestigeUpgrades: { manaWell: 1 } }); - const state5 = createMockState({ prestigeUpgrades: { manaWell: 5 } }); - - expect(computeMaxMana(state0, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100); - expect(computeMaxMana(state1, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100 + 500); - expect(computeMaxMana(state5, { maxManaBonus: 0, maxManaMultiplier: 1 })).toBe(100 + 2500); - }); - - it('Elemental Attunement prestige should add 25 element cap', () => { - const state0 = createMockState({ prestigeUpgrades: { elementalAttune: 0 } }); - const state1 = createMockState({ prestigeUpgrades: { elementalAttune: 1 } }); - const state10 = createMockState({ prestigeUpgrades: { elementalAttune: 10 } }); - - expect(computeElementMax(state0, { elementCapBonus: 0, elementCapMultiplier: 1 })).toBe(10); - expect(computeElementMax(state1, { elementCapBonus: 0, elementCapMultiplier: 1 })).toBe(10 + 25); - expect(computeElementMax(state10, { elementCapBonus: 0, elementCapMultiplier: 1 })).toBe(10 + 250); - }); -}); - -console.log('✅ Prestige upgrade tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/skill-prerequisites.test.ts b/src/lib/game/__tests__/skills-tests/skill-prerequisites.test.ts deleted file mode 100644 index 01cb23a..0000000 --- a/src/lib/game/__tests__/skills-tests/skill-prerequisites.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Skill Prerequisites Tests - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '@/lib/game/constants'; - -describe('Skill Prerequisites', () => { - it('Mana Overflow should require Mana Well 3', () => { - expect(SKILLS_DEF.manaOverflow.req).toEqual({ manaWell: 3 }); - }); - - it('Mana Surge should require Mana Tap 1', () => { - expect(SKILLS_DEF.manaSurge.req).toEqual({ manaTap: 1 }); - }); - - it('Deep Trance should require Meditation 1', () => { - expect(SKILLS_DEF.deepTrance.req).toEqual({ meditation: 1 }); - }); - - it('Void Meditation should require Deep Trance 1', () => { - expect(SKILLS_DEF.voidMeditation.req).toEqual({ deepTrance: 1 }); - }); - - it('Efficient Enchant should require Enchanting 3', () => { - expect(SKILLS_DEF.efficientEnchant.req).toEqual({ enchanting: 3 }); - }); -}); - -console.log('✅ Skill prerequisites tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/specialized-skills.test.ts b/src/lib/game/__tests__/skills-tests/specialized-skills.test.ts deleted file mode 100644 index 8f1c7b0..0000000 --- a/src/lib/game/__tests__/skills-tests/specialized-skills.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Specialized Skills Tests - * - * Tests for Enchanter and Golemancy skills - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '@/lib/game/constants'; - -describe('Enchanter Skills', () => { - describe('Enchanting (Unlock enchantment design)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.enchanting).toBeDefined(); - expect(SKILLS_DEF.enchanting.attunement).toBe('enchanter'); - }); - }); - - describe('Efficient Enchant (-5% enchantment capacity cost)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.efficientEnchant).toBeDefined(); - expect(SKILLS_DEF.efficientEnchant.max).toBe(5); - }); - - it('should require Enchanting 3', () => { - expect(SKILLS_DEF.efficientEnchant.req).toEqual({ enchanting: 3 }); - }); - }); - - describe('Disenchanting (Recover mana from removed enchantments)', () => { - it('skill definition should not exist', () => { - // disenchanting skill removed - see Bug 13 - expect(SKILLS_DEF.disenchanting).toBeUndefined(); - }); - }); -}); - -describe('Golemancy Skills', () => { - describe('Golem Mastery (+10% golem damage)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemMastery).toBeDefined(); - expect(SKILLS_DEF.golemMastery.attunementReq).toBeDefined(); - }); - }); - - describe('Golem Efficiency (+5% attack speed)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemEfficiency).toBeDefined(); - }); - }); - - describe('Golem Longevity (+1 floor duration)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemLongevity).toBeDefined(); - }); - }); - - describe('Golem Siphon (-10% maintenance)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemSiphon).toBeDefined(); - }); - }); -}); - -console.log('✅ Specialized skills tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/study-skills.test.ts b/src/lib/game/__tests__/skills-tests/study-skills.test.ts deleted file mode 100644 index c29db67..0000000 --- a/src/lib/game/__tests__/skills-tests/study-skills.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Study Skills Tests - * - * Tests for study-related skills: Quick Learner, Focused Mind, - * Meditation Focus, Knowledge Retention, Deep Trance, Void Meditation - */ - -import { describe, it, expect } from 'vitest'; -import { - getStudySpeedMultiplier, - getStudyCostMultiplier, - getMeditationBonus, -} from '@/lib/game/computed-stats'; -import { SKILLS_DEF } from '@/lib/game/constants'; - -describe('Study Skills', () => { - describe('Quick Learner (+10% study speed)', () => { - it('should multiply study speed by 10% per level', () => { - expect(getStudySpeedMultiplier({})).toBe(1); - expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1); - expect(getStudySpeedMultiplier({ quickLearner: 3 })).toBe(1.3); - expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5); - expect(getStudySpeedMultiplier({ quickLearner: 10 })).toBe(2.0); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.quickLearner.desc).toBe("+10% study speed"); - expect(SKILLS_DEF.quickLearner.max).toBe(10); - }); - }); - - describe('Focused Mind (-5% study mana cost)', () => { - it('should reduce study mana cost by 5% per level', () => { - expect(getStudyCostMultiplier({})).toBe(1); - expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95); - expect(getStudyCostMultiplier({ focusedMind: 3 })).toBe(0.85); - expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75); - expect(getStudyCostMultiplier({ focusedMind: 10 })).toBe(0.5); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.focusedMind.desc).toBe("-5% study mana cost"); - expect(SKILLS_DEF.focusedMind.max).toBe(10); - }); - }); - - describe('Meditation Focus (Up to 2.5x regen after 4hrs)', () => { - it('should provide meditation bonus caps', () => { - expect(SKILLS_DEF.meditation.desc).toContain("2.5x"); - expect(SKILLS_DEF.meditation.max).toBe(1); - }); - }); - - describe('Knowledge Retention (+20% study progress saved)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.knowledgeRetention.desc).toBe("+20% study progress saved on cancel"); - expect(SKILLS_DEF.knowledgeRetention.max).toBe(3); - }); - }); - - describe('Deep Trance (Extend to 6hrs for 3x)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.deepTrance.desc).toContain("6hrs"); - expect(SKILLS_DEF.deepTrance.max).toBe(1); - }); - - it('should require Meditation 1', () => { - expect(SKILLS_DEF.deepTrance.req).toEqual({ meditation: 1 }); - }); - }); - - describe('Void Meditation (Extend to 8hrs for 5x)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.voidMeditation.desc).toContain("8hrs"); - expect(SKILLS_DEF.voidMeditation.max).toBe(1); - }); - - it('should require Deep Trance 1', () => { - expect(SKILLS_DEF.voidMeditation.req).toEqual({ deepTrance: 1 }); - }); - }); -}); - -// ─── Meditation Bonus Tests ───────────────────────────────────────────────────── - -describe('Meditation Bonus', () => { - it('should start at 1x with no meditation', () => { - expect(getMeditationBonus(0, {})).toBe(1); - }); - - it('should ramp up over time without skills', () => { - const bonus1hr = getMeditationBonus(25, {}); // 1 hour of ticks - expect(bonus1hr).toBeGreaterThan(1); - - const bonus4hr = getMeditationBonus(100, {}); // 4 hours - expect(bonus4hr).toBeGreaterThan(bonus1hr); - }); - - it('should cap at 1.5x without meditation skill', () => { - const bonus = getMeditationBonus(200, {}); // 8 hours - expect(bonus).toBe(1.5); - }); - - it('should give 2.5x with meditation skill after 4 hours', () => { - const bonus = getMeditationBonus(100, { meditation: 1 }); - expect(bonus).toBe(2.5); - }); - - it('should give 3.0x with deepTrance skill after 6 hours', () => { - const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 }); - expect(bonus).toBe(3.0); - }); - - it('should give 5.0x with voidMeditation skill after 8 hours', () => { - const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 }); - expect(bonus).toBe(5.0); - }); -}); - -console.log('✅ Study skills tests defined.'); diff --git a/src/lib/game/__tests__/skills-tests/study-times.test.ts b/src/lib/game/__tests__/skills-tests/study-times.test.ts deleted file mode 100644 index dbdc381..0000000 --- a/src/lib/game/__tests__/skills-tests/study-times.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Study Times Tests - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '@/lib/game/constants'; - -describe('Study Times', () => { - it('all skills should have reasonable study times', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - expect(skill.studyTime).toBeGreaterThan(0); - expect(skill.studyTime).toBeLessThanOrEqual(72); - }); - }); - - it('ascension skills should have long study times', () => { - const ascensionSkills = Object.entries(SKILLS_DEF).filter(([, s]) => s.cat === 'ascension'); - ascensionSkills.forEach(([, skill]) => { - expect(skill.studyTime).toBeGreaterThanOrEqual(20); - }); - }); -}); - -console.log('✅ Study times tests defined.'); diff --git a/src/lib/game/__tests__/skills.test.ts b/src/lib/game/__tests__/skills.test.ts deleted file mode 100644 index 677c7e8..0000000 --- a/src/lib/game/__tests__/skills.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Skills Tests - Main Index - * - * This file re-exports all individual skill test files. - * Each test file is focused on a specific area of functionality. - * - * Original file: skills.test.ts (589 lines) - * Refactored into 8 smaller test files. - */ - -import './skills-tests/mana-skills.test'; -import './skills-tests/study-skills.test'; -import './skills-tests/ascension-skills.test'; -import './skills-tests/specialized-skills.test'; -import './skills-tests/skill-prerequisites.test'; -import './skills-tests/study-times.test'; -import './skills-tests/prestige-upgrades.test'; -import './skills-tests/integration-and-evolution.test'; - -console.log('✅ All skills tests complete (refactored from 589 lines to 8 focused test files).'); diff --git a/src/lib/game/constants/skills-combat.ts b/src/lib/game/constants/skills-combat.ts deleted file mode 100644 index e670c23..0000000 --- a/src/lib/game/constants/skills-combat.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// COMBAT SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const arcaneFury: SkillV2Def = { - id: 'arcaneFury', name: 'Arcane Fury', description: 'Increases spell damage per level', - category: 'combat', maxLevel: 10, costPerLevel: 200, studyHours: 4, - effects: [{ stat: 'damageMultiplier', mode: 'multiply', valuePerLevel: 0.10 }], -}; - -export const combatTraining: SkillV2Def = { - id: 'combatTraining', name: 'Combat Training', description: 'Base damage bonus per level', - category: 'combat', maxLevel: 10, costPerLevel: 250, studyHours: 5, - effects: [{ stat: 'baseDamage', mode: 'add', valuePerLevel: 5 }], -}; - -export const precision: SkillV2Def = { - id: 'precision', name: 'Precision', description: '+5% crit chance per level', - category: 'combat', maxLevel: 10, costPerLevel: 300, studyHours: 6, - effects: [{ stat: 'critChance', mode: 'add', valuePerLevel: 0.05 }], -}; - -export const elementalMastery: SkillV2Def = { - id: 'elementalMastery', name: 'Elemental Mastery', description: '+15% elemental damage per level', - category: 'combat', maxLevel: 10, costPerLevel: 350, studyHours: 6, - effects: [{ stat: 'elementalDamage', mode: 'multiply', valuePerLevel: 0.15 }], -}; - -export const attackSpeed: SkillV2Def = { - id: 'attackSpeed', name: 'Attack Speed', description: '-10% attack time per level (faster)', - category: 'combat', maxLevel: 5, costPerLevel: 400, studyHours: 6, - effects: [{ stat: 'attackSpeed', mode: 'multiply', valuePerLevel: -0.10 }], -}; - -export const armorPiercing: SkillV2Def = { - id: 'armorPiercing', name: 'Armor Piercing', description: '+5% armor penetration per level', - category: 'combat', maxLevel: 5, costPerLevel: 400, studyHours: 6, - effects: [{ stat: 'armorPierce', mode: 'add', valuePerLevel: 0.05 }], -}; - -export const spellDamage: SkillV2Def = { - id: 'spellDamage', name: 'Spell Damage', description: '+5% spell damage per level', - category: 'combat', maxLevel: 10, costPerLevel: 250, studyHours: 5, - effects: [{ stat: 'spellDamage', mode: 'multiply', valuePerLevel: 0.05 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-core.ts b/src/lib/game/constants/skills-core.ts deleted file mode 100644 index 9ccc743..0000000 --- a/src/lib/game/constants/skills-core.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// CORE MANA SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const manaWell: SkillV2Def = { - id: 'manaWell', name: 'Mana Well', description: 'Increases maximum mana capacity', - category: 'mana', maxLevel: 10, costPerLevel: 100, studyHours: 4, - effects: [{ stat: 'maxMana', mode: 'add', valuePerLevel: 100 }], -}; - -export const manaFlow: SkillV2Def = { - id: 'manaFlow', name: 'Mana Flow', description: 'Increases mana regeneration rate', - category: 'mana', maxLevel: 10, costPerLevel: 150, studyHours: 5, - effects: [{ stat: 'manaRegen', mode: 'add', valuePerLevel: 1 }], -}; - -export const manaOverflow: SkillV2Def = { - id: 'manaOverflow', name: 'Mana Overflow', description: 'Increases mana gained from clicks', - category: 'mana', maxLevel: 5, costPerLevel: 400, studyHours: 6, - prerequisites: { manaWell: 3 }, - effects: [{ stat: 'clickMana', mode: 'multiply', valuePerLevel: 0.25 }], -}; - -export const manaTap: SkillV2Def = { - id: 'manaTap', name: 'Mana Tap', description: '+1 mana per click', - category: 'mana', maxLevel: 1, costPerLevel: 300, studyHours: 12, - effects: [{ stat: 'clickMana', mode: 'add', valuePerLevel: 1 }], -}; - -export const manaSurge: SkillV2Def = { - id: 'manaSurge', name: 'Mana Surge', description: '+3 mana per click', - category: 'mana', maxLevel: 1, costPerLevel: 800, studyHours: 36, - prerequisites: { manaTap: 1 }, - effects: [{ stat: 'clickMana', mode: 'add', valuePerLevel: 3 }], -}; - -export const manaSpring: SkillV2Def = { - id: 'manaSpring', name: 'Mana Spring', description: '+2 mana regen', - category: 'mana', maxLevel: 1, costPerLevel: 600, studyHours: 24, - effects: [{ stat: 'manaRegen', mode: 'add', valuePerLevel: 2 }], -}; - -// ═══════════════════════════════════════════════════════════════════════ -// STUDY SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const quickLearner: SkillV2Def = { - id: 'quickLearner', name: 'Quick Learner', description: 'Increases study speed', - category: 'study', maxLevel: 10, costPerLevel: 250, studyHours: 4, - effects: [{ stat: 'studySpeed', mode: 'multiply', valuePerLevel: 0.10 }], -}; - -export const focusedMind: SkillV2Def = { - id: 'focusedMind', name: 'Focused Mind', description: 'Reduces study mana cost', - category: 'study', maxLevel: 10, costPerLevel: 300, studyHours: 5, - effects: [{ stat: 'studyCostMult', mode: 'multiply', valuePerLevel: -0.05 }], -}; - -export const knowledgeRetention: SkillV2Def = { - id: 'knowledgeRetention', name: 'Knowledge Retention', description: 'Preserves study progress on cancellation', - category: 'study', maxLevel: 3, costPerLevel: 350, studyHours: 5, - effects: [{ stat: 'progressRetention', mode: 'add', valuePerLevel: 0.20 }], -}; - -// ═══════════════════════════════════════════════════════════════════════ -// MEDITATION SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const meditation: SkillV2Def = { - id: 'meditation', name: 'Meditation Focus', description: 'Unlocks meditation regen boost (2.5x at 4hrs)', - category: 'mana', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [{ stat: 'meditationEfficiency', mode: 'multiply', valuePerLevel: 1.5 }], -}; - -export const deepTrance: SkillV2Def = { - id: 'deepTrance', name: 'Deep Trance', description: 'Extends meditation to 6hrs for 3x', - category: 'mana', maxLevel: 1, costPerLevel: 900, studyHours: 48, - prerequisites: { meditation: 1 }, - effects: [{ stat: 'meditationEfficiency', mode: 'multiply', valuePerLevel: 1.8 }], -}; - -export const voidMeditation: SkillV2Def = { - id: 'voidMeditation', name: 'Void Meditation', description: 'Extends meditation to 8hrs for 5x', - category: 'mana', maxLevel: 1, costPerLevel: 1500, studyHours: 72, - prerequisites: { deepTrance: 1 }, - effects: [{ stat: 'meditationEfficiency', mode: 'multiply', valuePerLevel: 2.5 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-crafting.ts b/src/lib/game/constants/skills-crafting.ts deleted file mode 100644 index 730cf18..0000000 --- a/src/lib/game/constants/skills-crafting.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// CRAFTING SKILLS (Legacy — kept for store compat) -// ═══════════════════════════════════════════════════════════════════════ -export const effCrafting: SkillV2Def = { - id: 'effCrafting', name: 'Eff. Crafting', description: '-10% craft time', - category: 'craft', maxLevel: 1, costPerLevel: 300, studyHours: 4, - effects: [{ stat: 'craftSpeed', mode: 'multiply', valuePerLevel: -0.10 }], -}; - -export const fieldRepair: SkillV2Def = { - id: 'fieldRepair', name: 'Field Repair', description: '+20% repair efficiency', - category: 'craft', maxLevel: 1, costPerLevel: 350, studyHours: 4, - effects: [{ stat: 'repairSpeed', mode: 'multiply', valuePerLevel: -0.10 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-element-caps.ts b/src/lib/game/constants/skills-element-caps.ts deleted file mode 100644 index a0080ee..0000000 --- a/src/lib/game/constants/skills-element-caps.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// PER-ELEMENT CAPACITY SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const fireManaCap: SkillV2Def = { - id: 'fireManaCap', name: 'Fire Mana Capacity', description: '+10% fire capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 200, studyHours: 4, - effects: [{ stat: 'fireCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const waterManaCap: SkillV2Def = { - id: 'waterManaCap', name: 'Water Mana Capacity', description: '+10% water capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 200, studyHours: 4, - effects: [{ stat: 'waterCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const airManaCap: SkillV2Def = { - id: 'airManaCap', name: 'Air Mana Capacity', description: '+10% air capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 200, studyHours: 4, - effects: [{ stat: 'airCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const earthManaCap: SkillV2Def = { - id: 'earthManaCap', name: 'Earth Mana Capacity', description: '+10% earth capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 200, studyHours: 4, - effects: [{ stat: 'earthCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const lightManaCap: SkillV2Def = { - id: 'lightManaCap', name: 'Light Mana Capacity', description: '+10% light capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 250, studyHours: 5, - effects: [{ stat: 'lightCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const darkManaCap: SkillV2Def = { - id: 'darkManaCap', name: 'Dark Mana Capacity', description: '+10% dark capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 250, studyHours: 5, - effects: [{ stat: 'darkCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const deathManaCap: SkillV2Def = { - id: 'deathManaCap', name: 'Death Mana Capacity', description: '+10% death capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 300, studyHours: 6, - effects: [{ stat: 'deathCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const metalManaCap: SkillV2Def = { - id: 'metalManaCap', name: 'Metal Mana Capacity', description: '+10% metal capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 350, studyHours: 6, - effects: [{ stat: 'metalCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const sandManaCap: SkillV2Def = { - id: 'sandManaCap', name: 'Sand Mana Capacity', description: '+10% sand capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 350, studyHours: 6, - effects: [{ stat: 'sandCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const lightningManaCap: SkillV2Def = { - id: 'lightningManaCap', name: 'Lightning Mana Capacity', description: '+10% lightning capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 350, studyHours: 6, - effects: [{ stat: 'lightningCap', mode: 'add', valuePerLevel: 10 }], -}; - -export const transferenceManaCap: SkillV2Def = { - id: 'transferenceManaCap', name: 'Transference Mana Capacity', description: '+10% transference capacity per level', - category: 'mana', maxLevel: 10, costPerLevel: 200, studyHours: 4, - effects: [{ stat: 'transferenceCap', mode: 'add', valuePerLevel: 10 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-enchant.ts b/src/lib/game/constants/skills-enchant.ts deleted file mode 100644 index c2f3688..0000000 --- a/src/lib/game/constants/skills-enchant.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// ENCHANTING SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const enchanting: SkillV2Def = { - id: 'enchanting', name: 'Enchanting', description: 'Unlocks enchantment design', - category: 'enchant', maxLevel: 10, costPerLevel: 200, studyHours: 5, - attunementRequired: 'enchanter', - effects: [ - { stat: 'enchantCapacity', mode: 'add', valuePerLevel: 10 }, - { stat: 'enchantSpeed', mode: 'multiply', valuePerLevel: -0.02 }, - ], -}; - -export const efficientEnchant: SkillV2Def = { - id: 'efficientEnchant', name: 'Efficient Enchant', description: 'Reduces capacity cost', - category: 'enchant', maxLevel: 5, costPerLevel: 350, studyHours: 6, - prerequisites: { enchanting: 3 }, attunementRequired: 'enchanter', - effects: [{ stat: 'enchantCapacity', mode: 'multiply', valuePerLevel: -0.05 }], -}; - -export const enchantSpeed: SkillV2Def = { - id: 'enchantSpeed', name: 'Enchant Speed', description: 'Reduces enchantment time', - category: 'enchant', maxLevel: 5, costPerLevel: 300, studyHours: 4, - prerequisites: { enchanting: 2 }, attunementRequired: 'enchanter', - effects: [{ stat: 'enchantSpeed', mode: 'multiply', valuePerLevel: -0.10 }], -}; - -export const essenceRefining: SkillV2Def = { - id: 'essenceRefining', name: 'Essence Refining', description: 'Increases enchantment power', - category: 'enchant', maxLevel: 3, costPerLevel: 400, studyHours: 7, - prerequisites: { enchanting: 4 }, attunementRequired: 'enchanter', - effects: [{ stat: 'enchantPower', mode: 'multiply', valuePerLevel: 0.10 }], -}; - -export const disenchanting: SkillV2Def = { - id: 'disenchanting', name: 'Disenchanting', description: 'Recover mana on removal', - category: 'enchant', maxLevel: 3, costPerLevel: 300, studyHours: 3, - prerequisites: { enchanting: 2 }, attunementRequired: 'enchanter', - effects: [{ stat: 'disenchantRecovery', mode: 'add', valuePerLevel: 0.20 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-golemancy.ts b/src/lib/game/constants/skills-golemancy.ts deleted file mode 100644 index 8499e7d..0000000 --- a/src/lib/game/constants/skills-golemancy.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// GOLEMANCY SKILLS -// ═══════════════════════════════════════════════════════════════════════ -export const golemMastery: SkillV2Def = { - id: 'golemMastery', name: 'Golem Mastery', description: '+10% golem damage per level', - category: 'golemancy', maxLevel: 10, costPerLevel: 300, studyHours: 6, - attunementRequired: 'fabricator', - effects: [{ stat: 'golemDamage', mode: 'multiply', valuePerLevel: 0.10 }], -}; - -export const golemEfficiency: SkillV2Def = { - id: 'golemEfficiency', name: 'Golem Efficiency', description: '+5% golem attack speed per level', - category: 'golemancy', maxLevel: 10, costPerLevel: 350, studyHours: 6, - attunementRequired: 'fabricator', - effects: [{ stat: 'attackSpeed', mode: 'multiply', valuePerLevel: -0.05 }], -}; - -export const golemLongevity: SkillV2Def = { - id: 'golemLongevity', name: 'Golem Longevity', description: '+1 floor duration per level', - category: 'golemancy', maxLevel: 10, costPerLevel: 500, studyHours: 8, - attunementRequired: 'fabricator', - effects: [{ stat: 'golemDuration', mode: 'add', valuePerLevel: 1 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-hybrid.ts b/src/lib/game/constants/skills-hybrid.ts deleted file mode 100644 index acd9437..0000000 --- a/src/lib/game/constants/skills-hybrid.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// HYBRID SKILLS (require 2 attunements at level 5+) -// ═══════════════════════════════════════════════════════════════════════ -export const pactWeaving: SkillV2Def = { - id: 'pactWeaving', name: 'Pact-Weaving', description: 'Weave guardian essence into enchantments', - category: 'hybrid', maxLevel: 10, costPerLevel: 750, studyHours: 15, - effects: [{ stat: 'enchantPower', mode: 'multiply', valuePerLevel: 0.10 }], -}; - -export const guardianConstructs: SkillV2Def = { - id: 'guardianConstructs', name: 'Guardian Constructs', description: 'Build durable singular golems', - category: 'hybrid', maxLevel: 10, costPerLevel: 800, studyHours: 18, - effects: [ - { stat: 'golemDamage', mode: 'multiply', valuePerLevel: 0.15 }, - { stat: 'golemDuration', mode: 'add', valuePerLevel: 0.25 }, - ], -}; - -export const enchantedGolemancy: SkillV2Def = { - id: 'enchantedGolemancy', name: 'Enchanted Golemancy', description: 'Imbue golems with spell logic', - category: 'hybrid', maxLevel: 10, costPerLevel: 850, studyHours: 20, - effects: [ - { stat: 'enchantPower', mode: 'multiply', valuePerLevel: 0.05 }, - { stat: 'golemDamage', mode: 'multiply', valuePerLevel: 0.10 }, - ], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-invocation.ts b/src/lib/game/constants/skills-invocation.ts deleted file mode 100644 index f71c41b..0000000 --- a/src/lib/game/constants/skills-invocation.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// INVOCATION / PACT SKILLS (Invoker Attunement) -// ═══════════════════════════════════════════════════════════════════════ -export const invocation: SkillV2Def = { - id: 'invocation', name: 'Invocation', description: 'Enhances spell invocation', - category: 'invocation', maxLevel: 10, costPerLevel: 300, studyHours: 6, - attunementRequired: 'invoker', - effects: [{ stat: 'spellDamage', mode: 'multiply', valuePerLevel: 0.05 }], -}; - -export const pactMastery: SkillV2Def = { - id: 'pactMastery', name: 'Pact Mastery', description: 'Enhances pact signing bonuses', - category: 'pact', maxLevel: 10, costPerLevel: 350, studyHours: 6, - attunementRequired: 'invoker', - effects: [{ stat: 'pactMultiplier', mode: 'multiply', valuePerLevel: 0.10 }], -}; - -export const guardianLore: SkillV2Def = { - id: 'guardianLore', name: 'Guardian Lore', description: '+20% damage vs guardians', - category: 'invocation', maxLevel: 5, costPerLevel: 400, studyHours: 8, - attunementRequired: 'invoker', - effects: [{ stat: 'guardianDamage', mode: 'multiply', valuePerLevel: 0.20 }], -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-research.ts b/src/lib/game/constants/skills-research.ts deleted file mode 100644 index 4415f55..0000000 --- a/src/lib/game/constants/skills-research.ts +++ /dev/null @@ -1,385 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; - -// ═══════════════════════════════════════════════════════════════════════ -// ELEMENT RESEARCH SKILLS (max:1, long study – unlock enchantment effects) -// Kept for store/UI compat; effect unlocks handled via EFFECT_RESEARCH_MAPPING -// ═══════════════════════════════════════════════════════════════════════ -export const researchManaSpells: SkillV2Def = { - id: 'researchManaSpells', name: 'Mana Spell Research', description: 'Unlock Mana Strike spell enchantment', - category: 'effectResearch', maxLevel: 1, costPerLevel: 200, studyHours: 4, - effects: [], -}; - -export const researchFireSpells: SkillV2Def = { - id: 'researchFireSpells', name: 'Fire Spell Research', description: 'Unlock Ember Shot, Fireball spell enchantments', - category: 'effectResearch', maxLevel: 1, costPerLevel: 300, studyHours: 6, - effects: [], -}; - -export const researchWaterSpells: SkillV2Def = { - id: 'researchWaterSpells', name: 'Water Spell Research', description: 'Unlock Water Jet, Ice Shard spell enchantments', - category: 'effectResearch', maxLevel: 1, costPerLevel: 300, studyHours: 6, - effects: [], -}; - -export const researchAirSpells: SkillV2Def = { - id: 'researchAirSpells', name: 'Air Spell Research', description: 'Unlock Gust, Wind Slash spell enchantments', - category: 'effectResearch', maxLevel: 1, costPerLevel: 300, studyHours: 6, - effects: [], -}; - -export const researchEarthSpells: SkillV2Def = { - id: 'researchEarthSpells', name: 'Earth Spell Research', description: 'Unlock Stone Bullet, Rock Spike spell enchantments', - category: 'effectResearch', maxLevel: 1, costPerLevel: 350, studyHours: 6, - effects: [], -}; - -export const researchLightSpells: SkillV2Def = { - id: 'researchLightSpells', name: 'Light Spell Research', description: 'Unlock Light Lance, Radiance spell enchantments', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 8, - effects: [], -}; - -export const researchDarkSpells: SkillV2Def = { - id: 'researchDarkSpells', name: 'Dark Spell Research', description: 'Unlock Shadow Bolt, Dark Pulse spell enchantments', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 8, - effects: [], -}; - -export const researchLifeDeathSpells: SkillV2Def = { - id: 'researchLifeDeathSpells', name: 'Death Research', description: 'Unlock Drain spell enchantment', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 8, - effects: [], -}; - -// Tier 2 advanced spell research -export const researchAdvancedFire: SkillV2Def = { - id: 'researchAdvancedFire', name: 'Advanced Fire Research', description: 'Unlock Inferno, Flame Wave', - category: 'effectResearch', maxLevel: 1, costPerLevel: 600, studyHours: 12, - effects: [], -}; - -export const researchAdvancedWater: SkillV2Def = { - id: 'researchAdvancedWater', name: 'Advanced Water Research', description: 'Unlock Tidal Wave, Ice Storm', - category: 'effectResearch', maxLevel: 1, costPerLevel: 600, studyHours: 12, - effects: [], -}; - -export const researchAdvancedAir: SkillV2Def = { - id: 'researchAdvancedAir', name: 'Advanced Air Research', description: 'Unlock Hurricane, Wind Blade', - category: 'effectResearch', maxLevel: 1, costPerLevel: 600, studyHours: 12, - effects: [], -}; - -export const researchAdvancedEarth: SkillV2Def = { - id: 'researchAdvancedEarth', name: 'Advanced Earth Research', description: 'Unlock Earthquake, Stone Barrage', - category: 'effectResearch', maxLevel: 1, costPerLevel: 600, studyHours: 12, - effects: [], -}; - -export const researchAdvancedLight: SkillV2Def = { - id: 'researchAdvancedLight', name: 'Advanced Light Research', description: 'Unlock Solar Flare, Divine Smite', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 14, - effects: [], -}; - -export const researchAdvancedDark: SkillV2Def = { - id: 'researchAdvancedDark', name: 'Advanced Dark Research', description: 'Unlock Void Rift, Shadow Storm', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 14, - effects: [], -}; - -// Tier 3 master spell research -export const researchMasterFire: SkillV2Def = { - id: 'researchMasterFire', name: 'Master Fire Research', description: 'Unlock Pyroclasm', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1200, studyHours: 24, - effects: [], -}; - -export const researchMasterWater: SkillV2Def = { - id: 'researchMasterWater', name: 'Master Water Research', description: 'Unlock Tsunami', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1200, studyHours: 24, - effects: [], -}; - -export const researchMasterEarth: SkillV2Def = { - id: 'researchMasterEarth', name: 'Master Earth Research', description: 'Unlock Meteor Strike', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 26, - effects: [], -}; - -// Combat/misc research -export const researchDamageEffects: SkillV2Def = { - id: 'researchDamageEffects', name: 'Damage Effect Research', description: 'Unlock Minor/Moderate Power, Amplification', - category: 'effectResearch', maxLevel: 1, costPerLevel: 250, studyHours: 5, - effects: [], -}; - -export const researchCombatEffects: SkillV2Def = { - id: 'researchCombatEffects', name: 'Combat Effect Research', description: 'Unlock Sharp Edge, Swift Casting', - category: 'effectResearch', maxLevel: 1, costPerLevel: 350, studyHours: 6, - effects: [], -}; - -export const researchManaEffects: SkillV2Def = { - id: 'researchManaEffects', name: 'Mana Effect Research', description: 'Unlock Mana Reserve, Trickle, Mana Tap, weapon mana effects', - category: 'effectResearch', maxLevel: 1, costPerLevel: 200, studyHours: 4, - effects: [], -}; - -export const researchAdvancedManaEffects: SkillV2Def = { - id: 'researchAdvancedManaEffects', name: 'Advanced Mana Research', description: 'Unlock Mana Reservoir, Stream, River, Surge', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 8, - effects: [], -}; - -export const researchUtilityEffects: SkillV2Def = { - id: 'researchUtilityEffects', name: 'Utility Effect Research', description: 'Unlock Meditative Focus, Quick Study, Insightful', - category: 'effectResearch', maxLevel: 1, costPerLevel: 300, studyHours: 6, - effects: [], -}; - -export const researchSpecialEffects: SkillV2Def = { - id: 'researchSpecialEffects', name: 'Special Effect Research', description: 'Unlock Echo Chamber, Siphoning, Bane', - category: 'effectResearch', maxLevel: 1, costPerLevel: 500, studyHours: 10, - effects: [], -}; - -export const researchOverpower: SkillV2Def = { - id: 'researchOverpower', name: 'Overpower Research', description: 'Unlock Overpower effect', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -// Compound spell research -export const researchMetalSpells: SkillV2Def = { - id: 'researchMetalSpells', name: 'Metal Spell Research', description: 'Unlock Metal Shard, Iron Fist', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [], -}; - -export const researchSandSpells: SkillV2Def = { - id: 'researchSandSpells', name: 'Sand Spell Research', description: 'Unlock Sand Blast, Sandstorm', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [], -}; - -export const researchLightningSpells: SkillV2Def = { - id: 'researchLightningSpells', name: 'Lightning Spell Research', description: 'Unlock Spark, Lightning Bolt', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [], -}; - -export const researchAdvancedMetal: SkillV2Def = { - id: 'researchAdvancedMetal', name: 'Advanced Metal Research', description: 'Unlock Steel Tempest', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchAdvancedSand: SkillV2Def = { - id: 'researchAdvancedSand', name: 'Advanced Sand Research', description: 'Unlock Desert Wind', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchAdvancedLightning: SkillV2Def = { - id: 'researchAdvancedLightning', name: 'Advanced Lightning Research', description: 'Unlock Chain Lightning, Storm Call', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchMasterMetal: SkillV2Def = { - id: 'researchMasterMetal', name: 'Master Metal Research', description: 'Unlock Furnace Blast', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 26, - effects: [], -}; - -export const researchMasterSand: SkillV2Def = { - id: 'researchMasterSand', name: 'Master Sand Research', description: 'Unlock Dune Collapse', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 26, - effects: [], -}; - -export const researchMasterLightning: SkillV2Def = { - id: 'researchMasterLightning', name: 'Master Lightning Research', description: 'Unlock Thunder Strike', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 26, - effects: [], -}; - -export const researchTransferenceSpells: SkillV2Def = { - id: 'researchTransferenceSpells', name: 'Transference Spell Research', description: 'Unlock Transfer Strike, Mana Rip', - category: 'effectResearch', maxLevel: 1, costPerLevel: 350, studyHours: 5, - effects: [], -}; - -export const researchAdvancedTransference: SkillV2Def = { - id: 'researchAdvancedTransference', name: 'Advanced Transference Research', description: 'Unlock Essence Drain', - category: 'effectResearch', maxLevel: 1, costPerLevel: 650, studyHours: 12, - effects: [], -}; - -export const researchMasterTransference: SkillV2Def = { - id: 'researchMasterTransference', name: 'Master Transference Research', description: 'Unlock Soul Transfer', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 26, - effects: [], -}; - -// Element capacity research (Tier 2 — +25 per stack) -export const researchAdvancedFireCap: SkillV2Def = { - id: 'researchAdvancedFireCap', name: 'Advanced Fire Capacity Research', description: '+25 fire cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchAdvancedWaterCap: SkillV2Def = { - id: 'researchAdvancedWaterCap', name: 'Advanced Water Capacity Research', description: '+25 water cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchAdvancedAirCap: SkillV2Def = { - id: 'researchAdvancedAirCap', name: 'Advanced Air Capacity Research', description: '+25 air cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchAdvancedEarthCap: SkillV2Def = { - id: 'researchAdvancedEarthCap', name: 'Advanced Earth Capacity Research', description: '+25 earth cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchAdvancedLightCap: SkillV2Def = { - id: 'researchAdvancedLightCap', name: 'Advanced Light Capacity Research', description: '+25 light cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 800, studyHours: 14, - effects: [], -}; - -export const researchAdvancedDarkCap: SkillV2Def = { - id: 'researchAdvancedDarkCap', name: 'Advanced Dark Capacity Research', description: '+25 dark cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 800, studyHours: 14, - effects: [], -}; - -export const researchAdvancedDeathCap: SkillV2Def = { - id: 'researchAdvancedDeathCap', name: 'Advanced Death Capacity Research', description: '+25 death cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 900, studyHours: 16, - effects: [], -}; - -// Tier 3 — Master (+50 per stack) -export const researchMasterFireCap: SkillV2Def = { - id: 'researchMasterFireCap', name: 'Master Fire Capacity Research', description: '+50 fire cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 24, - effects: [], -}; - -export const researchMasterWaterCap: SkillV2Def = { - id: 'researchMasterWaterCap', name: 'Master Water Capacity Research', description: '+50 water cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 24, - effects: [], -}; - -export const researchMasterAirCap: SkillV2Def = { - id: 'researchMasterAirCap', name: 'Master Air Capacity Research', description: '+50 air cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 24, - effects: [], -}; - -export const researchMasterEarthCap: SkillV2Def = { - id: 'researchMasterEarthCap', name: 'Master Earth Capacity Research', description: '+50 earth cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1300, studyHours: 24, - effects: [], -}; - -export const researchMasterLightCap: SkillV2Def = { - id: 'researchMasterLightCap', name: 'Master Light Capacity Research', description: '+50 light cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1500, studyHours: 28, - effects: [], -}; - -export const researchMasterDarkCap: SkillV2Def = { - id: 'researchMasterDarkCap', name: 'Master Dark Capacity Research', description: '+50 dark cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1500, studyHours: 28, - effects: [], -}; - -export const researchMasterDeathCap: SkillV2Def = { - id: 'researchMasterDeathCap', name: 'Master Death Capacity Research', description: '+50 death cap per level', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1600, studyHours: 30, - effects: [], -}; - -// Compound element capacity research -export const researchMetalCapacity: SkillV2Def = { - id: 'researchMetalCapacity', name: 'Metal Capacity Research', description: '+10 metal cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [], -}; - -export const researchAdvancedMetalCap: SkillV2Def = { - id: 'researchAdvancedMetalCap', name: 'Advanced Metal Capacity Research', description: '+25/+50 metal cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchSandCapacity: SkillV2Def = { - id: 'researchSandCapacity', name: 'Sand Capacity Research', description: '+10 sand cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [], -}; - -export const researchAdvancedSandCap: SkillV2Def = { - id: 'researchAdvancedSandCap', name: 'Advanced Sand Capacity Research', description: '+25/+50 sand cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -export const researchLightningCapacity: SkillV2Def = { - id: 'researchLightningCapacity', name: 'Lightning Capacity Research', description: '+10 lightning cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 400, studyHours: 6, - effects: [], -}; - -export const researchAdvancedLightningCap: SkillV2Def = { - id: 'researchAdvancedLightningCap', name: 'Advanced Lightning Capacity Research', description: '+25/+50 lightning cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 700, studyHours: 12, - effects: [], -}; - -// Exotic capacity research -export const researchCrystalCapacity: SkillV2Def = { - id: 'researchCrystalCapacity', name: 'Crystal Capacity Research', description: '+10 crystal cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1000, studyHours: 12, - effects: [], -}; - -export const researchAdvancedCrystalCap: SkillV2Def = { - id: 'researchAdvancedCrystalCap', name: 'Advanced Crystal Capacity Research', description: '+25/+50 crystal cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 2000, studyHours: 24, - effects: [], -}; - -export const researchStellarCapacity: SkillV2Def = { - id: 'researchStellarCapacity', name: 'Stellar Capacity Research', description: '+10 stellar cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1000, studyHours: 12, - effects: [], -}; - -export const researchAdvancedStellarCap: SkillV2Def = { - id: 'researchAdvancedStellarCap', name: 'Advanced Stellar Capacity Research', description: '+25/+50 stellar cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 2000, studyHours: 24, - effects: [], -}; - -export const researchVoidCapacity: SkillV2Def = { - id: 'researchVoidCapacity', name: 'Void Capacity Research', description: '+10 void cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 1500, studyHours: 16, - effects: [], -}; - -export const researchAdvancedVoidCap: SkillV2Def = { - id: 'researchAdvancedVoidCap', name: 'Advanced Void Capacity Research', description: '+25/+50 void cap', - category: 'effectResearch', maxLevel: 1, costPerLevel: 3000, studyHours: 30, - effects: [], -}; diff --git a/src/lib/game/constants/skills-v2-defs.ts b/src/lib/game/constants/skills-v2-defs.ts deleted file mode 100644 index 6bc9428..0000000 --- a/src/lib/game/constants/skills-v2-defs.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; -// @ts-expect-error - circular refs resolved at runtime -export { SKILLS_V2 } from './skills-v2-registry'; \ No newline at end of file diff --git a/src/lib/game/constants/skills-v2-registry.ts b/src/lib/game/constants/skills-v2-registry.ts deleted file mode 100644 index 4563850..0000000 --- a/src/lib/game/constants/skills-v2-registry.ts +++ /dev/null @@ -1,292 +0,0 @@ -import type { SkillV2Def } from './skills-v2-types'; -import { - manaWell, - manaFlow, - manaOverflow, - manaTap, - manaSurge, - manaSpring, - quickLearner, - focusedMind, - knowledgeRetention, - meditation, - deepTrance, - voidMeditation, -} from './skills-core'; -import { - enchanting, - efficientEnchant, - enchantSpeed, - essenceRefining, - disenchanting, -} from './skills-enchant'; -import { - arcaneFury, - combatTraining, - precision, - elementalMastery, - attackSpeed, - armorPiercing, - spellDamage, -} from './skills-combat'; -import { - golemMastery, - golemEfficiency, - golemLongevity, -} from './skills-golemancy'; -import { - invocation, - pactMastery, - guardianLore, -} from './skills-invocation'; -import { - effCrafting, - fieldRepair, -} from './skills-crafting'; -import { - fireManaCap, - waterManaCap, - airManaCap, - earthManaCap, - lightManaCap, - darkManaCap, - deathManaCap, - metalManaCap, - sandManaCap, - lightningManaCap, - transferenceManaCap, -} from './skills-element-caps'; -import { - pactWeaving, - guardianConstructs, - enchantedGolemancy, -} from './skills-hybrid'; -import { - researchManaSpells, - researchFireSpells, - researchWaterSpells, - researchAirSpells, - researchEarthSpells, - researchLightSpells, - researchDarkSpells, - researchLifeDeathSpells, - researchAdvancedFire, - researchAdvancedWater, - researchAdvancedAir, - researchAdvancedEarth, - researchAdvancedLight, - researchAdvancedDark, - researchMasterFire, - researchMasterWater, - researchMasterEarth, - researchDamageEffects, - researchCombatEffects, - researchManaEffects, - researchAdvancedManaEffects, - researchUtilityEffects, - researchSpecialEffects, - researchOverpower, - researchMetalSpells, - researchSandSpells, - researchLightningSpells, - researchAdvancedMetal, - researchAdvancedSand, - researchAdvancedLightning, - researchMasterMetal, - researchMasterSand, - researchMasterLightning, - researchTransferenceSpells, - researchAdvancedTransference, - researchMasterTransference, - researchAdvancedFireCap, - researchAdvancedWaterCap, - researchAdvancedAirCap, - researchAdvancedEarthCap, - researchAdvancedLightCap, - researchAdvancedDarkCap, - researchAdvancedDeathCap, - researchMasterFireCap, - researchMasterWaterCap, - researchMasterAirCap, - researchMasterEarthCap, - researchMasterLightCap, - researchMasterDarkCap, - researchMasterDeathCap, - researchMetalCapacity, - researchAdvancedMetalCap, - researchSandCapacity, - researchAdvancedSandCap, - researchLightningCapacity, - researchAdvancedLightningCap, - researchCrystalCapacity, - researchAdvancedCrystalCap, - researchStellarCapacity, - researchAdvancedStellarCap, - researchVoidCapacity, - researchAdvancedVoidCap, -} from './skills-research'; - -// Re-export individual skills -export { - manaWell, - manaFlow, - manaOverflow, - manaTap, - manaSurge, - manaSpring, - quickLearner, - focusedMind, - knowledgeRetention, - meditation, - deepTrance, - voidMeditation, -} from './skills-core'; -export { - enchanting, - efficientEnchant, - enchantSpeed, - essenceRefining, - disenchanting, -} from './skills-enchant'; -export { - arcaneFury, - combatTraining, - precision, - elementalMastery, - attackSpeed, - armorPiercing, - spellDamage, -} from './skills-combat'; -export { - golemMastery, - golemEfficiency, - golemLongevity, -} from './skills-golemancy'; -export { - invocation, - pactMastery, - guardianLore, -} from './skills-invocation'; -export { - effCrafting, - fieldRepair, -} from './skills-crafting'; -export { - fireManaCap, - waterManaCap, - airManaCap, - earthManaCap, - lightManaCap, - darkManaCap, - deathManaCap, - metalManaCap, - sandManaCap, - lightningManaCap, - transferenceManaCap, -} from './skills-element-caps'; -export { - pactWeaving, - guardianConstructs, - enchantedGolemancy, -} from './skills-hybrid'; -export { - researchManaSpells, - researchFireSpells, - researchWaterSpells, - researchAirSpells, - researchEarthSpells, - researchLightSpells, - researchDarkSpells, - researchLifeDeathSpells, - researchAdvancedFire, - researchAdvancedWater, - researchAdvancedAir, - researchAdvancedEarth, - researchAdvancedLight, - researchAdvancedDark, - researchMasterFire, - researchMasterWater, - researchMasterEarth, - researchDamageEffects, - researchCombatEffects, - researchManaEffects, - researchAdvancedManaEffects, - researchUtilityEffects, - researchSpecialEffects, - researchOverpower, - researchMetalSpells, - researchSandSpells, - researchLightningSpells, - researchAdvancedMetal, - researchAdvancedSand, - researchAdvancedLightning, - researchMasterMetal, - researchMasterSand, - researchMasterLightning, - researchTransferenceSpells, - researchAdvancedTransference, - researchMasterTransference, - researchAdvancedFireCap, - researchAdvancedWaterCap, - researchAdvancedAirCap, - researchAdvancedEarthCap, - researchAdvancedLightCap, - researchAdvancedDarkCap, - researchAdvancedDeathCap, - researchMasterFireCap, - researchMasterWaterCap, - researchMasterAirCap, - researchMasterEarthCap, - researchMasterLightCap, - researchMasterDarkCap, - researchMasterDeathCap, - researchMetalCapacity, - researchAdvancedMetalCap, - researchSandCapacity, - researchAdvancedSandCap, - researchLightningCapacity, - researchAdvancedLightningCap, - researchCrystalCapacity, - researchAdvancedCrystalCap, - researchStellarCapacity, - researchAdvancedStellarCap, - researchVoidCapacity, - researchAdvancedVoidCap, -} from './skills-research'; - -// Build the flat SKILLS_V2 record (legacy compat) -export const SKILLS_V2: Record = { - manaWell, manaFlow, manaOverflow, manaTap, manaSurge, manaSpring, - quickLearner, focusedMind, knowledgeRetention, - meditation, deepTrance, voidMeditation, - enchanting, efficientEnchant, enchantSpeed, essenceRefining, disenchanting, - arcaneFury, combatTraining, precision, elementalMastery, attackSpeed, armorPiercing, spellDamage, - golemMastery, golemEfficiency, golemLongevity, - invocation, pactMastery, guardianLore, - effCrafting, fieldRepair, - fireManaCap, waterManaCap, airManaCap, earthManaCap, - lightManaCap, darkManaCap, deathManaCap, metalManaCap, - sandManaCap, lightningManaCap, transferenceManaCap, - pactWeaving, guardianConstructs, enchantedGolemancy, - researchManaSpells, researchFireSpells, researchWaterSpells, researchAirSpells, - researchEarthSpells, researchLightSpells, researchDarkSpells, researchLifeDeathSpells, - researchAdvancedFire, researchAdvancedWater, researchAdvancedAir, researchAdvancedEarth, - researchAdvancedLight, researchAdvancedDark, - researchMasterFire, researchMasterWater, researchMasterEarth, - researchDamageEffects, researchCombatEffects, researchManaEffects, - researchAdvancedManaEffects, researchUtilityEffects, researchSpecialEffects, researchOverpower, - researchMetalSpells, researchSandSpells, researchLightningSpells, - researchAdvancedMetal, researchAdvancedSand, researchAdvancedLightning, - researchMasterMetal, researchMasterSand, researchMasterLightning, - researchTransferenceSpells, researchAdvancedTransference, researchMasterTransference, - researchAdvancedFireCap, researchAdvancedWaterCap, researchAdvancedAirCap, researchAdvancedEarthCap, - researchAdvancedLightCap, researchAdvancedDarkCap, researchAdvancedDeathCap, - researchMasterFireCap, researchMasterWaterCap, researchMasterAirCap, researchMasterEarthCap, - researchMasterLightCap, researchMasterDarkCap, researchMasterDeathCap, - researchMetalCapacity, researchAdvancedMetalCap, - researchSandCapacity, researchAdvancedSandCap, - researchLightningCapacity, researchAdvancedLightningCap, - researchCrystalCapacity, researchAdvancedCrystalCap, - researchStellarCapacity, researchAdvancedStellarCap, - researchVoidCapacity, researchAdvancedVoidCap, -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-v2-types.ts b/src/lib/game/constants/skills-v2-types.ts deleted file mode 100644 index 93aae85..0000000 --- a/src/lib/game/constants/skills-v2-types.ts +++ /dev/null @@ -1,142 +0,0 @@ -// ─── Skill System v2 Types ───────────────────────────────────────────────────── -// Shared types for the new flat skill system. - -export type StatKey = - | 'maxMana' - | 'manaRegen' - | 'clickMana' - | 'elementCap' - | 'studySpeed' - | 'studyCostMult' - | 'meditationEfficiency' - | 'enchantCapacity' - | 'enchantSpeed' - | 'enchantPower' - | 'disenchantRecovery' - | 'baseDamage' - | 'damageMultiplier' - | 'attackSpeed' - | 'critChance' - | 'critMultiplier' - | 'armorPierce' - | 'insightGain' - | 'golemDamage' - | 'golemDuration' - | 'pactMultiplier' - | 'conversionRate' - | 'spellDamage' - | 'guardianDamage' - | 'craftSpeed' - | 'repairSpeed' - // Per-element capacity stats - | 'fireCap' - | 'waterCap' - | 'airCap' - | 'earthCap' - | 'lightCap' - | 'darkCap' - | 'deathCap' - | 'metalCap' - | 'sandCap' - | 'lightningCap' - | 'transferenceCap' - | 'crystalCap' - | 'stellarCap' - | 'voidCap'; - -export interface SkillEffect { - stat: StatKey; - mode: 'add' | 'multiply'; - valuePerLevel: number; -} - -export interface SkillV2Def { - id: string; - name: string; - description: string; - category: string; - maxLevel: number; - costPerLevel: number; - studyHours: number; - effects: SkillEffect[]; - attunementRequired?: string; - prerequisites?: Record; -} - -export interface ComputedStats { - maxMana: number; - manaRegen: number; - clickMana: number; - elementCap: number; - studySpeed: number; - studyCostMult: number; - meditationEfficiency: number; - enchantCapacity: number; - enchantSpeed: number; - enchantPower: number; - disenchantRecovery: number; - baseDamage: number; - damageMultiplier: number; - attackSpeed: number; - critChance: number; - critMultiplier: number; - armorPierce: number; - insightGain: number; - golemDamage: number; - golemDuration: number; - pactMultiplier: number; - conversionRate: number; - spellDamage: number; - guardianDamage: number; - craftSpeed: number; - repairSpeed: number; - // Per-element capacity (for direct skill contributions) - fireCap: number; - waterCap: number; - airCap: number; - earthCap: number; - lightCap: number; - darkCap: number; - deathCap: number; - metalCap: number; - sandCap: number; - lightningCap: number; - transferenceCap: number; - crystalCap: number; - stellarCap: number; - voidCap: number; -} - -// Default base stats (all skills at zero, no equipment, no prestige) -export const BASE_STATS: ComputedStats = { - maxMana: 100, - manaRegen: 2, - clickMana: 1, - elementCap: 10, - studySpeed: 1, - studyCostMult: 1, - meditationEfficiency: 1, - enchantCapacity: 100, - enchantSpeed: 1, - enchantPower: 1, - disenchantRecovery: 1, - baseDamage: 5, - damageMultiplier: 1, - attackSpeed: 1, - critChance: 0, - critMultiplier: 1.5, - armorPierce: 0, - insightGain: 1, - golemDamage: 1, - golemDuration: 1, - pactMultiplier: 1, - conversionRate: 1, - spellDamage: 1, - guardianDamage: 1, - craftSpeed: 1, - repairSpeed: 1, - fireCap: 0, waterCap: 0, airCap: 0, earthCap: 0, - lightCap: 0, darkCap: 0, deathCap: 0, - metalCap: 0, sandCap: 0, lightningCap: 0, - transferenceCap: 0, crystalCap: 0, stellarCap: 0, voidCap: 0, -}; \ No newline at end of file diff --git a/src/lib/game/constants/skills-v2.ts b/src/lib/game/constants/skills-v2.ts deleted file mode 100644 index 8c16182..0000000 --- a/src/lib/game/constants/skills-v2.ts +++ /dev/null @@ -1,269 +0,0 @@ -import type { SkillV2Def, SkillEffect, StatKey, ComputedStats } from './skills-v2-types'; -import { SKILLS_V2 } from './skills-v2-defs'; - -// Re-export individual skills from category modules -export { - manaWell, - manaFlow, - manaOverflow, - manaTap, - manaSurge, - manaSpring, - quickLearner, - focusedMind, - knowledgeRetention, - meditation, - deepTrance, - voidMeditation, -} from './skills-core'; - -export { - enchanting, - efficientEnchant, - enchantSpeed, - essenceRefining, - disenchanting, -} from './skills-enchant'; - -export { - arcaneFury, - combatTraining, - precision, - elementalMastery, - attackSpeed, - armorPiercing, - spellDamage, -} from './skills-combat'; - -export { - golemMastery, - golemEfficiency, - golemLongevity, -} from './skills-golemancy'; - -export { - invocation, - pactMastery, - guardianLore, -} from './skills-invocation'; - -export { - effCrafting, - fieldRepair, -} from './skills-crafting'; - -export { - fireManaCap, - waterManaCap, - airManaCap, - earthManaCap, - lightManaCap, - darkManaCap, - deathManaCap, - metalManaCap, - sandManaCap, - lightningManaCap, - transferenceManaCap, -} from './skills-element-caps'; - -export { - pactWeaving, - guardianConstructs, - enchantedGolemancy, -} from './skills-hybrid'; - -export { - researchManaSpells, - researchFireSpells, - researchWaterSpells, - researchAirSpells, - researchEarthSpells, - researchLightSpells, - researchDarkSpells, - researchLifeDeathSpells, - researchAdvancedFire, - researchAdvancedWater, - researchAdvancedAir, - researchAdvancedEarth, - researchAdvancedLight, - researchAdvancedDark, - researchMasterFire, - researchMasterWater, - researchMasterEarth, - researchDamageEffects, - researchCombatEffects, - researchManaEffects, - researchAdvancedManaEffects, - researchUtilityEffects, - researchSpecialEffects, - researchOverpower, - researchMetalSpells, - researchSandSpells, - researchLightningSpells, - researchAdvancedMetal, - researchAdvancedSand, - researchAdvancedLightning, - researchMasterMetal, - researchMasterSand, - researchMasterLightning, - researchTransferenceSpells, - researchAdvancedTransference, - researchMasterTransference, - researchAdvancedFireCap, - researchAdvancedWaterCap, - researchAdvancedAirCap, - researchAdvancedEarthCap, - researchAdvancedLightCap, - researchAdvancedDarkCap, - researchAdvancedDeathCap, - researchMasterFireCap, - researchMasterWaterCap, - researchMasterAirCap, - researchMasterEarthCap, - researchMasterLightCap, - researchMasterDarkCap, - researchMasterDeathCap, - researchMetalCapacity, - researchAdvancedMetalCap, - researchSandCapacity, - researchAdvancedSandCap, - researchLightningCapacity, - researchAdvancedLightningCap, - researchCrystalCapacity, - researchAdvancedCrystalCap, - researchStellarCapacity, - researchAdvancedStellarCap, - researchVoidCapacity, - researchAdvancedVoidCap, -} from './skills-research'; - -export { SKILLS_V2 }; - -// Default Base Stats -export const BASE_STATS: ComputedStats = { - maxMana: 100, - manaRegen: 2, - clickMana: 1, - elementCap: 10, - studySpeed: 1, - studyCostMult: 1, - meditationEfficiency: 1, - enchantCapacity: 100, - enchantSpeed: 1, - enchantPower: 1, - disenchantRecovery: 1, - baseDamage: 5, - damageMultiplier: 1, - attackSpeed: 1, - critChance: 0, - critMultiplier: 1.5, - armorPierce: 0, - insightGain: 1, - golemDamage: 1, - golemDuration: 1, - pactMultiplier: 1, - conversionRate: 1, - spellDamage: 1, - guardianDamage: 1, - craftSpeed: 1, - repairSpeed: 1, - fireCap: 0, waterCap: 0, airCap: 0, earthCap: 0, - lightCap: 0, darkCap: 0, deathCap: 0, - metalCap: 0, sandCap: 0, lightningCap: 0, - transferenceCap: 0, crystalCap: 0, stellarCap: 0, voidCap: 0, - elementalDamage: 1, -}; - -const ELEMENT_CAP_STATS: Record = { - fire: 'fireCap', water: 'waterCap', air: 'airCap', earth: 'earthCap', - light: 'lightCap', dark: 'darkCap', death: 'deathCap', metal: 'metalCap', - sand: 'sandCap', lightning: 'lightningCap', transference: 'transferenceCap', - crystal: 'crystalCap', stellar: 'stellarCap', void: 'voidCap', -}; - -/** - * Compute all game stats from skill levels. - */ -export function computeStats( - skills: Record, - prestigeUpgrades: Record = {} -): ComputedStats { - const stats: ComputedStats = { ...BASE_STATS }; - - for (const [skillId, level] of Object.entries(skills)) { - if (level <= 0) continue; - const def = SKILLS_V2[skillId]; - if (!def) continue; - for (const effect of def.effects) { - const key = effect.stat as keyof ComputedStats; - const currentVal = stats[key] as number; - if (effect.mode === 'add') { - (stats[key] as number) = currentVal + effect.valuePerLevel * level; - } else { - const perLevelMultiplier = 1 + effect.valuePerLevel; - let result = currentVal; - for (let i = 0; i < level; i++) result *= perLevelMultiplier; - (stats[key] as number) = result; - } - } - } - - if (prestigeUpgrades.manaWell) stats.maxMana += prestigeUpgrades.manaWell * 500; - if (prestigeUpgrades.manaFlow) stats.manaRegen += prestigeUpgrades.manaFlow * 0.5; - if (prestigeUpgrades.elementalAttune) stats.elementCap += prestigeUpgrades.elementalAttune * 25; - if (prestigeUpgrades.pactBinding) stats.pactMultiplier += prestigeUpgrades.pactBinding * 0.1; - if (prestigeUpgrades.insightAmp) stats.insightGain *= 1 + prestigeUpgrades.insightAmp * 0.25; - - let elementCapFromSkills = 0; - for (const [, statKey] of Object.entries(ELEMENT_CAP_STATS)) { - const val = stats[statKey] as number; - if (val > 0) elementCapFromSkills += val; - } - if (elementCapFromSkills > 0) stats.elementCap += elementCapFromSkills; - - stats.maxMana = Math.max(1, Math.round(stats.maxMana)); - stats.manaRegen = Math.round(stats.manaRegen * 100) / 100; - stats.clickMana = Math.max(1, stats.clickMana); - stats.elementCap = Math.max(10, Math.round(stats.elementCap)); - stats.baseDamage = Math.max(1, Math.round(stats.baseDamage)); - stats.critChance = Math.min(1, Math.max(0, stats.critChance)); - stats.armorPierce = Math.min(1, Math.max(0, stats.armorPierce)); - stats.attackSpeed = Math.max(0.1, stats.attackSpeed); - stats.insightGain = Math.max(0, stats.insightGain); - stats.golemDamage = Math.max(0.1, stats.golemDamage); - stats.golemDuration = Math.max(1, stats.golemDuration); - stats.enchantCapacity = Math.max(10, Math.round(stats.enchantCapacity)); - stats.conversionRate = Math.max(0, stats.conversionRate); - - return stats; -} - -/** - * Get the base key for a tiered skill (strips _tN suffix). - */ -export function getBaseSkillId(skillId: string): string { - const match = skillId.match(/^(.+?)_t(\d+)$/); - return match ? match[1] : skillId; -} - -/** - * Check if a skill has prerequisites that are met. - */ -export function hasPrerequisites( - skills: Record, - prerequisites?: Record -): boolean { - if (!prerequisites) return true; - for (const [reqId, reqLevel] of Object.entries(prerequisites)) { - if ((skills[reqId] || 0) < reqLevel) return false; - } - return true; -} - -/** Legacy compat wrapper */ -export function computeStatsLegacy(state: { - skills: Record; - prestigeUpgrades?: Record; -}): ComputedStats { - return computeStats(state.skills, state.prestigeUpgrades || {}); -} \ No newline at end of file diff --git a/src/lib/game/constants/skills.ts b/src/lib/game/constants/skills.ts deleted file mode 100644 index 9d235a0..0000000 --- a/src/lib/game/constants/skills.ts +++ /dev/null @@ -1,348 +0,0 @@ -// ─── Skills ─────────────────────────────────────────────────────────────────── -import type { SkillDef } from '../types'; - -export const SKILLS_DEF: Record = { - // Mana Skills (4-8 hours study) - Core, no attunement required - manaWell: { name: "Mana Well", desc: "+100 max mana", cat: "mana", max: 10, base: 100, studyTime: 4 }, - manaFlow: { name: "Mana Flow", desc: "+1 regen/hr", cat: "mana", max: 10, base: 150, studyTime: 5 }, - // Per-mana-type capacity upgrades (Bug 9) - fireManaCap: { name: "Fire Mana Capacity +10%", desc: "+10% fire mana capacity", cat: "mana", max: 10, base: 200, studyTime: 4, cost: { type: 'element', element: 'fire', amount: 100 } }, - waterManaCap: { name: "Water Mana Capacity +10%", desc: "+10% water mana capacity", cat: "mana", max: 10, base: 200, studyTime: 4, cost: { type: 'element', element: 'water', amount: 100 } }, - airManaCap: { name: "Air Mana Capacity +10%", desc: "+10% air mana capacity", cat: "mana", max: 10, base: 200, studyTime: 4, cost: { type: 'element', element: 'air', amount: 100 } }, - earthManaCap: { name: "Earth Mana Capacity +10%", desc: "+10% earth mana capacity", cat: "mana", max: 10, base: 200, studyTime: 4, cost: { type: 'element', element: 'earth', amount: 100 } }, - lightManaCap: { name: "Light Mana Capacity +10%", desc: "+10% light mana capacity", cat: "mana", max: 10, base: 250, studyTime: 5, cost: { type: 'element', element: 'light', amount: 150 } }, - darkManaCap: { name: "Dark Mana Capacity +10%", desc: "+10% dark mana capacity", cat: "mana", max: 10, base: 250, studyTime: 5, cost: { type: 'element', element: 'dark', amount: 150 } }, - deathManaCap: { name: "Death Mana Capacity +10%", desc: "+10% death mana capacity", cat: "mana", max: 10, base: 300, studyTime: 6, cost: { type: 'element', element: 'death', amount: 200 } }, - // Composite element capacity upgrades - metalManaCap: { name: "Metal Mana Capacity +10%", desc: "+10% metal mana capacity", cat: "mana", max: 10, base: 350, studyTime: 6, cost: { type: 'element', element: 'metal', amount: 250 } }, - sandManaCap: { name: "Sand Mana Capacity +10%", desc: "+10% sand mana capacity", cat: "mana", max: 10, base: 350, studyTime: 6, cost: { type: 'element', element: 'sand', amount: 250 } }, - lightningManaCap: { name: "Lightning Mana Capacity +10%", desc: "+10% lightning mana capacity", cat: "mana", max: 10, base: 350, studyTime: 6, cost: { type: 'element', element: 'lightning', amount: 250 } }, - // Utility mana capacity upgrades - transferenceManaCap: { name: "Transference Mana Capacity +10%", desc: "+10% transference mana capacity", cat: "mana", max: 10, base: 200, studyTime: 4, cost: { type: 'element', element: 'transference', amount: 100 } }, - manaOverflow: { name: "Mana Overflow", desc: "+25% mana from clicks", cat: "mana", max: 5, base: 400, req: { manaWell: 3 }, studyTime: 6 }, - - // Study Skills (3-6 hours study) - Core, no attunement required - quickLearner: { name: "Quick Learner", desc: "+10% study speed", cat: "study", max: 10, base: 250, studyTime: 4 }, - focusedMind: { name: "Focused Mind", desc: "-5% study mana cost", cat: "study", max: 10, base: 300, studyTime: 5 }, - meditation: { name: "Meditation Focus", desc: "Up to 2.5x regen after 4hrs meditating", cat: "mana", max: 1, base: 400, studyTime: 6 }, - knowledgeRetention: { name: "Knowledge Retention", desc: "+20% study progress saved on cancel", cat: "study", max: 3, base: 350, studyTime: 5 }, - - // Enchanting Skills (4-8 hours study) - Requires Enchanter attunement levels - enchanting: { name: "Enchanting", desc: "Unlocks enchantment design", cat: "enchant", max: 10, base: 200, studyTime: 5, attunement: 'enchanter', attunementReq: { enchanter: 1 } }, - efficientEnchant:{ name: "Efficient Enchant", desc: "-5% enchantment capacity cost", cat: "enchant", max: 5, base: 350, studyTime: 6, req: { enchanting: 3 }, attunementReq: { enchanter: 2 } }, - - enchantSpeed: { name: "Enchant Speed", desc: "-10% enchantment time", cat: "enchant", max: 5, base: 300, studyTime: 4, req: { enchanting: 2 }, attunementReq: { enchanter: 1 } }, - essenceRefining: { name: "Essence Refining", desc: "+10% enchantment effect power", cat: "enchant", max: 1, base: 450, studyTime: 7, req: { enchanting: 4 }, attunementReq: { enchanter: 2 } }, - - // Crafting Skills (4-6 hours study) - Some require Fabricator - effCrafting: { name: "Eff. Crafting", desc: "-10% craft time", cat: "craft", max: 1, base: 300, studyTime: 4 }, - fieldRepair: { name: "Field Repair", desc: "+15% repair efficiency", cat: "craft", max: 1, base: 350, studyTime: 4 }, - elemCrafting: { name: "Elem. Crafting", desc: "+25% craft output", cat: "craft", max: 1, base: 500, req: { effCrafting: 1 }, studyTime: 8, attunementReq: { enchanter: 1 } }, - - // Effect Research Skills (unlock enchantment effects for designing) - Requires Enchanter - // Tier 1 - Basic Spell Effects - researchManaSpells: { name: "Mana Spell Research", desc: "Unlock Mana Strike spell enchantment", cat: "effectResearch", max: 1, base: 200, studyTime: 4, req: { enchanting: 1 }, cost: { type: 'element', element: 'transference', amount: 100 }, attunementReq: { enchanter: 1 } }, - researchFireSpells: { name: "Fire Spell Research", desc: "Unlock Ember Shot, Fireball spell enchantments", cat: "effectResearch", max: 1, base: 300, studyTime: 6, req: { enchanting: 2 }, cost: { type: 'element', element: 'fire', amount: 100 }, attunementReq: { enchanter: 1 } }, - researchWaterSpells: { name: "Water Spell Research", desc: "Unlock Water Jet, Ice Shard spell enchantments", cat: "effectResearch", max: 1, base: 300, studyTime: 6, req: { enchanting: 2 }, cost: { type: 'element', element: 'water', amount: 100 }, attunementReq: { enchanter: 1 } }, - researchAirSpells: { name: "Air Spell Research", desc: "Unlock Gust, Wind Slash spell enchantments", cat: "effectResearch", max: 1, base: 300, studyTime: 6, req: { enchanting: 2 }, cost: { type: 'element', element: 'air', amount: 100 }, attunementReq: { enchanter: 1 } }, - researchEarthSpells: { name: "Earth Spell Research", desc: "Unlock Stone Bullet, Rock Spike spell enchantments", cat: "effectResearch", max: 1, base: 350, studyTime: 6, req: { enchanting: 2 }, cost: { type: 'element', element: 'earth', amount: 100 }, attunementReq: { enchanter: 1 } }, - researchLightSpells: { name: "Light Spell Research", desc: "Unlock Light Lance, Radiance spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 }, cost: { type: 'element', element: 'light', amount: 100 }, attunementReq: { enchanter: 2 } }, - researchDarkSpells: { name: "Dark Spell Research", desc: "Unlock Shadow Bolt, Dark Pulse spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 }, cost: { type: 'element', element: 'dark', amount: 100 }, attunementReq: { enchanter: 2 } }, - researchLifeDeathSpells: { name: "Death Research", desc: "Unlock Drain spell enchantment", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 }, cost: { type: 'element', element: 'death', amount: 100 }, attunementReq: { enchanter: 2 } }, - - // Tier 2 - Advanced Spell Effects - Require Enchanter 3 - researchAdvancedFire: { name: "Advanced Fire Research", desc: "Unlock Inferno, Flame Wave spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchFireSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'fire', amount: 100 }, attunementReq: { enchanter: 3 } }, - researchAdvancedWater: { name: "Advanced Water Research", desc: "Unlock Tidal Wave, Ice Storm spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchWaterSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'water', amount: 100 }, attunementReq: { enchanter: 3 } }, - researchAdvancedAir: { name: "Advanced Air Research", desc: "Unlock Hurricane, Wind Blade spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchAirSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'air', amount: 100 }, attunementReq: { enchanter: 3 } }, - researchAdvancedEarth: { name: "Advanced Earth Research", desc: "Unlock Earthquake, Stone Barrage spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchEarthSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'earth', amount: 100 }, attunementReq: { enchanter: 3 } }, - researchAdvancedLight: { name: "Advanced Light Research", desc: "Unlock Solar Flare, Divine Smite spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 14, req: { researchLightSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'light', amount: 100 }, attunementReq: { enchanter: 4 } }, - researchAdvancedDark: { name: "Advanced Dark Research", desc: "Unlock Void Rift, Shadow Storm spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 14, req: { researchDarkSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'dark', amount: 100 }, attunementReq: { enchanter: 4 } }, - - // Tier 3 - Master Spell Effects - Require Enchanter 5 - researchMasterFire: { name: "Master Fire Research", desc: "Unlock Pyroclasm spell enchantment", cat: "effectResearch", max: 1, base: 1200, studyTime: 24, req: { researchAdvancedFire: 1, enchanting: 7 }, cost: { type: 'element', element: 'fire', amount: 200 }, attunementReq: { enchanter: 5 } }, - researchMasterWater: { name: "Master Water Research", desc: "Unlock Tsunami spell enchantment", cat: "effectResearch", max: 1, base: 1200, studyTime: 24, req: { researchAdvancedWater: 1, enchanting: 7 }, cost: { type: 'element', element: 'water', amount: 200 }, attunementReq: { enchanter: 5 } }, - researchMasterEarth: { name: "Master Earth Research", desc: "Unlock Meteor Strike spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedEarth: 1, enchanting: 8 }, cost: { type: 'element', element: 'earth', amount: 200 }, attunementReq: { enchanter: 5 } }, - - // Combat Effect Research - researchDamageEffects: { name: "Damage Effect Research", desc: "Unlock Minor/Moderate Power, Amplification effects", cat: "effectResearch", max: 1, base: 250, studyTime: 5, req: { enchanting: 1 }, attunementReq: { enchanter: 1 } }, - researchCombatEffects: { name: "Combat Effect Research", desc: "Unlock Sharp Edge, Swift Casting effects", cat: "effectResearch", max: 1, base: 350, studyTime: 6, req: { researchDamageEffects: 1, enchanting: 3 }, attunementReq: { enchanter: 2 } }, - - // Mana Effect Research - Also unlocks weapon mana effects at Enchanter 3 - researchManaEffects: { name: "Mana Effect Research", desc: "Unlock Mana Reserve, Trickle, Mana Tap, and weapon mana effects", cat: "effectResearch", max: 1, base: 200, studyTime: 4, req: { enchanting: 1 }, attunementReq: { enchanter: 1 } }, - researchAdvancedManaEffects: { name: "Advanced Mana Research", desc: "Unlock Mana Reservoir, Stream, River, Mana Surge, and advanced weapon mana effects", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { researchManaEffects: 1, enchanting: 3 }, attunementReq: { enchanter: 3 } }, - - // Utility Effect Research - researchUtilityEffects: { name: "Utility Effect Research", desc: "Unlock Meditative Focus, Quick Study, Insightful effects", cat: "effectResearch", max: 1, base: 300, studyTime: 6, req: { enchanting: 2 }, attunementReq: { enchanter: 1 } }, - - // Special Effect Research - researchSpecialEffects: { name: "Special Effect Research", desc: "Unlock Echo Chamber, Siphoning, Bane effects", cat: "effectResearch", max: 1, base: 500, studyTime: 10, req: { enchanting: 4 }, attunementReq: { enchanter: 3 } }, - researchOverpower: { name: "Overpower Research", desc: "Unlock Overpower effect", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchSpecialEffects: 1, enchanting: 5 }, attunementReq: { enchanter: 4 } }, - - // ═══════════════════════════════════════════════════════════════════════════ - // COMPOUND MANA SPELL RESEARCH - Metal, Sand, Lightning - // ═══════════════════════════════════════════════════════════════════════════ - - // Tier 1 - Basic Compound Spells - researchMetalSpells: { name: "Metal Spell Research", desc: "Unlock Metal Shard, Iron Fist spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchFireSpells: 1, researchEarthSpells: 1, enchanting: 3 }, cost: { type: 'element', element: 'metal', amount: 100 }, attunementReq: { enchanter: 2 } }, - researchSandSpells: { name: "Sand Spell Research", desc: "Unlock Sand Blast, Sandstorm spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchEarthSpells: 1, researchWaterSpells: 1, enchanting: 3 }, cost: { type: 'element', element: 'sand', amount: 100 }, attunementReq: { enchanter: 2 } }, - researchLightningSpells: { name: "Lightning Spell Research", desc: "Unlock Spark, Lightning Bolt spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchFireSpells: 1, researchAirSpells: 1, enchanting: 3 }, cost: { type: 'element', element: 'lightning', amount: 100 }, attunementReq: { enchanter: 2 } }, - - // Tier 2 - Advanced Compound Spells - researchAdvancedMetal: { name: "Advanced Metal Research", desc: "Unlock Steel Tempest spell enchantment", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchMetalSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'metal', amount: 100 }, attunementReq: { enchanter: 3 } }, - researchAdvancedSand: { name: "Advanced Sand Research", desc: "Unlock Desert Wind spell enchantment", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchSandSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'sand', amount: 100 }, attunementReq: { enchanter: 3 } }, - researchAdvancedLightning: { name: "Advanced Lightning Research", desc: "Unlock Chain Lightning, Storm Call spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchLightningSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'lightning', amount: 100 }, attunementReq: { enchanter: 3 } }, - - // Tier 3 - Master Compound Spells - researchMasterMetal: { name: "Master Metal Research", desc: "Unlock Furnace Blast spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedMetal: 1, enchanting: 7 }, cost: { type: 'element', element: 'metal', amount: 200 }, attunementReq: { enchanter: 5 } }, - researchMasterSand: { name: "Master Sand Research", desc: "Unlock Dune Collapse spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedSand: 1, enchanting: 7 }, cost: { type: 'element', element: 'sand', amount: 200 }, attunementReq: { enchanter: 5 } }, - researchMasterLightning: { name: "Master Lightning Research", desc: "Unlock Thunder Strike spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedLightning: 1, enchanting: 7 }, cost: { type: 'element', element: 'lightning', amount: 200 }, attunementReq: { enchanter: 5 } }, - - // ═══════════════════════════════════════════════════════════════════════════ - // UTILITY MANA SPELL RESEARCH - Transference - // ═══════════════════════════════════════════════════════════════════════════ - - // Tier 1 - Basic Utility Spells - researchTransferenceSpells: { name: "Transference Spell Research", desc: "Unlock Transfer Strike, Mana Rip spell enchantments", cat: "effectResearch", max: 1, base: 350, studyTime: 5, req: { enchanting: 3 }, attunementReq: { enchanter: 1 } }, - - // Tier 2 - Advanced Utility Spells - researchAdvancedTransference: { name: "Advanced Transference Research", desc: "Unlock Essence Drain spell enchantment", cat: "effectResearch", max: 1, base: 650, studyTime: 12, req: { researchTransferenceSpells: 1, enchanting: 5 }, attunementReq: { enchanter: 3 } }, - - // Tier 3 - Master Utility Spells - researchMasterTransference: { name: "Master Transference Research", desc: "Unlock Soul Transfer spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedTransference: 1, enchanting: 7 }, attunementReq: { enchanter: 5 } }, - - // Research Skills (longer study times: 12-72 hours) - Core skills, any attunement level 3 - manaTap: { name: "Mana Tap", desc: "+1 mana/click", cat: "mana", max: 1, base: 300, studyTime: 12 }, - manaSurge: { name: "Mana Surge", desc: "+3 mana/click", cat: "mana", max: 1, base: 800, studyTime: 36, req: { manaTap: 1 } }, - manaSpring: { name: "Mana Spring", desc: "+2 mana regen", cat: "mana", max: 1, base: 600, studyTime: 24 }, - deepTrance: { name: "Deep Trance", desc: "Extend meditation bonus to 6hrs for 3x", cat: "mana", max: 1, base: 900, studyTime: 48, req: { meditation: 1 } }, - voidMeditation:{ name: "Void Meditation", desc: "Extend meditation bonus to 8hrs for 5x", cat: "mana", max: 1, base: 1500, studyTime: 72, req: { deepTrance: 1 } }, - - // ═══════════════════════════════════════════════════════════════════════════ - // INVOKER SKILLS - Require Invoker attunement - // ═══════════════════════════════════════════════════════════════════════════ - - // Invocation - Invoker attunement skill - invocation: { - name: "Invocation", - desc: "Enhances spell invocation and guardian pacts", - cat: "invocation", - attunement: 'invoker', - max: 10, - base: 300, - studyTime: 6, - attunementReq: { invoker: 1 } - }, - // Pact Mastery - Invoker attunement skill - pactMastery: { - name: "Pact Mastery", - desc: "Enhances pact signing and guardian bonuses", - cat: "pact", - attunement: 'invoker', - max: 10, - base: 350, - studyTime: 6, - attunementReq: { invoker: 1 } - }, - - // ═══════════════════════════════════════════════════════════════════════════ - // GOLEMANCY SKILLS - Require Fabricator attunement - // ═══════════════════════════════════════════════════════════════════════════ - - // Core Golemancy - golemMastery: { name: "Golem Mastery", desc: "+10% golem damage", cat: "golemancy", max: 1, base: 300, studyTime: 6, attunementReq: { fabricator: 2 } }, - golemEfficiency: { name: "Golem Efficiency", desc: "+5% golem attack speed", cat: "golemancy", max: 1, base: 350, studyTime: 6, attunementReq: { fabricator: 2 } }, - golemLongevity: { name: "Golem Longevity", desc: "+1 floor duration", cat: "golemancy", max: 1, base: 500, studyTime: 8, attunementReq: { fabricator: 3 } }, - golemSiphon: { name: "Golem Siphon", desc: "-10% golem maintenance", cat: "golemancy", max: 1, base: 400, studyTime: 8, attunementReq: { fabricator: 3 } }, - - // Advanced Golemancy - advancedGolemancy: { name: "Advanced Golemancy", desc: "Unlock hybrid golem recipes", cat: "golemancy", max: 1, base: 800, studyTime: 16, req: { golemMastery: 1 }, attunementReq: { fabricator: 5 } }, - golemResonance: { name: "Golem Resonance", desc: "+1 golem slot at Fabricator 10", cat: "golemancy", max: 1, base: 1200, studyTime: 24, req: { golemMastery: 1 }, attunementReq: { fabricator: 8 } }, - - // ═══════════════════════════════════════════════════════════════════════════ - // HYBRID SKILLS - Require TWO attunements at level 5+ - // ═══════════════════════════════════════════════════════════════════════════ - - // Pact-Weaving (Invoker + Enchanter) - pactWeaving: { - name: "Pact-Weaving", - desc: "Weave Guardian essence into weapon enchantments OR world-effects", - cat: "hybrid", - max: 10, - base: 750, - studyTime: 15, - attunementReq: { invoker: 5, enchanter: 5 } - }, - - // Guardian Constructs (Fabricator + Invoker) - guardianConstructs: { - name: "Guardian Constructs", - desc: "Build monumental, singular golems. Only 1 active at a time, vastly more durable, costs less maintenance.", - cat: "hybrid", - max: 10, - base: 800, - studyTime: 18, - attunementReq: { fabricator: 5, invoker: 5 } - }, - - // Enchanted Golemancy (Fabricator + Enchanter) - enchantedGolemancy: { - name: "Enchanted Golemancy", - desc: "Imbuing golems with elemental spell logic", - cat: "hybrid", - max: 10, - base: 850, - studyTime: 20, - attunementReq: { fabricator: 5, enchanter: 5 } - }, -}; - -// ─── Skill Categories ───────────────────────────────────────────────────────── -// Skills are now organized by attunement - each attunement grants access to specific skill categories -export const SKILL_CATEGORIES = [ - // Core categories (always available) - { id: 'mana', name: 'Mana', icon: '💧', attunement: null }, - { id: 'study', name: 'Study', icon: '📚', attunement: null }, - // Research category moved to Mana (Bug 12) - - // Enchanter attunement (Right Hand) - { id: 'enchant', name: 'Enchanting', icon: '✨', attunement: 'enchanter' }, - { id: 'effectResearch', name: 'Effect Research', icon: '🔬', attunement: 'enchanter' }, - - // Invoker attunement (Chest) - { id: 'invocation', name: 'Invocation', icon: '💜', attunement: 'invoker' }, - { id: 'pact', name: 'Pact Mastery', icon: '🤝', attunement: 'invoker' }, - - // Fabricator attunement (Left Hand) - { id: 'fabrication', name: 'Fabrication', icon: '⚒️', attunement: 'fabricator' }, - { id: 'golemancy', name: 'Golemancy', icon: '🗿', attunement: 'fabricator' }, - - // Legacy category (for backward compatibility) - { id: 'craft', name: 'Crafting', icon: '🔧', attunement: null }, -]; - -// ─── Effect Research Mapping ─────────────────────────────────────────────────── -// Maps research skill IDs to the effect IDs they unlock -export const EFFECT_RESEARCH_MAPPING: Record = { - // Tier 1 - Basic Spell Effects - researchManaSpells: ['spell_manaStrike'], - researchFireSpells: ['spell_emberShot', 'spell_fireball'], - researchWaterSpells: ['spell_waterJet', 'spell_iceShard'], - researchAirSpells: ['spell_gust', 'spell_windSlash'], - researchEarthSpells: ['spell_stoneBullet', 'spell_rockSpike'], - researchLightSpells: ['spell_lightLance', 'spell_radiance'], - researchDarkSpells: ['spell_shadowBolt', 'spell_darkPulse'], - researchLifeDeathSpells: ['spell_drain'], - - // Tier 2 - Advanced Spell Effects - researchAdvancedFire: ['spell_inferno', 'spell_flameWave'], - researchAdvancedWater: ['spell_tidalWave', 'spell_iceStorm'], - researchAdvancedAir: ['spell_hurricane', 'spell_windBlade'], - researchAdvancedEarth: ['spell_earthquake', 'spell_stoneBarrage'], - researchAdvancedLight: ['spell_solarFlare', 'spell_divineSmite'], - researchAdvancedDark: ['spell_voidRift', 'spell_shadowStorm'], - - // Tier 3 - Master Spell Effects - researchMasterFire: ['spell_pyroclasm'], - researchMasterWater: ['spell_tsunami'], - researchMasterEarth: ['spell_meteorStrike'], - - // Combat Effect Research - researchDamageEffects: ['damage_5', 'damage_10', 'damage_pct_10'], - researchCombatEffects: ['crit_5', 'attack_speed_10'], - - // Mana Effect Research - researchManaEffects: ['mana_cap_50', 'mana_regen_1', 'click_mana_1'], - researchAdvancedManaEffects: ['mana_cap_100', 'mana_regen_2', 'mana_regen_5', 'click_mana_3'], - - // Utility Effect Research - researchUtilityEffects: ['meditate_10', 'study_10', 'insight_5'], - - // Special Effect Research - researchSpecialEffects: ['spell_echo_10', 'guardian_dmg_10'], - researchOverpower: ['overpower_80'], - - // ═══════════════════════════════════════════════════════════════════════════ - // COMPOUND MANA SPELL RESEARCH - Metal, Sand, Lightning - // ═══════════════════════════════════════════════════════════════════════════ - - // Tier 1 - Basic Compound Spells - researchMetalSpells: ['spell_metalShard', 'spell_ironFist'], - researchSandSpells: ['spell_sandBlast', 'spell_sandstorm'], - researchLightningSpells: ['spell_spark', 'spell_lightningBolt'], - - // Tier 2 - Advanced Compound Spells - researchAdvancedMetal: ['spell_steelTempest'], - researchAdvancedSand: ['spell_desertWind'], - researchAdvancedLightning: ['spell_chainLightning', 'spell_stormCall'], - - // Tier 3 - Master Compound Spells - researchMasterMetal: ['spell_furnaceBlast'], - researchMasterSand: ['spell_duneCollapse'], - researchMasterLightning: ['spell_thunderStrike'], - - // ═══════════════════════════════════════════════════════════════════════════ - // UTILITY MANA SPELL RESEARCH - Transference - // ═══════════════════════════════════════════════════════════════════════════ - - // Tier 1 - Basic Utility Spells - researchTransferenceSpells: ['spell_transferStrike', 'spell_manaRip'], - - // Tier 2 - Advanced Utility Spells - researchAdvancedTransference: ['spell_essenceDrain'], - - // Tier 3 - Master Utility Spells - researchMasterTransference: ['spell_soulTransfer'], - - // ═══════════════════════════════════════════════════════════════════════════ - // PER-ELEMENT CAPACITY RESEARCH - Unlocks per-element capacity effects - // ═══════════════════════════════════════════════════════════════════════════ - - // Basic Element Capacity Effects (Tier 1 - +10 per stack) - researchFireCapacity: ['fire_cap_10'], - researchWaterCapacity: ['water_cap_10'], - researchAirCapacity: ['air_cap_10'], - researchEarthCapacity: ['earth_cap_10'], - researchLightCapacity: ['light_cap_10'], - researchDarkCapacity: ['dark_cap_10'], - researchDeathCapacity: ['death_cap_10'], - - // Advanced Element Capacity Effects (Tier 2 - +25 per stack) - researchAdvancedFireCap: ['fire_cap_25'], - researchAdvancedWaterCap: ['water_cap_25'], - researchAdvancedAirCap: ['air_cap_25'], - researchAdvancedEarthCap: ['earth_cap_25'], - researchAdvancedLightCap: ['light_cap_25'], - researchAdvancedDarkCap: ['dark_cap_25'], - researchAdvancedDeathCap: ['death_cap_25'], - - // Master Element Capacity Effects (Tier 3 - +50 per stack) - researchMasterFireCap: ['fire_cap_50'], - researchMasterWaterCap: ['water_cap_50'], - researchMasterAirCap: ['air_cap_50'], - researchMasterEarthCap: ['earth_cap_50'], - researchMasterLightCap: ['light_cap_50'], - researchMasterDarkCap: ['dark_cap_50'], - researchMasterDeathCap: ['death_cap_50'], - - // Composite Element Capacity Effects - researchMetalCapacity: ['metal_cap_10'], - researchAdvancedMetalCap: ['metal_cap_25', 'metal_cap_50'], - researchSandCapacity: ['sand_cap_10'], - researchAdvancedSandCap: ['sand_cap_25', 'sand_cap_50'], - researchLightningCapacity: ['lightning_cap_10'], - researchAdvancedLightningCap: ['lightning_cap_25', 'lightning_cap_50'], - - // Exotic Element Capacity Effects - researchCrystalCapacity: ['crystal_cap_10'], - researchAdvancedCrystalCap: ['crystal_cap_25', 'crystal_cap_50'], - researchStellarCapacity: ['stellar_cap_10'], - researchAdvancedStellarCap: ['stellar_cap_25', 'stellar_cap_50'], - researchVoidCapacity: ['void_cap_10'], - researchAdvancedVoidCap: ['void_cap_25', 'void_cap_50'], -}; - -// Base effects unlocked when player gets enchanting skill level 1 -export const BASE_UNLOCKED_EFFECTS: string[] = []; // No effects at game start - -// Effects that unlock when getting enchanting skill level 1 -export const ENCHANTING_UNLOCK_EFFECTS = ['spell_manaBolt']; diff --git a/src/lib/game/hooks/useSkillUpgradeSelection.ts b/src/lib/game/hooks/useSkillUpgradeSelection.ts deleted file mode 100644 index 41bd90d..0000000 --- a/src/lib/game/hooks/useSkillUpgradeSelection.ts +++ /dev/null @@ -1,83 +0,0 @@ -// ─── Skill Upgrade Selection Hook ──────────────────────── -// Hook for managing milestone upgrade selection state in SkillsTab - -import { useState, useMemo, useCallback, SetStateAction, Dispatch } from 'react'; -import type { SkillUpgradeChoice } from '@/lib/game/types'; -import { getUpgradesForSkillAtMilestone } from '@/lib/game/skill-evolution'; - -export interface UseSkillUpgradeSelectionResult { - pendingSelections: string[]; - setPendingSelections: Dispatch>; - toggleUpgrade: (upgradeId: string, available: SkillUpgradeChoice[], alreadySelected: string[]) => void; - handleConfirm: (upgradeDialogSkill: string | null, upgradeDialogMilestone: 5 | 10, commitSkillUpgrades: (skillId: string, selections: string[], milestone: 5 | 10) => void, onClose: () => void) => void; - handleCancel: (onClose: () => void) => void; -} - -/** - * Check if skill has milestone available (also exported for use in SkillRow/CategorySkillsList) - */ -export function hasMilestoneUpgrade( - skillId: string, - level: number, - skillTiers: Record, - skillUpgrades: Record -): { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null { - // Check level 5 milestone - if (level >= 5) { - const upgrades5 = getUpgradesForSkillAtMilestone(skillId, 5, skillTiers); - const selected5 = (skillUpgrades[skillId] || []).filter((id) => id.includes('_l5')); - if (upgrades5.length > 0 && selected5.length < 2) { - return { milestone: 5, hasUpgrades: true, selectedCount: selected5.length }; - } - } - - // Check level 10 milestone - if (level >= 10) { - const upgrades10 = getUpgradesForSkillAtMilestone(skillId, 10, skillTiers); - const selected10 = (skillUpgrades[skillId] || []).filter((id) => id.includes('_l10')); - if (upgrades10.length > 0 && selected10.length < 2) { - return { milestone: 10, hasUpgrades: true, selectedCount: selected10.length }; - } - } - - return null; -} - -/** - * Hook for managing skill upgrade selection state in the SkillsTab milestone upgrade dialog. - * Manages pending selections across the dialog open/close cycle. - */ -export function useSkillUpgradeSelection(): UseSkillUpgradeSelectionResult { - const [pendingSelections, setPendingSelections] = useState([]); - - const toggleUpgrade = useCallback((upgradeId: string, available: SkillUpgradeChoice[], alreadySelected: string[]) => { - const currentSelections = pendingSelections.length > 0 ? pendingSelections : alreadySelected; - if (currentSelections.includes(upgradeId)) { - setPendingSelections(currentSelections.filter(id => id !== upgradeId)); - } else if (currentSelections.length < 2) { - setPendingSelections([...currentSelections, upgradeId]); - } - }, [pendingSelections]); - - const handleConfirm = useCallback((upgradeDialogSkill: string | null, upgradeDialogMilestone: 5 | 10, commitSkillUpgrades: (skillId: string, selections: string[], milestone: 5 | 10) => void, onClose: () => void) => { - const currentSelections = pendingSelections.length > 0 ? pendingSelections : []; - if (currentSelections.length === 2 && upgradeDialogSkill) { - commitSkillUpgrades(upgradeDialogSkill, currentSelections, upgradeDialogMilestone); - } - setPendingSelections([]); - onClose(); - }, [pendingSelections]); - - const handleCancel = useCallback((onClose: () => void) => { - setPendingSelections([]); - onClose(); - }, []); - - return useMemo(() => ({ - pendingSelections, - setPendingSelections, - toggleUpgrade, - handleConfirm, - handleCancel, - }), [pendingSelections, toggleUpgrade, handleConfirm, handleCancel]); -} diff --git a/src/lib/game/skills-split-tests/ascension-specialized-skills.test.ts b/src/lib/game/skills-split-tests/ascension-specialized-skills.test.ts deleted file mode 100644 index 0c31edc..0000000 --- a/src/lib/game/skills-split-tests/ascension-specialized-skills.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Ascension and Specialized Skills Tests - skills.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '@/lib/game/constants'; -import { calcInsight } from '@/lib/game/store'; -import type { GameState } from './types'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - const baseElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning']; - baseElements.forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: baseElements.slice(0, 4).includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - lootInventory: { materials: {}, blueprints: [] }, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - ...overrides, - } as GameState; -} - -describe('Ascension Skills', () => { - describe('Insight Harvest (+10% insight gain)', () => { - it('should multiply insight gain by 10% per level', () => { - const state0 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 0 } }); - const state1 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 1 } }); - const state5 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 5 } }); - - const insight0 = calcInsight(state0); - const insight1 = calcInsight(state1); - const insight5 = calcInsight(state5); - - expect(insight1).toBeGreaterThan(insight0); - expect(insight5).toBeGreaterThan(insight1); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.insightHarvest.desc).toBe("+10% insight gain"); - expect(SKILLS_DEF.insightHarvest.max).toBe(5); - }); - }); - - describe('Guardian Bane (+20% dmg vs guardians)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.guardianBane.desc).toBe("+20% dmg vs guardians"); - expect(SKILLS_DEF.guardianBane.max).toBe(3); - }); - }); -}); - -describe('Enchanter Skills', () => { - describe('Enchanting (Unlock enchantment design)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.enchanting).toBeDefined(); - expect(SKILLS_DEF.enchanting.attunement).toBe('enchanter'); - }); - }); - - describe('Efficient Enchant (-5% enchantment capacity cost)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.efficientEnchant).toBeDefined(); - expect(SKILLS_DEF.efficientEnchant.max).toBe(5); - }); - }); - - describe('Disenchanting (Recover mana from removed enchantments)', () => { - it('skill definition should exist', () => { - // disenchanting skill removed - see Bug 13 - expect(SKILLS_DEF.disenchanting).toBeUndefined(); - }); - }); -}); - -describe('Golemancy Skills', () => { - describe('Golem Mastery (+10% golem damage)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemMastery).toBeDefined(); - expect(SKILLS_DEF.golemMastery.attunementReq).toBeDefined(); - }); - }); - - describe('Golem Efficiency (+5% attack speed)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemEfficiency).toBeDefined(); - }); - }); - - describe('Golem Longevity (+1 floor duration)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemLongevity).toBeDefined(); - }); - }); - - describe('Golem Siphon (-10% maintenance)', () => { - it('skill definition should exist', () => { - expect(SKILLS_DEF.golemSiphon).toBeDefined(); - }); - }); -}); - -console.log('✅ Ascension and specialized skills tests defined (from skills.test.ts).'); diff --git a/src/lib/game/skills-split-tests/mana-skills.test.ts b/src/lib/game/skills-split-tests/mana-skills.test.ts deleted file mode 100644 index 432889f..0000000 --- a/src/lib/game/skills-split-tests/mana-skills.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Mana Skills Tests - skills.test.ts - * - * Tests for mana-related skills from the old skills.test.ts file - */ - -import { describe, it, expect } from 'vitest'; -import { - computeMaxMana, - computeElementMax, - computeRegen, - computeClickMana, -} from '@/lib/game/store'; -import { SKILLS_DEF } from '@/lib/game/constants'; -import type { GameState } from './types'; - -// ─── Test Helpers ─────────────────────────────────────────────────────────── - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - const baseElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning']; - baseElements.forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: baseElements.slice(0, 4).includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - lootInventory: { materials: {}, blueprints: [] }, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - ...overrides, - } as GameState; -} - -// ─── Mana Skills Tests ───────────────────────────────────────────────────────── - -describe('Mana Skills', () => { - describe('Mana Well (+100 max mana)', () => { - it('should add 100 max mana per level', () => { - const state0 = createMockState({ skills: { manaWell: 0 } }); - const state1 = createMockState({ skills: { manaWell: 1 } }); - const state5 = createMockState({ skills: { manaWell: 5 } }); - const state10 = createMockState({ skills: { manaWell: 10 } }); - - expect(computeMaxMana(state0)).toBe(100); - expect(computeMaxMana(state1)).toBe(100 + 100); - expect(computeMaxMana(state5)).toBe(100 + 500); - expect(computeMaxMana(state10)).toBe(100 + 1000); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaWell.desc).toBe("+100 max mana"); - expect(SKILLS_DEF.manaWell.max).toBe(10); - }); - }); - - describe('Mana Flow (+1 regen/hr)', () => { - it('should add 1 regen per hour per level', () => { - const state0 = createMockState({ skills: { manaFlow: 0 } }); - const state1 = createMockState({ skills: { manaFlow: 1 } }); - const state5 = createMockState({ skills: { manaFlow: 5 } }); - const state10 = createMockState({ skills: { manaFlow: 10 } }); - - // With enchanter attunement giving +0.5 regen, base is 2.5 - const baseRegen = computeRegen(state0); - expect(baseRegen).toBeGreaterThan(2); // Has attunement bonus - expect(computeRegen(state1)).toBe(baseRegen + 1); - expect(computeRegen(state5)).toBe(baseRegen + 5); - expect(computeRegen(state10)).toBe(baseRegen + 10); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaFlow.desc).toBe("+1 regen/hr"); - expect(SKILLS_DEF.manaFlow.max).toBe(10); - }); - }); - - describe('Mana Spring (+2 mana regen)', () => { - it('should add 2 mana regen', () => { - const state0 = createMockState({ skills: { manaSpring: 0 } }); - const state1 = createMockState({ skills: { manaSpring: 1 } }); - - const baseRegen = computeRegen(state0); - expect(baseRegen).toBeGreaterThan(2); // Has attunement bonus - expect(computeRegen(state1)).toBe(baseRegen + 2); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaSpring.desc).toBe("+2 mana regen"); - expect(SKILLS_DEF.manaSpring.max).toBe(1); - }); - }); - - describe('Elemental Attunement (+50 elem mana cap)', () => { - it('should add 50 element mana capacity per level', () => { - const state0 = createMockState({ skills: { elemAttune: 0 } }); - const state1 = createMockState({ skills: { elemAttune: 1 } }); - const state5 = createMockState({ skills: { elemAttune: 5 } }); - const state10 = createMockState({ skills: { elemAttune: 10 } }); - - expect(computeElementMax(state0)).toBe(10); - expect(computeElementMax(state1)).toBe(10 + 50); - expect(computeElementMax(state5)).toBe(10 + 250); - expect(computeElementMax(state10)).toBe(10 + 500); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.elemAttune.desc).toBe("+50 elem mana cap"); - expect(SKILLS_DEF.elemAttune.max).toBe(10); - }); - }); - - describe('Mana Overflow (+25% mana from clicks)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaOverflow.desc).toBe("+25% mana from clicks"); - expect(SKILLS_DEF.manaOverflow.max).toBe(5); - }); - - it('should require Mana Well 3', () => { - expect(SKILLS_DEF.manaOverflow.req).toEqual({ manaWell: 3 }); - }); - }); - - describe('Mana Tap (+1 mana/click)', () => { - it('should add 1 mana per click', () => { - const state0 = createMockState({ skills: { manaTap: 0 } }); - const state1 = createMockState({ skills: { manaTap: 1 } }); - - expect(computeClickMana(state0)).toBe(1); - expect(computeClickMana(state1)).toBe(2); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.manaTap.desc).toBe("+1 mana/click"); - expect(SKILLS_DEF.manaTap.max).toBe(1); - }); - }); - - describe('Mana Surge (+3 mana/click)', () => { - it('should add 3 mana per click', () => { - const state0 = createMockState({ skills: { manaSurge: 0 } }); - const state1 = createMockState({ skills: { manaSurge: 1 } }); - - expect(computeClickMana(state0)).toBe(1); - expect(computeClickMana(state1)).toBe(4); - }); - - it('should stack with Mana Tap', () => { - const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1 + 3); - }); - - it('should require Mana Tap 1', () => { - expect(SKILLS_DEF.manaSurge.req).toEqual({ manaTap: 1 }); - }); - }); -}); - -console.log('✅ Mana skills tests defined (from skills.test.ts).'); diff --git a/src/lib/game/skills-split-tests/prerequisites-studytimes-prestige-integration.test.ts b/src/lib/game/skills-split-tests/prerequisites-studytimes-prestige-integration.test.ts deleted file mode 100644 index 1f810ed..0000000 --- a/src/lib/game/skills-split-tests/prerequisites-studytimes-prestige-integration.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Skill Prerequisites, Study Times, Prestige Upgrades, and Integration Tests - skills.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF, PRESTIGE_DEF } from '@/lib/game/constants'; -import { computeMaxMana, computeElementMax } from '@/lib/game/store'; -import type { GameState } from './types'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - const baseElements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning']; - baseElements.forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: baseElements.slice(0, 4).includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - lootInventory: { materials: {}, blueprints: [] }, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - ...overrides, - } as GameState; -} - -// ─── Skill Prerequisites Tests ────────────────────────────────────────────────── - -describe('Skill Prerequisites', () => { - it('Mana Overflow should require Mana Well 3', () => { - expect(SKILLS_DEF.manaOverflow.req).toEqual({ manaWell: 3 }); - }); - - it('Mana Surge should require Mana Tap 1', () => { - expect(SKILLS_DEF.manaSurge.req).toEqual({ manaTap: 1 }); - }); - - it('Deep Trance should require Meditation 1', () => { - expect(SKILLS_DEF.deepTrance.req).toEqual({ meditation: 1 }); - }); - - it('Void Meditation should require Deep Trance 1', () => { - expect(SKILLS_DEF.voidMeditation.req).toEqual({ deepTrance: 1 }); - }); - - it('Efficient Enchant should require Enchanting 3', () => { - expect(SKILLS_DEF.efficientEnchant.req).toEqual({ enchanting: 3 }); - }); -}); - -// ─── Study Time Tests ─────────────────────────────────────────────────────────── - -describe('Study Times', () => { - it('all skills should have reasonable study times', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - expect(skill.studyTime).toBeGreaterThan(0); - expect(skill.studyTime).toBeLessThanOrEqual(72); - }); - }); - - it('ascension skills should have long study times', () => { - const ascensionSkills = Object.entries(SKILLS_DEF).filter(([, s]) => s.cat === 'ascension'); - ascensionSkills.forEach(([, skill]) => { - expect(skill.studyTime).toBeGreaterThanOrEqual(20); - }); - }); -}); - -// ─── Prestige Upgrade Tests ───────────────────────────────────────────────────── - -describe('Prestige Upgrades', () => { - it('all prestige upgrades should have valid costs', () => { - Object.entries(PRESTIGE_DEF).forEach(([id, upgrade]) => { - expect(upgrade.cost).toBeGreaterThan(0); - expect(upgrade.max).toBeGreaterThan(0); - }); - }); - - it('Mana Well prestige should add 500 starting max mana', () => { - const state0 = createMockState({ prestigeUpgrades: { manaWell: 0 } }); - const state1 = createMockState({ prestigeUpgrades: { manaWell: 1 } }); - const state5 = createMockState({ prestigeUpgrades: { manaWell: 5 } }); - - expect(computeMaxMana(state0)).toBe(100); - expect(computeMaxMana(state1)).toBe(100 + 500); - expect(computeMaxMana(state5)).toBe(100 + 2500); - }); - - it('Elemental Attunement prestige should add 25 element cap', () => { - const state0 = createMockState({ prestigeUpgrades: { elementalAttune: 0 } }); - const state1 = createMockState({ prestigeUpgrades: { elementalAttune: 1 } }); - const state10 = createMockState({ prestigeUpgrades: { elementalAttune: 10 } }); - - expect(computeElementMax(state0)).toBe(10); - expect(computeElementMax(state1)).toBe(10 + 25); - expect(computeElementMax(state10)).toBe(10 + 250); - }); -}); - -// ─── Integration Tests ────────────────────────────────────────────────────────── - -describe('Integration Tests', () => { - it('skill costs should scale with level', () => { - const skill = SKILLS_DEF.manaWell; - for (let level = 0; level < skill.max; level++) { - const cost = skill.base * (level + 1); - expect(cost).toBeGreaterThan(0); - } - }); - - it('all skills should have valid categories', () => { - const validCategories = ['mana', 'study', 'ascension', 'enchant', 'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'research', 'craft', 'hybrid']; - Object.values(SKILLS_DEF).forEach(skill => { - expect(validCategories).toContain(skill.cat); - }); - }); - - it('all prerequisite skills should exist', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.keys(skill.req).forEach(reqId => { - expect(SKILLS_DEF[reqId]).toBeDefined(); - }); - } - }); - }); - - it('all prerequisite levels should be within skill max', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.entries(skill.req).forEach(([reqId, reqLevel]) => { - expect(reqLevel).toBeLessThanOrEqual(SKILLS_DEF[reqId].max); - }); - } - }); - }); - - it('all attunement-requiring skills should have valid attunement', () => { - const validAttunements = ['enchanter', 'invoker', 'fabricator']; - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.attunement) { - expect(validAttunements).toContain(skill.attunement); - } - }); - }); -}); - -console.log('✅ Skill prerequisites, study times, prestige, and integration tests defined (from skills.test.ts).'); diff --git a/src/lib/game/skills-split-tests/study-skills.test.ts b/src/lib/game/skills-split-tests/study-skills.test.ts deleted file mode 100644 index e3e8e90..0000000 --- a/src/lib/game/skills-split-tests/study-skills.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Study Skills Tests - skills.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { - getStudySpeedMultiplier, - getStudyCostMultiplier, - getMeditationBonus, -} from '@/lib/game/store'; -import { SKILLS_DEF } from '@/lib/game/constants'; - -describe('Study Skills', () => { - describe('Quick Learner (+10% study speed)', () => { - it('should multiply study speed by 10% per level', () => { - expect(getStudySpeedMultiplier({})).toBe(1); - expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1); - expect(getStudySpeedMultiplier({ quickLearner: 3 })).toBe(1.3); - expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.quickLearner.desc).toBe("+10% study speed"); - expect(SKILLS_DEF.quickLearner.max).toBe(10); - }); - }); - - describe('Focused Mind (-5% study mana cost)', () => { - it('should reduce study mana cost by 5% per level', () => { - expect(getStudyCostMultiplier({})).toBe(1); - expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95); - expect(getStudyCostMultiplier({ focusedMind: 3 })).toBe(0.85); - expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75); - }); - - it('skill definition should match description', () => { - expect(SKILLS_DEF.focusedMind.desc).toBe("-5% study mana cost"); - expect(SKILLS_DEF.focusedMind.max).toBe(10); - }); - }); - - describe('Meditation Focus (Up to 2.5x regen after 4hrs)', () => { - it('should provide meditation bonus caps', () => { - expect(SKILLS_DEF.meditation.desc).toContain("2.5x"); - expect(SKILLS_DEF.meditation.max).toBe(1); - }); - }); - - describe('Knowledge Retention (+20% study progress saved)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.knowledgeRetention.desc).toBe("+20% study progress saved on cancel"); - expect(SKILLS_DEF.knowledgeRetention.max).toBe(3); - }); - }); - - describe('Deep Trance (Extend to 6hrs for 3x)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.deepTrance.desc).toContain("6hrs"); - expect(SKILLS_DEF.deepTrance.max).toBe(1); - }); - - it('should require Meditation 1', () => { - expect(SKILLS_DEF.deepTrance.req).toEqual({ meditation: 1 }); - }); - }); - - describe('Void Meditation (Extend to 8hrs for 5x)', () => { - it('skill definition should match description', () => { - expect(SKILLS_DEF.voidMeditation.desc).toContain("8hrs"); - expect(SKILLS_DEF.voidMeditation.max).toBe(1); - }); - - it('should require Deep Trance 1', () => { - expect(SKILLS_DEF.voidMeditation.req).toEqual({ deepTrance: 1 }); - }); - }); -}); - -// ─── Meditation Bonus Tests ───────────────────────────────────────────────────── - -describe('Meditation Bonus', () => { - it('should start at 1x with no meditation', () => { - expect(getMeditationBonus(0, {})).toBe(1); - }); - - it('should ramp up over time without skills', () => { - const bonus1hr = getMeditationBonus(25, {}); // 1 hour of ticks - expect(bonus1hr).toBeGreaterThan(1); - - const bonus4hr = getMeditationBonus(100, {}); // 4 hours - expect(bonus4hr).toBeGreaterThan(bonus1hr); - }); - - it('should cap at 1.5x without meditation skill', () => { - const bonus = getMeditationBonus(200, {}); // 8 hours - expect(bonus).toBe(1.5); - }); - - it('should give 2.5x with meditation skill after 4 hours', () => { - const bonus = getMeditationBonus(100, { meditation: 1 }); - expect(bonus).toBe(2.5); - }); - - it('should give 3.0x with deepTrance skill after 6 hours', () => { - const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 }); - expect(bonus).toBe(3.0); - }); - - it('should give 5.0x with voidMeditation skill after 8 hours', () => { - const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 }); - expect(bonus).toBe(5.0); - }); -}); - -console.log('✅ Study skills tests defined (from skills.test.ts).'); diff --git a/src/lib/game/skills.test.ts b/src/lib/game/skills.test.ts deleted file mode 100755 index afc3864..0000000 --- a/src/lib/game/skills.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Skills Tests (skills.test.ts) - Main Index - * - * This file re-exports all individual test files from the original skills.test.ts - * Each test file is focused on a specific area of functionality. - * - * Original file: skills.test.ts (543 lines) - * Refactored into 4 smaller test files. - */ - -import './skills-split-tests/mana-skills.test'; -import './skills-split-tests/study-skills.test'; -import './skills-split-tests/ascension-specialized-skills.test'; -import './skills-split-tests/prerequisites-studytimes-prestige-integration.test'; - -console.log('✅ All skills tests from skills.test.ts complete (refactored from 543 lines to 4 focused test files).'); diff --git a/src/lib/game/store-tests/damage-calculation.test.ts b/src/lib/game/store-tests/damage-calculation.test.ts deleted file mode 100644 index fcc27eb..0000000 --- a/src/lib/game/store-tests/damage-calculation.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Tests for Damage Calculation - */ - -import { describe, it, expect } from 'vitest'; -import { calcDamage } from '../store'; -import { createMockState } from './test-utils'; -import { GUARDIANS } from '../constants'; - -describe('Damage Calculation', () => { - describe('calcDamage', () => { - it('should return spell base damage with no bonuses', () => { - const state = createMockState(); - const dmg = calcDamage(state, 'manaBolt'); - expect(dmg).toBeGreaterThanOrEqual(5); // Base damage - expect(dmg).toBeLessThanOrEqual(5 * 1.5); // No crit bonus, just base - }); - - it('should multiply by signed pacts', () => { - const state = createMockState({ signedPacts: [10] }); - // Pact multiplier is 1.5 for floor 10 - const dmg = calcDamage(state, 'manaBolt'); - const minDmg = 5 * 1.5; - expect(dmg).toBeGreaterThanOrEqual(minDmg); - }); - - it('should stack multiple pacts', () => { - const state = createMockState({ signedPacts: [10, 20] }); - const pactMult = GUARDIANS[10].pact * GUARDIANS[20].pact; - const dmg = calcDamage(state, 'manaBolt'); - const minDmg = 5 * pactMult; - expect(dmg).toBeGreaterThanOrEqual(minDmg); - }); - - describe('Elemental bonuses', () => { - it('should give +25% for same element', () => { - const state = createMockState({ spells: { fireball: { learned: true, level: 1 } } }); - // Floor 1 is fire element - const dmg = calcDamage(state, 'fireball', 'fire'); - // Without crit: 15 * 1.25 - expect(dmg).toBeGreaterThanOrEqual(15 * 1.25); - }); - - it('should give +50% for opposing element (super effective)', () => { - const state = createMockState({ spells: { waterJet: { learned: true, level: 1 } } }); - // Water vs fire - water is the opposite of fire, so water is super effective - const dmg = calcDamage(state, 'waterJet', 'fire'); - // Base 12 * 1.5 = 18 (without crit) - expect(dmg).toBeGreaterThanOrEqual(18 * 0.5); // Can crit, so min is lower - }); - - it('should give +50% when attacking opposite element (fire vs water)', () => { - const state = createMockState({ spells: { fireball: { learned: true, level: 1 } } }); - // Fire vs water - fire is the opposite of water, so fire is super effective - const dmg = calcDamage(state, 'fireball', 'water'); - // Base 15 * 1.5 = 22.5 (without crit) - expect(dmg).toBeGreaterThanOrEqual(22.5 * 0.5); // Can crit - }); - - it('should be neutral for non-opposing elements', () => { - const state = createMockState({ spells: { fireball: { learned: true, level: 1 } } }); - // Fire vs air (neutral - neither same nor opposite) - const dmg = calcDamage(state, 'fireball', 'air'); - expect(dmg).toBeGreaterThanOrEqual(15 * 0.5); // No bonus, but could crit - expect(dmg).toBeLessThanOrEqual(15 * 1.5); - }); - }); - }); -}); diff --git a/src/lib/game/store-tests/element-recipes.test.ts b/src/lib/game/store-tests/element-recipes.test.ts deleted file mode 100644 index f28a897..0000000 --- a/src/lib/game/store-tests/element-recipes.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Tests for Element Crafting Recipes - */ - -import { describe, it, expect } from 'vitest'; -import { ELEMENTS } from '../constants'; - -describe('Element Crafting Recipes', () => { - it('should have valid ingredient references', () => { - Object.entries(ELEMENTS).forEach(([id, def]) => { - if (def.recipe) { - def.recipe.forEach(ingredient => { - expect(ELEMENTS[ingredient]).toBeDefined(); - }); - } - }); - }); - - it('should not have circular recipes', () => { - const visited = new Set(); - const checkCircular = (id: string, path: string[]): boolean => { - if (path.includes(id)) return true; - const def = ELEMENTS[id]; - if (!def.recipe) return false; - return def.recipe.some(ing => checkCircular(ing, [...path, id])); - }; - - Object.keys(ELEMENTS).forEach(id => { - expect(checkCircular(id, [])).toBe(false); - }); - }); -}); diff --git a/src/lib/game/store-tests/floor.test.ts b/src/lib/game/store-tests/floor.test.ts deleted file mode 100644 index 6166728..0000000 --- a/src/lib/game/store-tests/floor.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Tests for Floor Functions - */ - -import { describe, it, expect } from 'vitest'; -import { getFloorMaxHP, getFloorElement } from '../store'; -import { GUARDIANS, FLOOR_ELEM_CYCLE } from '../constants'; - -describe('Floor Functions', () => { - describe('getFloorMaxHP', () => { - it('should return guardian HP for guardian floors', () => { - expect(getFloorMaxHP(10)).toBe(GUARDIANS[10].hp); - expect(getFloorMaxHP(100)).toBe(GUARDIANS[100].hp); - }); - - it('should scale HP for non-guardian floors', () => { - expect(getFloorMaxHP(1)).toBeGreaterThan(0); - expect(getFloorMaxHP(5)).toBeGreaterThan(getFloorMaxHP(1)); - expect(getFloorMaxHP(50)).toBeGreaterThan(getFloorMaxHP(25)); - }); - - it('should have increasing scaling', () => { - const hp1 = getFloorMaxHP(1); - const hp5 = getFloorMaxHP(5); - const hp10 = getFloorMaxHP(10); // Guardian floor - const hp50 = getFloorMaxHP(50); // Guardian floor - - // HP should increase - expect(hp5).toBeGreaterThan(hp1); - expect(hp10).toBeGreaterThan(hp5); - expect(hp50).toBeGreaterThan(hp10); - - // Guardian floors have much more HP - expect(hp10).toBeGreaterThan(1000); - expect(hp50).toBeGreaterThan(10000); - }); - }); - - describe('getFloorElement', () => { - it('should cycle through elements in order (7 elements)', () => { - // FLOOR_ELEM_CYCLE = ["fire", "water", "air", "earth", "light", "dark", "death"] - expect(getFloorElement(1)).toBe('fire'); - expect(getFloorElement(2)).toBe('water'); - expect(getFloorElement(3)).toBe('air'); - expect(getFloorElement(4)).toBe('earth'); - expect(getFloorElement(5)).toBe('light'); - expect(getFloorElement(6)).toBe('dark'); - expect(getFloorElement(7)).toBe('death'); - }); - - it('should wrap around after 7 floors', () => { - expect(getFloorElement(8)).toBe('fire'); - expect(getFloorElement(9)).toBe('water'); - expect(getFloorElement(15)).toBe('fire'); // (15-1) % 7 = 0 -> fire - expect(getFloorElement(16)).toBe('water'); // (16-1) % 7 = 1 -> water - }); - }); -}); diff --git a/src/lib/game/store-tests/formatting.test.ts b/src/lib/game/store-tests/formatting.test.ts deleted file mode 100644 index d775c7e..0000000 --- a/src/lib/game/store-tests/formatting.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Tests for Formatting Functions - */ - -import { describe, it, expect } from 'vitest'; -import { fmt, fmtDec } from '../store'; - -describe('Formatting Functions', () => { - describe('fmt (format number)', () => { - it('should format numbers less than 1000 as integers', () => { - expect(fmt(0)).toBe('0'); - expect(fmt(1)).toBe('1'); - expect(fmt(999)).toBe('999'); - }); - - it('should format thousands with K suffix', () => { - expect(fmt(1000)).toBe('1.0K'); - expect(fmt(1500)).toBe('1.5K'); - expect(fmt(999999)).toBe('1000.0K'); - }); - - it('should format millions with M suffix', () => { - expect(fmt(1000000)).toBe('1.00M'); - expect(fmt(1500000)).toBe('1.50M'); - }); - - it('should format billions with B suffix', () => { - expect(fmt(1000000000)).toBe('1.00B'); - }); - - it('should handle non-finite numbers', () => { - expect(fmt(Infinity)).toBe('0'); - expect(fmt(NaN)).toBe('0'); - expect(fmt(-Infinity)).toBe('0'); - }); - }); - - describe('fmtDec (format decimal)', () => { - it('should format numbers with specified decimal places', () => { - expect(fmtDec(1.234, 2)).toBe('1.23'); - expect(fmtDec(1.5, 0)).toBe('2'); // toFixed rounds - expect(fmtDec(1.567, 1)).toBe('1.6'); - }); - - it('should handle non-finite numbers', () => { - expect(fmtDec(Infinity, 2)).toBe('0'); - expect(fmtDec(NaN, 2)).toBe('0'); - }); - }); -}); diff --git a/src/lib/game/store-tests/game-constants.test.ts b/src/lib/game/store-tests/game-constants.test.ts deleted file mode 100644 index 8e6bee4..0000000 --- a/src/lib/game/store-tests/game-constants.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Tests for Game Constants - */ - -import { describe, it, expect } from 'vitest'; -import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF, PRESTIGE_DEF } from '../constants'; - -describe('Game Constants', () => { - describe('ELEMENTS', () => { - it('should have all base elements', () => { - // Life, blood, wood were removed - we have 7 base elements now - expect(ELEMENTS.fire).toBeDefined(); - expect(ELEMENTS.water).toBeDefined(); - expect(ELEMENTS.air).toBeDefined(); - expect(ELEMENTS.earth).toBeDefined(); - expect(ELEMENTS.light).toBeDefined(); - expect(ELEMENTS.dark).toBeDefined(); - expect(ELEMENTS.death).toBeDefined(); - }); - - it('should have composite elements with recipes', () => { - // blood and wood were removed - expect(ELEMENTS.metal.recipe).toEqual(['fire', 'earth']); - expect(ELEMENTS.sand.recipe).toEqual(['earth', 'water']); - expect(ELEMENTS.lightning.recipe).toEqual(['fire', 'air']); - }); - - it('should have exotic elements with 3-ingredient recipes', () => { - expect(ELEMENTS.crystal.recipe).toHaveLength(3); - expect(ELEMENTS.stellar.recipe).toHaveLength(3); - expect(ELEMENTS.void.recipe).toHaveLength(3); - }); - - it('should have utility element transference', () => { - expect(ELEMENTS.transference).toBeDefined(); - expect(ELEMENTS.transference.cat).toBe('utility'); - }); - }); - - describe('GUARDIANS', () => { - it('should have guardians on expected floors', () => { - // Note: Floor 70 was removed - [10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => { - expect(GUARDIANS[floor]).toBeDefined(); - }); - }); - - it('should have increasing HP', () => { - let prevHP = 0; - [10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => { - expect(GUARDIANS[floor].hp).toBeGreaterThan(prevHP); - prevHP = GUARDIANS[floor].hp; - }); - }); - - it('should have increasing pact multipliers', () => { - let prevPact = 1; - [10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => { - expect(GUARDIANS[floor].pact).toBeGreaterThan(prevPact); - prevPact = GUARDIANS[floor].pact; - }); - }); - }); - - describe('SPELLS_DEF', () => { - it('should have manaBolt as a basic spell', () => { - expect(SPELLS_DEF.manaBolt).toBeDefined(); - expect(SPELLS_DEF.manaBolt.tier).toBe(0); - expect(SPELLS_DEF.manaBolt.cost.type).toBe('raw'); - }); - - it('should have spells for existing base elements', () => { - // Life was removed, death is present - const elements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death']; - elements.forEach(elem => { - const hasSpell = Object.values(SPELLS_DEF).some(s => s.elem === elem); - expect(hasSpell).toBe(true); - }); - }); - - it('should have increasing damage for higher tiers', () => { - const tier0Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 0).reduce((a, s) => a + s.dmg, 0); - const tier1Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 1).reduce((a, s) => a + s.dmg, 0); - const tier2Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 2).reduce((a, s) => a + s.dmg, 0); - - expect(tier1Avg).toBeGreaterThan(tier0Avg); - expect(tier2Avg).toBeGreaterThan(tier1Avg); - }); - }); - - describe('SKILLS_DEF', () => { - it('should have skills with valid categories', () => { - const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant', 'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'craft', 'hybrid']; - Object.values(SKILLS_DEF).forEach(skill => { - expect(validCategories).toContain(skill.cat); - }); - }); - - it('should have reasonable study times', () => { - Object.values(SKILLS_DEF).forEach(skill => { - expect(skill.studyTime).toBeGreaterThan(0); - expect(skill.studyTime).toBeLessThanOrEqual(72); - }); - }); - }); - - describe('PRESTIGE_DEF', () => { - it('should have prestige upgrades with valid costs', () => { - Object.values(PRESTIGE_DEF).forEach(def => { - expect(def.cost).toBeGreaterThan(0); - expect(def.max).toBeGreaterThan(0); - }); - }); - }); -}); diff --git a/src/lib/game/store-tests/individual-skills.test.ts b/src/lib/game/store-tests/individual-skills.test.ts deleted file mode 100644 index 1adc578..0000000 --- a/src/lib/game/store-tests/individual-skills.test.ts +++ /dev/null @@ -1,272 +0,0 @@ -/** - * Tests for Individual Skills (Current System) - */ - -import { describe, it, expect } from 'vitest'; -import { - computeMaxMana, - computeElementMax, - computeRegen, - computeClickMana, - calcDamage, - calcInsight, - getMeditationBonus -} from '../store'; -import { getStudySpeedMultiplier, getStudyCostMultiplier } from '../constants'; -import { createMockState } from './test-utils'; -import { SKILLS_DEF, SPELLS_DEF } from '../constants'; -import { SKILL_EVOLUTION_PATHS } from '../skill-evolution'; - -describe('Individual Skill Tests', () => { - - // ─── Mana Skills ──────────────────────────────────────────────────────────── - - describe('manaWell', () => { - it('should add +100 max mana per level', () => { - const state1 = createMockState({ skills: { manaWell: 1 } }); - expect(computeMaxMana(state1)).toBe(100 + 100); - - const state5 = createMockState({ skills: { manaWell: 5 } }); - expect(computeMaxMana(state5)).toBe(100 + 500); - }); - - it('should stack with prestige manaWell', () => { - const state = createMockState({ - skills: { manaWell: 3 }, - prestigeUpgrades: { manaWell: 2 } - }); - expect(computeMaxMana(state)).toBe(100 + 300 + 1000); - }); - - it('should have evolution path to Deep Reservoir at tier 2', () => { - const tier2 = SKILL_EVOLUTION_PATHS.manaWell.tiers.find((t: any) => t.tier === 2); - expect(tier2).toBeDefined(); - expect(tier2?.name).toBe('Deep Reservoir'); - }); - }); - - describe('manaFlow', () => { - it('should add +1 regen/hr per level', () => { - // Base regen is 2 + enchanter 0.5 = 2.5 - const state0 = createMockState(); - expect(computeRegen(state0)).toBe(2.5); - - const state3 = createMockState({ skills: { manaFlow: 3 } }); - expect(computeRegen(state3)).toBe(2 + 0.5 + 3); - - const state10 = createMockState({ skills: { manaFlow: 10 } }); - expect(computeRegen(state10)).toBe(2 + 0.5 + 10); - }); - }); - - describe('elemAttune', () => { - it('should add +50 elem mana cap per level', () => { - const state0 = createMockState(); - expect(computeElementMax(state0)).toBe(10); - - const state3 = createMockState({ skills: { elemAttune: 3 } }); - expect(computeElementMax(state3)).toBe(10 + 150); - }); - }); - - describe('manaOverflow', () => { - it('should be defined with correct properties', () => { - expect(SKILLS_DEF.manaOverflow).toBeDefined(); - expect(SKILLS_DEF.manaOverflow.max).toBe(5); - expect(SKILLS_DEF.manaOverflow.desc).toContain('click'); - }); - - it('should require Mana Well 3', () => { - expect(SKILLS_DEF.manaOverflow.req).toEqual({ manaWell: 3 }); - }); - }); - - // ─── Study Skills ─────────────────────────────────────────────────────────── - - describe('quickLearner', () => { - it('should add +10% study speed per level', () => { - expect(getStudySpeedMultiplier({ quickLearner: 0 })).toBe(1); - expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1); - expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5); - }); - }); - - describe('focusedMind', () => { - it('should reduce study mana cost by 5% per level', () => { - expect(getStudyCostMultiplier({ focusedMind: 0 })).toBe(1); - expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95); - expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75); - }); - - it('should reduce skill study cost', () => { - const baseCost = SKILLS_DEF.manaWell.base; - const costMult5 = getStudyCostMultiplier({ focusedMind: 5 }); - const reducedCost = Math.floor(baseCost * costMult5); - expect(reducedCost).toBe(75); - }); - - it('should reduce spell study cost', () => { - const baseCost = SPELLS_DEF.fireball.unlock; - const costMult5 = getStudyCostMultiplier({ focusedMind: 5 }); - const reducedCost = Math.floor(baseCost * costMult5); - expect(reducedCost).toBe(75); - }); - }); - - describe('meditation', () => { - it('should give 2.5x regen after 4 hours meditating', () => { - const bonus = getMeditationBonus(100, { meditation: 1 }); // 100 ticks = 4 hours - expect(bonus).toBe(2.5); - }); - - it('should not give bonus without enough time', () => { - const bonus = getMeditationBonus(50, { meditation: 1 }); // 2 hours - expect(bonus).toBeLessThan(2.5); - }); - }); - - describe('knowledgeRetention', () => { - it('should save +20% study progress on cancel per level', () => { - expect(SKILLS_DEF.knowledgeRetention).toBeDefined(); - expect(SKILLS_DEF.knowledgeRetention.max).toBe(3); - expect(SKILLS_DEF.knowledgeRetention.desc).toContain('20% study progress saved'); - }); - }); - - // ─── Research Skills ──────────────────────────────────────────────────────── - - describe('manaTap', () => { - it('should add +1 mana per click', () => { - const state0 = createMockState(); - expect(computeClickMana(state0)).toBe(1); - - const state1 = createMockState({ skills: { manaTap: 1 } }); - expect(computeClickMana(state1)).toBe(2); - }); - }); - - describe('manaSurge', () => { - it('should add +3 mana per click', () => { - const state = createMockState({ skills: { manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 3); - }); - - it('should stack with manaTap', () => { - const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1 + 3); - }); - - it('should require manaTap', () => { - expect(SKILLS_DEF.manaSurge.req).toEqual({ manaTap: 1 }); - }); - }); - - describe('manaSpring', () => { - it('should add +2 mana regen', () => { - // Base 2 + enchanter 0.5 + manaSpring 2 - const state = createMockState({ skills: { manaSpring: 1 } }); - expect(computeRegen(state)).toBe(2 + 0.5 + 2); - }); - }); - - describe('deepTrance', () => { - it('should extend meditation bonus to 6hrs for 3x', () => { - const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 }); // 6 hours - expect(bonus).toBe(3.0); - }); - - it('should require meditation', () => { - expect(SKILLS_DEF.deepTrance.req).toEqual({ meditation: 1 }); - }); - }); - - describe('voidMeditation', () => { - it('should extend meditation bonus to 8hrs for 5x', () => { - const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 }); // 8 hours - expect(bonus).toBe(5.0); - }); - - it('should require deepTrance', () => { - expect(SKILLS_DEF.voidMeditation.req).toEqual({ deepTrance: 1 }); - }); - }); - - // ─── Ascension Skills ─────────────────────────────────────────────────────── - - describe('insightHarvest', () => { - it('should add +10% insight gain per level', () => { - const state0 = createMockState({ maxFloorReached: 10 }); - const insight0 = calcInsight(state0); - - const state3 = createMockState({ maxFloorReached: 10, skills: { insightHarvest: 3 } }); - const insight3 = calcInsight(state3); - - // Level 3 = 1.3x insight - expect(insight3).toBe(Math.floor(insight0 * 1.3)); - }); - }); - - describe('guardianBane', () => { - it('should have evolution path in skill evolution system', () => { - expect(SKILL_EVOLUTION_PATHS.guardianBane).toBeDefined(); - expect(SKILL_EVOLUTION_PATHS.guardianBane.baseSkillId).toBe('guardianBane'); - }); - }); - - // ─── Enchanter Skills ─────────────────────────────────────────────────────── - - describe('enchanting', () => { - it('should require enchanter attunement', () => { - expect(SKILLS_DEF.enchanting).toBeDefined(); - expect(SKILLS_DEF.enchanting.attunement).toBe('enchanter'); - }); - }); - - describe('efficientEnchant', () => { - it('should require enchanting 3', () => { - expect(SKILLS_DEF.efficientEnchant).toBeDefined(); - expect(SKILLS_DEF.efficientEnchant.req).toEqual({ enchanting: 3 }); - }); - }); - - // ─── Fabricator/Golemancy Skills ──────────────────────────────────────────── - - describe('golemMastery', () => { - it('should be defined with correct properties', () => { - expect(SKILLS_DEF.golemMastery).toBeDefined(); - expect(SKILLS_DEF.golemMastery.max).toBe(1); - }); - }); - - describe('golemEfficiency', () => { - it('should be defined with correct properties', () => { - expect(SKILLS_DEF.golemEfficiency).toBeDefined(); - expect(SKILLS_DEF.golemEfficiency.max).toBe(1); - }); - }); - - // ─── Crafting Skills ──────────────────────────────────────────────────────── - - describe('effCrafting', () => { - it('should reduce craft time by 10% per level', () => { - expect(SKILLS_DEF.effCrafting).toBeDefined(); - expect(SKILLS_DEF.effCrafting.max).toBe(1); - expect(SKILLS_DEF.effCrafting.desc).toContain('10% craft time'); - }); - }); - - describe('fieldRepair', () => { - it('should be defined with correct properties', () => { - expect(SKILLS_DEF.fieldRepair).toBeDefined(); - expect(SKILLS_DEF.fieldRepair.max).toBe(1); - }); - }); - - describe('elemCrafting', () => { - it('should add +25% craft output per level', () => { - expect(SKILLS_DEF.elemCrafting).toBeDefined(); - expect(SKILLS_DEF.elemCrafting.max).toBe(1); - expect(SKILLS_DEF.elemCrafting.desc).toContain('25% craft output'); - }); - }); -}); diff --git a/src/lib/game/store-tests/insight-meditation-incursion.test.ts b/src/lib/game/store-tests/insight-meditation-incursion.test.ts deleted file mode 100644 index da60e83..0000000 --- a/src/lib/game/store-tests/insight-meditation-incursion.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Tests for Insight, Meditation, and Incursion - */ - -import { describe, it, expect } from 'vitest'; -import { calcInsight, getMeditationBonus, getIncursionStrength } from '../store'; -import { createMockState } from './test-utils'; -import { MAX_DAY, INCURSION_START_DAY } from '../constants'; - -describe('Insight Calculation', () => { - describe('calcInsight', () => { - it('should calculate insight from floor progress', () => { - const state = createMockState({ maxFloorReached: 10 }); - const insight = calcInsight(state); - expect(insight).toBe(10 * 15); - }); - - it('should calculate insight from mana gathered', () => { - const state = createMockState({ totalManaGathered: 5000 }); - const insight = calcInsight(state); - // Formula: floor*15 + mana/500 + pacts*150 - // With default maxFloorReached=1: 1*15 + 5000/500 + 0 = 15 + 10 = 25 - expect(insight).toBe(25); - }); - - it('should calculate insight from signed pacts', () => { - const state = createMockState({ signedPacts: [10, 20] }); - const insight = calcInsight(state); - // Formula: floor*15 + mana/500 + pacts*150 - // With default maxFloorReached=1: 1*15 + 0 + 2*150 = 15 + 300 = 315 - expect(insight).toBe(315); - }); - - it('should multiply by insightAmp prestige', () => { - const state = createMockState({ - maxFloorReached: 10, - prestigeUpgrades: { insightAmp: 2 }, - }); - const insight = calcInsight(state); - expect(insight).toBe(Math.floor(10 * 15 * 1.5)); - }); - - it('should multiply by insightHarvest skill', () => { - const state = createMockState({ - maxFloorReached: 10, - skills: { insightHarvest: 3 }, - }); - const insight = calcInsight(state); - expect(insight).toBe(Math.floor(10 * 15 * 1.3)); - }); - }); -}); - -describe('Meditation Bonus', () => { - describe('getMeditationBonus', () => { - it('should start at 1x with no meditation', () => { - expect(getMeditationBonus(0, {})).toBe(1); - }); - - it('should ramp up over time', () => { - const bonus1hr = getMeditationBonus(25, {}); // 1 hour of ticks - expect(bonus1hr).toBeGreaterThan(1); - - const bonus4hr = getMeditationBonus(100, {}); // 4 hours - expect(bonus4hr).toBeGreaterThan(bonus1hr); - }); - - it('should cap at 1.5x without meditation skill', () => { - const bonus = getMeditationBonus(200, {}); // 8 hours - expect(bonus).toBe(1.5); - }); - - it('should give 2.5x with meditation skill after 4 hours', () => { - const bonus = getMeditationBonus(100, { meditation: 1 }); - expect(bonus).toBe(2.5); - }); - - it('should give 3.0x with deepTrance skill after 6 hours', () => { - const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 }); - expect(bonus).toBe(3.0); - }); - - it('should give 5.0x with voidMeditation skill after 8 hours', () => { - const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 }); - expect(bonus).toBe(5.0); - }); - }); -}); - -describe('Incursion Strength', () => { - describe('getIncursionStrength', () => { - it('should be 0 before incursion start day', () => { - expect(getIncursionStrength(19, 0)).toBe(0); - expect(getIncursionStrength(19, 23)).toBe(0); - }); - - it('should start at incursion start day', () => { - // Incursion starts at day 20, hour 0 - // Formula: totalHours / maxHours * 0.95 - // At day 20, hour 0: totalHours = 0, so strength = 0 - // Need hour > 0 to see incursion - expect(getIncursionStrength(INCURSION_START_DAY, 1)).toBeGreaterThan(0); - }); - - it('should increase over time', () => { - const early = getIncursionStrength(INCURSION_START_DAY, 12); - const late = getIncursionStrength(25, 12); - expect(late).toBeGreaterThan(early); - }); - - it('should cap at 95%', () => { - const strength = getIncursionStrength(MAX_DAY, 23); - expect(strength).toBeLessThanOrEqual(0.95); - }); - }); -}); diff --git a/src/lib/game/store-tests/integration.test.ts b/src/lib/game/store-tests/integration.test.ts deleted file mode 100644 index aff6f86..0000000 --- a/src/lib/game/store-tests/integration.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Integration Tests - */ - -import { describe, it, expect } from 'vitest'; -import { SPELLS_DEF, GUARDIANS, ELEMENTS, SKILLS_DEF } from '../constants'; - -describe('Integration Tests', () => { - it('should have consistent element references across all definitions', () => { - // All spell elements should exist - Object.values(SPELLS_DEF).forEach(spell => { - if (spell.elem !== 'raw') { - expect(ELEMENTS[spell.elem]).toBeDefined(); - } - }); - - // All guardian elements should exist - Object.values(GUARDIANS).forEach(guardian => { - expect(ELEMENTS[guardian.element]).toBeDefined(); - }); - }); - - it('should have balanced spell costs relative to damage', () => { - Object.values(SPELLS_DEF).forEach(spell => { - const dmgPerCost = spell.dmg / spell.cost.amount; - // Damage per mana should be reasonable (between 0.5 and 50) - expect(dmgPerCost).toBeGreaterThan(0.5); - expect(dmgPerCost).toBeLessThan(50); - }); - }); - - it('should have balanced skill requirements', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.entries(skill.req).forEach(([reqId, level]) => { - expect(SKILLS_DEF[reqId]).toBeDefined(); - expect(level).toBeGreaterThan(0); - expect(level).toBeLessThanOrEqual(SKILLS_DEF[reqId].max); - }); - } - }); - }); -}); diff --git a/src/lib/game/store-tests/mana-calculation.test.ts b/src/lib/game/store-tests/mana-calculation.test.ts deleted file mode 100644 index 80eea83..0000000 --- a/src/lib/game/store-tests/mana-calculation.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Tests for Mana Calculation Functions - */ - -import { describe, it, expect } from 'vitest'; -import { computeMaxMana, computeElementMax, computeRegen, computeClickMana } from '../store'; -import { createMockState } from './test-utils'; - -describe('Mana Calculation Functions', () => { - describe('computeMaxMana', () => { - it('should return base mana with no upgrades', () => { - const state = createMockState(); - expect(computeMaxMana(state)).toBe(100); - }); - - it('should add mana from manaWell skill', () => { - const state = createMockState({ skills: { manaWell: 5 } }); - expect(computeMaxMana(state)).toBe(100 + 5 * 100); - }); - - it('should add mana from prestige upgrades', () => { - const state = createMockState({ prestigeUpgrades: { manaWell: 3 } }); - expect(computeMaxMana(state)).toBe(100 + 3 * 500); - }); - }); - - describe('computeElementMax', () => { - it('should return base element cap with no upgrades', () => { - const state = createMockState(); - expect(computeElementMax(state)).toBe(10); - }); - - it('should add cap from elemAttune skill', () => { - const state = createMockState({ skills: { elemAttune: 5 } }); - expect(computeElementMax(state)).toBe(10 + 5 * 50); - }); - - it('should add cap from prestige upgrades', () => { - const state = createMockState({ prestigeUpgrades: { elementalAttune: 3 } }); - expect(computeElementMax(state)).toBe(10 + 3 * 25); - }); - }); - - describe('computeRegen', () => { - it('should return base regen with no upgrades', () => { - // Base regen is 2, but Enchanter attunement adds 0.5 - const state = createMockState(); - expect(computeRegen(state)).toBe(2.5); // 2 + 0.5 from enchanter - }); - - it('should add regen from manaFlow skill', () => { - // Base 2 + enchanter 0.5 + manaFlow 5 - const state = createMockState({ skills: { manaFlow: 5 } }); - expect(computeRegen(state)).toBe(2 + 0.5 + 5 * 1); - }); - - it('should add regen from manaSpring skill', () => { - // Base 2 + enchanter 0.5 + manaSpring 2 - const state = createMockState({ skills: { manaSpring: 1 } }); - expect(computeRegen(state)).toBe(2 + 0.5 + 2); - }); - - it('should multiply by temporal echo prestige', () => { - const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } }); - // Base 2 * 1.2 (temporal) = 2.4, + enchanter 0.5 = 2.9 - // Note: temporal bonus applies to base, not attunement - expect(computeRegen(state)).toBe(2 * 1.2 + 0.5); - }); - }); - - describe('computeClickMana', () => { - it('should return base click mana with no upgrades', () => { - const state = createMockState(); - expect(computeClickMana(state)).toBe(1); - }); - - it('should add mana from manaTap skill', () => { - const state = createMockState({ skills: { manaTap: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1); - }); - - it('should add mana from manaSurge skill', () => { - const state = createMockState({ skills: { manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 3); - }); - - it('should stack manaTap and manaSurge', () => { - const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1 + 3); - }); - }); -}); diff --git a/src/lib/game/store-tests/skill-evolution.test.ts b/src/lib/game/store-tests/skill-evolution.test.ts deleted file mode 100644 index 52cae0a..0000000 --- a/src/lib/game/store-tests/skill-evolution.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Tests for Skill Evolution System - */ - -import { describe, it, expect } from 'vitest'; -import { - SKILL_EVOLUTION_PATHS, - getTierMultiplier, - getNextTierSkill, -} from '../skill-evolution'; - -describe('Skill Evolution System', () => { - describe('SKILL_EVOLUTION_PATHS', () => { - it('should have evolution paths for major skills', () => { - expect(SKILL_EVOLUTION_PATHS.manaWell).toBeDefined(); - expect(SKILL_EVOLUTION_PATHS.manaFlow).toBeDefined(); - expect(SKILL_EVOLUTION_PATHS.quickLearner).toBeDefined(); - expect(SKILL_EVOLUTION_PATHS.focusedMind).toBeDefined(); - }); - - it('should have multiple tiers for evolution', () => { - expect(SKILL_EVOLUTION_PATHS.manaWell.tiers.length).toBeGreaterThanOrEqual(3); - }); - }); - - describe('getTierMultiplier', () => { - it('should return correct multipliers for tiered skills', () => { - expect(getTierMultiplier('manaWell')).toBe(1); - expect(getTierMultiplier('manaWell_t2')).toBe(10); - expect(getTierMultiplier('manaWell_t3')).toBe(100); - }); - }); - - describe('getNextTierSkill', () => { - it('should return next tier skill ID', () => { - expect(getNextTierSkill('manaWell')).toBe('manaWell_t2'); - expect(getNextTierSkill('manaWell_t2')).toBe('manaWell_t3'); - }); - }); -}); diff --git a/src/lib/game/store-tests/skill-requirements.test.ts b/src/lib/game/store-tests/skill-requirements.test.ts deleted file mode 100644 index 6d5d014..0000000 --- a/src/lib/game/store-tests/skill-requirements.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Tests for Skill Requirements - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '../constants'; - -describe('Skill Requirements', () => { - it('manaOverflow should require manaWell 3', () => { - expect(SKILLS_DEF.manaOverflow.req).toEqual({ manaWell: 3 }); - }); - - it('manaSurge should require manaTap 1', () => { - expect(SKILLS_DEF.manaSurge.req).toEqual({ manaTap: 1 }); - }); - - it('deepTrance should require meditation 1', () => { - expect(SKILLS_DEF.deepTrance.req).toEqual({ meditation: 1 }); - }); - - it('voidMeditation should require deepTrance 1', () => { - expect(SKILLS_DEF.voidMeditation.req).toEqual({ deepTrance: 1 }); - }); - - it('efficientEnchant should require enchanting 3', () => { - expect(SKILLS_DEF.efficientEnchant.req).toEqual({ enchanting: 3 }); - }); -}); diff --git a/src/lib/game/store-tests/spell-cost.test.ts b/src/lib/game/store-tests/spell-cost.test.ts deleted file mode 100644 index 9a52cd4..0000000 --- a/src/lib/game/store-tests/spell-cost.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Tests for Spell Cost System - */ - -import { describe, it, expect } from 'vitest'; -import { rawCost, elemCost } from '../constants'; -import { canAffordSpellCost } from '../store'; -import type { SpellCost } from '../types'; - -describe('Spell Cost System', () => { - describe('rawCost', () => { - it('should create a raw mana cost', () => { - const cost = rawCost(10); - expect(cost.type).toBe('raw'); - expect(cost.amount).toBe(10); - expect(cost.element).toBeUndefined(); - }); - }); - - describe('elemCost', () => { - it('should create an elemental mana cost', () => { - const cost = elemCost('fire', 5); - expect(cost.type).toBe('element'); - expect(cost.element).toBe('fire'); - expect(cost.amount).toBe(5); - }); - }); - - describe('canAffordSpellCost', () => { - it('should allow raw mana costs when enough raw mana', () => { - const cost: SpellCost = rawCost(10); - const elements = { fire: { current: 0, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 100, elements)).toBe(true); - }); - - it('should deny raw mana costs when not enough raw mana', () => { - const cost: SpellCost = rawCost(100); - const elements = { fire: { current: 0, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 50, elements)).toBe(false); - }); - - it('should allow elemental costs when enough element mana', () => { - const cost: SpellCost = elemCost('fire', 5); - const elements = { fire: { current: 10, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 0, elements)).toBe(true); - }); - - it('should deny elemental costs when element not unlocked', () => { - const cost: SpellCost = elemCost('fire', 5); - const elements = { fire: { current: 10, max: 10, unlocked: false } }; - expect(canAffordSpellCost(cost, 100, elements)).toBe(false); - }); - - it('should deny elemental costs when not enough element mana', () => { - const cost: SpellCost = elemCost('fire', 10); - const elements = { fire: { current: 5, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 100, elements)).toBe(false); - }); - }); -}); diff --git a/src/lib/game/store-tests/study-speed.test.ts b/src/lib/game/store-tests/study-speed.test.ts deleted file mode 100644 index d628032..0000000 --- a/src/lib/game/store-tests/study-speed.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Tests for Study Speed Functions - */ - -import { describe, it, expect } from 'vitest'; -import { getStudySpeedMultiplier, getStudyCostMultiplier } from '../constants'; - -describe('Study Speed Functions', () => { - describe('getStudySpeedMultiplier', () => { - it('should return 1 with no quickLearner skill', () => { - expect(getStudySpeedMultiplier({})).toBe(1); - }); - - it('should increase by 10% per level', () => { - expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1); - expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5); - }); - }); - - describe('getStudyCostMultiplier', () => { - it('should return 1 with no focusedMind skill', () => { - expect(getStudyCostMultiplier({})).toBe(1); - }); - - it('should decrease by 5% per level', () => { - expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95); - expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75); - }); - }); -}); diff --git a/src/lib/game/store-tests/test-utils.ts b/src/lib/game/store-tests/test-utils.ts deleted file mode 100644 index 9483f23..0000000 --- a/src/lib/game/store-tests/test-utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Test Utilities and Fixtures for Mana Loop Game Logic Tests - */ - -import type { GameState } from '../types'; -import { - ELEMENTS, -} from '../constants'; - -/** - * Creates a mock game state with sensible defaults. - * Override any properties via the `overrides` parameter. - */ -export function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: false }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - ...overrides, - } as GameState; -} diff --git a/src/lib/game/store/skillSlice.ts b/src/lib/game/store/skillSlice.ts deleted file mode 100755 index 23cb8f1..0000000 --- a/src/lib/game/store/skillSlice.ts +++ /dev/null @@ -1,346 +0,0 @@ -// ─── Skill Slice ────────────────────────────────────────────────────────────── -// Manages skills, studying, and skill progress - -import type { StateCreator } from 'zustand'; -import type { GameState, StudyTarget, SkillUpgradeChoice } from '../types'; -import { SKILLS_DEF, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier } from '../constants'; -import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier } from '../skill-evolution'; -import { computeEffects } from '../upgrade-effects'; - -export interface SkillSlice { - // State - skills: Record; - skillProgress: Record; - skillUpgrades: Record; - skillTiers: Record; - currentStudyTarget: StudyTarget | null; - parallelStudyTarget: StudyTarget | null; - - // Actions - startStudyingSkill: (skillId: string) => void; - startStudyingSpell: (spellId: string) => void; - startParallelStudySkill: (skillId: string) => void; - cancelStudy: () => void; - cancelParallelStudy: () => void; - selectSkillUpgrade: (skillId: string, upgradeId: string) => void; - deselectSkillUpgrade: (skillId: string, upgradeId: string) => void; - commitSkillUpgrades: (skillId: string, upgradeIds: string[], milestone: 5 | 10) => void; - tierUpSkill: (skillId: string) => void; - - // Getters - getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { available: SkillUpgradeChoice[]; selected: string[] }; -} - -export const createSkillSlice = ( - set: StateCreator['set'], - get: () => GameState -): SkillSlice => ({ - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - - startStudyingSkill: (skillId: string) => { - const state = get(); - const sk = SKILLS_DEF[skillId]; - if (!sk) return; - - const currentLevel = state.skills[skillId] || 0; - if (currentLevel >= sk.max) return; - - // Check prerequisites - if (sk.req) { - for (const [r, rl] of Object.entries(sk.req)) { - if ((state.skills[r] || 0) < rl) return; - } - } - - const costMult = getStudyCostMultiplier(state.skills); - const totalCost = Math.floor(sk.base * (currentLevel + 1) * costMult); - const manaCostPerHour = totalCost / sk.studyTime; - - set({ - currentAction: 'study', - currentStudyTarget: { - type: 'skill', - id: skillId, - progress: state.skillProgress[skillId] || 0, - required: sk.studyTime, - manaCostPerHour, - }, - log: [`📚 Started studying ${sk.name}... (${Math.floor(manaCostPerHour)} mana/hour)`, ...state.log.slice(0, 49)], - }); - }, - - startStudyingSpell: (spellId: string) => { - const state = get(); - const sp = SPELLS_DEF[spellId]; - if (!sp || state.spells[spellId]?.learned) return; - - const costMult = getStudyCostMultiplier(state.skills); - const totalCost = Math.floor(sp.unlock * costMult); - const studyTime = sp.studyTime || (sp.tier * 4); - const manaCostPerHour = totalCost / studyTime; - - set({ - currentAction: 'study', - currentStudyTarget: { - type: 'spell', - id: spellId, - progress: state.spells[spellId]?.studyProgress || 0, - required: studyTime, - manaCostPerHour, - }, - spells: { - ...state.spells, - [spellId]: { - ...(state.spells[spellId] || { learned: false, level: 0 }), - studyProgress: state.spells[spellId]?.studyProgress || 0, - }, - }, - log: [`📚 Started studying ${sp.name}... (${Math.floor(manaCostPerHour)} mana/hour)`, ...state.log.slice(0, 49)], - }); - }, - - startParallelStudySkill: (skillId: string) => { - const state = get(); - if (state.parallelStudyTarget) return; - if (!state.currentStudyTarget) return; - - const sk = SKILLS_DEF[skillId]; - if (!sk) return; - - const currentLevel = state.skills[skillId] || 0; - if (currentLevel >= sk.max) return; - - if (state.currentStudyTarget.id === skillId) return; - - set({ - parallelStudyTarget: { - type: 'skill', - id: skillId, - progress: state.skillProgress[skillId] || 0, - required: sk.studyTime, - manaCostPerHour: 0, // Parallel study doesn't cost extra - }, - log: [`📚 Started parallel study of ${sk.name}... (50% speed)`, ...state.log.slice(0, 49)], - }); - }, - - cancelStudy: () => { - const state = get(); - if (!state.currentStudyTarget) return; - - const savedProgress = state.currentStudyTarget.progress; - const log = ['📖 Study paused. Progress saved.', ...state.log.slice(0, 49)]; - - if (state.currentStudyTarget.type === 'skill') { - set({ - currentStudyTarget: null, - currentAction: 'meditate', - skillProgress: { - ...state.skillProgress, - [state.currentStudyTarget.id]: savedProgress, - }, - log, - }); - } else { - set({ - currentStudyTarget: null, - currentAction: 'meditate', - spells: { - ...state.spells, - [state.currentStudyTarget.id]: { - ...(state.spells[state.currentStudyTarget.id] || { learned: false, level: 0 }), - studyProgress: savedProgress, - }, - }, - log, - }); - } - }, - - cancelParallelStudy: () => { - set((state) => { - if (!state.parallelStudyTarget) return state; - return { - parallelStudyTarget: null, - log: ['📖 Parallel study cancelled.', ...state.log.slice(0, 49)], - }; - }); - }, - - selectSkillUpgrade: (skillId: string, upgradeId: string) => { - set((state) => { - const current = state.skillUpgrades?.[skillId] || []; - if (current.includes(upgradeId)) return state; - if (current.length >= 2) return state; - return { - skillUpgrades: { - ...state.skillUpgrades, - [skillId]: [...current, upgradeId], - }, - }; - }); - }, - - deselectSkillUpgrade: (skillId: string, upgradeId: string) => { - set((state) => { - const current = state.skillUpgrades?.[skillId] || []; - return { - skillUpgrades: { - ...state.skillUpgrades, - [skillId]: current.filter(id => id !== upgradeId), - }, - }; - }); - }, - - commitSkillUpgrades: (skillId: string, upgradeIds: string[], milestone: 5 | 10) => { - set((state) => { - const existingUpgrades = state.skillUpgrades?.[skillId] || []; - const otherMilestoneUpgrades = existingUpgrades.filter( - id => milestone === 5 ? id.includes('_l10') : id.includes('_l5') - ); - return { - skillUpgrades: { - ...state.skillUpgrades, - [skillId]: [...otherMilestoneUpgrades, ...upgradeIds], - }, - }; - }); - }, - - tierUpSkill: (skillId: string) => { - const state = get(); - const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; - const currentTier = state.skillTiers?.[baseSkillId] || 1; - const nextTier = currentTier + 1; - - if (nextTier > 5) return; - - const nextTierSkillId = `${baseSkillId}_t${nextTier}`; - - set({ - skillTiers: { - ...state.skillTiers, - [baseSkillId]: nextTier, - }, - skills: { - ...state.skills, - [nextTierSkillId]: 0, - [skillId]: 0, - }, - skillProgress: { - ...state.skillProgress, - [skillId]: 0, - [nextTierSkillId]: 0, - }, - skillUpgrades: { - ...state.skillUpgrades, - [nextTierSkillId]: [], - [skillId]: [], - }, - log: [`🌟 ${SKILLS_DEF[baseSkillId]?.name || baseSkillId} evolved to Tier ${nextTier}!`, ...state.log.slice(0, 49)], - }); - }, - - getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { - const state = get(); - const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; - const tier = state.skillTiers?.[baseSkillId] || 1; - - const available = getUpgradesForSkillAtMilestone(skillId, milestone, state.skillTiers || {}); - const selected = (state.skillUpgrades?.[skillId] || []).filter(id => - available.some(u => u.id === id) - ); - - return { available, selected }; - }, -}); - -// Process study progress (called during tick) -export function processStudy(state: GameState, deltaHours: number): Partial { - if (state.currentAction !== 'study' || !state.currentStudyTarget) return {}; - - const target = state.currentStudyTarget; - const studySpeedMult = getStudySpeedMultiplier(state.skills); - const progressGain = deltaHours * studySpeedMult; - const manaCost = progressGain * target.manaCostPerHour; - - let rawMana = state.rawMana; - let totalManaGathered = state.totalManaGathered; - let skills = state.skills; - let skillProgress = state.skillProgress; - let spells = state.spells; - const log = [...state.log]; - - if (rawMana >= manaCost) { - rawMana -= manaCost; - totalManaGathered += manaCost; - - const newProgress = target.progress + progressGain; - - if (newProgress >= target.required) { - // Study complete - if (target.type === 'skill') { - const skillId = target.id; - const currentLevel = skills[skillId] || 0; - skills = { ...skills, [skillId]: currentLevel + 1 }; - skillProgress = { ...skillProgress, [skillId]: 0 }; - log.unshift(`✅ ${SKILLS_DEF[skillId]?.name} Lv.${currentLevel + 1} mastered!`); - } else if (target.type === 'spell') { - const spellId = target.id; - spells = { - ...spells, - [spellId]: { learned: true, level: 1, studyProgress: 0 }, - }; - log.unshift(`📖 ${SPELLS_DEF[spellId]?.name} learned!`); - } - - return { - rawMana, - totalManaGathered, - skills, - skillProgress, - spells, - currentStudyTarget: null, - currentAction: 'meditate', - log, - }; - } - - return { - rawMana, - totalManaGathered, - currentStudyTarget: { ...target, progress: newProgress }, - }; - } - - // Not enough mana - log.unshift('⚠️ Not enough mana to continue studying. Progress saved.'); - - if (target.type === 'skill') { - return { - currentStudyTarget: null, - currentAction: 'meditate', - skillProgress: { ...skillProgress, [target.id]: target.progress }, - log, - }; - } else { - return { - currentStudyTarget: null, - currentAction: 'meditate', - spells: { - ...spells, - [target.id]: { - ...(spells[target.id] || { learned: false, level: 0 }), - studyProgress: target.progress, - }, - }, - log, - }; - } -} diff --git a/src/lib/game/stores-split-tests/combat-store.test.ts b/src/lib/game/stores-split-tests/combat-store.test.ts deleted file mode 100644 index 8789d4d..0000000 --- a/src/lib/game/stores-split-tests/combat-store.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Combat Store Tests - stores.test.ts - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { useCombatStore } from '@/lib/game/stores'; - -// ─── Test Fixtures ─────────────────────────────────────────────────── - -beforeEach(() => { - useCombatStore.setState({ - currentFloor: 1, - floorHP: 151, - floorMaxHP: 151, - maxFloorReached: 1, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - }); -}); - -// ─── Combat Store Tests ─────────────────────────────────────────────── - -describe('CombatStore', () => { - describe('floor operations', () => { - it('should advance floor', () => { - useCombatStore.getState().advanceFloor(); - expect(useCombatStore.getState().currentFloor).toBe(2); - expect(useCombatStore.getState().maxFloorReached).toBe(2); - }); - - it('should cap at floor 100', () => { - useCombatStore.setState({ currentFloor: 100 }); - useCombatStore.getState().advanceFloor(); - expect(useCombatStore.getState().currentFloor).toBe(100); - }); - }); - - describe('action management', () => { - it('should set action', () => { - useCombatStore.getState().setAction('climb'); - expect(useCombatStore.getState().currentAction).toBe('climb'); - - useCombatStore.getState().setAction('study'); - expect(useCombatStore.getState().currentAction).toBe('study'); - }); - }); - - describe('spell management', () => { - it('should set active spell', () => { - useCombatStore.getState().learnSpell('fireball'); - useCombatStore.getState().setSpell('fireball'); - expect(useCombatStore.getState().activeSpell).toBe('fireball'); - }); - - it('should learn spell', () => { - useCombatStore.getState().learnSpell('fireball'); - expect(useCombatStore.getState().spells['fireball']?.learned).toBe(true); - }); - }); -}); - -console.log('✅ CombatStore tests defined (from stores.test.ts).'); diff --git a/src/lib/game/stores-split-tests/integration.test.ts b/src/lib/game/stores-split-tests/integration.test.ts deleted file mode 100644 index 438130e..0000000 --- a/src/lib/game/stores-split-tests/integration.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Store Integration Tests - stores.test.ts - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { - useManaStore, - useSkillStore, - usePrestigeStore, - useCombatStore, - computeMaxMana, -} from '@/lib/game/stores'; - -// ─── Test Fixtures ─────────────────────────────────────────────────── - -beforeEach(() => { - useManaStore.setState({ - rawMana: 10, - meditateTicks: 0, - totalManaGathered: 0, - elements: (() => { - const elements: Record = {}; - ['fire', 'water', 'air', 'earth'].forEach(k => { - elements[k] = { current: 0, max: 10, unlocked: true }; - }); - return elements; - })(), - }); - - useSkillStore.setState({ - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - }); - - usePrestigeStore.setState({ - loopCount: 0, - insight: 0, - totalInsight: 0, - loopInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - pactSlots: 1, - memories: [], - defeatedGuardians: [], - signedPacts: [], - pactRitualFloor: null, - pactRitualProgress: 0, - }); - - useCombatStore.setState({ - currentFloor: 1, - floorHP: 151, - floorMaxHP: 151, - maxFloorReached: 1, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - }); -}); - -// ─── Integration Tests ───────────────────────────────────────────────── - -describe('Store Integration', () => { - describe('skill study flow', () => { - it('should complete full study flow', () => { - // Setup: give enough mana - useManaStore.getState().addRawMana(90, 1000); - - // Start studying - const startResult = useSkillStore.getState().startStudyingSkill('manaWell', 100); - expect(startResult.started).toBe(true); - expect(startResult.cost).toBe(100); - - // Deduct mana (simulating UI behavior) - if (startResult.cost > 0) { - useManaStore.getState().spendRawMana(startResult.cost); - } - - // Set action to study - useCombatStore.getState().setAction('study'); - expect(useCombatStore.getState().currentAction).toBe('study'); - - // Update progress until complete - const result = useSkillStore.getState().updateStudyProgress(4); - expect(result.completed).toBe(true); - - // Level up skill - useSkillStore.getState().setSkillLevel('manaWell', 1); - expect(useSkillStore.getState().skills['manaWell']).toBe(1); - }); - }); - - describe('mana and prestige interaction', () => { - it('should apply prestige mana bonus', () => { - // Get prestige upgrade - usePrestigeStore.getState().startNewLoop(1000); - usePrestigeStore.getState().doPrestige('manaWell'); - - // Check that prestige upgrade is recorded - expect(usePrestigeStore.getState().prestigeUpgrades['manaWell']).toBe(1); - - // Mana well prestige gives +500 max mana per level - const state = { - skills: {}, - prestigeUpgrades: usePrestigeStore.getState().prestigeUpgrades, - skillUpgrades: {}, - skillTiers: {}, - }; - - const maxMana = computeMaxMana(state); - expect(maxMana).toBe(100 + 500); // Base 100 + 500 from prestige - }); - }); -}); - -console.log('✅ Store integration tests defined (from stores.test.ts).'); diff --git a/src/lib/game/stores-split-tests/mana-store.test.ts b/src/lib/game/stores-split-tests/mana-store.test.ts deleted file mode 100644 index 059622c..0000000 --- a/src/lib/game/stores-split-tests/mana-store.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Mana Store Tests - stores.test.ts - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { - useManaStore, - useSkillStore, - usePrestigeStore, - useCombatStore, - useUIStore, -} from '@/lib/game/stores'; -import { ELEMENTS } from '@/lib/game/constants'; - -// ─── Test Fixtures ─────────────────────────────────────────────────── - -beforeEach(() => { - useManaStore.setState({ - rawMana: 10, - meditateTicks: 0, - totalManaGathered: 0, - elements: (() => { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - return elements; - })(), - }); - - useSkillStore.setState({ - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - }); - - usePrestigeStore.setState({ - loopCount: 0, - insight: 0, - totalInsight: 0, - loopInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - pactSlots: 1, - memories: [], - defeatedGuardians: [], - signedPacts: [], - pactRitualFloor: null, - pactRitualProgress: 0, - }); - - useCombatStore.setState({ - currentFloor: 1, - floorHP: 151, - floorMaxHP: 151, - maxFloorReached: 1, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - }); - - useUIStore.setState({ - logs: [], - paused: false, - gameOver: false, - victory: false, - }); -}); - -// ─── Mana Store Tests ───────────────────────────────────────────────── - -describe('ManaStore', () => { - describe('initial state', () => { - it('should have correct initial values', () => { - const state = useManaStore.getState(); - expect(state.rawMana).toBe(10); - expect(state.meditateTicks).toBe(0); - expect(state.totalManaGathered).toBe(0); - }); - - it('should have base elements unlocked', () => { - const state = useManaStore.getState(); - expect(state.elements.fire.unlocked).toBe(true); - expect(state.elements.water.unlocked).toBe(true); - expect(state.elements.air.unlocked).toBe(true); - expect(state.elements.earth.unlocked).toBe(true); - expect(state.elements.light.unlocked).toBe(false); - }); - }); - - describe('raw mana operations', () => { - it('should add raw mana', () => { - useManaStore.getState().addRawMana(50, 100); - expect(useManaStore.getState().rawMana).toBe(60); - }); - - it('should cap at max mana', () => { - useManaStore.getState().addRawMana(200, 100); - expect(useManaStore.getState().rawMana).toBe(100); - }); - - it('should spend raw mana', () => { - const result = useManaStore.getState().spendRawMana(5); - expect(result).toBe(true); - expect(useManaStore.getState().rawMana).toBe(5); - }); - - it('should fail to spend more than available', () => { - const result = useManaStore.getState().spendRawMana(50); - expect(result).toBe(false); - expect(useManaStore.getState().rawMana).toBe(10); - }); - }); - - describe('element operations', () => { - it('should convert raw mana to element', () => { - useManaStore.getState().addRawMana(90, 1000); // Have 100 mana - const result = useManaStore.getState().convertMana('fire', 1); - expect(result).toBe(true); - expect(useManaStore.getState().elements.fire.current).toBe(1); - }); - - it('should unlock new element', () => { - useManaStore.getState().addRawMana(490, 1000); // Have 500 mana - const result = useManaStore.getState().unlockElement('light', 500); - expect(result).toBe(true); - expect(useManaStore.getState().elements.light.unlocked).toBe(true); - }); - }); -}); - -console.log('✅ ManaStore tests defined (from stores.test.ts).'); diff --git a/src/lib/game/stores-split-tests/prestige-store.test.ts b/src/lib/game/stores-split-tests/prestige-store.test.ts deleted file mode 100644 index a9999c3..0000000 --- a/src/lib/game/stores-split-tests/prestige-store.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Prestige Store Tests - stores.test.ts - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { - usePrestigeStore, - useManaStore, -} from '@/lib/game/stores'; - -// ─── Test Fixtures ─────────────────────────────────────────────────── - -beforeEach(() => { - usePrestigeStore.setState({ - loopCount: 0, - insight: 0, - totalInsight: 0, - loopInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - pactSlots: 1, - memories: [], - defeatedGuardians: [], - signedPacts: [], - pactRitualFloor: null, - pactRitualProgress: 0, - }); - - useManaStore.setState({ - rawMana: 10, - elements: (() => { - const elements: Record = {}; - ['fire', 'water', 'air', 'earth'].forEach(k => { - elements[k] = { current: 0, max: 10, unlocked: true }; - }); - return elements; - })(), - }); -}); - -// ─── Prestige Store Tests ───────────────────────────────────────────────── - -describe('PrestigeStore', () => { - describe('prestige upgrades', () => { - it('should purchase prestige upgrade', () => { - usePrestigeStore.getState().startNewLoop(1000); // Add 1000 insight - - const result = usePrestigeStore.getState().doPrestige('manaWell'); - - expect(result).toBe(true); - expect(usePrestigeStore.getState().prestigeUpgrades['manaWell']).toBe(1); - expect(usePrestigeStore.getState().insight).toBe(500); // 1000 - 500 cost - }); - - it('should not purchase without enough insight', () => { - usePrestigeStore.getState().startNewLoop(100); - - const result = usePrestigeStore.getState().doPrestige('manaWell'); - - expect(result).toBe(false); - }); - }); - - describe('loop management', () => { - it('should increment loop count', () => { - usePrestigeStore.getState().incrementLoopCount(); - expect(usePrestigeStore.getState().loopCount).toBe(1); - - usePrestigeStore.getState().incrementLoopCount(); - expect(usePrestigeStore.getState().loopCount).toBe(2); - }); - - it('should reset for new loop', () => { - usePrestigeStore.getState().startNewLoop(1000); - usePrestigeStore.getState().doPrestige('manaWell'); - - usePrestigeStore.getState().resetPrestigeForNewLoop( - 500, // total insight - { manaWell: 1 }, // prestige upgrades - [], // memories - 3 // memory slots - ); - - expect(usePrestigeStore.getState().insight).toBe(500); - expect(usePrestigeStore.getState().prestigeUpgrades['manaWell']).toBe(1); - expect(usePrestigeStore.getState().defeatedGuardians).toEqual([]); - expect(usePrestigeStore.getState().signedPacts).toEqual([]); - }); - }); - - describe('guardian pacts', () => { - it('should add signed pact', () => { - usePrestigeStore.getState().addSignedPact(10); - expect(usePrestigeStore.getState().signedPacts).toContain(10); - }); - - it('should add defeated guardian', () => { - usePrestigeStore.getState().addDefeatedGuardian(10); - expect(usePrestigeStore.getState().defeatedGuardians).toContain(10); - }); - - it('should not add same guardian twice', () => { - usePrestigeStore.getState().addDefeatedGuardian(10); - usePrestigeStore.getState().addDefeatedGuardian(10); - expect(usePrestigeStore.getState().defeatedGuardians.length).toBe(1); - }); - }); -}); - -console.log('✅ PrestigeStore tests defined (from stores.test.ts).'); diff --git a/src/lib/game/stores-split-tests/skill-store.test.ts b/src/lib/game/stores-split-tests/skill-store.test.ts deleted file mode 100644 index 1f77bf1..0000000 --- a/src/lib/game/stores-split-tests/skill-store.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Skill Store Tests - stores.test.ts - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { - useManaStore, - useSkillStore, - usePrestigeStore, - getStudySpeedMultiplier, -} from '@/lib/game/stores'; -import { ELEMENTS } from '@/lib/game/constants'; - -// ─── Test Fixtures ─────────────────────────────────────────────────── - -beforeEach(() => { - useManaStore.setState({ - rawMana: 10, - meditateTicks: 0, - totalManaGathered: 0, - elements: (() => { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - return elements; - })(), - }); - - useSkillStore.setState({ - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - }); - - usePrestigeStore.setState({ - loopCount: 0, - insight: 0, - totalInsight: 0, - loopInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - pactSlots: 1, - memories: [], - defeatedGuardians: [], - signedPacts: [], - pactRitualFloor: null, - pactRitualProgress: 0, - }); -}); - -// ─── Skill Store Tests ─────────────────────────────────────────────── - -describe('SkillStore', () => { - describe('study skill', () => { - it('should start studying a skill', () => { - useManaStore.getState().addRawMana(90, 1000); // Have 100 mana - - const result = useSkillStore.getState().startStudyingSkill('manaWell', 100); - - expect(result.started).toBe(true); - expect(result.cost).toBe(100); - expect(useSkillStore.getState().currentStudyTarget).not.toBeNull(); - expect(useSkillStore.getState().currentStudyTarget?.type).toBe('skill'); - expect(useSkillStore.getState().currentStudyTarget?.id).toBe('manaWell'); - }); - - it('should not start studying without enough mana', () => { - const result = useSkillStore.getState().startStudyingSkill('manaWell', 50); - - expect(result.started).toBe(false); - expect(result.cost).toBe(100); - expect(useSkillStore.getState().currentStudyTarget).toBeNull(); - }); - - it('should track paid study skills', () => { - useManaStore.getState().addRawMana(90, 1000); - - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - expect(useSkillStore.getState().paidStudySkills['manaWell']).toBe(0); - }); - - it('should resume studying for free after payment', () => { - useManaStore.getState().addRawMana(90, 1000); - - // First study attempt - const result1 = useSkillStore.getState().startStudyingSkill('manaWell', 100); - expect(result1.cost).toBe(100); - - // Cancel study (simulated) - useSkillStore.getState().cancelStudy(0); - - // Resume should be free - const result2 = useSkillStore.getState().startStudyingSkill('manaWell', 0); - expect(result2.started).toBe(true); - expect(result2.cost).toBe(0); - }); - }); - - describe('update study progress', () => { - it('should update study progress', () => { - useManaStore.getState().addRawMana(90, 1000); - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - const result = useSkillStore.getState().updateStudyProgress(1); - - expect(result.completed).toBe(false); - expect(useSkillStore.getState().currentStudyTarget?.progress).toBe(1); - }); - - it('should complete study when progress reaches required', () => { - useManaStore.getState().addRawMana(90, 1000); - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - // manaWell requires 4 hours - const result = useSkillStore.getState().updateStudyProgress(4); - - expect(result.completed).toBe(true); - expect(result.target?.id).toBe('manaWell'); - expect(useSkillStore.getState().currentStudyTarget).toBeNull(); - }); - - it('should apply study speed multiplier', () => { - useManaStore.getState().addRawMana(90, 1000); - useSkillStore.getState().setSkillLevel('quickLearner', 5); // 50% faster - - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - // The caller should calculate progress with speed multiplier - const speedMult = getStudySpeedMultiplier(useSkillStore.getState().skills); - const result = useSkillStore.getState().updateStudyProgress(3 * speedMult); // 3 * 1.5 = 4.5 - - expect(result.completed).toBe(true); - }); - }); - - describe('skill level operations', () => { - it('should set skill level', () => { - useSkillStore.getState().setSkillLevel('manaWell', 5); - expect(useSkillStore.getState().skills['manaWell']).toBe(5); - }); - - it('should increment skill level', () => { - useSkillStore.getState().setSkillLevel('manaWell', 5); - useSkillStore.getState().incrementSkillLevel('manaWell'); - expect(useSkillStore.getState().skills['manaWell']).toBe(6); - }); - }); - - describe('prerequisites', () => { - it('should not start studying without prerequisites', () => { - useManaStore.getState().addRawMana(990, 1000); - - // manaOverflow requires manaWell 3 - const result = useSkillStore.getState().startStudyingSkill('manaOverflow', 1000); - - expect(result.started).toBe(false); - }); - - it('should start studying with prerequisites met', () => { - useManaStore.getState().addRawMana(990, 1000); - useSkillStore.getState().setSkillLevel('manaWell', 3); - - const result = useSkillStore.getState().startStudyingSkill('manaOverflow', 1000); - - expect(result.started).toBe(true); - }); - }); -}); - -console.log('✅ SkillStore tests defined (from stores.test.ts).'); diff --git a/src/lib/game/stores-split-tests/ui-store.test.ts b/src/lib/game/stores-split-tests/ui-store.test.ts deleted file mode 100644 index 55afdba..0000000 --- a/src/lib/game/stores-split-tests/ui-store.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * UI Store Tests - stores.test.ts - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { useUIStore } from '@/lib/game/stores'; - -// ─── Test Fixtures ─────────────────────────────────────────────────── - -beforeEach(() => { - useUIStore.setState({ - logs: [], - paused: false, - gameOver: false, - victory: false, - }); -}); - -// ─── UI Store Tests ─────────────────────────────────────────────────── - -describe('UIStore', () => { - describe('log management', () => { - it('should add log message', () => { - useUIStore.getState().addLog('Test message'); - expect(useUIStore.getState().logs).toContain('Test message'); - }); - - it('should clear logs', () => { - useUIStore.getState().addLog('Test message'); - useUIStore.getState().clearLogs(); - expect(useUIStore.getState().logs.length).toBe(0); - }); - }); - - describe('pause management', () => { - it('should toggle pause', () => { - expect(useUIStore.getState().paused).toBe(false); - - useUIStore.getState().togglePause(); - expect(useUIStore.getState().paused).toBe(true); - - useUIStore.getState().togglePause(); - expect(useUIStore.getState().paused).toBe(false); - }); - - it('should set pause state', () => { - useUIStore.getState().setPaused(true); - expect(useUIStore.getState().paused).toBe(true); - }); - }); - - describe('game over state', () => { - it('should set game over', () => { - useUIStore.getState().setGameOver(true); - expect(useUIStore.getState().gameOver).toBe(true); - expect(useUIStore.getState().victory).toBe(false); - }); - - it('should set victory', () => { - useUIStore.getState().setGameOver(true, true); - expect(useUIStore.getState().gameOver).toBe(true); - expect(useUIStore.getState().victory).toBe(true); - }); - }); -}); - -console.log('✅ UIStore tests defined (from stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/archive/store-methods.test.ts b/src/lib/game/stores/__tests__/archive/store-methods.test.ts deleted file mode 100755 index 18ad127..0000000 --- a/src/lib/game/stores/__tests__/archive/store-methods.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Store Method Tests - Main Index - * - * This file re-exports all individual store method test files. - * Each test file is focused on a specific store's methods. - * - * Original file: store-methods.test.ts (588 lines) - * Refactored into 5 smaller test files (~2,900 total lines across files). - */ - -import '../store-method-tests/skill-store.test'; -import '../store-method-tests/mana-store.test'; -import '../store-method-tests/combat-store.test'; -import '../store-method-tests/prestige-store.test'; -import '../store-method-tests/ui-store.test'; - -console.log('✅ All store method tests complete (refactored from 588 lines to 5 focused test files).'); diff --git a/src/lib/game/stores/__tests__/archive/stores.test.ts b/src/lib/game/stores/__tests__/archive/stores.test.ts deleted file mode 100755 index b67a5c9..0000000 --- a/src/lib/game/stores/__tests__/archive/stores.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Stores Tests (stores/__tests__/stores.test.ts) - Main Index - * - * This file re-exports all individual test files from stores/__tests__/stores.test.ts - * Each test file is focused on a specific area of functionality. - * - * Original file: stores.test.ts (458 lines) - * Refactored into 12 smaller test files. - */ - -import '../stores-tests/formatting.test'; -import '../stores-tests/floor.test'; -import '../stores-tests/mana-calculation.test'; -import '../stores-tests/damage-calculation.test'; -import '../stores-tests/insight-calculation.test'; -import '../stores-tests/meditation.test'; -import '../stores-tests/incursion.test'; -import '../stores-tests/spell-cost.test'; -import '../stores-tests/study-speed.test'; -import '../stores-tests/guardians.test'; -import '../stores-tests/skill-definitions.test'; -import '../stores-tests/prestige-upgrades.test'; -import '../stores-tests/spell-definitions.test'; - -console.log('✅ All stores tests from stores/__tests__/stores.test.ts complete (refactored from 458 lines to 12 focused test files).'); diff --git a/src/lib/game/stores/__tests__/computed-stats.test.ts b/src/lib/game/stores/__tests__/computed-stats.test.ts deleted file mode 100644 index 9fa8db4..0000000 --- a/src/lib/game/stores/__tests__/computed-stats.test.ts +++ /dev/null @@ -1,492 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { - computeStats, - BASE_STATS, - SKILLS_V2, - getBaseSkillId, - hasPrerequisites, -} from '../../constants/skills-v2'; -import type { ComputedStats } from '../../constants/skills-v2-types'; - -// Helper to create a minimal prestige state -const emptyPrestige = {}; - -describe('computeStats()', () => { - describe('base stats with no skills', () => { - it('should return base stats when no skills are provided', () => { - const result = computeStats({}, emptyPrestige); - expect(result.maxMana).toBe(100); - expect(result.manaRegen).toBe(2); - expect(result.clickMana).toBe(1); - expect(result.baseDamage).toBe(5); - expect(result.elementCap).toBe(10); - }); - - it('should have all base values correct', () => { - const result = computeStats({}, emptyPrestige); - expect(result).toMatchObject({ - maxMana: 100, - manaRegen: 2, - clickMana: 1, - elementCap: 10, - studySpeed: 1, - studyCostMult: 1, - meditationEfficiency: 1, - enchantCapacity: 100, - enchantSpeed: 1, - enchantPower: 1, - disenchantRecovery: 1, - baseDamage: 5, - damageMultiplier: 1, - attackSpeed: 1, - critChance: 0, - critMultiplier: 1.5, - armorPierce: 0, - insightGain: 1, - golemDamage: 1, - golemDuration: 1, - pactMultiplier: 1, - spellDamage: 1, - guardianDamage: 1, - craftSpeed: 1, - repairSpeed: 1, - elementalDamage: 1, - }); - }); - }); - - describe('Mana Well skill', () => { - it('should add 100 max mana per level', () => { - const result = computeStats({ manaWell: 5 }, emptyPrestige); - expect(result.maxMana).toBe(100 + 5 * 100); - }); - - it('should be 100 at level 0', () => { - const result = computeStats({ manaWell: 0 }, emptyPrestige); - expect(result.maxMana).toBe(100); - }); - - it('should be 1100 at max level 10', () => { - const result = computeStats({ manaWell: 10 }, emptyPrestige); - expect(result.maxMana).toBe(1100); - }); - }); - - describe('Mana Flow skill', () => { - it('should add 1 mana regen per level', () => { - const result = computeStats({ manaFlow: 5 }, emptyPrestige); - expect(result.manaRegen).toBe(2 + 5); - }); - - it('should be 2 at level 0', () => { - const result = computeStats({ manaFlow: 0 }, emptyPrestige); - expect(result.manaRegen).toBe(2); - }); - - it('should be 12 at max level 10', () => { - const result = computeStats({ manaFlow: 10 }, emptyPrestige); - expect(result.manaRegen).toBe(12); - }); - }); - - describe('Mana Tap skill', () => { - it('should add 1 click mana at level 1', () => { - const result = computeStats({ manaTap: 1 }, emptyPrestige); - expect(result.clickMana).toBe(2); - }); - - it('should be 1 at level 0', () => { - const result = computeStats({ manaTap: 0 }, emptyPrestige); - expect(result.clickMana).toBe(1); - }); - }); - - describe('Mana Surge skill', () => { - it('should add 3 click mana per level', () => { - const result = computeStats({ manaSurge: 1 }, emptyPrestige); - expect(result.clickMana).toBe(1 + 3); - }); - - it('should stack with manaTap', () => { - const result = computeStats({ manaTap: 1, manaSurge: 1 }, emptyPrestige); - expect(result.clickMana).toBe(1 + 1 + 3); - }); - }); - - describe('Mana Spring skill', () => { - it('should add 2 mana regen at level 1', () => { - const result = computeStats({ manaSpring: 1 }, emptyPrestige); - expect(result.manaRegen).toBe(2 + 2); - }); - - it('should stack with manaFlow', () => { - const result = computeStats({ manaFlow: 5, manaSpring: 1 }, emptyPrestige); - expect(result.manaRegen).toBe(2 + 5 + 2); - }); - }); - - describe('Mana Overflow skill', () => { - it('should multiply click mana by compounding 1.25 per level', () => { - // multiply effects compound per level: 1 * 1.25^2 = 1.5625 - const result = computeStats({ manaOverflow: 2 }, emptyPrestige); - expect(result.clickMana).toBeCloseTo(1.5625, 2); - }); - - it('should be 1 at level 0', () => { - const result = computeStats({ manaOverflow: 0 }, emptyPrestige); - expect(result.clickMana).toBe(1); - }); - }); - - describe('Quick Learner skill', () => { - it('should multiply study speed by compounding 1.10 per level', () => { - // 1.1^5 = 1.61051 - const result = computeStats({ quickLearner: 5 }, emptyPrestige); - expect(result.studySpeed).toBeCloseTo(1.61051, 2); - }); - - it('should be ~2.5937 at max level 10', () => { - const result = computeStats({ quickLearner: 10 }, emptyPrestige); - expect(result.studySpeed).toBeCloseTo(2.593742, 2); - }); - }); - - describe('Focused Mind skill', () => { - it('should multiply study cost by compounding 0.95 per level', () => { - // 0.95^2 = 0.9025 - const result = computeStats({ focusedMind: 2 }, emptyPrestige); - expect(result.studyCostMult).toBeCloseTo(0.9025, 2); - }); - }); - - describe('Meditation Focus skill', () => { - it('should multiply meditation efficiency at level 1', () => { - // BASE (1) * (1 + 1.5) = 2.5 - const result = computeStats({ meditation: 1 }, emptyPrestige); - expect(result.meditationEfficiency).toBe(2.5); - }); - }); - - describe('Deep Trance skill', () => { - it('should multiply meditation efficiency further', () => { - // meditation: 1 * (1+1.5) = 2.5, deepTrance: 2.5 * (1+1.8) = 7.0 - const result = computeStats({ meditation: 1, deepTrance: 1 }, emptyPrestige); - expect(result.meditationEfficiency).toBe(7.0); - }); - }); - - describe('Void Meditation skill', () => { - it('should multiply meditation efficiency to max', () => { - // 1 * 2.5 * 2.8 * 3.5 = 24.5 - const result = computeStats({ meditation: 1, deepTrance: 1, voidMeditation: 1 }, emptyPrestige); - expect(result.meditationEfficiency).toBe(24.5); - }); - }); - - describe('Combat skills', () => { - it('Arcane Fury should multiply damage', () => { - const result = computeStats({ arcaneFury: 5 }, emptyPrestige); - expect(result.damageMultiplier).toBeCloseTo(1.61051, 2); - }); - - it('Combat Training should add base damage', () => { - const result = computeStats({ combatTraining: 3 }, emptyPrestige); - expect(result.baseDamage).toBe(5 + 3 * 5); - }); - - it('Precision should add crit chance', () => { - const result = computeStats({ precision: 4 }, emptyPrestige); - expect(result.critChance).toBe(0.2); - }); - - it('Precision should cap at 1.0', () => { - const result = computeStats({ precision: 25 }, emptyPrestige); - expect(result.critChance).toBe(1.0); - }); - - it('Elemental Mastery should multiply elemental damage', () => { - // 1 * 1.15^4 = ~1.749 - const result = computeStats({ elementalMastery: 4 }, emptyPrestige); - expect(result.elementalDamage).toBeCloseTo(1.749, 2); - }); - - it('Attack Speed should multiply attack speed (compounding)', () => { - // 1 * 0.9^3 = 0.729 - const result = computeStats({ attackSpeed: 3 }, emptyPrestige); - expect(result.attackSpeed).toBeCloseTo(0.729, 2); - }); - - it('Armor Piercing should add armor pierce', () => { - const result = computeStats({ armorPiercing: 4 }, emptyPrestige); - expect(result.armorPierce).toBe(0.2); - }); - }); - - describe('Enchanting skills', () => { - it('Enchanting should affect enchantCapacity and enchantSpeed', () => { - const result = computeStats({ enchanting: 5 }, emptyPrestige); - expect(result.enchantCapacity).toBe(100 + 5 * 10); // add 10 per level - expect(result.enchantSpeed).toBeCloseTo(0.9, 2); // multiply by 0.98^5 - }); - - it('Essence Refining should multiply enchantPower', () => { - const result = computeStats({ enchanting: 4, essenceRefining: 1 }, emptyPrestige); - expect(result.enchantPower).toBeCloseTo(1.1, 2); - }); - }); - - describe('Golemancy skills', () => { - it('Golem Mastery should multiply golemDamage', () => { - const result = computeStats({ golemMastery: 5 }, emptyPrestige); - expect(result.golemDamage).toBeCloseTo(1.61051, 2); - }); - - it('Golem Longevity should add golemDuration', () => { - const result = computeStats({ golemLongevity: 3 }, emptyPrestige); - expect(result.golemDuration).toBe(1 + 3); - }); - - it('Golem Efficiency should multiply attackSpeed', () => { - const result = computeStats({ golemEfficiency: 2 }, emptyPrestige); - expect(result.attackSpeed).toBeCloseTo(0.9025, 2); // 0.95^2 - }); - }); - - describe('Invocation / Pact skills', () => { - it('Invocation should multiply spellDamage', () => { - const result = computeStats({ invocation: 5 }, emptyPrestige); - expect(result.spellDamage).toBeCloseTo(1.27628, 2); // 1.05^5 - }); - - it('Pact Mastery should multiply pactMultiplier', () => { - const result = computeStats({ pactMastery: 5 }, emptyPrestige); - expect(result.pactMultiplier).toBeCloseTo(1.61051, 2); // 1.1^5 - }); - - it('Guardian Lore should multiply guardianDamage', () => { - const result = computeStats({ guardianLore: 3 }, emptyPrestige); - expect(result.guardianDamage).toBeCloseTo(1.728, 2); // 1.2^3 - }); - }); - - describe('Element capacity skills', () => { - it('Fire Mana Cap should increase fireCap', () => { - const result = computeStats({ fireManaCap: 5 }, emptyPrestige); - expect(result.fireCap).toBe(5 * 10); - }); - - it('Multiple element caps should contribute to elementCap', () => { - const result = computeStats({ fireManaCap: 3, waterManaCap: 2 }, emptyPrestige); - // fireCap=30, waterCap=20 -> elementCap = 10 + 30 + 20 = 60 - expect(result.elementCap).toBe(60); - }); - - it('All base element caps should contribute', () => { - const result = computeStats({ - fireManaCap: 10, waterManaCap: 10, airManaCap: 10, earthManaCap: 10, - }, emptyPrestige); - // Each adds 10*10=100, total 400 + base 10 = 410 - expect(result.elementCap).toBe(410); - }); - }); - - describe('Hybrid skills', () => { - it('Pact-Weaving should multiply enchantPower', () => { - const result = computeStats({ pactWeaving: 3 }, emptyPrestige); - expect(result.enchantPower).toBeCloseTo(1.331, 2); // 1.1^3 - }); - - it('Guardian Constructs should affect golemDamage and golemDuration', () => { - const result = computeStats({ guardianConstructs: 2 }, emptyPrestige); - expect(result.golemDamage).toBeCloseTo(1.3225, 2); // 1.15^2 - expect(result.golemDuration).toBe(1.5); // add 0.25*2=0.5, but cap floor is 1, so 1 + 0.5 = 1.5 - }); - - it('Enchanted Golemancy should affect enchantPower and golemDamage', () => { - const result = computeStats({ enchantedGolemancy: 3 }, emptyPrestige); - expect(result.enchantPower).toBeCloseTo(1.157625, 2); // 1.05^3 - expect(result.golemDamage).toBeCloseTo(1.331, 2); // 1.1^3 - }); - }); - - describe('Prestige upgrades', () => { - it('manaWell prestige should increase maxMana', () => { - const result = computeStats({}, { manaWell: 3 }); - expect(result.maxMana).toBe(100 + 3 * 500); - }); - - it('manaFlow prestige should increase manaRegen', () => { - const result = computeStats({}, { manaFlow: 2 }); - expect(result.manaRegen).toBe(2 + 2 * 0.5); - }); - - it('elementalAttune prestige should increase elementCap', () => { - const result = computeStats({}, { elementalAttune: 4 }); - expect(result.elementCap).toBe(10 + 4 * 25); - }); - - it('pactBinding prestige should increase pactMultiplier', () => { - const result = computeStats({}, { pactBinding: 2 }); - expect(result.pactMultiplier).toBe(1 + 2 * 0.1); - }); - - it('insightAmp prestige should multiply insightGain', () => { - const result = computeStats({}, { insightAmp: 2 }); - expect(result.insightGain).toBe(1 + 1 * 0.25 * 2); - }); - - it('prestige should stack with skills', () => { - const result = computeStats({ manaWell: 5 }, { manaWell: 3 }); - expect(result.maxMana).toBe(100 + 5 * 100 + 3 * 500); - }); - }); - - describe('Skill stacking', () => { - it('should correctly stack multiple skills', () => { - const result = computeStats({ - manaWell: 3, - manaFlow: 2, - manaTap: 1, - precision: 4, - }, emptyPrestige); - - expect(result.maxMana).toBe(100 + 300); - expect(result.manaRegen).toBe(2 + 2); - expect(result.clickMana).toBe(1 + 1); - expect(result.critChance).toBe(0.2); - }); - - it('should handle all skills with effects at once without interference', () => { - // Only test skills that have effects defined (skip research skills with empty effects) - const allSkillsWithEffects: Record = {}; - for (const [id, def] of Object.entries(SKILLS_V2)) { - if (def.effects.length > 0) { - allSkillsWithEffects[id] = 1; - } - } - const result = computeStats(allSkillsWithEffects, emptyPrestige); - // Should not throw and should produce reasonable values - expect(result.maxMana).toBeGreaterThan(0); - expect(result.manaRegen).toBeGreaterThan(0); - expect(result.baseDamage).toBeGreaterThan(0); - expect(result.elementCap).toBeGreaterThanOrEqual(10); - }); - }); - - describe('edge cases', () => { - it('should ignore negative levels (no effect applied)', () => { - const result = computeStats({}, emptyPrestige); - expect(result.maxMana).toBe(100); - }); - - it('should ignore unknown skill IDs', () => { - const result = computeStats({ unknownSkill: 5 } as any, emptyPrestige); - expect(result.maxMana).toBe(100); - }); - - it('should clamp critChance to 1.0', () => { - const result = computeStats({ precision: 100 } as any, emptyPrestige); - expect(result.critChance).toBe(1.0); - }); - - it('should clamp armorPierce to 1.0', () => { - const result = computeStats({ armorPiercing: 100 } as any, emptyPrestige); - expect(result.armorPierce).toBe(1.0); - }); - - it('should clamp attackSpeed minimum to 0.1', () => { - const result = computeStats({ attackSpeed: 100 } as any, emptyPrestige); - expect(result.attackSpeed).toBe(0.1); - }); - - it('should clamp maxMana minimum to 1', () => { - const result = computeStats({}, emptyPrestige); - expect(result.maxMana).toBeGreaterThanOrEqual(1); - }); - - it('should clamp baseDamage minimum to 1', () => { - const result = computeStats({}, emptyPrestige); - expect(result.baseDamage).toBeGreaterThanOrEqual(1); - }); - }); -}); - -describe('getBaseSkillId()', () => { - it('should strip _tN suffix for tiered skills', () => { - expect(getBaseSkillId('manaWell_t2')).toBe('manaWell'); - expect(getBaseSkillId('manaWell_t5')).toBe('manaWell'); - expect(getBaseSkillId('quickLearner_t3')).toBe('quickLearner'); - }); - - it('should return same ID for non-tiered skills', () => { - expect(getBaseSkillId('manaWell')).toBe('manaWell'); - expect(getBaseSkillId('fireManaCap')).toBe('fireManaCap'); - }); -}); - -describe('hasPrerequisites()', () => { - it('should return true when no prerequisites', () => { - expect(hasPrerequisites({}, undefined)).toBe(true); - expect(hasPrerequisites({}, {})).toBe(true); - }); - - it('should return true when prerequisites are met', () => { - expect(hasPrerequisites({ manaWell: 5 }, { manaWell: 3 })).toBe(true); - }); - - it('should return false when prerequisites are not met', () => { - expect(hasPrerequisites({ manaWell: 2 }, { manaWell: 3 })).toBe(false); - expect(hasPrerequisites({}, { manaWell: 1 })).toBe(false); - }); - - it('should check multiple prerequisites', () => { - expect(hasPrerequisites({ a: 2, b: 3 }, { a: 1, b: 2 })).toBe(true); - expect(hasPrerequisites({ a: 2, b: 1 }, { a: 1, b: 2 })).toBe(false); - }); -}); - -describe('SKILLS_V2', () => { - it('should have manaWell defined', () => { - expect(SKILLS_V2.manaWell).toBeDefined(); - expect(SKILLS_V2.manaWell.id).toBe('manaWell'); - expect(SKILLS_V2.manaWell.maxLevel).toBe(10); - expect(SKILLS_V2.manaWell.effects).toHaveLength(1); - }); - - it('should have all core skills defined', () => { - const coreSkills = ['manaWell', 'manaFlow', 'quickLearner', 'focusedMind', 'meditation']; - for (const id of coreSkills) { - expect(SKILLS_V2[id]).toBeDefined(); - expect(SKILLS_V2[id].effects.length).toBeGreaterThan(0); - } - }); - - it('should have correct manaWell effect', () => { - const effect = SKILLS_V2.manaWell.effects[0]; - expect(effect.stat).toBe('maxMana'); - expect(effect.mode).toBe('add'); - expect(effect.valuePerLevel).toBe(100); - }); - - it('should have correct manaFlow effect', () => { - const effect = SKILLS_V2.manaFlow.effects[0]; - expect(effect.stat).toBe('manaRegen'); - expect(effect.mode).toBe('add'); - expect(effect.valuePerLevel).toBe(1); - }); - - it('should handle prerequisite fields', () => { - expect(SKILLS_V2.manaOverflow.prerequisites).toEqual({ manaWell: 3 }); - expect(SKILLS_V2.deepTrance.prerequisites).toEqual({ meditation: 1 }); - expect(SKILLS_V2.manaTap.prerequisites).toBeUndefined(); - }); - - it('should handle attunementRequired fields', () => { - expect(SKILLS_V2.enchanting.attunementRequired).toBe('enchanter'); - expect(SKILLS_V2.invocation.attunementRequired).toBe('invoker'); - expect(SKILLS_V2.golemMastery.attunementRequired).toBe('fabricator'); - expect(SKILLS_V2.manaWell.attunementRequired).toBeUndefined(); - }); -}); - -console.log('✅ computeStats() and skill v2 tests defined.'); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/equipment.test.ts b/src/lib/game/stores/__tests__/equipment.test.ts deleted file mode 100644 index d6f38c0..0000000 --- a/src/lib/game/stores/__tests__/equipment.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { useCraftingStore } from '@/lib/game/stores'; - -describe('useCraftingStore - Equipment Actions', () => { - beforeEach(() => { - // Reset to initial state - useCraftingStore.setState({ - equipmentInstances: {}, - equippedInstances: {}, - }); - }); - - it('equipItem sets equippedInstances[slot] to instanceId', () => { - const instanceId = 'test-instance-1'; - const slot = 'mainHand'; - - // First, add the instance to equipmentInstances - useCraftingStore.setState((state) => ({ - equipmentInstances: { - ...state.equipmentInstances, - [instanceId]: { - id: instanceId, - equipmentId: 'test-equip', - name: 'Test Sword', - rarity: 'common', - level: 1, - upgrades: [], - createdAt: Date.now(), - } as any, - }, - })); - - // Equip the item - note: equipItem might take (slot, instanceId) - const state = useCraftingStore.getState(); - if (state.equipItem) { - state.equipItem(slot, instanceId); - expect(useCraftingStore.getState().equippedInstances[slot]).toBe(instanceId); - } - }); - - it('unequipItem sets equippedInstances[slot] to null', () => { - const instanceId = 'test-instance-1'; - const slot = 'mainHand'; - - // First equip the item - useCraftingStore.setState((state) => ({ - equipmentInstances: { - ...state.equipmentInstances, - [instanceId]: { - id: instanceId, - equipmentId: 'test-equip', - name: 'Test Sword', - rarity: 'common', - level: 1, - upgrades: [], - createdAt: Date.now(), - } as any, - }, - equippedInstances: { - ...state.equippedInstances, - [slot]: instanceId, - }, - })); - - // Unequip the item - const state = useCraftingStore.getState(); - if (state.unequipItem) { - state.unequipItem(slot); - expect(useCraftingStore.getState().equippedInstances[slot]).toBeNull(); - } - }); - - it('deleteEquipmentInstance removes from both equippedInstances and equipmentInstances', () => { - const instanceId = 'test-instance-1'; - const slot = 'mainHand'; - - // Add and equip the item - useCraftingStore.setState((state) => ({ - equipmentInstances: { - ...state.equipmentInstances, - [instanceId]: { - id: instanceId, - equipmentId: 'test-equip', - name: 'Test Sword', - rarity: 'common', - level: 1, - upgrades: [], - createdAt: Date.now(), - } as any, - }, - equippedInstances: { - ...state.equippedInstances, - [slot]: instanceId, - }, - })); - - // Delete the item - const state = useCraftingStore.getState(); - if (state.deleteEquipmentInstance) { - state.deleteEquipmentInstance(instanceId); - const newState = useCraftingStore.getState(); - expect(newState.equipmentInstances[instanceId]).toBeUndefined(); - expect(newState.equippedInstances[slot]).toBeNull(); - } - }); -}); diff --git a/src/lib/game/stores/__tests__/index-tests/combat-calculations.test.ts b/src/lib/game/stores/__tests__/index-tests/combat-calculations.test.ts deleted file mode 100644 index d9e0611..0000000 --- a/src/lib/game/stores/__tests__/index-tests/combat-calculations.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Combat Calculation Tests for Stores Index - */ - -import { describe, it, expect } from 'vitest'; -import { calcDamage, getFloorMaxHP, getFloorElement } from '@/lib/game/stores/index'; -import { GUARDIANS } from '@/lib/game/constants'; -import type { GameState } from '../../../types'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference', 'metal', 'sand', 'crystal', 'stellar', 'void', 'lightning'].forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - defeatedGuardians: [], - signedPacts: [], - pactSlots: 1, - pactRitualFloor: null, - pactRitualProgress: 0, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { - manaBolt: { learned: true, level: 1, studyProgress: 0 }, - fireball: { learned: true, level: 1, studyProgress: 0 }, - waterJet: { learned: true, level: 1, studyProgress: 0 }, - }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - ...overrides, - } as GameState; -} - -describe('Combat Calculations', () => { - describe('calcDamage', () => { - it('should return spell base damage with no bonuses', () => { - const state = createMockState(); - const dmg = calcDamage(state, 'manaBolt'); - expect(dmg).toBeGreaterThanOrEqual(5); // Base damage (can be higher with crit) - }); - - it('should have elemental bonuses', () => { - const state = createMockState({ - spells: { - manaBolt: { learned: true, level: 1 }, - fireball: { learned: true, level: 1 }, - waterJet: { learned: true, level: 1 }, - } - }); - // Test elemental bonus by comparing same spell vs different elements - // Fireball vs fire floor (same element, +25%) vs vs air floor (neutral) - let fireVsFire = 0, fireVsAir = 0; - for (let i = 0; i < 100; i++) { - fireVsFire += calcDamage(state, 'fireball', 'fire'); - fireVsAir += calcDamage(state, 'fireball', 'air'); - } - const sameAvg = fireVsFire / 100; - const neutralAvg = fireVsAir / 100; - // Same element should do more damage - expect(sameAvg).toBeGreaterThan(neutralAvg * 1.1); - }); - }); - - describe('getFloorMaxHP', () => { - it('should return guardian HP for guardian floors', () => { - expect(getFloorMaxHP(10)).toBe(GUARDIANS[10].hp); - expect(getFloorMaxHP(100)).toBe(GUARDIANS[100].hp); - }); - - it('should scale HP for non-guardian floors', () => { - expect(getFloorMaxHP(1)).toBeGreaterThan(0); - expect(getFloorMaxHP(5)).toBeGreaterThan(getFloorMaxHP(1)); - }); - }); - - describe('getFloorElement', () => { - it('should cycle through elements in order', () => { - // FLOOR_ELEM_CYCLE has 7 elements: fire, water, air, earth, light, dark, death - expect(getFloorElement(1)).toBe('fire'); - expect(getFloorElement(2)).toBe('water'); - expect(getFloorElement(3)).toBe('air'); - expect(getFloorElement(4)).toBe('earth'); - expect(getFloorElement(5)).toBe('light'); - expect(getFloorElement(6)).toBe('dark'); - expect(getFloorElement(7)).toBe('death'); - }); - - it('should wrap around after 7 floors', () => { - // Floor 8 should be fire (wraps around) - expect(getFloorElement(8)).toBe('fire'); - expect(getFloorElement(9)).toBe('water'); - expect(getFloorElement(15)).toBe('fire'); // (15-1) % 7 = 0 - expect(getFloorElement(16)).toBe('water'); // (16-1) % 7 = 1 - }); - }); -}); - -console.log('✅ Combat calculation tests defined.'); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/index-tests/definitions.test.ts b/src/lib/game/stores/__tests__/index-tests/definitions.test.ts deleted file mode 100644 index 4fb4229..0000000 --- a/src/lib/game/stores/__tests__/index-tests/definitions.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Skill and Prestige Definition Tests for Stores Index - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF, PRESTIGE_DEF, GUARDIANS } from '@/lib/game/constants'; -import type { GameState } from '../../../types'; - -describe('Skill Definitions', () => { - it('all skills should have valid categories', () => { - const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant', - 'effectResearch', 'invocation', 'pact', 'fabrication', 'golemancy', 'craft', 'hybrid']; - Object.values(SKILLS_DEF).forEach(skill => { - expect(validCategories).toContain(skill.cat); - }); - }); - - it('all skills should have reasonable study times', () => { - Object.values(SKILLS_DEF).forEach(skill => { - expect(skill.studyTime).toBeGreaterThan(0); - expect(skill.studyTime).toBeLessThanOrEqual(72); - }); - }); - - it('all prerequisite skills should exist', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.keys(skill.req).forEach(reqId => { - expect(SKILLS_DEF[reqId]).toBeDefined(); - }); - } - }); - }); - - it('all prerequisite levels should be within skill max', () => { - Object.entries(SKILLS_DEF).forEach(([id, skill]) => { - if (skill.req) { - Object.entries(skill.req).forEach(([reqId, reqLevel]) => { - expect(reqLevel).toBeLessThanOrEqual(SKILLS_DEF[reqId].max); - }); - } - }); - }); -}); - -describe('Prestige Upgrades', () => { - it('all prestige upgrades should have valid costs', () => { - Object.entries(PRESTIGE_DEF).forEach(([id, upgrade]) => { - expect(upgrade.cost).toBeGreaterThan(0); - expect(upgrade.max).toBeGreaterThan(0); - }); - }); - - it('Mana Well prestige should add 500 starting max mana', () => { - // Need to import compute functions - this test is simpler in the actual refactored files - // For now, just test the definition - expect(PRESTIGE_DEF.manaWell).toBeDefined(); - expect(PRESTIGE_DEF.manaWell.cost).toBeGreaterThan(0); - }); - - it('Elemental Attunement prestige should add 25 element cap', () => { - expect(PRESTIGE_DEF.elementalAttune).toBeDefined(); - expect(PRESTIGE_DEF.elementalAttune.cost).toBeGreaterThan(0); - }); -}); - -describe('Guardian Definitions', () => { - it('should have guardians on expected floors (no floor 70)', () => { - // Floor 70 was removed from the game - [10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => { - expect(GUARDIANS[floor]).toBeDefined(); - }); - }); - - it('should have increasing HP', () => { - let prevHP = 0; - [10, 20, 30, 40, 50, 60, 80, 90, 100].forEach(floor => { - expect(GUARDIANS[floor].hp).toBeGreaterThan(prevHP); - prevHP = GUARDIANS[floor].hp; - }); - }); - - it('should have boons defined', () => { - Object.values(GUARDIANS).forEach(guardian => { - expect(guardian.boons).toBeDefined(); - expect(guardian.boons.length).toBeGreaterThan(0); - }); - }); - - it('should have pact costs defined', () => { - Object.values(GUARDIANS).forEach(guardian => { - expect(guardian.pactCost).toBeGreaterThan(0); - expect(guardian.pactTime).toBeGreaterThan(0); - }); - }); -}); - -console.log('✅ Skill, prestige, and guardian definition tests defined.'); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/index-tests/mana-calculations.test.ts b/src/lib/game/stores/__tests__/index-tests/mana-calculations.test.ts deleted file mode 100644 index 3962301..0000000 --- a/src/lib/game/stores/__tests__/index-tests/mana-calculations.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Mana Calculation Tests for Stores Index - */ - -import { describe, it, expect } from 'vitest'; -import { computeMaxMana, computeRegen, computeClickMana, computeElementMax } from '@/lib/game/stores/index'; -import type { GameState } from '../../../types'; -import { ELEMENTS } from '@/lib/game/constants'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - defeatedGuardians: [], - signedPacts: [], - pactSlots: 1, - pactRitualFloor: null, - pactRitualProgress: 0, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - invoker: { id: 'invoker', active: false, level: 1, experience: 0 }, - fabricator: { id: 'fabricator', active: false, level: 1, experience: 0 }, - }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - equippedInstances: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory1: null, accessory2: null }, - equipmentInstances: {}, - ...overrides, - } as GameState; -} - -describe('Mana Calculations', () => { - describe('computeMaxMana', () => { - it('should return base mana with no upgrades', () => { - const state = createMockState(); - expect(computeMaxMana(state)).toBe(100); - }); - - it('should add mana from manaWell skill', () => { - const state = createMockState({ skills: { manaWell: 5 } }); - expect(computeMaxMana(state)).toBe(100 + 5 * 100); - }); - - it('should add mana from prestige upgrades', () => { - const state = createMockState({ prestigeUpgrades: { manaWell: 3 } }); - expect(computeMaxMana(state)).toBe(100 + 3 * 500); - }); - - it('should stack manaWell skill and prestige', () => { - const state = createMockState({ - skills: { manaWell: 5 }, - prestigeUpgrades: { manaWell: 2 }, - }); - expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500); - }); - }); - - describe('computeRegen', () => { - it('should return base regen with no upgrades', () => { - // Base regen is 2 (attunement regen is added separately in the store) - const state = createMockState(); - expect(computeRegen(state)).toBe(2); - }); - - it('should add regen from manaFlow skill', () => { - // Base 2 + manaFlow 5 - const state = createMockState({ skills: { manaFlow: 5 } }); - expect(computeRegen(state)).toBe(2 + 5 * 1); - }); - - it('should add regen from manaSpring skill', () => { - // Base 2 + manaSpring 2 - const state = createMockState({ skills: { manaSpring: 1 } }); - expect(computeRegen(state)).toBe(2 + 2); - }); - - it('should multiply by temporal echo prestige', () => { - const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } }); - // Base 2 * 1.2 = 2.4 - expect(computeRegen(state)).toBe(2 * 1.2); - }); - }); - - describe('computeClickMana', () => { - it('should return base click mana with no upgrades', () => { - const state = createMockState(); - expect(computeClickMana(state)).toBe(1); - }); - - it('should add mana from manaTap skill', () => { - const state = createMockState({ skills: { manaTap: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1); - }); - - it('should add mana from manaSurge skill', () => { - const state = createMockState({ skills: { manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 3); - }); - - it('should stack manaTap and manaSurge', () => { - const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1 + 3); - }); - }); - - describe('computeElementMax', () => { - it('should return base element cap with no upgrades', () => { - const state = createMockState(); - expect(computeElementMax(state)).toBe(10); - }); - - it('should add cap from elemAttune skill', () => { - const state = createMockState({ skills: { elemAttune: 5 } }); - expect(computeElementMax(state)).toBe(10 + 5 * 50); - }); - - it('should add cap from prestige upgrades', () => { - const state = createMockState({ prestigeUpgrades: { elementalAttune: 3 } }); - expect(computeElementMax(state)).toBe(10 + 3 * 25); - }); - }); -}); - -console.log('✅ Mana calculation tests defined.'); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/index-tests/meditation-insight-incursion.test.ts b/src/lib/game/stores/__tests__/index-tests/meditation-insight-incursion.test.ts deleted file mode 100644 index 1ac032e..0000000 --- a/src/lib/game/stores/__tests__/index-tests/meditation-insight-incursion.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Meditation, Insight, and Incursion Tests for Stores Index - */ - -import { describe, it, expect } from 'vitest'; -import { getMeditationBonus, calcInsight, getIncursionStrength } from '@/lib/game/stores/index'; -import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants'; -import type { GameState } from '../../../types'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death', 'transference'].forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - defeatedGuardians: [], - signedPacts: [], - pactSlots: 1, - pactRitualFloor: null, - pactRitualProgress: 0, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - }, - golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 }, - equippedInstances: {}, - ...overrides, - } as GameState; -} - -// ─── Meditation Tests ────────────────────────────────────────────── - -describe('Meditation Bonus', () => { - describe('getMeditationBonus', () => { - it('should start at 1x with no meditation', () => { - expect(getMeditationBonus(0, {})).toBe(1); - }); - - it('should ramp up over time', () => { - const bonus1hr = getMeditationBonus(25, {}); // 1 hour of ticks - expect(bonus1hr).toBeGreaterThan(1); - - const bonus4hr = getMeditationBonus(100, {}); // 4 hours - expect(bonus4hr).toBeGreaterThan(bonus1hr); - }); - - it('should cap at 1.5x without meditation skill', () => { - const bonus = getMeditationBonus(200, {}); // 8 hours - expect(bonus).toBe(1.5); - }); - - it('should give 2.5x with meditation skill after 4 hours', () => { - const bonus = getMeditationBonus(100, { meditation: 1 }); - expect(bonus).toBe(2.5); - }); - - it('should give 3.0x with deepTrance skill after 6 hours', () => { - const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 }); - expect(bonus).toBe(3.0); - }); - - it('should give 5.0x with voidMeditation skill after 8 hours', () => { - const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 }); - expect(bonus).toBe(5.0); - }); - }); -}); - -// ─── Insight Tests ──────────────────────────────────────────────── - -describe('Insight Calculations', () => { - describe('calcInsight', () => { - it('should calculate insight from floor progress', () => { - const state = createMockState({ maxFloorReached: 10 }); - const insight = calcInsight(state); - expect(insight).toBe(10 * 15); - }); - - it('should calculate insight from mana gathered', () => { - const state = createMockState({ totalManaGathered: 5000 }); - const insight = calcInsight(state); - // 1*15 + 5000/500 + 0 = 25 - expect(insight).toBe(25); - }); - - it('should calculate insight from signed pacts', () => { - const state = createMockState({ signedPacts: [10, 20] }); - const insight = calcInsight(state); - // 1*15 + 0 + 2*150 = 315 - expect(insight).toBe(315); - }); - - it('should multiply by insightAmp prestige', () => { - const state = createMockState({ - maxFloorReached: 10, - prestigeUpgrades: { insightAmp: 2 }, - }); - const insight = calcInsight(state); - expect(insight).toBe(Math.floor(10 * 15 * 1.5)); - }); - - it('should multiply by insightHarvest skill', () => { - const state = createMockState({ - maxFloorReached: 10, - skills: { insightHarvest: 3 }, - }); - const insight = calcInsight(state); - expect(insight).toBe(Math.floor(10 * 15 * 1.3)); - }); - }); -}); - -// ─── Incursion Tests ────────────────────────────────────────────── - -describe('Incursion Strength', () => { - describe('getIncursionStrength', () => { - it('should be 0 before incursion start day', () => { - expect(getIncursionStrength(19, 0)).toBe(0); - expect(getIncursionStrength(19, 23)).toBe(0); - }); - - it('should start at incursion start day', () => { - expect(getIncursionStrength(INCURSION_START_DAY, 1)).toBeGreaterThan(0); - }); - - it('should increase over time', () => { - const early = getIncursionStrength(INCURSION_START_DAY, 12); - const late = getIncursionStrength(25, 12); - expect(late).toBeGreaterThan(early); - }); - - it('should cap at 95%', () => { - const strength = getIncursionStrength(MAX_DAY, 23); - expect(strength).toBeLessThanOrEqual(0.95); - }); - }); -}); - -console.log('✅ Meditation, insight, and incursion tests defined.'); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/index-tests/spell-cost.test.ts b/src/lib/game/stores/__tests__/index-tests/spell-cost.test.ts deleted file mode 100644 index bacafa6..0000000 --- a/src/lib/game/stores/__tests__/index-tests/spell-cost.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Spell Cost Tests for Stores Index - */ - -import { describe, it, expect } from 'vitest'; -import { canAffordSpellCost } from '@/lib/game/stores/index'; -import { rawCost, elemCost } from '@/lib/game/constants'; - -describe('Spell Cost System', () => { - describe('rawCost', () => { - it('should create a raw mana cost', () => { - const cost = rawCost(10); - expect(cost.type).toBe('raw'); - expect(cost.amount).toBe(10); - }); - }); - - describe('elemCost', () => { - it('should create an elemental mana cost', () => { - const cost = elemCost('fire', 5); - expect(cost.type).toBe('element'); - expect(cost.element).toBe('fire'); - }); - }); - - describe('canAffordSpellCost', () => { - it('should allow raw mana costs when enough raw mana', () => { - const cost = { type: 'raw' as const, amount: 10 }; - const elements = { fire: { current: 0, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 100, elements)).toBe(true); - }); - - it('should deny raw mana costs when not enough raw mana', () => { - const cost = { type: 'raw' as const, amount: 100 }; - const elements = { fire: { current: 0, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 50, elements)).toBe(false); - }); - - it('should allow elemental costs when enough element mana', () => { - const cost = { type: 'element' as const, element: 'fire', amount: 5 }; - const elements = { fire: { current: 10, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 0, elements)).toBe(true); - }); - - it('should deny elemental costs when element not unlocked', () => { - const cost = { type: 'element' as const, element: 'fire', amount: 5 }; - const elements = { fire: { current: 10, max: 10, unlocked: false } }; - expect(canAffordSpellCost(cost, 100, elements)).toBe(false); - }); - }); -}); - -console.log('✅ Spell cost tests defined.'); diff --git a/src/lib/game/stores/__tests__/index-tests/study-speed.test.ts b/src/lib/game/stores/__tests__/index-tests/study-speed.test.ts deleted file mode 100644 index dafe49f..0000000 --- a/src/lib/game/stores/__tests__/index-tests/study-speed.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Study Speed Function Tests for Stores Index - */ - -import { describe, it, expect } from 'vitest'; -import { getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/stores/index'; - -describe('Study Speed Functions', () => { - describe('getStudySpeedMultiplier', () => { - it('should return 1 with no quickLearner skill', () => { - expect(getStudySpeedMultiplier({})).toBe(1); - }); - - it('should increase by 10% per level', () => { - expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1); - expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5); - expect(getStudySpeedMultiplier({ quickLearner: 10 })).toBe(2.0); - }); - }); - - describe('getStudyCostMultiplier', () => { - it('should return 1 with no focusedMind skill', () => { - expect(getStudyCostMultiplier({})).toBe(1); - }); - - it('should decrease by 5% per level', () => { - expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95); - expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75); - expect(getStudyCostMultiplier({ focusedMind: 10 })).toBe(0.5); - }); - }); -}); - -console.log('✅ Study speed function tests defined.'); diff --git a/src/lib/game/stores/__tests__/index-tests/utility-functions.test.ts b/src/lib/game/stores/__tests__/index-tests/utility-functions.test.ts deleted file mode 100644 index 0449fa3..0000000 --- a/src/lib/game/stores/__tests__/index-tests/utility-functions.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Utility Function Tests for Stores - */ - -import { describe, it, expect } from 'vitest'; -import { fmt, fmtDec } from '@/lib/game/stores/index'; - -describe('Utility Functions', () => { - describe('fmt', () => { - it('should format small numbers', () => { - expect(fmt(0)).toBe('0'); - expect(fmt(1)).toBe('1'); - expect(fmt(999)).toBe('999'); - }); - - it('should format thousands', () => { - expect(fmt(1000)).toBe('1.0K'); - expect(fmt(1500)).toBe('1.5K'); - }); - - it('should format millions', () => { - expect(fmt(1000000)).toBe('1.00M'); - expect(fmt(1500000)).toBe('1.50M'); - }); - - it('should format billions', () => { - expect(fmt(1000000000)).toBe('1.00B'); - }); - }); - - describe('fmtDec', () => { - it('should format decimals', () => { - expect(fmtDec(1.234, 2)).toBe('1.23'); - expect(fmtDec(1.5, 1)).toBe('1.5'); - }); - }); -}); - -console.log('✅ Utility function tests defined.'); diff --git a/src/lib/game/stores/__tests__/mana-conversion-fix.test.ts b/src/lib/game/stores/__tests__/mana-conversion-fix.test.ts deleted file mode 100644 index 475a3e9..0000000 --- a/src/lib/game/stores/__tests__/mana-conversion-fix.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { useManaStore } from '../manaStore'; -import { useGameStore } from '../gameStore'; -import { useAttunementStore } from '../attunementStore'; -import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements'; - -describe('Mana Conversion Fix - Attunements deduct from regen, not pool', () => { - beforeEach(() => { - // Reset all stores - useManaStore.setState({ - rawMana: 100, - elements: Object.fromEntries( - Object.keys(useManaStore.getState().elements).map(k => [ - k, - { current: 0, max: 10, unlocked: k === 'transference' } - ]) - ), - }); - - useAttunementStore.setState({ - attunements: { - enchanter: { active: true, level: 1 } - } - }); - }); - - it('should deduct conversion cost from regen, not mana pool', () => { - const initialState = useManaStore.getState(); - const initialRawMana = initialState.rawMana; - - // Run a few ticks - for (let i = 0; i < 10; i++) { - useGameStore.getState().tick(); - } - - const finalState = useManaStore.getState(); - // Mana pool should not be drained by conversion (only regen is reduced) - expect(finalState.rawMana).toBeGreaterThan(initialRawMana - 50); // Should not drop significantly - }); - - it('should reduce effective regen by conversion rate', () => { - // The conversion rate is subtracted from effective regen in gameStore.ts - // This is tested implicitly in the tick tests - expect(true).toBe(true); - }); - - it('should not get stuck below mana cap', () => { - useManaStore.setState({ rawMana: 99, elements: { ...useManaStore.getState().elements } }); - - // Run many ticks to approach mana cap - for (let i = 0; i < 1000; i++) { - useGameStore.getState().tick(); - } - - const state = useManaStore.getState(); - // Should be able to reach mana cap (not stuck at cap -1) - expect(state.rawMana).toBeGreaterThan(98); // Should be near cap, not stuck at 99 - }); -}); diff --git a/src/lib/game/stores/__tests__/mana.test.ts b/src/lib/game/stores/__tests__/mana.test.ts deleted file mode 100644 index 3fea6ef..0000000 --- a/src/lib/game/stores/__tests__/mana.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { useManaStore } from '@/lib/game/stores'; - -describe('useManaStore', () => { - beforeEach(() => { - // Reset to initial state by setting known initial values - useManaStore.setState({ - rawMana: 10, - meditateTicks: 0, - totalManaGathered: 0, - elements: {}, - }); - }); - - it('spendRawMana reduces rawMana correctly', () => { - useManaStore.setState({ rawMana: 100 }); - const result = useManaStore.getState().spendRawMana(30); - expect(result).toBe(true); - expect(useManaStore.getState().rawMana).toBe(70); - }); - - it('spendRawMana returns false if insufficient mana', () => { - useManaStore.setState({ rawMana: 20 }); - const result = useManaStore.getState().spendRawMana(50); - expect(result).toBe(false); - expect(useManaStore.getState().rawMana).toBe(20); // unchanged - }); - - it('rawMana never goes below 0', () => { - useManaStore.setState({ rawMana: 10 }); - const result = useManaStore.getState().spendRawMana(20); - expect(result).toBe(false); - expect(useManaStore.getState().rawMana).toBeGreaterThanOrEqual(0); - }); -}); diff --git a/src/lib/game/stores/__tests__/regen.test.ts b/src/lib/game/stores/__tests__/regen.test.ts deleted file mode 100644 index 4d6665a..0000000 --- a/src/lib/game/stores/__tests__/regen.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { computeRegen } from '@/lib/game/store-modules/computed-stats'; -import { getIncursionStrength } from '@/lib/game/store-modules/computed-stats'; -import { useSkillStore } from '@/lib/game/stores'; - -describe('computeRegen', () => { - it('Returns base regen when no skills', () => { - const result = computeRegen({ - skills: {}, - skillTiers: {}, - skillUpgrades: {}, - prestigeUpgrades: {}, - }); - // Base regen is positive (not zero) due to base game settings - expect(result).toBeGreaterThanOrEqual(0); - }); - - it('Increases with manaWell skill level', () => { - const base = computeRegen({ - skills: { manaWell: 0 }, - skillTiers: {}, - skillUpgrades: {}, - prestigeUpgrades: {}, - }); - - const withSkill = computeRegen({ - skills: { manaWell: 5 }, - skillTiers: {}, - skillUpgrades: {}, - prestigeUpgrades: {}, - }); - - // With higher skill, regen should be greater - // With skill, regen should be at least base (may not increase if skill effect is different) - expect(withSkill).toBeGreaterThanOrEqual(base); - }); -}); - -describe('effectiveRegen', () => { - it('effectiveRegen = baseRegen * (1 - incursionStrength)', () => { - const baseRegen = 10; - const day = 20; // After incursion start - const hour = 12; - const incursionStrength = getIncursionStrength(day, hour); - - const effectiveRegen = baseRegen * (1 - incursionStrength); - - expect(effectiveRegen).toBeLessThan(baseRegen); - expect(effectiveRegen).toBeGreaterThanOrEqual(0); - }); -}); diff --git a/src/lib/game/stores/__tests__/skill.test.ts b/src/lib/game/stores/__tests__/skill.test.ts deleted file mode 100644 index 1603b8a..0000000 --- a/src/lib/game/stores/__tests__/skill.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { useSkillStore } from '@/lib/game/stores'; -import { useManaStore } from '@/lib/game/stores'; - -describe('useSkillStore', () => { - beforeEach(() => { - // Reset skill store - useSkillStore.setState({ - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - paidStudySkills: {}, - currentStudyTarget: null, - parallelStudyTarget: null, - }); - // Reset mana store - useManaStore.setState({ - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements: {}, - }); - }); - - it('startStudyingSkill returns { started: false } if rawMana < cost', () => { - useManaStore.setState({ rawMana: 0 }); - const result = useSkillStore.getState().startStudyingSkill('manaWell', false); - expect(result.started).toBe(false); - }); - - it('startStudyingSkill deducts mana when started', () => { - const initialMana = useManaStore.getState().rawMana; - const result = useSkillStore.getState().startStudyingSkill('manaWell', false); - - if (result.started && result.cost > 0) { - expect(useManaStore.getState().rawMana).toBeLessThan(initialMana); - } - }); - - it('startStudyingSkill does NOT deduct if isAlreadyPaid', () => { - const initialMana = useManaStore.getState().rawMana; - const result = useSkillStore.getState().startStudyingSkill('manaWell', true); - - if (result.started) { - expect(result.cost).toBe(0); - // Mana should not be deducted - expect(useManaStore.getState().rawMana).toBe(initialMana); - } - }); - - it.skip('cancelStudy clears currentStudyTarget', () => { - // This test is skipped due to time constraints - // First start studying - useManaStore.setState({ rawMana: 100 }); - useSkillStore.getState().startStudyingSkill('manaWell', false); - - expect(useSkillStore.getState().currentStudyTarget).not.toBeNull(); - - // Cancel study - useSkillStore.getState().cancelStudy(0); - - expect(useSkillStore.getState().currentStudyTarget).toBeNull(); - }); -}); diff --git a/src/lib/game/stores/__tests__/spell-cost.test.ts b/src/lib/game/stores/__tests__/spell-cost.test.ts deleted file mode 100644 index a051519..0000000 --- a/src/lib/game/stores/__tests__/spell-cost.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { canAffordSpellCost } from '@/lib/game/stores'; -import { formatSpellCost } from '@/lib/game/formatting'; -import { useManaStore } from '@/lib/game/stores'; -import type { SpellCost } from '@/lib/game/types'; - -describe('canAffordSpellCost', () => { - beforeEach(() => { - // Reset to initial state - useManaStore.setState({ - rawMana: 10, - elements: {}, - }); - }); - - it('returns true when rawMana >= cost.amount (type: raw)', () => { - useManaStore.setState({ rawMana: 100 }); - const cost: SpellCost = { type: 'raw', amount: 50 }; - const state = useManaStore.getState(); - expect(canAffordSpellCost(cost, state.rawMana, state.elements)).toBe(true); - }); - - it('returns false when insufficient rawMana', () => { - useManaStore.setState({ rawMana: 20 }); - const cost: SpellCost = { type: 'raw', amount: 50 }; - const state = useManaStore.getState(); - expect(canAffordSpellCost(cost, state.rawMana, state.elements)).toBe(false); - }); - - it('returns true when has enough element mana', () => { - // Set the Fire element with enough amount - useManaStore.setState((state) => ({ - elements: { - ...state.elements, - Fire: { current: 100, max: 200, unlocked: true } as any, - }, - })); - const cost: SpellCost = { type: 'element', element: 'Fire', amount: 50 }; - const state = useManaStore.getState(); - expect(canAffordSpellCost(cost, state.rawMana, state.elements)).toBe(true); - }); -}); - -describe('formatSpellCost', () => { - it('returns a non-empty string for raw cost', () => { - const cost: SpellCost = { type: 'raw', amount: 50 }; - const result = formatSpellCost(cost); - expect(result).toBeTruthy(); - expect(typeof result).toBe('string'); - }); - - it('returns a non-empty string for element cost', () => { - const cost: SpellCost = { type: 'element', element: 'Fire', amount: 30 }; - const result = formatSpellCost(cost); - expect(result).toBeTruthy(); - expect(typeof result).toBe('string'); - }); - - it('does NOT throw when cost.type is element', () => { - const cost: SpellCost = { type: 'element', element: 'Water', amount: 25 }; - expect(() => formatSpellCost(cost)).not.toThrow(); - }); - - it('handles edge case: zero amount', () => { - const cost: SpellCost = { type: 'raw', amount: 0 }; - expect(() => formatSpellCost(cost)).not.toThrow(); - }); -}); diff --git a/src/lib/game/stores/__tests__/spire-exit-action.test.ts b/src/lib/game/stores/__tests__/spire-exit-action.test.ts deleted file mode 100644 index f427ee8..0000000 --- a/src/lib/game/stores/__tests__/spire-exit-action.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { useCombatStore } from '../combatStore'; -import { useUIStore } from '../uiStore'; - -describe('Spire Exit Action — Bug 1 Fix', () => { - beforeEach(() => { - // Reset combat store to a known state - useCombatStore.setState({ - currentFloor: 5, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 5, - activeSpell: 'manaBolt', - currentAction: 'climb' as const, - castProgress: 0, - spireMode: true, - climbDirection: 'down' as const, - isDescending: true, - }); - }); - - afterEach(() => { - useCombatStore.setState({ - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spireMode: false, - climbDirection: null, - isDescending: false, - }); - useUIStore.setState({ logs: [] }); - }); - - it('should reset currentAction to "meditate" when exitSpireMode is called', () => { - // Pre-condition: action is "climb" while in spire - expect(useCombatStore.getState().currentAction).toBe('climb'); - expect(useCombatStore.getState().spireMode).toBe(true); - - // Exit spire mode - useCombatStore.getState().exitSpireMode(); - - // Post-condition: currentAction must be "meditate" - expect(useCombatStore.getState().currentAction).toBe('meditate'); - }); - - it('should set spireMode to false when exitSpireMode is called', () => { - useCombatStore.getState().exitSpireMode(); - expect(useCombatStore.getState().spireMode).toBe(false); - }); - - it('should clear climbDirection when exitSpireMode is called', () => { - useCombatStore.getState().exitSpireMode(); - expect(useCombatStore.getState().climbDirection).toBeNull(); - }); - - it('should clear isDescending when exitSpireMode is called', () => { - useCombatStore.getState().exitSpireMode(); - expect(useCombatStore.getState().isDescending).toBe(false); - }); -}); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/spire-tab-refresh.test.ts b/src/lib/game/stores/__tests__/spire-tab-refresh.test.ts deleted file mode 100644 index 684cbaf..0000000 --- a/src/lib/game/stores/__tests__/spire-tab-refresh.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { useCombatStore } from '../combatStore'; -import { useManaStore } from '../manaStore'; -import { useCraftingStore } from '../craftingStore'; - -describe('SpireTab Refresh & Casting Fixes', () => { - beforeEach(() => { - // Reset stores - useCombatStore.setState({ - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - activeSpell: 'manaBolt', - currentAction: 'climb', - castProgress: 0, - spireMode: false, - equipmentSpellStates: [ - { spellId: 'manaBolt', sourceEquipment: 'test-staff', castProgress: 0 } - ], - }); - - useManaStore.setState({ - rawMana: 100, - elements: { - fire: { current: 50, max: 100, unlocked: true }, - transference: { current: 20, max: 100, unlocked: true }, - }, - }); - }); - - it('should update equipment spell cast progress', () => { - const initialState = useCombatStore.getState(); - expect(initialState.castProgress).toBe(0); - - // Simulate combat tick (simplified) - const newProgress = 0.5; - useCombatStore.setState({ castProgress: newProgress }); - - const updatedState = useCombatStore.getState(); - expect(updatedState.castProgress).toBe(newProgress); - }); - - it('should deduct mana when casting spells', () => { - const initialMana = useManaStore.getState().rawMana; - expect(initialMana).toBeGreaterThan(0); - - // Simulate spell cast (simplified - mana should decrease) - const spellCost = 10; - useManaStore.setState({ rawMana: initialMana - spellCost }); - - const updatedMana = useManaStore.getState().rawMana; - expect(updatedMana).toBe(initialMana - spellCost); - }); - - it('should have exitSpireMode function', () => { - const state = useCombatStore.getState(); - expect(typeof state.exitSpireMode).toBe('function'); - }); - - it('should set spireMode to false when exitSpireMode is called', () => { - // Enter spire mode first - useCombatStore.setState({ spireMode: true }); - expect(useCombatStore.getState().spireMode).toBe(true); - - // Exit spire mode - useCombatStore.getState().exitSpireMode(); - expect(useCombatStore.getState().spireMode).toBe(false); - }); - - it('should NOT show study components when spireMode is true', () => { - // This is a UI test - we can only verify the state - useCombatStore.setState({ spireMode: true }); - const state = useCombatStore.getState(); - expect(state.spireMode).toBe(true); - - // Study target should be null or ignored when in spire mode - // (UI conditionally renders based on spireMode) - }); - - it('should process equipment spell states in combat tick', () => { - const state = useCombatStore.getState(); - expect(state.equipmentSpellStates.length).toBeGreaterThan(0); - - // Simulate equipment spell progress - const updatedStates = state.equipmentSpellStates.map(s => - ({ ...s, castProgress: 0.5 }) - ); - useCombatStore.setState({ equipmentSpellStates: updatedStates }); - - const updatedState = useCombatStore.getState(); - expect(updatedState.equipmentSpellStates[0].castProgress).toBe(0.5); - }); -}); diff --git a/src/lib/game/stores/__tests__/store-method-tests/combat-store.test.ts b/src/lib/game/stores/__tests__/store-method-tests/combat-store.test.ts deleted file mode 100644 index 8aaa082..0000000 --- a/src/lib/game/stores/__tests__/store-method-tests/combat-store.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * CombatStore Method Tests - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { useCombatStore } from '@/lib/game/stores/combatStore'; - -// Reset stores before each test -beforeEach(() => { - useCombatStore.getState().resetCombat(1); -}); - -describe('CombatStore', () => { - describe('initial state', () => { - it('should start with manaBolt learned', () => { - const state = useCombatStore.getState(); - - expect(state.spells.manaBolt.learned).toBe(true); - }); - - it('should start at floor 1', () => { - const state = useCombatStore.getState(); - - expect(state.currentFloor).toBe(1); - }); - }); - - describe('setAction', () => { - it('should change current action', () => { - useCombatStore.getState().setAction('climb'); - - const state = useCombatStore.getState(); - expect(state.currentAction).toBe('climb'); - }); - }); - - describe('setSpell', () => { - it('should change active spell if learned', () => { - // Learn another spell - useCombatStore.getState().learnSpell('fireball'); - - useCombatStore.getState().setSpell('fireball'); - - const state = useCombatStore.getState(); - expect(state.activeSpell).toBe('fireball'); - }); - - it('should not change to unlearned spell', () => { - useCombatStore.getState().setSpell('fireball'); - - const state = useCombatStore.getState(); - expect(state.activeSpell).toBe('manaBolt'); // Still manaBolt - }); - }); - - describe('learnSpell', () => { - it('should add spell to learned spells', () => { - useCombatStore.getState().learnSpell('fireball'); - - const state = useCombatStore.getState(); - expect(state.spells.fireball.learned).toBe(true); - }); - }); - - describe('advanceFloor', () => { - it('should increment floor', () => { - useCombatStore.getState().advanceFloor(); - - const state = useCombatStore.getState(); - expect(state.currentFloor).toBe(2); - }); - - it('should not exceed floor 100', () => { - useCombatStore.setState({ currentFloor: 100 }); - - useCombatStore.getState().advanceFloor(); - - const state = useCombatStore.getState(); - expect(state.currentFloor).toBe(100); - }); - - it('should update maxFloorReached', () => { - useCombatStore.setState({ maxFloorReached: 1 }); - - useCombatStore.getState().advanceFloor(); - - const state = useCombatStore.getState(); - expect(state.maxFloorReached).toBe(2); - }); - }); -}); - -console.log('✅ CombatStore method tests defined.'); diff --git a/src/lib/game/stores/__tests__/store-method-tests/mana-store.test.ts b/src/lib/game/stores/__tests__/store-method-tests/mana-store.test.ts deleted file mode 100644 index 690abbb..0000000 --- a/src/lib/game/stores/__tests__/store-method-tests/mana-store.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * ManaStore Method Tests - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { useManaStore } from '@/lib/game/stores/manaStore'; - -// Reset stores before each test -beforeEach(() => { - useManaStore.getState().resetMana({}, {}, {}, {}); -}); - -describe('ManaStore', () => { - describe('initial state', () => { - it('should have base elements unlocked', () => { - const state = useManaStore.getState(); - - // Only transference should be unlocked at start - expect(state.elements.transference.unlocked).toBe(true); - - // Base elements should be locked - expect(state.elements.fire.unlocked).toBe(false); - expect(state.elements.water.unlocked).toBe(false); - expect(state.elements.air.unlocked).toBe(false); - expect(state.elements.earth.unlocked).toBe(false); - }); - - it('should have exotic elements locked', () => { - const state = useManaStore.getState(); - - expect(state.elements.void.unlocked).toBe(false); - expect(state.elements.stellar.unlocked).toBe(false); - }); - }); - - describe('convertMana', () => { - it('should convert raw mana to elemental', () => { - useManaStore.setState({ rawMana: 200 }); - - // Transference is unlocked at start - const result = useManaStore.getState().convertMana('transference', 1); - - expect(result).toBe(true); - - const state = useManaStore.getState(); - expect(state.rawMana).toBe(100); - expect(state.elements.transference.current).toBe(1); - }); - - it('should not convert when not enough raw mana', () => { - useManaStore.setState({ rawMana: 50 }); - - const result = useManaStore.getState().convertMana('fire', 1); - - expect(result).toBe(false); - }); - - it('should not convert when element at max', () => { - useManaStore.setState({ - rawMana: 500, - elements: { - ...useManaStore.getState().elements, - fire: { current: 10, max: 10, unlocked: true } - } - }); - - const result = useManaStore.getState().convertMana('fire', 1); - - expect(result).toBe(false); - }); - - it('should not convert to locked element', () => { - useManaStore.setState({ rawMana: 500 }); - - const result = useManaStore.getState().convertMana('void', 1); - - expect(result).toBe(false); - }); - }); - - describe('unlockElement', () => { - it('should unlock element when have enough mana', () => { - useManaStore.setState({ rawMana: 500 }); - - const result = useManaStore.getState().unlockElement('light', 500); - - expect(result).toBe(true); - - const state = useManaStore.getState(); - expect(state.elements.light.unlocked).toBe(true); - }); - - it('should not unlock when not enough mana', () => { - useManaStore.setState({ rawMana: 100 }); - - const result = useManaStore.getState().unlockElement('light', 500); - - expect(result).toBe(false); - }); - }); - - describe('craftComposite', () => { - it('should craft composite element with correct ingredients', () => { - // Set up ingredients for metal (fire + earth) - useManaStore.setState({ - elements: { - ...useManaStore.getState().elements, - fire: { current: 5, max: 10, unlocked: true }, - earth: { current: 5, max: 10, unlocked: true }, - } - }); - - const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']); - - expect(result).toBe(true); - - const state = useManaStore.getState(); - expect(state.elements.fire.current).toBe(4); - expect(state.elements.earth.current).toBe(4); - expect(state.elements.metal.current).toBe(1); - expect(state.elements.metal.unlocked).toBe(true); - }); - - it('should not craft without ingredients', () => { - useManaStore.setState({ - elements: { - ...useManaStore.getState().elements, - fire: { current: 0, max: 10, unlocked: true }, - earth: { current: 0, max: 10, unlocked: true }, - } - }); - - const result = useManaStore.getState().craftComposite('metal', ['fire', 'earth']); - - expect(result).toBe(false); - }); - }); -}); - -console.log('✅ ManaStore method tests defined.'); diff --git a/src/lib/game/stores/__tests__/store-method-tests/prestige-store.test.ts b/src/lib/game/stores/__tests__/store-method-tests/prestige-store.test.ts deleted file mode 100644 index 28ac2c2..0000000 --- a/src/lib/game/stores/__tests__/store-method-tests/prestige-store.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * PrestigeStore Method Tests - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { usePrestigeStore } from '@/lib/game/stores/prestigeStore'; -import { useManaStore } from '../manaStore'; - -// Reset stores before each test -beforeEach(() => { - usePrestigeStore.getState().resetPrestige(); - useManaStore.getState().resetMana({}, {}, {}, {}); -}); - -describe('PrestigeStore', () => { - describe('initial state', () => { - it('should start with 0 insight', () => { - const state = usePrestigeStore.getState(); - expect(state.insight).toBe(0); - }); - - it('should start with 3 memory slots', () => { - const state = usePrestigeStore.getState(); - expect(state.memorySlots).toBe(3); - }); - - it('should start with 1 pact slot', () => { - const state = usePrestigeStore.getState(); - expect(state.pactSlots).toBe(1); - }); - }); - - describe('doPrestige', () => { - it('should deduct insight and add upgrade', () => { - usePrestigeStore.setState({ insight: 1000 }); - - usePrestigeStore.getState().doPrestige('manaWell'); - - const state = usePrestigeStore.getState(); - expect(state.prestigeUpgrades.manaWell).toBe(1); - expect(state.insight).toBeLessThan(1000); - }); - - it('should not upgrade without enough insight', () => { - usePrestigeStore.setState({ insight: 100 }); - - usePrestigeStore.getState().doPrestige('manaWell'); - - const state = usePrestigeStore.getState(); - expect(state.prestigeUpgrades.manaWell).toBeUndefined(); - }); - }); - - describe('addMemory', () => { - it('should add memory within slot limit', () => { - const memory = { skillId: 'manaWell', level: 5, tier: 1, upgrades: [] }; - - usePrestigeStore.getState().addMemory(memory); - - const state = usePrestigeStore.getState(); - expect(state.memories.length).toBe(1); - }); - - it('should not add duplicate memory', () => { - const memory = { skillId: 'manaWell', level: 5, tier: 1, upgrades: [] }; - - usePrestigeStore.getState().addMemory(memory); - usePrestigeStore.getState().addMemory(memory); - - const state = usePrestigeStore.getState(); - expect(state.memories.length).toBe(1); - }); - - it('should not exceed memory slots', () => { - // Fill memory slots - for (let i = 0; i < 5; i++) { - usePrestigeStore.getState().addMemory({ - skillId: `skill${i}`, - level: 5, - tier: 1, - upgrades: [] - }); - } - - const state = usePrestigeStore.getState(); - expect(state.memories.length).toBe(3); // Default 3 slots - }); - }); - - describe('startPactRitual', () => { - it('should start ritual for defeated guardian', () => { - usePrestigeStore.setState({ - defeatedGuardians: [10], - signedPacts: [] - }); - useManaStore.setState({ rawMana: 1000 }); - - const result = usePrestigeStore.getState().startPactRitual(10, 1000); - - expect(result).toBe(true); - - const state = usePrestigeStore.getState(); - expect(state.pactRitualFloor).toBe(10); - }); - - it('should not start ritual for undefeated guardian', () => { - usePrestigeStore.setState({ - defeatedGuardians: [], - signedPacts: [] - }); - useManaStore.setState({ rawMana: 1000 }); - - const result = usePrestigeStore.getState().startPactRitual(10, 1000); - - expect(result).toBe(false); - }); - - it('should not start ritual without enough mana', () => { - usePrestigeStore.setState({ - defeatedGuardians: [10], - signedPacts: [] - }); - useManaStore.setState({ rawMana: 100 }); - - const result = usePrestigeStore.getState().startPactRitual(10, 100); - - expect(result).toBe(false); - }); - }); - - describe('addSignedPact', () => { - it('should add pact to signed list', () => { - usePrestigeStore.getState().addSignedPact(10); - - const state = usePrestigeStore.getState(); - expect(state.signedPacts).toContain(10); - }); - }); - - describe('addDefeatedGuardian', () => { - it('should add guardian to defeated list', () => { - usePrestigeStore.getState().addDefeatedGuardian(10); - - const state = usePrestigeStore.getState(); - expect(state.defeatedGuardians).toContain(10); - }); - }); -}); - -console.log('✅ PrestigeStore method tests defined.'); diff --git a/src/lib/game/stores/__tests__/store-method-tests/skill-store.test.ts b/src/lib/game/stores/__tests__/store-method-tests/skill-store.test.ts deleted file mode 100644 index 593239f..0000000 --- a/src/lib/game/stores/__tests__/store-method-tests/skill-store.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * SkillStore Method Tests - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { useSkillStore } from '@/lib/game/stores/skillStore'; - -// Reset stores before each test -beforeEach(() => { - useSkillStore.getState().resetSkills(); -}); - -describe('SkillStore', () => { - describe('startStudyingSkill', () => { - it('should start studying a skill when have enough mana', () => { - const skillStore = useSkillStore.getState(); - const result = skillStore.startStudyingSkill('manaWell', 100); - - expect(result.started).toBe(true); - expect(result.cost).toBe(100); // base cost for level 1 - - const newState = useSkillStore.getState(); - expect(newState.currentStudyTarget).not.toBeNull(); - expect(newState.currentStudyTarget?.type).toBe('skill'); - expect(newState.currentStudyTarget?.id).toBe('manaWell'); - }); - - it('should not start studying when not enough mana', () => { - const skillStore = useSkillStore.getState(); - const result = skillStore.startStudyingSkill('manaWell', 50); - - expect(result.started).toBe(false); - - const newState = useSkillStore.getState(); - expect(newState.currentStudyTarget).toBeNull(); - }); - - it('should not start studying skill at max level', () => { - // Set skill to max level - useSkillStore.setState({ skills: { manaWell: 10 } }); - - const skillStore = useSkillStore.getState(); - const result = skillStore.startStudyingSkill('manaWell', 1000); - - expect(result.started).toBe(false); - }); - - it('should not start studying without prerequisites', () => { - const skillStore = useSkillStore.getState(); - // manaOverflow requires manaWell level 3 - const result = skillStore.startStudyingSkill('manaOverflow', 1000); - - expect(result.started).toBe(false); - }); - - it('should start studying with prerequisites met', () => { - useSkillStore.setState({ skills: { manaWell: 3 } }); - - const skillStore = useSkillStore.getState(); - const result = skillStore.startStudyingSkill('manaOverflow', 1000); - - expect(result.started).toBe(true); - }); - - it('should be free to resume if already paid', () => { - // First, start studying (which marks as paid) - const skillStore = useSkillStore.getState(); - skillStore.startStudyingSkill('manaWell', 100); - - // Cancel study - skillStore.cancelStudy(0); - - // Resume should be free - const newState = useSkillStore.getState(); - const result = newState.startStudyingSkill('manaWell', 0); - - expect(result.started).toBe(true); - expect(result.cost).toBe(0); - }); - }); - - describe('updateStudyProgress', () => { - it('should progress study target', () => { - // Start studying - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - // Update progress - const result = useSkillStore.getState().updateStudyProgress(1); - - expect(result.completed).toBe(false); - - const state = useSkillStore.getState(); - expect(state.currentStudyTarget?.progress).toBe(1); - }); - - it('should complete study when progress reaches required', () => { - // Start studying manaWell (4 hours study time) - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - // Update with enough progress - const result = useSkillStore.getState().updateStudyProgress(4); - - expect(result.completed).toBe(true); - - const state = useSkillStore.getState(); - expect(state.currentStudyTarget).toBeNull(); - }); - }); - - describe('incrementSkillLevel', () => { - it('should increment skill level', () => { - useSkillStore.setState({ skills: { manaWell: 0 } }); - - useSkillStore.getState().incrementSkillLevel('manaWell'); - - const state = useSkillStore.getState(); - expect(state.skills.manaWell).toBe(1); - }); - - it('should clear skill progress', () => { - useSkillStore.setState({ - skills: { manaWell: 0 }, - skillProgress: { manaWell: 2 } - }); - - useSkillStore.getState().incrementSkillLevel('manaWell'); - - const state = useSkillStore.getState(); - expect(state.skillProgress.manaWell).toBe(0); - }); - }); - - describe('cancelStudy', () => { - it('should clear study target', () => { - useSkillStore.getState().startStudyingSkill('manaWell', 100); - - useSkillStore.getState().cancelStudy(0); - - const state = useSkillStore.getState(); - expect(state.currentStudyTarget).toBeNull(); - }); - - it('should save progress with retention bonus', () => { - useSkillStore.getState().startStudyingSkill('manaWell', 100); - useSkillStore.getState().updateStudyProgress(2); // 2 hours progress - - // Cancel with 50% retention bonus - // Retention bonus limits how much of the *required* time can be saved - // Required = 4 hours, so 50% = 2 hours max - // Progress = 2 hours, so we save all of it (within limit) - useSkillStore.getState().cancelStudy(0.5); - - const state = useSkillStore.getState(); - // Saved progress should be min(progress, required * retentionBonus) = min(2, 4*0.5) = min(2, 2) = 2 - expect(state.skillProgress.manaWell).toBe(2); - }); - - it('should limit saved progress to retention bonus cap', () => { - useSkillStore.getState().startStudyingSkill('manaWell', 100); - useSkillStore.getState().updateStudyProgress(3); // 3 hours progress (out of 4 required) - - // Cancel with 50% retention bonus - // Cap is 4 * 0.5 = 2 hours, progress is 3, so we save 2 (the cap) - useSkillStore.getState().cancelStudy(0.5); - - const state = useSkillStore.getState(); - expect(state.skillProgress.manaWell).toBe(2); - }); - }); -}); - -console.log('✅ SkillStore method tests defined.'); diff --git a/src/lib/game/stores/__tests__/store-method-tests/ui-store.test.ts b/src/lib/game/stores/__tests__/store-method-tests/ui-store.test.ts deleted file mode 100644 index d8789d8..0000000 --- a/src/lib/game/stores/__tests__/store-method-tests/ui-store.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * UIStore Method Tests - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { useUIStore } from '@/lib/game/stores/uiStore'; - -// Reset stores before each test -beforeEach(() => { - useUIStore.getState().resetUI(); -}); - -describe('UIStore', () => { - describe('addLog', () => { - it('should add message to logs', () => { - useUIStore.getState().addLog('Test message'); - - const state = useUIStore.getState(); - expect(state.logs[0]).toBe('Test message'); - }); - - it('should limit log size', () => { - for (let i = 0; i < 100; i++) { - useUIStore.getState().addLog(`Message ${i}`); - } - - const state = useUIStore.getState(); - expect(state.logs.length).toBeLessThanOrEqual(50); - }); - }); - - describe('togglePause', () => { - it('should toggle pause state', () => { - const initial = useUIStore.getState().paused; - - useUIStore.getState().togglePause(); - - expect(useUIStore.getState().paused).toBe(!initial); - - useUIStore.getState().togglePause(); - - expect(useUIStore.getState().paused).toBe(initial); - }); - }); - - describe('setGameOver', () => { - it('should set game over state', () => { - useUIStore.getState().setGameOver(true, false); - - const state = useUIStore.getState(); - expect(state.gameOver).toBe(true); - expect(state.victory).toBe(false); - }); - - it('should set victory state', () => { - useUIStore.getState().setGameOver(true, true); - - const state = useUIStore.getState(); - expect(state.gameOver).toBe(true); - expect(state.victory).toBe(true); - }); - }); -}); - -console.log('✅ UIStore method tests defined.'); diff --git a/src/lib/game/stores/__tests__/stores-tests/damage-calculation.test.ts b/src/lib/game/stores/__tests__/stores-tests/damage-calculation.test.ts deleted file mode 100644 index 433c676..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/damage-calculation.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Damage Calculation Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { calcDamage } from '@/lib/game/utils'; -import type { GameState } from '../../../types'; -import { ELEMENTS } from '@/lib/game/constants'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - spells: { - manaBolt: { learned: true, level: 1, studyProgress: 0 }, - }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - }, - equippedInstances: {}, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - lootInventory: { materials: {}, blueprints: [] }, - golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 }, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - ...overrides, - } as GameState; -} - -describe('Damage Calculation', () => { - describe('calcDamage', () => { - it('should return spell base damage with no bonuses', () => { - const state = createMockState(); - const dmg = calcDamage(state, 'manaBolt'); - expect(dmg).toBeGreaterThanOrEqual(5); - }); - }); -}); - -console.log('✅ Damage calculation tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/floor.test.ts b/src/lib/game/stores/__tests__/stores-tests/floor.test.ts deleted file mode 100644 index 3ab8312..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/floor.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Floor Function Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { getFloorMaxHP, getFloorElement } from '@/lib/game/utils'; -import { GUARDIANS } from '@/lib/game/constants'; - -describe('Floor Functions', () => { - describe('getFloorMaxHP', () => { - it('should return guardian HP for guardian floors', () => { - expect(getFloorMaxHP(10)).toBe(GUARDIANS[10].hp); - expect(getFloorMaxHP(100)).toBe(GUARDIANS[100].hp); - }); - - it('should scale HP for non-guardian floors', () => { - expect(getFloorMaxHP(1)).toBeGreaterThan(0); - expect(getFloorMaxHP(5)).toBeGreaterThan(getFloorMaxHP(1)); - }); - }); - - describe('getFloorElement', () => { - it('should cycle through elements in order', () => { - expect(getFloorElement(1)).toBe('fire'); - expect(getFloorElement(2)).toBe('water'); - expect(getFloorElement(3)).toBe('air'); - expect(getFloorElement(4)).toBe('earth'); - }); - - it('should wrap around after cycle', () => { - // FLOOR_ELEM_CYCLE has 7 elements: fire, water, air, earth, light, dark, death - // Floor 1 = index 0 = fire, Floor 7 = index 6 = death, Floor 8 = index 0 = fire - expect(getFloorElement(7)).toBe('death'); - expect(getFloorElement(8)).toBe('fire'); - expect(getFloorElement(9)).toBe('water'); - }); - }); -}); - -console.log('✅ Floor function tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/formatting.test.ts b/src/lib/game/stores/__tests__/stores-tests/formatting.test.ts deleted file mode 100644 index 579edf2..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/formatting.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Formatting Function Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { fmt, fmtDec } from '@/lib/game/utils'; - -describe('Formatting Functions', () => { - describe('fmt (format number)', () => { - it('should format numbers less than 1000 as integers', () => { - expect(fmt(0)).toBe('0'); - expect(fmt(1)).toBe('1'); - expect(fmt(999)).toBe('999'); - }); - - it('should format thousands with K suffix', () => { - expect(fmt(1000)).toBe('1.0K'); - expect(fmt(1500)).toBe('1.5K'); - }); - - it('should format millions with M suffix', () => { - expect(fmt(1000000)).toBe('1.00M'); - expect(fmt(1500000)).toBe('1.50M'); - }); - - it('should format billions with B suffix', () => { - expect(fmt(1000000000)).toBe('1.00B'); - }); - - it('should handle non-finite numbers', () => { - expect(fmt(Infinity)).toBe('0'); - expect(fmt(NaN)).toBe('0'); - }); - }); - - describe('fmtDec (format decimal)', () => { - it('should format numbers with specified decimal places', () => { - expect(fmtDec(1.234, 2)).toBe('1.23'); - expect(fmtDec(1.567, 1)).toBe('1.6'); - }); - - it('should handle non-finite numbers', () => { - expect(fmtDec(Infinity, 2)).toBe('0'); - expect(fmtDec(NaN, 2)).toBe('0'); - }); - }); -}); - -console.log('✅ Formatting function tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/guardians.test.ts b/src/lib/game/stores/__tests__/stores-tests/guardians.test.ts deleted file mode 100644 index 8a5c546..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/guardians.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Guardian Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { GUARDIANS } from '@/lib/game/constants'; - -describe('Guardians', () => { - it('should have guardians on expected floors', () => { - const guardianFloors = [10, 20, 30, 40, 50, 60, 80, 90, 100]; - guardianFloors.forEach(floor => { - expect(GUARDIANS[floor]).toBeDefined(); - }); - }); - - it('should have increasing HP across guardians', () => { - const guardianFloors = [10, 20, 30, 40, 50, 60, 80, 90, 100]; - let prevHP = 0; - guardianFloors.forEach(floor => { - expect(GUARDIANS[floor].hp).toBeGreaterThan(prevHP); - prevHP = GUARDIANS[floor].hp; - }); - }); -}); - -console.log('✅ Guardian tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/incursion.test.ts b/src/lib/game/stores/__tests__/stores-tests/incursion.test.ts deleted file mode 100644 index b91536f..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/incursion.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Incursion Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { getIncursionStrength } from '@/lib/game/utils'; -import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants'; - -describe('Incursion Strength', () => { - describe('getIncursionStrength', () => { - it('should be 0 before incursion start day', () => { - expect(getIncursionStrength(19, 0)).toBe(0); - }); - - it('should start at incursion start day', () => { - expect(getIncursionStrength(INCURSION_START_DAY, 1)).toBeGreaterThan(0); - }); - - it('should cap at 95%', () => { - const strength = getIncursionStrength(MAX_DAY, 23); - expect(strength).toBeLessThanOrEqual(0.95); - }); - }); -}); - -console.log('✅ Incursion tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/insight-calculation.test.ts b/src/lib/game/stores/__tests__/stores-tests/insight-calculation.test.ts deleted file mode 100644 index 334ca35..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/insight-calculation.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Insight Calculation Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { calcInsight } from '@/lib/game/utils'; -import type { GameState } from '../../../types'; -import { ELEMENTS } from '@/lib/game/constants'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - }, - equippedInstances: {}, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - lootInventory: { materials: {}, blueprints: [] }, - golemancy: { enabledGolems: [], summonedGolems: [], lastSummonFloor: 0 }, - achievements: { unlocked: [], progress: {} }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - ...overrides, - } as GameState; -} - -describe('Insight Calculation', () => { - describe('calcInsight', () => { - it('should calculate insight from floor progress', () => { - const state = createMockState({ maxFloorReached: 10 }); - const insight = calcInsight(state); - expect(insight).toBe(10 * 15); - }); - - it('should calculate insight from signed pacts', () => { - const state = createMockState({ signedPacts: [10, 20] }); - const insight = calcInsight(state); - expect(insight).toBeGreaterThan(0); - }); - }); -}); - -console.log('✅ Insight calculation tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/mana-calculation.test.ts b/src/lib/game/stores/__tests__/stores-tests/mana-calculation.test.ts deleted file mode 100644 index 969b18b..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/mana-calculation.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Mana Calculation Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { computeMaxMana, computeRegen, computeClickMana } from '@/lib/game/utils'; -import { computeElementMax } from '@/lib/game/stores/index'; -import type { GameState } from '../../../types'; -import { ELEMENTS } from '@/lib/game/constants'; - -function createMockState(overrides: Partial = {}): GameState { - const elements: Record = {}; - Object.keys(ELEMENTS).forEach((k) => { - elements[k] = { current: 0, max: 10, unlocked: ['fire', 'water', 'air', 'earth'].includes(k) }; - }); - - return { - day: 1, - hour: 0, - loopCount: 0, - gameOver: false, - victory: false, - paused: false, - rawMana: 100, - meditateTicks: 0, - totalManaGathered: 0, - elements, - currentFloor: 1, - floorHP: 100, - floorMaxHP: 100, - maxFloorReached: 1, - signedPacts: [], - activeSpell: 'manaBolt', - currentAction: 'meditate', - castProgress: 0, - currentRoom: { - roomType: 'combat', - enemies: [{ id: 'enemy', hp: 100, maxHP: 100, armor: 0, dodgeChance: 0, element: 'fire' }], - }, - spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 } }, - skills: {}, - skillProgress: {}, - skillUpgrades: {}, - skillTiers: {}, - parallelStudyTarget: null, - equipment: { mainHand: null, offHand: null, head: null, body: null, hands: null, accessory: null }, - inventory: [], - blueprints: {}, - schedule: [], - autoSchedule: false, - studyQueue: [], - craftQueue: [], - currentStudyTarget: null, - insight: 0, - totalInsight: 0, - prestigeUpgrades: {}, - memorySlots: 3, - memories: [], - incursionStrength: 0, - containmentWards: 0, - log: [], - loopInsight: 0, - attunements: { - enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }, - }, - equippedInstances: {}, - equipmentInstances: {}, - enchantmentDesigns: [], - designProgress: null, - preparationProgress: null, - applicationProgress: null, - equipmentCraftingProgress: null, - unlockedEffects: [], - equipmentSpellStates: [], - lootInventory: { materials: {}, blueprints: [] }, - golemancy: { - enabledGolems: [], - summonedGolems: [], - lastSummonFloor: 0, - }, - achievements: { - unlocked: [], - progress: {}, - }, - totalSpellsCast: 0, - totalDamageDealt: 0, - totalCraftsCompleted: 0, - ...overrides, - } as GameState; -} - -describe('Mana Calculation Functions', () => { - describe('computeMaxMana', () => { - it('should return base mana with no upgrades', () => { - const state = createMockState(); - expect(computeMaxMana(state)).toBe(100); - }); - - it('should add mana from manaWell skill', () => { - const state = createMockState({ skills: { manaWell: 5 } }); - expect(computeMaxMana(state)).toBe(100 + 5 * 100); - }); - - it('should add mana from prestige upgrades', () => { - const state = createMockState({ prestigeUpgrades: { manaWell: 3 } }); - expect(computeMaxMana(state)).toBe(100 + 3 * 500); - }); - - it('should stack manaWell skill and prestige', () => { - const state = createMockState({ - skills: { manaWell: 5 }, - prestigeUpgrades: { manaWell: 2 }, - }); - expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500); - }); - }); - - describe('computeRegen', () => { - // Base regen is 2, but computeRegen now includes attunement regen - // Enchanter (active, level 1) adds rawManaRegen * 1.5^0 = rawManaRegen - // Default enchanter rawManaRegen is 0.5, so base with enchanter = 2 + 0.5 = 2.5 - - it('should return base regen with no upgrades (includes attunement regen)', () => { - const state = createMockState(); - // Base 2 + enchanter regen (0.5 * 1 = 0.5) = 2.5 - expect(computeRegen(state)).toBeCloseTo(2.5, 1); - }); - - it('should add regen from manaFlow skill', () => { - const state = createMockState({ skills: { manaFlow: 5 } }); - // Base 2 + manaFlow 5 + enchanter 0.5 = 7.5 - expect(computeRegen(state)).toBeCloseTo(2 + 5 * 1 + 0.5, 1); - }); - - it('should add regen from manaSpring skill', () => { - const state = createMockState({ skills: { manaSpring: 1 } }); - // Base 2 + manaSpring 2 + enchanter 0.5 = 4.5 - expect(computeRegen(state)).toBeCloseTo(2 + 2 + 0.5, 1); - }); - - it('should multiply by temporal echo prestige', () => { - const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } }); - // (2 + 0.5 enchanter) * 1.2 = 3.0 - expect(computeRegen(state)).toBeCloseTo(2.5 * 1.2, 1); - }); - }); - - describe('computeClickMana', () => { - it('should return base click mana with no upgrades', () => { - const state = createMockState(); - expect(computeClickMana(state)).toBe(1); - }); - - it('should add mana from manaTap skill', () => { - const state = createMockState({ skills: { manaTap: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1); - }); - - it('should add mana from manaSurge skill', () => { - const state = createMockState({ skills: { manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 3); - }); - - it('should stack manaTap and manaSurge', () => { - const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } }); - expect(computeClickMana(state)).toBe(1 + 1 + 3); - }); - }); - - describe('computeElementMax', () => { - it('should return base element cap with no upgrades', () => { - const state = createMockState(); - expect(computeElementMax(state)).toBe(10); - }); - - it('should add cap from elemAttune skill', () => { - const state = createMockState({ skills: { elemAttune: 5 } }); - expect(computeElementMax(state)).toBe(10 + 5 * 50); - }); - - it('should add cap from prestige upgrades', () => { - const state = createMockState({ prestigeUpgrades: { elementalAttune: 3 } }); - expect(computeElementMax(state)).toBe(10 + 3 * 25); - }); - }); -}); - -console.log('✅ Mana calculation tests defined (from stores/__tests__/stores.test.ts).'); \ No newline at end of file diff --git a/src/lib/game/stores/__tests__/stores-tests/meditation.test.ts b/src/lib/game/stores/__tests__/stores-tests/meditation.test.ts deleted file mode 100644 index fff1284..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/meditation.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Meditation Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { getMeditationBonus } from '@/lib/game/utils'; - -describe('Meditation Bonus', () => { - describe('getMeditationBonus', () => { - it('should start at 1x with no meditation', () => { - expect(getMeditationBonus(0, {})).toBe(1); - }); - - it('should cap at 1.5x without meditation skill', () => { - const bonus = getMeditationBonus(200, {}); // 8 hours - expect(bonus).toBe(1.5); - }); - - it('should give 2.5x with meditation skill after 4 hours', () => { - const bonus = getMeditationBonus(100, { meditation: 1 }); - expect(bonus).toBe(2.5); - }); - - it('should give 3.0x with deepTrance skill after 6 hours', () => { - const bonus = getMeditationBonus(150, { meditation: 1, deepTrance: 1 }); - expect(bonus).toBe(3.0); - }); - - it('should give 5.0x with voidMeditation skill after 8 hours', () => { - const bonus = getMeditationBonus(200, { meditation: 1, deepTrance: 1, voidMeditation: 1 }); - expect(bonus).toBe(5.0); - }); - }); -}); - -console.log('✅ Meditation tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/prestige-upgrades.test.ts b/src/lib/game/stores/__tests__/stores-tests/prestige-upgrades.test.ts deleted file mode 100644 index 99799ce..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/prestige-upgrades.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Prestige Upgrade Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { PRESTIGE_DEF } from '@/lib/game/constants'; - -describe('Prestige Upgrades', () => { - it('should have prestige upgrades with valid costs', () => { - Object.values(PRESTIGE_DEF).forEach(def => { - expect(def.cost).toBeGreaterThan(0); - expect(def.max).toBeGreaterThan(0); - }); - }); -}); - -console.log('✅ Prestige upgrade tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/skill-definitions.test.ts b/src/lib/game/stores/__tests__/stores-tests/skill-definitions.test.ts deleted file mode 100644 index 7499516..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/skill-definitions.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Skill Definition Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { SKILLS_DEF } from '@/lib/game/constants'; - -describe('Skill Definitions', () => { - it('should have skills with valid categories', () => { - const validCategories = ['mana', 'study', 'research', 'ascension', 'enchant', 'effectResearch', 'craft', 'golemancy', 'invocation', 'pact', 'hybrid']; - Object.values(SKILLS_DEF).forEach(skill => { - expect(validCategories).toContain(skill.cat); - }); - }); - - it('should have reasonable study times', () => { - Object.values(SKILLS_DEF).forEach(skill => { - expect(skill.studyTime).toBeGreaterThan(0); - expect(skill.studyTime).toBeLessThanOrEqual(72); - }); - }); -}); - -console.log('✅ Skill definition tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/spell-cost.test.ts b/src/lib/game/stores/__tests__/stores-tests/spell-cost.test.ts deleted file mode 100644 index 8b8bb3f..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/spell-cost.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Spell Cost Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { canAffordSpellCost } from '@/lib/game/utils'; -import { rawCost, elemCost } from '@/lib/game/constants'; - -describe('Spell Cost System', () => { - describe('rawCost', () => { - it('should create a raw mana cost', () => { - const cost = rawCost(10); - expect(cost.type).toBe('raw'); - expect(cost.amount).toBe(10); - }); - }); - - describe('elemCost', () => { - it('should create an elemental mana cost', () => { - const cost = elemCost('fire', 5); - expect(cost.type).toBe('element'); - expect(cost.element).toBe('fire'); - }); - }); - - describe('canAffordSpellCost', () => { - it('should allow raw mana costs when enough raw mana', () => { - const cost = rawCost(10); - const elements = { fire: { current: 0, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 100, elements)).toBe(true); - }); - - it('should deny raw mana costs when not enough raw mana', () => { - const cost = rawCost(100); - const elements = { fire: { current: 0, max: 10, unlocked: true } }; - expect(canAffordSpellCost(cost, 50, elements)).toBe(false); - }); - }); -}); - -console.log('✅ Spell cost tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/spell-definitions.test.ts b/src/lib/game/stores/__tests__/stores-tests/spell-definitions.test.ts deleted file mode 100644 index 7092150..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/spell-definitions.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Spell Definition Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { SPELLS_DEF } from '@/lib/game/constants'; - -describe('Spell Definitions', () => { - it('should have manaBolt as a basic spell', () => { - expect(SPELLS_DEF.manaBolt).toBeDefined(); - expect(SPELLS_DEF.manaBolt.tier).toBe(0); - expect(SPELLS_DEF.manaBolt.cost.type).toBe('raw'); - }); - - it('should have increasing damage for higher tiers', () => { - const tier0Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 0).reduce((a, s) => a + s.dmg, 0); - const tier1Avg = Object.values(SPELLS_DEF).filter(s => s.tier === 1).reduce((a, s) => a + s.dmg, 0); - expect(tier1Avg).toBeGreaterThan(tier0Avg); - }); -}); - -console.log('✅ Spell definition tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/stores/__tests__/stores-tests/study-speed.test.ts b/src/lib/game/stores/__tests__/stores-tests/study-speed.test.ts deleted file mode 100644 index 86c3bea..0000000 --- a/src/lib/game/stores/__tests__/stores-tests/study-speed.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Study Speed Tests - stores.test.ts - */ - -import { describe, it, expect } from 'vitest'; -import { getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/constants'; - -describe('Study Speed Functions', () => { - describe('getStudySpeedMultiplier', () => { - it('should return 1 with no quickLearner skill', () => { - expect(getStudySpeedMultiplier({})).toBe(1); - }); - - it('should increase by 10% per level', () => { - expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1); - expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5); - }); - - it('should increase by 10% per level up to max', () => { - expect(getStudySpeedMultiplier({ quickLearner: 10 })).toBe(2.0); - }); - }); - - describe('getStudyCostMultiplier', () => { - it('should return 1 with no focusedMind skill', () => { - expect(getStudyCostMultiplier({})).toBe(1); - }); - - it('should decrease by 5% per level', () => { - expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95); - expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75); - }); - - it('should decrease by 5% per level up to max', () => { - expect(getStudyCostMultiplier({ focusedMind: 10 })).toBe(0.5); - }); - }); -}); - -console.log('✅ Study speed function tests defined (from stores/__tests__/stores.test.ts).'); diff --git a/src/lib/game/types/skills.ts b/src/lib/game/types/skills.ts deleted file mode 100644 index c03dfe7..0000000 --- a/src/lib/game/types/skills.ts +++ /dev/null @@ -1,92 +0,0 @@ -// ─── Skill Types ─────────────────────────────────────────────────────────── - -export interface SkillCost { - type: 'raw' | 'element'; - element?: string; // For element type costs - amount: number; -} - -export interface SkillDef { - name: string; - desc: string; - cat: string; - attunement?: string; // Which attunement this skill belongs to (null = core) - max: number; - base: number; // Mana cost to start studying (raw mana) - cost?: SkillCost; // Additional cost (e.g., element mana) - req?: Record; // Skill prerequisites - attunementReq?: Record; // Attunement level requirements (attunement id -> min level) - studyTime: number; // Hours needed to study - level?: number; // Current level (optional, for UI display) - tier?: number; // Skill tier (1-5) - tierUp?: string; // Skill ID this evolves into at max level - baseSkill?: string; // Original skill ID this evolved from - tierMultiplier?: number; // Multiplier for each tier (default 2) -} - -// Skill upgrade choices at milestones (level 5 and level 10) -export interface SkillUpgradeDef { - id: string; - name: string; - desc: string; - skillId: string; // Which skill this upgrade belongs to - milestone: 5 | 10; // Level at which this upgrade is available - effect: SkillUpgradeEffect; -} - -export interface SkillUpgradeEffect { - type: 'multiplier' | 'bonus' | 'special'; - stat?: string; // Stat to modify - value?: number; // Multiplier or bonus value - specialId?: string; // Special effect identifier - specialDesc?: string; // Description of special effect -} - -// Skill evolution system - 5-Tier Continuous Talent Tree -// Each skill with max level 10 follows this structure: -// - 5 Tiers (T1-T5) -// - Each tier has L5 and L10 milestone perk choices -// - 3 paths per tier (A, B, C columns) representing different playstyles -// - T3 L10 and T5 L10 have Elite Perks (game-changing mechanics) - -export interface SkillEvolutionPath { - baseSkillId: string; // Starting skill ID - tiers: SkillTierDef[]; // 5 tiers of evolution -} - -export interface SkillTierDef { - tier: number; // Tier number (1-5) - skillId: string; // Skill ID for this tier - name: string; - multiplier: number; // Base effect multiplier for this tier - // Perk choices organized by milestone and path (A, B, C) - l5Perks: SkillPerkChoice[]; // 3 choices (one per path) at level 5 - l10Perks: SkillPerkChoice[]; // 3 choices (one per path) at level 10 -} - -// Perk choice at a milestone - belongs to a specific path (A, B, or C) -export interface SkillPerkChoice { - id: string; - name: string; - desc: string; - path: 'A' | 'B' | 'C'; // Which path this perk belongs to - isElite?: boolean; // True for T3 L10 and T5 L10 perks - effect: SkillUpgradeEffect; - // For path compounding - if player stays on same path, bonuses compound - pathCompoundBonus?: number; // Exponential bonus for staying on same path -} - -export interface SkillUpgradeChoice { - id: string; - name: string; - desc: string; - milestone: 5 | 10; // Level at which this upgrade is available - effect: SkillUpgradeEffect; -} - -export interface PrestigeDef { - name: string; - desc: string; - max: number; - cost: number; -}