refactor: remove legacy store.ts and crafting-slice.ts, complete modular store migration
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
- Delete store.ts (355 LOC monolithic store, zero imports) - Delete crafting-slice.ts (379 LOC legacy crafting module) - Inline createStartingEquipment() into craftingStore.ts - Remove legacy equipment/inventory fields from GameState - Remove EquipmentDef from game.ts imports (unused) - Fix duplicate EquipmentSpellState export in types.ts - Fix bluePrintId typo in craftingStore.ts - Update stores/index.ts to import CraftingState/CraftingActions from craftingStore.types - Update EquipmentTab.test.ts to test store state instead of deleted module - Clean up stale comments referencing crafting-slice.ts - Reduce TS errors from 83 to 72 by removing conflicting legacy types
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-20T07:28:09.937Z
|
Generated: 2026-05-20T10:00:51.281Z
|
||||||
Found: 3 circular chain(s) — these MUST be fixed before modifying involved files.
|
Found: 1 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||||
|
|
||||||
1. Processed 125 files (1.3s) (4 warnings)
|
1. Processed 127 files (1.4s) (4 warnings)
|
||||||
2. 1) data/equipment/index.ts > data/equipment/utils.ts
|
|
||||||
3. 2) data/golems/index.ts > data/golems/utils.ts
|
|
||||||
|
|
||||||
## How to fix
|
## How to fix
|
||||||
1. Identify which import in the chain can be extracted to a shared types/utils file.
|
1. Identify which import in the chain can be extracted to a shared types/utils file.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-20T07:28:08.406Z",
|
"generated": "2026-05-20T10:00:49.725Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
@@ -299,6 +299,17 @@
|
|||||||
"data/equipment/catalysts.ts": [
|
"data/equipment/catalysts.ts": [
|
||||||
"data/equipment/types.ts"
|
"data/equipment/types.ts"
|
||||||
],
|
],
|
||||||
|
"data/equipment/equipment-types-data.ts": [
|
||||||
|
"data/equipment/accessories.ts",
|
||||||
|
"data/equipment/body.ts",
|
||||||
|
"data/equipment/casters.ts",
|
||||||
|
"data/equipment/catalysts.ts",
|
||||||
|
"data/equipment/feet.ts",
|
||||||
|
"data/equipment/hands.ts",
|
||||||
|
"data/equipment/head.ts",
|
||||||
|
"data/equipment/shields.ts",
|
||||||
|
"data/equipment/swords.ts"
|
||||||
|
],
|
||||||
"data/equipment/feet.ts": [
|
"data/equipment/feet.ts": [
|
||||||
"data/equipment/types.ts"
|
"data/equipment/types.ts"
|
||||||
],
|
],
|
||||||
@@ -313,6 +324,7 @@
|
|||||||
"data/equipment/body.ts",
|
"data/equipment/body.ts",
|
||||||
"data/equipment/casters.ts",
|
"data/equipment/casters.ts",
|
||||||
"data/equipment/catalysts.ts",
|
"data/equipment/catalysts.ts",
|
||||||
|
"data/equipment/equipment-types-data.ts",
|
||||||
"data/equipment/feet.ts",
|
"data/equipment/feet.ts",
|
||||||
"data/equipment/hands.ts",
|
"data/equipment/hands.ts",
|
||||||
"data/equipment/head.ts",
|
"data/equipment/head.ts",
|
||||||
@@ -331,7 +343,7 @@
|
|||||||
"types/equipmentSlot.ts"
|
"types/equipmentSlot.ts"
|
||||||
],
|
],
|
||||||
"data/equipment/utils.ts": [
|
"data/equipment/utils.ts": [
|
||||||
"data/equipment/index.ts",
|
"data/equipment/equipment-types-data.ts",
|
||||||
"data/equipment/types.ts"
|
"data/equipment/types.ts"
|
||||||
],
|
],
|
||||||
"data/fabricator-recipes.ts": [
|
"data/fabricator-recipes.ts": [
|
||||||
@@ -343,19 +355,22 @@
|
|||||||
"data/golems/elemental-golems.ts": [
|
"data/golems/elemental-golems.ts": [
|
||||||
"data/golems/types.ts"
|
"data/golems/types.ts"
|
||||||
],
|
],
|
||||||
|
"data/golems/golems-data.ts": [
|
||||||
|
"data/golems/base-golems.ts",
|
||||||
|
"data/golems/elemental-golems.ts",
|
||||||
|
"data/golems/hybrid-golems.ts"
|
||||||
|
],
|
||||||
"data/golems/hybrid-golems.ts": [
|
"data/golems/hybrid-golems.ts": [
|
||||||
"data/golems/types.ts"
|
"data/golems/types.ts"
|
||||||
],
|
],
|
||||||
"data/golems/index.ts": [
|
"data/golems/index.ts": [
|
||||||
"data/golems/base-golems.ts",
|
"data/golems/golems-data.ts",
|
||||||
"data/golems/elemental-golems.ts",
|
|
||||||
"data/golems/hybrid-golems.ts",
|
|
||||||
"data/golems/types.ts",
|
"data/golems/types.ts",
|
||||||
"data/golems/utils.ts"
|
"data/golems/utils.ts"
|
||||||
],
|
],
|
||||||
"data/golems/types.ts": [],
|
"data/golems/types.ts": [],
|
||||||
"data/golems/utils.ts": [
|
"data/golems/utils.ts": [
|
||||||
"data/golems/index.ts",
|
"data/golems/golems-data.ts",
|
||||||
"data/golems/types.ts"
|
"data/golems/types.ts"
|
||||||
],
|
],
|
||||||
"data/guardian-encounters.ts": [
|
"data/guardian-encounters.ts": [
|
||||||
|
|||||||
@@ -330,10 +330,8 @@ Mana-Loop/
|
|||||||
│ │ ├── crafting-equipment.ts
|
│ │ ├── crafting-equipment.ts
|
||||||
│ │ ├── crafting-loot.ts
|
│ │ ├── crafting-loot.ts
|
||||||
│ │ ├── crafting-prep.ts
|
│ │ ├── crafting-prep.ts
|
||||||
│ │ ├── crafting-slice.ts
|
|
||||||
│ │ ├── crafting-utils.ts
|
│ │ ├── crafting-utils.ts
|
||||||
│ │ ├── effects.ts
|
│ │ ├── effects.ts
|
||||||
│ │ ├── store.ts
|
|
||||||
│ │ └── types.ts
|
│ │ └── types.ts
|
||||||
│ └── utils.ts
|
│ └── utils.ts
|
||||||
├── test-results/
|
├── test-results/
|
||||||
|
|||||||
@@ -83,25 +83,25 @@ describe('Equipment type definitions', () => {
|
|||||||
// ─── Test: Starting equipment ──────────────────────────────────────────────────
|
// ─── Test: Starting equipment ──────────────────────────────────────────────────
|
||||||
|
|
||||||
describe('Starting equipment', () => {
|
describe('Starting equipment', () => {
|
||||||
it('createStartingEquipment returns valid equippedInstances', async () => {
|
it('crafting store initial state has valid equippedInstances', async () => {
|
||||||
const { createStartingEquipment } = await import('@/lib/game/crafting-slice');
|
const { useCraftingStore } = await import('@/lib/game/stores/craftingStore');
|
||||||
const { equippedInstances, equipmentInstances } = createStartingEquipment();
|
const state = useCraftingStore.getState();
|
||||||
|
|
||||||
expect(equippedInstances.mainHand).toBeTruthy();
|
expect(state.equippedInstances.mainHand).toBeTruthy();
|
||||||
expect(equippedInstances.body).toBeTruthy();
|
expect(state.equippedInstances.body).toBeTruthy();
|
||||||
expect(equippedInstances.feet).toBeTruthy();
|
expect(state.equippedInstances.feet).toBeTruthy();
|
||||||
expect(equippedInstances.offHand).toBeNull();
|
expect(state.equippedInstances.offHand).toBeNull();
|
||||||
expect(equippedInstances.head).toBeNull();
|
expect(state.equippedInstances.head).toBeNull();
|
||||||
expect(equippedInstances.hands).toBeNull();
|
expect(state.equippedInstances.hands).toBeNull();
|
||||||
expect(equippedInstances.accessory1).toBeNull();
|
expect(state.equippedInstances.accessory1).toBeNull();
|
||||||
expect(equippedInstances.accessory2).toBeNull();
|
expect(state.equippedInstances.accessory2).toBeNull();
|
||||||
|
|
||||||
expect(Object.keys(equipmentInstances).length).toBe(3);
|
expect(Object.keys(state.equipmentInstances).length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('starting equipment instances have valid fields', async () => {
|
it('starting equipment instances have valid fields', async () => {
|
||||||
const { createStartingEquipment } = await import('@/lib/game/crafting-slice');
|
const { useCraftingStore } = await import('@/lib/game/stores/craftingStore');
|
||||||
const { equipmentInstances } = createStartingEquipment();
|
const { equipmentInstances } = useCraftingStore.getState();
|
||||||
|
|
||||||
for (const instance of Object.values(equipmentInstances)) {
|
for (const instance of Object.values(equipmentInstances)) {
|
||||||
expect(instance.instanceId).toBeTruthy();
|
expect(instance.instanceId).toBeTruthy();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ─── Crafting Application System ────────────────────────────────────────────
|
// ─── Crafting Application System ────────────────────────────────────────────
|
||||||
// Application system functions extracted from crafting-slice.ts
|
// Application system functions
|
||||||
|
|
||||||
import type { EquipmentInstance, AppliedEnchantment, EnchantmentDesign, ApplicationProgress } from './types';
|
import type { EquipmentInstance, AppliedEnchantment, EnchantmentDesign, ApplicationProgress } from './types';
|
||||||
import { calculateApplicationTime, calculateApplicationManaPerHour } from './crafting-utils';
|
import { calculateApplicationTime, calculateApplicationManaPerHour } from './crafting-utils';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ─── Attunement System ──────────────────────────────────────────────────────
|
// ─── Attunement System ──────────────────────────────────────────────────────
|
||||||
// Attunement system functions extracted from crafting-slice.ts
|
// Attunement system functions
|
||||||
|
|
||||||
import type { AttunementState } from './types';
|
import type { AttunementState } from './types';
|
||||||
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ─── Equipment Crafting System ──────────────────────────────────────────────
|
// ─── Equipment Crafting System ──────────────────────────────────────────────
|
||||||
// Equipment crafting functions extracted from crafting-slice.ts
|
// Equipment crafting functions
|
||||||
|
|
||||||
import type { EquipmentInstance, EquipmentCraftingProgress } from './types';
|
import type { EquipmentInstance, EquipmentCraftingProgress } from './types';
|
||||||
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ─── Crafting Loot Inventory System ────────────────────────────────────────────
|
// ─── Crafting Loot Inventory System ────────────────────────────────────────────
|
||||||
// Loot inventory functions extracted from crafting-slice.ts
|
// Loot inventory functions
|
||||||
|
|
||||||
import type { LootInventory } from './types';
|
import type { LootInventory } from './types';
|
||||||
import type { CraftingRecipe } from './data/crafting-recipes';
|
import type { CraftingRecipe } from './data/crafting-recipes';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ─── Crafting Preparation System ────────────────────────────────────────────
|
// ─── Crafting Preparation System ────────────────────────────────────────────
|
||||||
// Preparation system functions extracted from crafting-slice.ts
|
// Preparation system functions
|
||||||
|
|
||||||
import type { EquipmentInstance, PreparationProgress } from './types';
|
import type { EquipmentInstance, PreparationProgress } from './types';
|
||||||
import { calculatePrepTime, calculatePrepManaCost, calculateManaPerHourForPrep } from './crafting-utils';
|
import { calculatePrepTime, calculatePrepManaCost, calculateManaPerHourForPrep } from './crafting-utils';
|
||||||
|
|||||||
@@ -1,379 +0,0 @@
|
|||||||
// ─── Crafting Store Slice ─────────────────────────────────────────────────────
|
|
||||||
// Core slice logic for equipment and enchantment system. Extracted logic lives
|
|
||||||
// in focused modules: crafting-utils, crafting-design, crafting-prep,
|
|
||||||
// crafting-apply, crafting-equipment, crafting-loot, crafting-attunements.
|
|
||||||
|
|
||||||
import type { GameState, EquipmentInstance, AppliedEnchantment, EnchantmentDesign, DesignEffect, EquipmentCraftingProgress, LootInventory, AttunementState } from './types';
|
|
||||||
import { EQUIPMENT_TYPES } from './data/equipment';
|
|
||||||
import type { EquipmentSlot } from './data/equipment';
|
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
|
||||||
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
|
||||||
import { SPELLS_DEF } from './constants';
|
|
||||||
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
|
||||||
import { computeEffects } from './effects/upgrade-effects';
|
|
||||||
import { hasSpecial, SPECIAL_EFFECTS } from './effects/special-effects';
|
|
||||||
import type { ComputedEffects } from './effects/upgrade-effects.types';
|
|
||||||
|
|
||||||
// ─── Crafting Modules ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
import * as CraftingUtils from './crafting-utils';
|
|
||||||
import * as CraftingDesign from './crafting-design';
|
|
||||||
import * as CraftingPrep from './crafting-prep';
|
|
||||||
import * as CraftingApply from './crafting-apply';
|
|
||||||
import * as CraftingEquipment from './crafting-equipment';
|
|
||||||
import * as CraftingLoot from './crafting-loot';
|
|
||||||
import * as CraftingAttunements from './crafting-attunements';
|
|
||||||
import * as CraftingActions from './crafting-actions';
|
|
||||||
|
|
||||||
// ─── Initial Equipment Setup ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function createStartingEquipment() {
|
|
||||||
const staffId = CraftingUtils.generateInstanceId();
|
|
||||||
const staffInstance: EquipmentInstance = {
|
|
||||||
instanceId: staffId,
|
|
||||||
typeId: 'basicStaff',
|
|
||||||
name: 'Basic Staff',
|
|
||||||
enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }],
|
|
||||||
usedCapacity: 50,
|
|
||||||
totalCapacity: 50,
|
|
||||||
rarity: 'common',
|
|
||||||
quality: 100,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const shirtId = CraftingUtils.generateInstanceId();
|
|
||||||
const shirtInstance: EquipmentInstance = {
|
|
||||||
instanceId: shirtId,
|
|
||||||
typeId: 'civilianShirt',
|
|
||||||
name: 'Civilian Shirt',
|
|
||||||
enchantments: [],
|
|
||||||
usedCapacity: 0,
|
|
||||||
totalCapacity: 30,
|
|
||||||
rarity: 'common',
|
|
||||||
quality: 100,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const shoesId = CraftingUtils.generateInstanceId();
|
|
||||||
const shoesInstance: EquipmentInstance = {
|
|
||||||
instanceId: shoesId,
|
|
||||||
typeId: 'civilianShoes',
|
|
||||||
name: 'Civilian Shoes',
|
|
||||||
enchantments: [],
|
|
||||||
usedCapacity: 0,
|
|
||||||
totalCapacity: 15,
|
|
||||||
rarity: 'common',
|
|
||||||
quality: 100,
|
|
||||||
tags: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
equippedInstances: {
|
|
||||||
mainHand: staffId,
|
|
||||||
offHand: null,
|
|
||||||
head: null,
|
|
||||||
body: shirtId,
|
|
||||||
hands: null,
|
|
||||||
feet: shoesId,
|
|
||||||
accessory1: null,
|
|
||||||
accessory2: null,
|
|
||||||
},
|
|
||||||
equipmentInstances: {
|
|
||||||
[staffId]: staffInstance,
|
|
||||||
[shirtId]: shirtInstance,
|
|
||||||
[shoesId]: shoesInstance,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Crafting Actions Interface ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
export type CraftingActions = {
|
|
||||||
createEquipmentInstance: (typeId: string) => string | null;
|
|
||||||
equipItem: (instanceId: string, slot: EquipmentSlot) => boolean;
|
|
||||||
unequipItem: (slot: EquipmentSlot) => void;
|
|
||||||
deleteEquipmentInstance: (instanceId: string) => void;
|
|
||||||
startDesigningEnchantment: (name: string, equipmentTypeId: string, effects: DesignEffect[]) => boolean;
|
|
||||||
cancelDesign: () => void;
|
|
||||||
saveDesign: (design: EnchantmentDesign) => void;
|
|
||||||
deleteDesign: (designId: string) => void;
|
|
||||||
startPreparing: (equipmentInstanceId: string) => boolean;
|
|
||||||
cancelPreparation: () => void;
|
|
||||||
startApplying: (equipmentInstanceId: string, designId: string) => boolean;
|
|
||||||
pauseApplication: () => void;
|
|
||||||
resumeApplication: () => void;
|
|
||||||
cancelApplication: () => void;
|
|
||||||
disenchantEquipment: (instanceId: string) => void;
|
|
||||||
startCraftingEquipment: (blueprintId: string) => boolean;
|
|
||||||
cancelEquipmentCrafting: () => void;
|
|
||||||
deleteMaterial: (materialId: string, amount: number) => void;
|
|
||||||
getEquipmentSpells: () => string[];
|
|
||||||
getEquipmentEffects: () => Record<string, number>;
|
|
||||||
getAvailableCapacity: (instanceId: string) => number;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─── Crafting Store Slice ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function createCraftingSlice(
|
|
||||||
set: (fn: (state: GameState) => Partial<GameState>) => void,
|
|
||||||
get: () => GameState & CraftingActions
|
|
||||||
): CraftingActions {
|
|
||||||
return {
|
|
||||||
createEquipmentInstance: (typeId) => CraftingActions.createEquipmentInstance(typeId, set),
|
|
||||||
equipItem: (instanceId, slot) => CraftingActions.equipItem(instanceId, slot, get, set),
|
|
||||||
unequipItem: (slot) => CraftingActions.unequipItem(slot, set),
|
|
||||||
deleteEquipmentInstance: (instanceId) => CraftingActions.deleteEquipmentInstance(instanceId, get, set),
|
|
||||||
startDesigningEnchantment: (name, equipmentTypeId, effects) =>
|
|
||||||
CraftingActions.startDesigningEnchantment(name, equipmentTypeId, effects, get, set),
|
|
||||||
cancelDesign: () => CraftingActions.cancelDesign(get, set),
|
|
||||||
saveDesign: (design) => CraftingActions.saveDesign(design, get, set),
|
|
||||||
deleteDesign: (designId) => CraftingActions.deleteDesign(designId, set),
|
|
||||||
startPreparing: (equipmentInstanceId) => CraftingActions.startPreparing(equipmentInstanceId, get, set),
|
|
||||||
cancelPreparation: () => CraftingActions.cancelPreparation(set),
|
|
||||||
startApplying: (equipmentInstanceId, designId) =>
|
|
||||||
CraftingActions.startApplying(equipmentInstanceId, designId, get, set),
|
|
||||||
pauseApplication: () => CraftingActions.pauseApplication(get, set),
|
|
||||||
resumeApplication: () => CraftingActions.resumeApplication(get, set),
|
|
||||||
cancelApplication: () => CraftingActions.cancelApplication(set),
|
|
||||||
disenchantEquipment: (instanceId) => CraftingActions.disenchantEquipment(instanceId, get, set),
|
|
||||||
startCraftingEquipment: (blueprintId) => CraftingActions.startCraftingEquipment(blueprintId, get, set),
|
|
||||||
cancelEquipmentCrafting: () => CraftingActions.cancelEquipmentCrafting(get, set),
|
|
||||||
deleteMaterial: (materialId, amount) => CraftingActions.deleteMaterial(materialId, amount, get, set),
|
|
||||||
getEquipmentSpells: () => CraftingActions.getEquipmentSpells(get),
|
|
||||||
getEquipmentEffects: () => CraftingActions.getEquipmentEffects(get),
|
|
||||||
getAvailableCapacity: (instanceId) => CraftingActions.getAvailableCapacity(instanceId, get),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Tick Processing for Crafting ────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function processCraftingTick(
|
|
||||||
state: GameState,
|
|
||||||
effects: { rawMana: number; log: string[] }
|
|
||||||
): Partial<GameState> {
|
|
||||||
const { rawMana, log } = effects;
|
|
||||||
let updates: Partial<GameState> = {};
|
|
||||||
|
|
||||||
const computedEffects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
|
||||||
|
|
||||||
// Process design progress (slot 1)
|
|
||||||
if (state.currentAction === 'design' && state.designProgress) {
|
|
||||||
const designResult = CraftingDesign.calculateDesignProgress(
|
|
||||||
state.designProgress.progress,
|
|
||||||
state.designProgress.required,
|
|
||||||
computedEffects,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (designResult.isComplete) {
|
|
||||||
const completedDesign = CraftingDesign.createCompletedDesignFromProgress(
|
|
||||||
{
|
|
||||||
designId: state.designProgress.designId,
|
|
||||||
name: state.designProgress.name,
|
|
||||||
equipmentType: state.designProgress.equipmentType,
|
|
||||||
effects: state.designProgress.effects,
|
|
||||||
required: state.designProgress.required,
|
|
||||||
},
|
|
||||||
((state.skillUpgrades || {})['efficientEnchant'] || []).length * 0.05
|
|
||||||
);
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
designProgress: null,
|
|
||||||
currentAction: 'meditate' as const,
|
|
||||||
enchantmentDesigns: [...state.enchantmentDesigns, completedDesign],
|
|
||||||
log: [`✅ Enchantment design "${completedDesign.name}" complete!`, ...log],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
designProgress: { ...state.designProgress, progress: designResult.progress },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process second design progress (slot 2)
|
|
||||||
if (state.designProgress2) {
|
|
||||||
const designResult2 = CraftingDesign.calculateSecondDesignProgress(
|
|
||||||
state.designProgress2.progress,
|
|
||||||
state.designProgress2.required,
|
|
||||||
computedEffects,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
if (designResult2.isComplete) {
|
|
||||||
const completedDesign = CraftingDesign.createCompletedDesignFromProgress(
|
|
||||||
{
|
|
||||||
designId: state.designProgress2.designId,
|
|
||||||
name: state.designProgress2.name,
|
|
||||||
equipmentType: state.designProgress2.equipmentType,
|
|
||||||
effects: state.designProgress2.effects,
|
|
||||||
required: state.designProgress2.required,
|
|
||||||
},
|
|
||||||
((state.skillUpgrades || {})['efficientEnchant'] || []).length * 0.05
|
|
||||||
);
|
|
||||||
const shouldTransitionToMeditate = !state.designProgress;
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
designProgress2: null,
|
|
||||||
currentAction: shouldTransitionToMeditate ? 'meditate' as const : state.currentAction,
|
|
||||||
enchantmentDesigns: [...state.enchantmentDesigns, completedDesign],
|
|
||||||
log: [`✅ Enchantment design "${completedDesign.name}" complete! (2nd slot)`, ...log],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
designProgress2: { ...state.designProgress2, progress: designResult2.progress },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process preparation progress
|
|
||||||
if (state.currentAction === 'prepare' && state.preparationProgress) {
|
|
||||||
const instance = state.equipmentInstances[state.preparationProgress.equipmentInstanceId];
|
|
||||||
const manaPerTick = instance ? CraftingPrep.getPreparationManaCostForTick(instance) : 0;
|
|
||||||
|
|
||||||
if (rawMana >= manaPerTick) {
|
|
||||||
const tickResult = CraftingPrep.calculatePreparationTick(
|
|
||||||
state.preparationProgress.progress,
|
|
||||||
state.preparationProgress.required,
|
|
||||||
manaPerTick
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tickResult.isComplete) {
|
|
||||||
if (instance) {
|
|
||||||
const completeResult = CraftingPrep.completePreparation(instance, state.preparationProgress.manaCostPaid);
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
rawMana: rawMana - tickResult.manaConsumed + completeResult.manaRecovered,
|
|
||||||
preparationProgress: null,
|
|
||||||
currentAction: 'meditate' as const,
|
|
||||||
equipmentInstances: {
|
|
||||||
...state.equipmentInstances,
|
|
||||||
[instance.instanceId]: completeResult.updatedInstance,
|
|
||||||
},
|
|
||||||
log: [completeResult.logMessage, ...log],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
preparationProgress: null,
|
|
||||||
currentAction: 'meditate' as const,
|
|
||||||
rawMana: rawMana - tickResult.manaConsumed,
|
|
||||||
log: ['✅ Preparation complete!', ...log],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
rawMana: rawMana - tickResult.manaConsumed,
|
|
||||||
preparationProgress: {
|
|
||||||
...state.preparationProgress,
|
|
||||||
progress: tickResult.progress,
|
|
||||||
manaCostPaid: tickResult.manaCostPaid,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process application progress
|
|
||||||
if (state.currentAction === 'enchant' && state.applicationProgress && !state.applicationProgress.paused) {
|
|
||||||
const app = state.applicationProgress;
|
|
||||||
const manaPerTick = CraftingApply.getApplicationManaCostForTick(app.manaPerHour);
|
|
||||||
|
|
||||||
if (rawMana >= manaPerTick) {
|
|
||||||
const tickResult = CraftingApply.calculateApplicationTick(
|
|
||||||
app.progress,
|
|
||||||
app.required,
|
|
||||||
app.manaSpent,
|
|
||||||
manaPerTick,
|
|
||||||
computedEffects
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tickResult.isComplete) {
|
|
||||||
const instance = state.equipmentInstances[app.equipmentInstanceId];
|
|
||||||
const design = state.enchantmentDesigns.find(d => d.id === app.designId);
|
|
||||||
if (instance && design) {
|
|
||||||
const applyResult = CraftingApply.applyEnchantments(instance, design, computedEffects);
|
|
||||||
const xpGain = CraftingAttunements.gainEnchantingXP(state.attunements, applyResult.xpGained);
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
rawMana: rawMana - tickResult.manaConsumed,
|
|
||||||
applicationProgress: null,
|
|
||||||
currentAction: 'meditate' as const,
|
|
||||||
attunements: {
|
|
||||||
...state.attunements,
|
|
||||||
enchanter: xpGain.attunements.enchanter,
|
|
||||||
},
|
|
||||||
equipmentInstances: {
|
|
||||||
...state.equipmentInstances,
|
|
||||||
[instance.instanceId]: applyResult.updatedInstance,
|
|
||||||
},
|
|
||||||
log: [applyResult.logMessage, ...log],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
rawMana: rawMana - tickResult.manaConsumed,
|
|
||||||
applicationProgress: { ...app, progress: tickResult.progress, manaSpent: tickResult.manaSpent },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process equipment crafting progress
|
|
||||||
if (state.currentAction === 'craft' && state.equipmentCraftingProgress) {
|
|
||||||
const craft = state.equipmentCraftingProgress;
|
|
||||||
const tickResult = CraftingEquipment.calculateCraftingTick(craft.progress, craft.required);
|
|
||||||
|
|
||||||
if (tickResult.isComplete) {
|
|
||||||
const recipe = CraftingEquipment.getRecipe(craft.blueprintId);
|
|
||||||
if (recipe) {
|
|
||||||
const craftResult = CraftingEquipment.completeEquipmentCrafting(craft.blueprintId, recipe);
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
equipmentCraftingProgress: null,
|
|
||||||
currentAction: 'meditate' as const,
|
|
||||||
equipmentInstances: {
|
|
||||||
...state.equipmentInstances,
|
|
||||||
[craftResult.instanceId]: craftResult.instance,
|
|
||||||
},
|
|
||||||
totalCraftsCompleted: (state.totalCraftsCompleted || 0) + 1,
|
|
||||||
log: [craftResult.logMessage, ...log],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
equipmentCraftingProgress: null,
|
|
||||||
currentAction: 'meditate' as const,
|
|
||||||
log: ['⚠️ Crafting failed - invalid recipe!', ...log],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updates = {
|
|
||||||
...updates,
|
|
||||||
equipmentCraftingProgress: { ...craft, progress: tickResult.progress },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return updates;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Export helper to get equipment instance spells ─────────────────────────
|
|
||||||
|
|
||||||
export function getSpellsFromEquipment(instances: Record<string, EquipmentInstance>, equippedIds: (string | null)[]): string[] {
|
|
||||||
const spells: string[] = [];
|
|
||||||
for (const id of equippedIds) {
|
|
||||||
if (!id) continue;
|
|
||||||
const instance = instances[id];
|
|
||||||
if (!instance) continue;
|
|
||||||
for (const ench of instance.enchantments) {
|
|
||||||
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
|
|
||||||
if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) {
|
|
||||||
spells.push(effectDef.effect.spellId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [...new Set(spells)];
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// ─── Crafting Helper Utilities ─────────────────────────────────────────────────
|
// ─── Crafting Helper Utilities ─────────────────────────────────────────────────
|
||||||
// Instance/ID generation and helper functions extracted from crafting-slice.ts
|
// Instance/ID generation and helper functions
|
||||||
|
|
||||||
import type { EquipmentInstance, EnchantmentDesign, DesignEffect } from './types';
|
import type { EquipmentInstance, EnchantmentDesign, DesignEffect } from './types';
|
||||||
import { EQUIPMENT_TYPES, type EquipmentCategory, type EquipmentSlot } from './data/equipment';
|
import { EQUIPMENT_TYPES, type EquipmentCategory, type EquipmentSlot } from './data/equipment';
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
// ─── Game Store (Refactored) ──────────────────────────────────────────────
|
|
||||||
// Main entry point - imports from modular store components
|
|
||||||
|
|
||||||
import { create } from 'zustand';
|
|
||||||
import { persist } from 'zustand/middleware';
|
|
||||||
import type { GameState, GameAction, ActivityLogEntry } from './types';
|
|
||||||
|
|
||||||
import { addActivityLogEntry } from './utils/activity-log';
|
|
||||||
import {
|
|
||||||
computeMaxMana, computeRegen, computeClickMana,
|
|
||||||
getMeditationBonus,
|
|
||||||
} from './utils/mana-utils';
|
|
||||||
import {
|
|
||||||
calcDamage, calcInsight, getIncursionStrength, canAffordSpellCost, deductSpellCost,
|
|
||||||
} from './utils/combat-utils';
|
|
||||||
import { generateFloorState } from './utils/room-utils';
|
|
||||||
|
|
||||||
// Re-export formatting functions for backward compatibility
|
|
||||||
export { fmt, fmtDec } from './utils/formatting';
|
|
||||||
export { getFloorMaxHP, getFloorElement } from './utils/floor-utils';
|
|
||||||
|
|
||||||
// Re-export computed stats functions for backward compatibility and tests
|
|
||||||
export {
|
|
||||||
computeMaxMana, computeRegen, computeClickMana,
|
|
||||||
getMeditationBonus,
|
|
||||||
} from './utils/mana-utils';
|
|
||||||
export {
|
|
||||||
calcDamage, calcInsight, getIncursionStrength, canAffordSpellCost, deductSpellCost,
|
|
||||||
} from './utils/combat-utils';
|
|
||||||
|
|
||||||
// ─── Initial State ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
interface MakeInitialOptions {
|
|
||||||
loopCount?: number;
|
|
||||||
totalInsight?: number;
|
|
||||||
insight?: number;
|
|
||||||
prestigeUpgrades?: Record<string, number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeInitial(opts?: MakeInitialOptions): GameState {
|
|
||||||
return {
|
|
||||||
day: 1,
|
|
||||||
hour: 0,
|
|
||||||
rawMana: 100,
|
|
||||||
maxMana: 100,
|
|
||||||
elements: {},
|
|
||||||
skills: {},
|
|
||||||
skillUpgrades: {},
|
|
||||||
skillTiers: {},
|
|
||||||
spells: {},
|
|
||||||
currentAction: 'meditate' as GameAction,
|
|
||||||
currentStudyTarget: null,
|
|
||||||
parallelStudyTarget: null,
|
|
||||||
activeSpell: null,
|
|
||||||
currentFloor: 100,
|
|
||||||
floorHP: 1000,
|
|
||||||
floorMaxHP: 1000,
|
|
||||||
currentRoom: generateFloorState(100),
|
|
||||||
maxFloorReached: 100,
|
|
||||||
paused: false,
|
|
||||||
gameOver: false,
|
|
||||||
victory: false,
|
|
||||||
loopCount: opts?.loopCount ?? 0,
|
|
||||||
totalInsight: opts?.totalInsight ?? 0,
|
|
||||||
insight: opts?.insight ?? 0,
|
|
||||||
loopInsight: 0,
|
|
||||||
prestigeUpgrades: opts?.prestigeUpgrades ?? {},
|
|
||||||
signedPacts: [],
|
|
||||||
attunements: {},
|
|
||||||
golemancy: { enabledGolems: [] },
|
|
||||||
memories: [],
|
|
||||||
memorySlots: 0,
|
|
||||||
log: [],
|
|
||||||
activityLog: [],
|
|
||||||
meditateTicks: 0,
|
|
||||||
totalManaGathered: 0,
|
|
||||||
equippedInstances: {},
|
|
||||||
equipmentInstances: {},
|
|
||||||
lootInventory: {},
|
|
||||||
blueprints: {},
|
|
||||||
spireMode: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Game Store Interface ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export interface GameStore extends GameState {
|
|
||||||
tick: () => void;
|
|
||||||
gatherMana: () => void;
|
|
||||||
setAction: (action: GameAction) => void;
|
|
||||||
addActivityLog: (eventType: string, message: string, details?: ActivityLogEntry['details']) => void;
|
|
||||||
setSpell: (spellId: string) => void;
|
|
||||||
cancelStudy: () => void;
|
|
||||||
convertMana: (element: string, amount: number) => void;
|
|
||||||
unlockElement: (element: string) => void;
|
|
||||||
doPrestige: (id: string, selectedManaType?: string) => void;
|
|
||||||
startNewLoop: () => void;
|
|
||||||
togglePause: () => void;
|
|
||||||
resetGame: () => void;
|
|
||||||
addLog: (message: string) => void;
|
|
||||||
addAttunementXP: (attunementId: string, amount: number) => void;
|
|
||||||
toggleGolem: (golemId: string) => void;
|
|
||||||
setEnabledGolems: (golemIds: string[]) => void;
|
|
||||||
debugUnlockAttunement: (attunementId: string) => void;
|
|
||||||
debugAddElementalMana: (element: string, amount: number) => void;
|
|
||||||
debugSetTime: (day: number, hour: number) => void;
|
|
||||||
debugAddAttunementXP: (attunementId: string, amount: number) => void;
|
|
||||||
debugSetFloor: (floor: number) => void;
|
|
||||||
resetFloorHP: () => void;
|
|
||||||
getMaxMana: () => number;
|
|
||||||
getRegen: () => number;
|
|
||||||
getClickMana: () => number;
|
|
||||||
getDamage: (spellId: string) => number;
|
|
||||||
getMeditationMultiplier: () => number;
|
|
||||||
canCastSpell: (spellId: string) => boolean;
|
|
||||||
enterSpireMode: () => void;
|
|
||||||
climbDownFloor: () => void;
|
|
||||||
exitSpireMode: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Store Implementation ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export const useGameStore = create<GameStore>()(
|
|
||||||
persist(
|
|
||||||
(set, get) => ({
|
|
||||||
...makeInitial(),
|
|
||||||
|
|
||||||
getMaxMana: () => computeMaxMana(get()),
|
|
||||||
getRegen: () => computeRegen(get()),
|
|
||||||
getClickMana: () => computeClickMana(get()),
|
|
||||||
getDamage: (spellId: string) => calcDamage(get(), spellId),
|
|
||||||
getMeditationMultiplier: () => getMeditationBonus(get().meditateTicks, {}),
|
|
||||||
|
|
||||||
canCastSpell: (spellId: string) => {
|
|
||||||
const state = get();
|
|
||||||
const spell = state.spells?.[spellId];
|
|
||||||
if (!spell) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
addLog: (message: string) => {
|
|
||||||
set((state) => ({
|
|
||||||
log: [message, ...(state.log || []).slice(0, 49)],
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
addActivityLog: (eventType: string, message: string, details?: ActivityLogEntry['details']) => {
|
|
||||||
set((state) => ({
|
|
||||||
activityLog: addActivityLogEntry(state, eventType, message, details),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
tick: () => {
|
|
||||||
const state = get();
|
|
||||||
if (state.gameOver || state.paused) return;
|
|
||||||
|
|
||||||
const maxMana = computeMaxMana(state);
|
|
||||||
const baseRegen = computeRegen(state);
|
|
||||||
|
|
||||||
let hour = state.hour + 1;
|
|
||||||
let day = state.day;
|
|
||||||
if (hour >= 24) { hour -= 24; day += 1; }
|
|
||||||
|
|
||||||
if (day > 100) {
|
|
||||||
const insightGained = calcInsight(state);
|
|
||||||
set({
|
|
||||||
day, hour, gameOver: true, victory: false, loopInsight: insightGained,
|
|
||||||
log: [`⏰ The loop ends. Gained ${insightGained} Insight.`, ...state.log.slice(0, 49)],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rawMana = state.rawMana + baseRegen;
|
|
||||||
rawMana = Math.min(rawMana, maxMana);
|
|
||||||
|
|
||||||
set({
|
|
||||||
day, hour, rawMana,
|
|
||||||
meditateTicks: state.currentAction === 'meditate' ? state.meditateTicks + 1 : 0,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
gatherMana: () => {
|
|
||||||
const state = get();
|
|
||||||
const clickMana = computeClickMana(state);
|
|
||||||
const maxMana = computeMaxMana(state);
|
|
||||||
set((s) => ({
|
|
||||||
rawMana: Math.min(s.rawMana + clickMana, maxMana),
|
|
||||||
totalManaGathered: s.totalManaGathered + clickMana,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
setAction: (action: GameAction) => {
|
|
||||||
set({ currentAction: action });
|
|
||||||
},
|
|
||||||
|
|
||||||
setSpell: (spellId: string) => {
|
|
||||||
set({ activeSpell: spellId });
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelStudy: () => {
|
|
||||||
set({ currentStudyTarget: null, currentAction: 'meditate' });
|
|
||||||
},
|
|
||||||
|
|
||||||
convertMana: (element: string, amount: number) => {
|
|
||||||
set((s) => {
|
|
||||||
const elem = s.elements?.[element];
|
|
||||||
if (!elem || !elem.unlocked) return s;
|
|
||||||
const canConvert = Math.min(amount, Math.floor(s.rawMana / 10), elem.max - elem.current);
|
|
||||||
if (canConvert > 0) {
|
|
||||||
return {
|
|
||||||
rawMana: s.rawMana - canConvert * 10,
|
|
||||||
elements: { ...s.elements, [element]: { ...elem, current: elem.current + canConvert } },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
unlockElement: (element: string) => {
|
|
||||||
set((s) => ({
|
|
||||||
elements: { ...s.elements, [element]: { ...s.elements[element], unlocked: true } },
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
doPrestige: (id: string, selectedManaType?: string) => {
|
|
||||||
set((s) => ({
|
|
||||||
prestigeUpgrades: { ...s.prestigeUpgrades, [id]: (s.prestigeUpgrades[id] || 0) + 1 },
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
startNewLoop: () => {
|
|
||||||
const state = get();
|
|
||||||
const insightGained = state.loopInsight || 0;
|
|
||||||
set({
|
|
||||||
...makeInitial({
|
|
||||||
loopCount: state.loopCount + 1,
|
|
||||||
totalInsight: (state.totalInsight || 0) + insightGained,
|
|
||||||
insight: (state.insight || 0) + insightGained,
|
|
||||||
prestigeUpgrades: state.prestigeUpgrades,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePause: () => {
|
|
||||||
set((s) => ({ paused: !s.paused }));
|
|
||||||
},
|
|
||||||
|
|
||||||
resetGame: () => {
|
|
||||||
set(makeInitial());
|
|
||||||
},
|
|
||||||
|
|
||||||
addAttunementXP: (attunementId: string, amount: number) => {
|
|
||||||
set((s) => {
|
|
||||||
const attState = s.attunements?.[attunementId];
|
|
||||||
if (!attState) return s;
|
|
||||||
return {
|
|
||||||
attunements: { ...s.attunements, [attunementId]: { ...attState, experience: attState.experience + amount } },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleGolem: (golemId: string) => {
|
|
||||||
set((s) => {
|
|
||||||
const enabledGolems = s.golemancy?.enabledGolems || [];
|
|
||||||
const isEnabled = enabledGolems.includes(golemId);
|
|
||||||
return {
|
|
||||||
golemancy: { ...s.golemancy, enabledGolems: isEnabled ? enabledGolems.filter(id => id !== golemId) : [...enabledGolems, golemId] },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setEnabledGolems: (golemIds: string[]) => {
|
|
||||||
set((s) => ({
|
|
||||||
golemancy: { ...s.golemancy, enabledGolems: golemIds },
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
debugUnlockAttunement: (attunementId: string) => {
|
|
||||||
set((s) => ({
|
|
||||||
attunements: { ...s.attunements, [attunementId]: { id: attunementId, active: true, level: 1, experience: 0 } },
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
debugAddElementalMana: (element: string, amount: number) => {
|
|
||||||
set((s) => {
|
|
||||||
const elem = s.elements?.[element];
|
|
||||||
if (!elem) return s;
|
|
||||||
return {
|
|
||||||
elements: { ...s.elements, [element]: { ...elem, current: Math.min(elem.current + amount, elem.max) } },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
debugSetTime: (day: number, hour: number) => {
|
|
||||||
set({ day, hour });
|
|
||||||
},
|
|
||||||
|
|
||||||
debugAddAttunementXP: (attunementId: string, amount: number) => {
|
|
||||||
get().addAttunementXP(attunementId, amount);
|
|
||||||
},
|
|
||||||
|
|
||||||
debugSetFloor: (floor: number) => {
|
|
||||||
set((s) => ({
|
|
||||||
currentFloor: floor,
|
|
||||||
currentRoom: generateFloorState(floor),
|
|
||||||
floorMaxHP: 100 + floor * 50,
|
|
||||||
floorHP: 100 + floor * 50,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFloorHP: () => {
|
|
||||||
set((s) => ({
|
|
||||||
floorHP: s.floorMaxHP,
|
|
||||||
currentRoom: generateFloorState(s.currentFloor),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
enterSpireMode: () => {
|
|
||||||
set({ spireMode: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
climbDownFloor: () => {
|
|
||||||
set((s) => {
|
|
||||||
if (s.currentFloor <= 1) return s;
|
|
||||||
const newFloor = s.currentFloor - 1;
|
|
||||||
return {
|
|
||||||
currentFloor: newFloor,
|
|
||||||
currentRoom: generateFloorState(newFloor),
|
|
||||||
floorMaxHP: 100 + newFloor * 50,
|
|
||||||
floorHP: 100 + newFloor * 50,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
exitSpireMode: () => {
|
|
||||||
set({ spireMode: false });
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: 'mana-loop-game-store',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// ─── Game Loop Hook ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useGameLoop() {
|
|
||||||
const tick = useGameStore((s) => s.tick);
|
|
||||||
return {
|
|
||||||
start: () => {
|
|
||||||
const interval = setInterval(tick, 1000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ import * as CraftingUtils from '../crafting-utils';
|
|||||||
import * as CraftingDesign from '../crafting-design';
|
import * as CraftingDesign from '../crafting-design';
|
||||||
import { useManaStore } from './manaStore';
|
import { useManaStore } from './manaStore';
|
||||||
import { useCombatStore } from './combatStore';
|
import { useCombatStore } from './combatStore';
|
||||||
import { createStartingEquipment } from '../crafting-slice';
|
|
||||||
import { useUIStore } from './uiStore';
|
import { useUIStore } from './uiStore';
|
||||||
import * as ApplicationActions from '../crafting-actions/application-actions';
|
import * as ApplicationActions from '../crafting-actions/application-actions';
|
||||||
import * as PreparationActions from '../crafting-actions/preparation-actions';
|
import * as PreparationActions from '../crafting-actions/preparation-actions';
|
||||||
@@ -16,7 +16,45 @@ import * as CraftingEquipment from '../crafting-equipment';
|
|||||||
export const useCraftingStore = create<CraftingStore>()(
|
export const useCraftingStore = create<CraftingStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => {
|
(set, get) => {
|
||||||
const startingEquipment = createStartingEquipment();
|
const staffId = CraftingUtils.generateInstanceId();
|
||||||
|
const staffInstance = {
|
||||||
|
instanceId: staffId,
|
||||||
|
typeId: 'basicStaff',
|
||||||
|
name: 'Basic Staff',
|
||||||
|
enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }],
|
||||||
|
usedCapacity: 50,
|
||||||
|
totalCapacity: 50,
|
||||||
|
rarity: 'common',
|
||||||
|
quality: 100,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const shirtId = CraftingUtils.generateInstanceId();
|
||||||
|
const shirtInstance = {
|
||||||
|
instanceId: shirtId,
|
||||||
|
typeId: 'civilianShirt',
|
||||||
|
name: 'Civilian Shirt',
|
||||||
|
enchantments: [],
|
||||||
|
usedCapacity: 0,
|
||||||
|
totalCapacity: 30,
|
||||||
|
rarity: 'common',
|
||||||
|
quality: 100,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const shoesId = CraftingUtils.generateInstanceId();
|
||||||
|
const shoesInstance = {
|
||||||
|
instanceId: shoesId,
|
||||||
|
typeId: 'civilianShoes',
|
||||||
|
name: 'Civilian Shoes',
|
||||||
|
enchantments: [],
|
||||||
|
usedCapacity: 0,
|
||||||
|
totalCapacity: 15,
|
||||||
|
rarity: 'common',
|
||||||
|
quality: 100,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Initial state
|
// Initial state
|
||||||
designProgress: null,
|
designProgress: null,
|
||||||
@@ -26,7 +64,21 @@ export const useCraftingStore = create<CraftingStore>()(
|
|||||||
equipmentCraftingProgress: null,
|
equipmentCraftingProgress: null,
|
||||||
enchantmentDesigns: [],
|
enchantmentDesigns: [],
|
||||||
unlockedEffects: [],
|
unlockedEffects: [],
|
||||||
...startingEquipment,
|
equippedInstances: {
|
||||||
|
mainHand: staffId,
|
||||||
|
offHand: null,
|
||||||
|
head: null,
|
||||||
|
body: shirtId,
|
||||||
|
hands: null,
|
||||||
|
feet: shoesId,
|
||||||
|
accessory1: null,
|
||||||
|
accessory2: null,
|
||||||
|
},
|
||||||
|
equipmentInstances: {
|
||||||
|
[staffId]: staffInstance,
|
||||||
|
[shirtId]: shirtInstance,
|
||||||
|
[shoesId]: shoesInstance,
|
||||||
|
},
|
||||||
lootInventory: {
|
lootInventory: {
|
||||||
materials: {},
|
materials: {},
|
||||||
blueprints: [],
|
blueprints: [],
|
||||||
@@ -177,7 +229,7 @@ export const useCraftingStore = create<CraftingStore>()(
|
|||||||
|
|
||||||
// Check if we can start crafting
|
// Check if we can start crafting
|
||||||
const check = CraftingEquipment.canStartEquipmentCrafting(
|
const check = CraftingEquipment.canStartEquipmentCrafting(
|
||||||
bluePrintId,
|
blueprintId,
|
||||||
state.lootInventory.blueprints.includes(blueprintId),
|
state.lootInventory.blueprints.includes(blueprintId),
|
||||||
state.lootInventory.materials,
|
state.lootInventory.materials,
|
||||||
rawMana,
|
rawMana,
|
||||||
@@ -188,7 +240,7 @@ export const useCraftingStore = create<CraftingStore>()(
|
|||||||
|
|
||||||
// Initialize crafting
|
// Initialize crafting
|
||||||
const result = CraftingEquipment.initializeEquipmentCrafting(
|
const result = CraftingEquipment.initializeEquipmentCrafting(
|
||||||
bluePrintId,
|
blueprintId,
|
||||||
state.lootInventory.materials,
|
state.lootInventory.materials,
|
||||||
rawMana
|
rawMana
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export { useCombatStore, makeInitialSpells } from './combatStore';
|
|||||||
export type { CombatState } from './combat-state.types';
|
export type { CombatState } from './combat-state.types';
|
||||||
|
|
||||||
export { useCraftingStore } from './craftingStore';
|
export { useCraftingStore } from './craftingStore';
|
||||||
export type { CraftingState, CraftingActions } from './craftingStore';
|
export type { CraftingState, CraftingActions } from './craftingStore.types';
|
||||||
|
|
||||||
export { useAttunementStore } from './attunementStore';
|
export { useAttunementStore } from './attunementStore';
|
||||||
export type { AttunementStoreState } from './attunementStore';
|
export type { AttunementStoreState } from './attunementStore';
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export type {
|
|||||||
GameActionType,
|
GameActionType,
|
||||||
ActivityEventType,
|
ActivityEventType,
|
||||||
ActivityLogEntry,
|
ActivityLogEntry,
|
||||||
EquipmentSpellState,
|
|
||||||
} from './types/game';
|
} from './types/game';
|
||||||
|
|
||||||
// ─── New: Memory Type Definition ─────────────────────────────────────────────
|
// ─── New: Memory Type Definition ─────────────────────────────────────────────
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import type { AttunementState } from './attunements';
|
import type { AttunementState } from './attunements';
|
||||||
import type { ElementState } from './elements';
|
import type { ElementState } from './elements';
|
||||||
import type { SpellState } from './spells';
|
import type { SpellState } from './spells';
|
||||||
import type { EquipmentInstance, EnchantmentDesign, DesignProgress, PreparationProgress, ApplicationProgress, EquipmentCraftingProgress, EquipmentDef, BlueprintDef, LootInventory, EquipmentSpellState } from './equipment';
|
import type { EquipmentInstance, EnchantmentDesign, DesignProgress, PreparationProgress, ApplicationProgress, EquipmentCraftingProgress, BlueprintDef, LootInventory, EquipmentSpellState } from './equipment';
|
||||||
|
|
||||||
// ─── Activity Log Types ─────────────────────────────────────────────────
|
// ─── Activity Log Types ─────────────────────────────────────────────────
|
||||||
export type ActivityEventType =
|
export type ActivityEventType =
|
||||||
@@ -182,9 +182,6 @@ export interface GameState {
|
|||||||
// Equipment spell states for multi-casting
|
// Equipment spell states for multi-casting
|
||||||
equipmentSpellStates: EquipmentSpellState[];
|
equipmentSpellStates: EquipmentSpellState[];
|
||||||
|
|
||||||
// Legacy Equipment (for backward compatibility)
|
|
||||||
equipment: Record<string, EquipmentDef | null>;
|
|
||||||
inventory: EquipmentDef[];
|
|
||||||
|
|
||||||
// Blueprints
|
// Blueprints
|
||||||
blueprints: Record<string, BlueprintDef>;
|
blueprints: Record<string, BlueprintDef>;
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ export function computeMaxMana(
|
|||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// computeElementMax is now in ../store.ts with support for unlockedManaTypeUpgrades
|
// computeElementMax has been removed — element max is computed in manaStore.ts
|
||||||
// This file no longer exports computeElementMax to avoid duplicate export issues
|
|
||||||
// Import computeElementMax from '../store' instead
|
|
||||||
|
|
||||||
export function computeRegen(
|
export function computeRegen(
|
||||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
|
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
|
||||||
|
|||||||
Reference in New Issue
Block a user