8a7ddaae27
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
- CombatState: split into CombatState (data) + CombatActions + CombatStore - PrestigeState: split into PrestigeState (data) + PrestigeActions + PrestigeStore - ManaState: split into ManaState (data) + ManaActions + ManaStore - GameState: deprecated, removed from barrel exports - crafting-actions: updated to use CraftingState instead of GameState - combat-utils/mana-utils: replaced Pick<GameState,...> with focused interfaces - DisciplineCardProps: split into Definition + Runtime + Callbacks - stores/index.ts: now exports both State and Actions types
383 lines
13 KiB
TypeScript
383 lines
13 KiB
TypeScript
// ─── Crafting Store ─────────────────────────────────────────────────────
|
|
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import type { DesignProgress, EnchantmentDesign, DesignEffect } from '../types';
|
|
import type { EquipmentSlot } from '../types/equipmentSlot';
|
|
import type { CraftingStore, CraftingState } from './craftingStore.types';
|
|
import * as CraftingUtils from '../crafting-utils';
|
|
import * as CraftingDesign from '../crafting-design';
|
|
import { useManaStore } from './manaStore';
|
|
import { useCombatStore } from './combatStore';
|
|
|
|
import { useUIStore } from './uiStore';
|
|
import * as ApplicationActions from '../crafting-actions/application-actions';
|
|
import * as PreparationActions from '../crafting-actions/preparation-actions';
|
|
import * as CraftingEquipment from '../crafting-equipment';
|
|
|
|
export const useCraftingStore = create<CraftingStore>()(
|
|
persist(
|
|
(set, get) => {
|
|
const staffId = CraftingUtils.generateInstanceId();
|
|
const staffInstance = {
|
|
instanceId: staffId,
|
|
typeId: 'basicStaff',
|
|
name: 'Basic Staff',
|
|
enchantments: [{ effectId: 'spell_manaBolt', stacks: 1, actualCost: 50 }],
|
|
usedCapacity: 50,
|
|
totalCapacity: 50,
|
|
rarity: 'common',
|
|
quality: 100,
|
|
tags: [],
|
|
};
|
|
|
|
const shirtId = CraftingUtils.generateInstanceId();
|
|
const shirtInstance = {
|
|
instanceId: shirtId,
|
|
typeId: 'civilianShirt',
|
|
name: 'Civilian Shirt',
|
|
enchantments: [],
|
|
usedCapacity: 0,
|
|
totalCapacity: 30,
|
|
rarity: 'common',
|
|
quality: 100,
|
|
tags: [],
|
|
};
|
|
|
|
const shoesId = CraftingUtils.generateInstanceId();
|
|
const shoesInstance = {
|
|
instanceId: shoesId,
|
|
typeId: 'civilianShoes',
|
|
name: 'Civilian Shoes',
|
|
enchantments: [],
|
|
usedCapacity: 0,
|
|
totalCapacity: 15,
|
|
rarity: 'common',
|
|
quality: 100,
|
|
tags: [],
|
|
};
|
|
|
|
return {
|
|
// Initial state
|
|
designProgress: null,
|
|
designProgress2: null,
|
|
preparationProgress: null,
|
|
applicationProgress: null,
|
|
equipmentCraftingProgress: null,
|
|
enchantmentDesigns: [],
|
|
unlockedEffects: [],
|
|
equippedInstances: {
|
|
mainHand: staffId,
|
|
offHand: null,
|
|
head: null,
|
|
body: shirtId,
|
|
hands: null,
|
|
feet: shoesId,
|
|
accessory1: null,
|
|
accessory2: null,
|
|
},
|
|
equipmentInstances: {
|
|
[staffId]: staffInstance,
|
|
[shirtId]: shirtInstance,
|
|
[shoesId]: shoesInstance,
|
|
},
|
|
lootInventory: {
|
|
materials: {},
|
|
blueprints: [],
|
|
},
|
|
enchantmentSelection: {
|
|
selectedEquipmentType: null,
|
|
selectedEffects: [],
|
|
designName: '',
|
|
selectedDesign: null,
|
|
selectedEquipmentInstance: null,
|
|
},
|
|
|
|
// Actions
|
|
setDesignProgress: (progress) => set({ designProgress: progress }),
|
|
setDesignProgress2: (progress) => set({ designProgress2: progress }),
|
|
setPreparationProgress: (progress) => set({ preparationProgress: progress }),
|
|
setApplicationProgress: (progress) => set({ applicationProgress: progress }),
|
|
setEquipmentCraftingProgress: (progress) => set({ equipmentCraftingProgress: progress }),
|
|
|
|
// Enchantment design actions
|
|
startDesigningEnchantment: (name, equipmentTypeId, effects) => {
|
|
const state = get(); // crafting state
|
|
|
|
const validation = CraftingDesign.validateDesignEffects(effects, equipmentTypeId, 0);
|
|
if (!validation.valid) return false;
|
|
|
|
const equipType = CraftingUtils.getEquipmentType(equipmentTypeId);
|
|
if (!equipType) return false;
|
|
|
|
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, 0);
|
|
|
|
if (totalCapacityCost > equipType.baseCapacity) return false;
|
|
|
|
let updates: Partial<CraftingState> = {};
|
|
|
|
if (!state.designProgress) {
|
|
updates = {
|
|
designProgress: {
|
|
designId: CraftingUtils.generateDesignId(),
|
|
progress: 0,
|
|
required: CraftingDesign.calculateDesignTime(effects),
|
|
name,
|
|
equipmentType: equipmentTypeId,
|
|
effects,
|
|
},
|
|
};
|
|
// Update currentAction in combatStore
|
|
useCombatStore.setState({ currentAction: 'design' });
|
|
} else if (!state.designProgress2) {
|
|
updates = {
|
|
designProgress2: {
|
|
designId: CraftingUtils.generateDesignId(),
|
|
progress: 0,
|
|
required: CraftingDesign.calculateDesignTime(effects),
|
|
name,
|
|
equipmentType: equipmentTypeId,
|
|
effects,
|
|
},
|
|
};
|
|
useCombatStore.setState({ currentAction: 'design' });
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
set(updates);
|
|
return true;
|
|
},
|
|
|
|
cancelDesign: () => {
|
|
const state = get();
|
|
if (state.designProgress) {
|
|
set({ designProgress: null });
|
|
useCombatStore.setState({ currentAction: 'meditate' });
|
|
} else if (state.designProgress2) {
|
|
set({ designProgress2: null });
|
|
}
|
|
},
|
|
|
|
deleteDesign: (designId) => {
|
|
set((state) => ({
|
|
enchantmentDesigns: state.enchantmentDesigns.filter(d => d.id !== designId),
|
|
}));
|
|
},
|
|
|
|
// Enchantment design save
|
|
saveDesign: (design) => {
|
|
const state = get();
|
|
if (state.designProgress2 && state.designProgress2.designId === design.id) {
|
|
set((s) => ({
|
|
enchantmentDesigns: [...s.enchantmentDesigns, design],
|
|
designProgress2: null,
|
|
}));
|
|
} else {
|
|
set((s) => ({
|
|
enchantmentDesigns: [...s.enchantmentDesigns, design],
|
|
designProgress: null,
|
|
}));
|
|
useCombatStore.setState({ currentAction: 'meditate' });
|
|
}
|
|
},
|
|
|
|
// Enchantment application actions
|
|
startApplying: (equipmentInstanceId, designId) => {
|
|
const currentAction = useCombatStore.getState().currentAction;
|
|
return ApplicationActions.startApplying(
|
|
equipmentInstanceId,
|
|
designId,
|
|
get,
|
|
set as unknown as (partial: Partial<CraftingState>) => void,
|
|
currentAction
|
|
);
|
|
},
|
|
|
|
pauseApplication: () => {
|
|
ApplicationActions.pauseApplication(get, set as unknown as (partial: Partial<CraftingState>) => void);
|
|
},
|
|
|
|
resumeApplication: () => {
|
|
ApplicationActions.resumeApplication(get, set as unknown as (partial: Partial<CraftingState>) => void);
|
|
},
|
|
|
|
cancelApplication: () => {
|
|
ApplicationActions.cancelApplication(set as unknown as (partial: Partial<CraftingState>) => void);
|
|
useCombatStore.setState({ currentAction: 'meditate' });
|
|
},
|
|
|
|
// Preparation actions
|
|
startPreparing: (equipmentInstanceId) => {
|
|
const rawMana = useManaStore.getState().rawMana;
|
|
const result = PreparationActions.startPreparing(
|
|
equipmentInstanceId,
|
|
rawMana,
|
|
get,
|
|
set
|
|
);
|
|
if (result) {
|
|
useCombatStore.setState({ currentAction: 'prepare' });
|
|
}
|
|
return result;
|
|
},
|
|
|
|
cancelPreparation: () => {
|
|
PreparationActions.cancelPreparation(set);
|
|
useCombatStore.setState({ currentAction: 'meditate' });
|
|
},
|
|
|
|
// Equipment crafting actions
|
|
startCraftingEquipment: (blueprintId: string) => {
|
|
const state = get();
|
|
const rawMana = useManaStore.getState().rawMana;
|
|
const currentAction = useCombatStore.getState().currentAction;
|
|
|
|
// Check if we can start crafting
|
|
const check = CraftingEquipment.canStartEquipmentCrafting(
|
|
blueprintId,
|
|
state.lootInventory.blueprints.includes(blueprintId),
|
|
state.lootInventory.materials,
|
|
rawMana,
|
|
currentAction
|
|
);
|
|
|
|
if (!check.canCraft) return false;
|
|
|
|
// Initialize crafting
|
|
const result = CraftingEquipment.initializeEquipmentCrafting(
|
|
blueprintId,
|
|
state.lootInventory.materials,
|
|
rawMana
|
|
);
|
|
|
|
// Update crafting store state
|
|
set((s) => ({
|
|
lootInventory: {
|
|
...s.lootInventory,
|
|
materials: result.newMaterials,
|
|
},
|
|
equipmentCraftingProgress: result.progress,
|
|
}));
|
|
|
|
// Update mana store (deduct mana)
|
|
useManaStore.setState((s) => ({ rawMana: s.rawMana - result.manaCost }));
|
|
|
|
// Update combat store (set current action)
|
|
useCombatStore.setState({ currentAction: 'craft' });
|
|
|
|
return true;
|
|
},
|
|
|
|
cancelEquipmentCrafting: () => {
|
|
const state = get();
|
|
const progress = state.equipmentCraftingProgress;
|
|
if (!progress) return;
|
|
|
|
// Get cancel result (mana refund)
|
|
const cancelResult = CraftingEquipment.cancelEquipmentCrafting(
|
|
progress.blueprintId,
|
|
progress.manaSpent
|
|
);
|
|
|
|
// Update crafting store state
|
|
set({ equipmentCraftingProgress: null });
|
|
|
|
// Refund mana to mana store
|
|
useManaStore.setState((s) => ({ rawMana: s.rawMana + cancelResult.manaRefund }));
|
|
|
|
// Update combat store (reset action)
|
|
useCombatStore.setState({ currentAction: 'meditate' });
|
|
|
|
// Add log message to UI store
|
|
useUIStore.getState().addLog(cancelResult.logMessage);
|
|
},
|
|
|
|
// Enchantment selection actions
|
|
setSelectedEquipmentType: (type) => {
|
|
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentType: type }}));
|
|
},
|
|
setSelectedEffects: (effects) => {
|
|
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEffects: effects } }));
|
|
},
|
|
setDesignName: (name) => {
|
|
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, designName: name } }));
|
|
},
|
|
setSelectedDesign: (id) => {
|
|
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedDesign: id } }));
|
|
},
|
|
setSelectedEquipmentInstance: (id) => {
|
|
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentInstance: id } }));
|
|
},
|
|
resetEnchantmentSelection: () => {
|
|
set((s) => ({
|
|
enchantmentSelection: {
|
|
selectedEquipmentType: null,
|
|
selectedEffects: [],
|
|
designName: '',
|
|
selectedDesign: null,
|
|
selectedEquipmentInstance: null,
|
|
},
|
|
}));
|
|
},
|
|
|
|
// Loot inventory actions
|
|
deleteMaterial: (materialId: string, amount: number) => {
|
|
set((state) => {
|
|
const newMaterials = { ...state.lootInventory.materials };
|
|
const currentAmount = newMaterials[materialId] || 0;
|
|
const newAmount = Math.max(0, currentAmount - amount);
|
|
|
|
if (newAmount <= 0) {
|
|
delete newMaterials[materialId];
|
|
} else {
|
|
newMaterials[materialId] = newAmount;
|
|
}
|
|
|
|
return {
|
|
lootInventory: {
|
|
...state.lootInventory,
|
|
materials: newMaterials,
|
|
},
|
|
};
|
|
});
|
|
},
|
|
|
|
deleteEquipmentInstance: (instanceId: string) => {
|
|
set((state) => {
|
|
let newEquipped = { ...state.equippedInstances };
|
|
for (const [slot, id] of Object.entries(newEquipped)) {
|
|
if (id === instanceId) {
|
|
newEquipped[slot as EquipmentSlot] = null;
|
|
}
|
|
}
|
|
|
|
const newInstances = { ...state.equipmentInstances };
|
|
delete newInstances[instanceId];
|
|
|
|
return {
|
|
equippedInstances: newEquipped,
|
|
equipmentInstances: newInstances,
|
|
};
|
|
});
|
|
},
|
|
};
|
|
},
|
|
{
|
|
name: 'mana-loop-crafting',
|
|
partialize: (state) => ({
|
|
designProgress: state.designProgress,
|
|
designProgress2: state.designProgress2,
|
|
preparationProgress: state.preparationProgress,
|
|
applicationProgress: state.applicationProgress,
|
|
equipmentCraftingProgress: state.equipmentCraftingProgress,
|
|
enchantmentDesigns: state.enchantmentDesigns,
|
|
unlockedEffects: state.unlockedEffects,
|
|
equipmentInstances: state.equipmentInstances,
|
|
equippedInstances: state.equippedInstances,
|
|
lootInventory: state.lootInventory,
|
|
enchantmentSelection: state.enchantmentSelection,
|
|
}),
|
|
}
|
|
)
|
|
);
|