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
+157 -9
View File
@@ -7,6 +7,7 @@ import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchant
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes'; import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
import { SPELLS_DEF } from './constants'; import { SPELLS_DEF } from './constants';
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements'; import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
import { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects';
// ─── Helper Functions ───────────────────────────────────────────────────────── // ─── Helper Functions ─────────────────────────────────────────────────────────
@@ -301,8 +302,18 @@ export function createCraftingSlice(
const designId = `design_${Date.now()}`; const designId = `design_${Date.now()}`;
const designTime = calculateDesignTime(effects); const designTime = calculateDesignTime(effects);
// Store design data in progress // Check for ENCHANT_MASTERY: allow 2 concurrent designs
set(() => ({ 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', currentAction: 'design',
designProgress: { designProgress: {
designId, designId,
@@ -312,24 +323,57 @@ export function createCraftingSlice(
equipmentType: equipmentTypeId, equipmentType: equipmentTypeId,
effects, 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; return true;
}, },
cancelDesign: () => { cancelDesign: () => {
const state = get();
// Check if cancelling designProgress2
if (state.designProgress2 && !state.designProgress) {
set(() => ({
designProgress2: null,
}));
} else {
set(() => ({ set(() => ({
currentAction: 'meditate', currentAction: 'meditate',
designProgress: null, designProgress: null,
})); }));
}
}, },
saveDesign: (design: EnchantmentDesign) => { saveDesign: (design: EnchantmentDesign) => {
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) => ({ set((state) => ({
enchantmentDesigns: [...state.enchantmentDesigns, design], enchantmentDesigns: [...state.enchantmentDesigns, design],
designProgress: null, designProgress: null,
currentAction: 'meditate', currentAction: 'meditate',
})); }));
}
}, },
deleteDesign: (designId: string) => { deleteDesign: (designId: string) => {
@@ -616,9 +660,28 @@ export function processCraftingTick(
const { rawMana, log } = effects; const { rawMana, log } = effects;
let updates: Partial<GameState> = {}; let updates: Partial<GameState> = {};
// Get computed effects for special effect checks
const computedEffects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
// Process design progress // Process design progress
if (state.currentAction === 'design' && state.designProgress) { 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) { if (progress >= state.designProgress.required) {
// Design complete - auto-save the design using stored data // Design complete - auto-save the design using stored data
const dp = state.designProgress; 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 // Process preparation progress
if (state.currentAction === 'prepare' && state.preparationProgress) { if (state.currentAction === 'prepare' && state.preparationProgress) {
const prep = state.preparationProgress; const prep = state.preparationProgress;
@@ -693,20 +806,55 @@ export function processCraftingTick(
const manaCost = app.manaPerHour * 0.04; // HOURS_PER_TICK const manaCost = app.manaPerHour * 0.04; // HOURS_PER_TICK
if (rawMana >= manaCost) { if (rawMana >= manaCost) {
const progress = app.progress + 0.04; let progress = app.progress + 0.04;
const manaSpent = app.manaSpent + manaCost; 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) { if (progress >= app.required) {
// Apply the enchantment! // Apply the enchantment!
const instance = state.equipmentInstances[app.equipmentInstanceId]; const instance = state.equipmentInstances[app.equipmentInstanceId];
const design = state.enchantmentDesigns.find(d => d.id === app.designId); const design = state.enchantmentDesigns.find(d => d.id === app.designId);
if (instance && design) { if (instance && design) {
const newEnchantments: AppliedEnchantment[] = design.effects.map(eff => ({ // 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, effectId: eff.effectId,
stacks: eff.stacks, stacks,
actualCost: eff.capacityCost, actualCost,
})); };
});
// Calculate and grant attunement XP to enchanter // Calculate and grant attunement XP to enchanter
const xpGained = calculateEnchantingXP(design.totalCapacityUsed); const xpGained = calculateEnchantingXP(design.totalCapacityUsed);
+1
View File
@@ -698,6 +698,7 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
equipmentInstances: startingEquipment.equipmentInstances, equipmentInstances: startingEquipment.equipmentInstances,
enchantmentDesigns: [], enchantmentDesigns: [],
designProgress: null, designProgress: null,
designProgress2: null,
preparationProgress: null, preparationProgress: null,
applicationProgress: null, applicationProgress: null,
equipmentCraftingProgress: null, equipmentCraftingProgress: null,
+1
View File
@@ -142,6 +142,7 @@ export interface GameState {
// Crafting Progress // Crafting Progress
designProgress: DesignProgress | null; designProgress: DesignProgress | null;
designProgress2: DesignProgress | null; // For ENCHANT_MASTERY (2 concurrent designs)
preparationProgress: PreparationProgress | null; preparationProgress: PreparationProgress | null;
applicationProgress: ApplicationProgress | null; applicationProgress: ApplicationProgress | null;
equipmentCraftingProgress: EquipmentCraftingProgress | null; equipmentCraftingProgress: EquipmentCraftingProgress | null;