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 { 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,35 +302,78 @@ 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(
|
||||||
currentAction: 'design',
|
computeEffects(state.skillUpgrades || {}, state.skillTiers || {}),
|
||||||
designProgress: {
|
SPECIAL_EFFECTS.ENCHANT_MASTERY
|
||||||
designId,
|
);
|
||||||
progress: 0,
|
|
||||||
required: designTime,
|
|
||||||
name,
|
|
||||||
equipmentType: equipmentTypeId,
|
|
||||||
effects,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
// 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;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelDesign: () => {
|
cancelDesign: () => {
|
||||||
set(() => ({
|
const state = get();
|
||||||
currentAction: 'meditate',
|
// Check if cancelling designProgress2
|
||||||
designProgress: null,
|
if (state.designProgress2 && !state.designProgress) {
|
||||||
}));
|
set(() => ({
|
||||||
|
designProgress2: null,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
set(() => ({
|
||||||
|
currentAction: 'meditate',
|
||||||
|
designProgress: null,
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
saveDesign: (design: EnchantmentDesign) => {
|
saveDesign: (design: EnchantmentDesign) => {
|
||||||
set((state) => ({
|
const state = get();
|
||||||
enchantmentDesigns: [...state.enchantmentDesigns, design],
|
// Check if saving from designProgress2
|
||||||
designProgress: null,
|
if (state.designProgress2 && state.designProgress2.designId === design.id) {
|
||||||
currentAction: 'meditate',
|
set((state) => ({
|
||||||
}));
|
enchantmentDesigns: [...state.enchantmentDesigns, design],
|
||||||
|
designProgress2: null,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
set((state) => ({
|
||||||
|
enchantmentDesigns: [...state.enchantmentDesigns, design],
|
||||||
|
designProgress: null,
|
||||||
|
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
|
||||||
effectId: eff.effectId,
|
const isPureEssenceActive = hasSpecial(computedEffects, SPECIAL_EFFECTS.PURE_ESSENCE);
|
||||||
stacks: eff.stacks,
|
|
||||||
actualCost: eff.capacityCost,
|
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
|
// Calculate and grant attunement XP to enchanter
|
||||||
const xpGained = calculateEnchantingXP(design.totalCapacityUsed);
|
const xpGained = calculateEnchantingXP(design.totalCapacityUsed);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user