Phase 4: Enchanting special effects (6)
This commit is contained in:
+176
-28
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user