diff --git a/src/lib/game/crafting-slice.ts b/src/lib/game/crafting-slice.ts index f901bc2..f81c79a 100755 --- a/src/lib/game/crafting-slice.ts +++ b/src/lib/game/crafting-slice.ts @@ -7,6 +7,7 @@ import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchant 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, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects'; // ─── Helper Functions ───────────────────────────────────────────────────────── @@ -301,35 +302,78 @@ export function createCraftingSlice( const designId = `design_${Date.now()}`; const designTime = calculateDesignTime(effects); - // Store design data in progress - set(() => ({ - currentAction: 'design', - designProgress: { - designId, - progress: 0, - required: designTime, - name, - equipmentType: equipmentTypeId, - effects, - }, - })); + // Check for ENCHANT_MASTERY: allow 2 concurrent designs + const hasEnchantMastery = hasSpecial( + computeEffects(state.skillUpgrades || {}, state.skillTiers || {}), + SPECIAL_EFFECTS.ENCHANT_MASTERY + ); + // Determine which design slot to use + let updates: any = {}; + + if (!state.designProgress) { + // First slot is free + updates = { + currentAction: 'design', + designProgress: { + designId, + progress: 0, + required: designTime, + name, + equipmentType: equipmentTypeId, + effects, + }, + }; + } else if (hasEnchantMastery && !state.designProgress2) { + // Second slot available with ENCHANT_MASTERY + updates = { + designProgress2: { + designId, + progress: 0, + required: designTime, + name, + equipmentType: equipmentTypeId, + effects, + }, + }; + } else { + return false; // No slot available + } + + set(() => updates); return true; }, cancelDesign: () => { - set(() => ({ - currentAction: 'meditate', - designProgress: null, - })); + const state = get(); + // Check if cancelling designProgress2 + if (state.designProgress2 && !state.designProgress) { + set(() => ({ + designProgress2: null, + })); + } else { + set(() => ({ + currentAction: 'meditate', + designProgress: null, + })); + } }, saveDesign: (design: EnchantmentDesign) => { - set((state) => ({ - enchantmentDesigns: [...state.enchantmentDesigns, design], - designProgress: null, - currentAction: 'meditate', - })); + const state = get(); + // Check if saving from designProgress2 + if (state.designProgress2 && state.designProgress2.designId === design.id) { + set((state) => ({ + enchantmentDesigns: [...state.enchantmentDesigns, design], + designProgress2: null, + })); + } else { + set((state) => ({ + enchantmentDesigns: [...state.enchantmentDesigns, design], + designProgress: null, + currentAction: 'meditate', + })); + } }, deleteDesign: (designId: string) => { @@ -616,9 +660,28 @@ export function processCraftingTick( const { rawMana, log } = effects; let updates: Partial = {}; + // Get computed effects for special effect checks + const computedEffects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {}); + // Process design progress if (state.currentAction === 'design' && state.designProgress) { - const progress = state.designProgress.progress + 0.04; // HOURS_PER_TICK + // Check for INSTANT_DESIGNS special effect (10% chance instant completion) + let progress = state.designProgress.progress + 0.04; // HOURS_PER_TICK + + // HASTY_ENCHANTER: +25% speed for repeat designs + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) { + const designId = state.designProgress.designId; + const isRepeatDesign = state.enchantmentDesigns.some(d => d.equipmentType === state.designProgress?.equipmentType); + if (isRepeatDesign) { + progress += 0.04 * 0.25; // +25% speed + } + } + + // INSTANT_DESIGNS: 10% chance of instant completion + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < 0.10) { + progress = state.designProgress.required; + } + if (progress >= state.designProgress.required) { // Design complete - auto-save the design using stored data const dp = state.designProgress; @@ -653,6 +716,56 @@ export function processCraftingTick( } } + // Process second design progress (for ENCHANT_MASTERY) + if (state.designProgress2) { + let progress2 = state.designProgress2.progress + 0.04; // HOURS_PER_TICK + + // HASTY_ENCHANTER: +25% speed for repeat designs + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) { + const isRepeatDesign = state.enchantmentDesigns.some(d => d.equipmentType === state.designProgress2?.equipmentType); + if (isRepeatDesign) { + progress2 += 0.04 * 0.25; // +25% speed + } + } + + // INSTANT_DESIGNS: 10% chance of instant completion + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < 0.10) { + progress2 = state.designProgress2.required; + } + + if (progress2 >= state.designProgress2.required) { + // Design complete - auto-save the design using stored data + const dp = state.designProgress2; + const efficiencyBonus = (state.skills.efficientEnchant || 0) * 0.05; + const totalCapacityCost = calculateDesignCapacityCost(dp.effects, efficiencyBonus); + + const completedDesign: EnchantmentDesign = { + id: dp.designId, + name: dp.name, + equipmentType: dp.equipmentType, + effects: dp.effects, + totalCapacityUsed: totalCapacityCost, + designTime: dp.required, + created: Date.now(), + }; + + updates = { + ...updates, + designProgress2: null, + enchantmentDesigns: [...state.enchantmentDesigns, completedDesign], + log: [`✅ Enchantment design "${dp.name}" complete! (2nd slot)`, ...log], + }; + } else { + updates = { + ...updates, + designProgress2: { + ...state.designProgress2, + progress: progress2, + }, + }; + } + } + // Process preparation progress if (state.currentAction === 'prepare' && state.preparationProgress) { const prep = state.preparationProgress; @@ -693,20 +806,55 @@ export function processCraftingTick( const manaCost = app.manaPerHour * 0.04; // HOURS_PER_TICK if (rawMana >= manaCost) { - const progress = app.progress + 0.04; + let progress = app.progress + 0.04; const manaSpent = app.manaSpent + manaCost; + // Check for free enchantment chances + // ENCHANT_PRESERVATION: 25% chance free enchant + // THRIFTY_ENCHANTER: +10% chance free enchantment + // OPTIMIZED_ENCHANTING: +25% chance free enchantment + let freeEnchantChance = 0; + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.ENCHANT_PRESERVATION)) { + freeEnchantChance += 0.25; + } + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.THRIFTY_ENCHANTER)) { + freeEnchantChance += 0.10; + } + if (hasSpecial(computedEffects, SPECIAL_EFFECTS.OPTIMIZED_ENCHANTING)) { + freeEnchantChance += 0.25; + } + + // If free enchant triggers, complete instantly + if (freeEnchantChance > 0 && Math.random() < freeEnchantChance) { + progress = app.required; + } + if (progress >= app.required) { // Apply the enchantment! const instance = state.equipmentInstances[app.equipmentInstanceId]; const design = state.enchantmentDesigns.find(d => d.id === app.designId); if (instance && design) { - const newEnchantments: AppliedEnchantment[] = design.effects.map(eff => ({ - effectId: eff.effectId, - stacks: eff.stacks, - actualCost: eff.capacityCost, - })); + // PURE_ESSENCE: +25% power for tier 1 enchants + const isPureEssenceActive = hasSpecial(computedEffects, SPECIAL_EFFECTS.PURE_ESSENCE); + + const newEnchantments: AppliedEnchantment[] = design.effects.map(eff => { + let stacks = eff.stacks; + let actualCost = eff.capacityCost; + + // Check if this is a tier 1 enchantment (heuristic: baseCapacityCost < 100) + const effectDef = ENCHANTMENT_EFFECTS[eff.effectId]; + if (isPureEssenceActive && effectDef && effectDef.baseCapacityCost < 100) { + // +25% power = increase stacks by 25% (rounded up) + stacks = Math.ceil(stacks * 1.25); + } + + return { + effectId: eff.effectId, + stacks, + actualCost, + }; + }); // Calculate and grant attunement XP to enchanter const xpGained = calculateEnchantingXP(design.totalCapacityUsed); diff --git a/src/lib/game/store.ts b/src/lib/game/store.ts index b34db30..d497d2e 100755 --- a/src/lib/game/store.ts +++ b/src/lib/game/store.ts @@ -698,6 +698,7 @@ function makeInitial(overrides: Partial = {}): GameState { equipmentInstances: startingEquipment.equipmentInstances, enchantmentDesigns: [], designProgress: null, + designProgress2: null, preparationProgress: null, applicationProgress: null, equipmentCraftingProgress: null, diff --git a/src/lib/game/types/game.ts b/src/lib/game/types/game.ts index 337dfbf..5c2ba11 100644 --- a/src/lib/game/types/game.ts +++ b/src/lib/game/types/game.ts @@ -142,6 +142,7 @@ export interface GameState { // Crafting Progress designProgress: DesignProgress | null; + designProgress2: DesignProgress | null; // For ENCHANT_MASTERY (2 concurrent designs) preparationProgress: PreparationProgress | null; applicationProgress: ApplicationProgress | null; equipmentCraftingProgress: EquipmentCraftingProgress | null;