d2d28887b1
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m9s
- Refactored page.tsx (613→252 lines) with GameOverScreen and LeftPanel extracted - Refactored StatsTab.tsx (584→92 lines) with section components - Refactored SkillsTab.tsx (434→54 lines) with sub-components - Created modular structure for GameContext, LootInventory, and other components - All extracted components organized into feature directories
253 lines
7.4 KiB
TypeScript
253 lines
7.4 KiB
TypeScript
// ─── Crafting Slice Logic ─────────────────────────────────────────────────
|
|
|
|
import type { StateCreator } from 'zustand';
|
|
import type { CraftingStore } from './types';
|
|
import type {
|
|
DesignEffect,
|
|
EnchantmentDesign,
|
|
EquipmentInstance
|
|
} from '../../types';
|
|
import type { EquipmentSlot } from '../../data/equipment';
|
|
import { initialCraftingState } from './initial-state';
|
|
import {
|
|
generateInstanceId,
|
|
generateDesignId,
|
|
createEquipmentInstance,
|
|
calculateDesignTime,
|
|
calculatePreparationTime,
|
|
calculatePreparationManaCost,
|
|
calculateApplicationTime,
|
|
calculateApplicationManaPerHour
|
|
} from './utils';
|
|
import {
|
|
EQUIPMENT_SLOTS,
|
|
getEquipmentType
|
|
} from '../../data/equipment';
|
|
import { createSelectors } from './selectors';
|
|
import {
|
|
processDesignTick,
|
|
processPreparationTick,
|
|
processApplicationTick
|
|
} from './tick-processors';
|
|
|
|
// ─── Cached Skills Workaround ──────────────────────────────────────────────
|
|
// We need to access skills from the main store - this is a workaround
|
|
// The store will pass skills when calling these methods
|
|
|
|
let cachedSkills: Record<string, number> = {};
|
|
|
|
export function setCachedSkills(skills: Record<string, number>): void {
|
|
cachedSkills = skills;
|
|
}
|
|
|
|
// ─── Slice Creator ─────────────────────────────────────────────────────────
|
|
|
|
export const createCraftingSlice: StateCreator<CraftingStore, [], [], CraftingStore> = (set, get) => {
|
|
const selectors = createSelectors(get);
|
|
|
|
return {
|
|
...initialCraftingState,
|
|
|
|
// Equipment management
|
|
createEquipment: (typeId: string, slot?: EquipmentSlot) => {
|
|
const instance = createEquipmentInstance(typeId);
|
|
|
|
set((state) => ({
|
|
equipmentInstances: {
|
|
...state.equipmentInstances,
|
|
[instance.instanceId]: instance,
|
|
},
|
|
}));
|
|
|
|
// Auto-equip if slot provided
|
|
if (slot) {
|
|
get().equipInstance(instance.instanceId, slot);
|
|
}
|
|
|
|
return instance;
|
|
},
|
|
|
|
equipInstance: (instanceId: string, slot: EquipmentSlot) => {
|
|
const instance = get().equipmentInstances[instanceId];
|
|
if (!instance) return;
|
|
|
|
const typeDef = getEquipmentType(instance.typeId);
|
|
if (!typeDef) return;
|
|
|
|
// Check if equipment can go in this slot
|
|
if (typeDef.slot !== slot) {
|
|
// For accessories, both accessory1 and accessory2 are valid
|
|
if (typeDef.category !== 'accessory' || (slot !== 'accessory1' && slot !== 'accessory2')) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
set((state) => ({
|
|
equippedInstances: {
|
|
...state.equippedInstances,
|
|
[slot]: instanceId,
|
|
},
|
|
}));
|
|
},
|
|
|
|
unequipSlot: (slot: EquipmentSlot) => {
|
|
set((state) => ({
|
|
equippedInstances: {
|
|
...state.equippedInstances,
|
|
[slot]: null,
|
|
},
|
|
}));
|
|
},
|
|
|
|
deleteInstance: (instanceId: string) => {
|
|
set((state) => {
|
|
const newInstanceMap = { ...state.equipmentInstances };
|
|
delete newInstanceMap[instanceId];
|
|
|
|
// Remove from equipped slots
|
|
const newEquipped = { ...state.equippedInstances };
|
|
for (const slot of EQUIPMENT_SLOTS) {
|
|
if (newEquipped[slot] === instanceId) {
|
|
newEquipped[slot] = null;
|
|
}
|
|
}
|
|
|
|
return {
|
|
equipmentInstances: newInstanceMap,
|
|
equippedInstances: newEquipped,
|
|
};
|
|
});
|
|
},
|
|
|
|
// Enchantment design
|
|
startDesign: (name: string, equipmentType: string, effects: DesignEffect[]) => {
|
|
const totalCapacity = effects.reduce((sum, e) => sum + e.capacityCost, 0);
|
|
const designTime = calculateDesignTime(effects);
|
|
|
|
const design: EnchantmentDesign = {
|
|
id: generateDesignId(),
|
|
name,
|
|
equipmentType,
|
|
effects,
|
|
totalCapacityUsed: totalCapacity,
|
|
designTime,
|
|
created: Date.now(),
|
|
};
|
|
|
|
set((state) => ({
|
|
enchantmentDesigns: [...state.enchantmentDesigns, design],
|
|
designProgress: {
|
|
designId: design.id,
|
|
progress: 0,
|
|
required: designTime,
|
|
name,
|
|
equipmentType,
|
|
effects,
|
|
},
|
|
}));
|
|
},
|
|
|
|
cancelDesign: () => {
|
|
const progress = get().designProgress;
|
|
if (!progress) return;
|
|
|
|
set((state) => ({
|
|
designProgress: null,
|
|
enchantmentDesigns: state.enchantmentDesigns.filter(d => d.id !== progress.designId),
|
|
}));
|
|
},
|
|
|
|
deleteDesign: (designId: string) => {
|
|
set((state) => ({
|
|
enchantmentDesigns: state.enchantmentDesigns.filter(d => d.id !== designId),
|
|
}));
|
|
},
|
|
|
|
// Equipment preparation
|
|
startPreparation: (instanceId: string) => {
|
|
const instance = get().equipmentInstances[instanceId];
|
|
if (!instance) return;
|
|
|
|
const prepTime = calculatePreparationTime(instance.typeId);
|
|
const manaCost = calculatePreparationManaCost(instance.typeId);
|
|
|
|
set({
|
|
preparationProgress: {
|
|
equipmentInstanceId: instanceId,
|
|
progress: 0,
|
|
required: prepTime,
|
|
manaCostPaid: 0,
|
|
},
|
|
});
|
|
},
|
|
|
|
cancelPreparation: () => {
|
|
set({ preparationProgress: null });
|
|
},
|
|
|
|
// Enchantment application
|
|
startApplication: (instanceId: string, designId: string) => {
|
|
const instance = get().equipmentInstances[instanceId];
|
|
const design = get().enchantmentDesigns.find(d => d.id === designId);
|
|
|
|
if (!instance || !design) return;
|
|
|
|
const appTime = calculateApplicationTime(design.effects, cachedSkills);
|
|
const manaPerHour = calculateApplicationManaPerHour(design.effects);
|
|
|
|
set({
|
|
applicationProgress: {
|
|
equipmentInstanceId: instanceId,
|
|
designId,
|
|
progress: 0,
|
|
required: appTime,
|
|
manaPerHour,
|
|
paused: false,
|
|
manaSpent: 0,
|
|
},
|
|
});
|
|
},
|
|
|
|
pauseApplication: () => {
|
|
const progress = get().applicationProgress;
|
|
if (!progress) return;
|
|
|
|
set({
|
|
applicationProgress: { ...progress, paused: true },
|
|
});
|
|
},
|
|
|
|
resumeApplication: () => {
|
|
const progress = get().applicationProgress;
|
|
if (!progress) return;
|
|
|
|
set({
|
|
applicationProgress: { ...progress, paused: false },
|
|
});
|
|
},
|
|
|
|
cancelApplication: () => {
|
|
set({ applicationProgress: null });
|
|
},
|
|
|
|
// Tick processing - delegated to tick-processors module
|
|
processDesignTick: (hours: number) => {
|
|
return processDesignTick(get(), set, hours);
|
|
},
|
|
|
|
processPreparationTick: (hours: number, manaAvailable: number) => {
|
|
return processPreparationTick(get(), set, hours, manaAvailable);
|
|
},
|
|
|
|
processApplicationTick: (hours: number, manaAvailable: number) => {
|
|
return processApplicationTick(get(), set, get, hours, manaAvailable, cachedSkills);
|
|
},
|
|
|
|
// Selectors - delegated to selectors module
|
|
getEquippedInstance: selectors.getEquippedInstance,
|
|
getAllEquipped: selectors.getAllEquipped,
|
|
getAvailableSpells: selectors.getAvailableSpells,
|
|
getEquipmentEffects: selectors.getEquipmentEffects,
|
|
};
|
|
};
|