Phase 4: Enchanting special effects (6)

This commit is contained in:
Refactoring Agent
2026-04-25 09:53:48 +02:00
parent 8d1d328c3f
commit 77f181b4a1
3 changed files with 178 additions and 28 deletions
+176 -28
View File
@@ -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<GameState> = {};
// 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);