195 lines
7.4 KiB
TypeScript
195 lines
7.4 KiB
TypeScript
// ─── Crafting Application System ────────────────────────────────────────────
|
|
// Application system functions
|
|
|
|
import type { EquipmentInstance, AppliedEnchantment, EnchantmentDesign, ApplicationProgress } from './types';
|
|
import { calculateApplicationTime, calculateApplicationManaPerHour } from './crafting-utils';
|
|
import { HOURS_PER_TICK } from './constants';
|
|
import { hasSpecial, SPECIAL_EFFECTS } from './effects/special-effects';
|
|
import type { ComputedEffects } from './effects/upgrade-effects.types';
|
|
import type { AttunementState } from './types';
|
|
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
|
import { ENCHANTMENT_EFFECTS } from './data/enchantment-effects';
|
|
|
|
// ─── Application Validation ─────────────────────────────────────────────────
|
|
|
|
export function canApplyEnchantment(
|
|
instance: EquipmentInstance | undefined,
|
|
design: EnchantmentDesign | undefined,
|
|
currentAction: string
|
|
): { canApply: boolean; reason?: string } {
|
|
if (!instance) return { canApply: false, reason: 'Equipment instance not found' };
|
|
if (!design) return { canApply: false, reason: 'Enchantment design not found' };
|
|
if (currentAction !== 'meditate') return { canApply: false, reason: 'Must be in meditate state' };
|
|
if (!instance.tags?.includes('Ready for Enchantment')) return { canApply: false, reason: 'Equipment must be prepared for enchanting' };
|
|
if (instance.usedCapacity + design.totalCapacityUsed > instance.totalCapacity) return { canApply: false, reason: 'Not enough capacity on equipment' };
|
|
return { canApply: true };
|
|
}
|
|
|
|
// ─── Application Resource Calculation ────────────────────────────────────────
|
|
|
|
export interface ApplicationCosts {
|
|
time: number;
|
|
manaPerHour: number;
|
|
manaPerTick: number;
|
|
}
|
|
|
|
export function calculateApplicationCosts(design: EnchantmentDesign): ApplicationCosts {
|
|
const time = calculateApplicationTime(design);
|
|
const manaPerHour = calculateApplicationManaPerHour(design);
|
|
const manaPerTick = manaPerHour * HOURS_PER_TICK;
|
|
return { time, manaPerHour, manaPerTick };
|
|
}
|
|
|
|
// ─── Application Progress ───────────────────────────────────────────────────
|
|
|
|
export function initializeApplicationProgress(
|
|
equipmentInstanceId: string,
|
|
designId: string,
|
|
design: EnchantmentDesign
|
|
): ApplicationProgress {
|
|
const costs = calculateApplicationCosts(design);
|
|
return {
|
|
equipmentInstanceId,
|
|
designId,
|
|
progress: 0,
|
|
required: costs.time,
|
|
manaPerHour: costs.manaPerHour,
|
|
paused: false,
|
|
manaSpent: 0,
|
|
};
|
|
}
|
|
|
|
// Free enchant chance per special effect
|
|
const FREE_ENCHANT_CHANCES: Record<string, number> = {
|
|
[SPECIAL_EFFECTS.ENCHANT_PRESERVATION]: 0.25,
|
|
[SPECIAL_EFFECTS.THRIFTY_ENCHANTER]: 0.10,
|
|
[SPECIAL_EFFECTS.OPTIMIZED_ENCHANTING]: 0.25,
|
|
};
|
|
|
|
export interface ApplicationTickResult {
|
|
progress: number;
|
|
manaSpent: number;
|
|
manaConsumed: number;
|
|
isComplete: boolean;
|
|
triggeredFreeEnchant: boolean;
|
|
}
|
|
|
|
export function calculateApplicationTick(
|
|
currentProgress: number,
|
|
required: number,
|
|
currentManaSpent: number,
|
|
manaPerTick: number,
|
|
computedEffects: ComputedEffects
|
|
): ApplicationTickResult {
|
|
let progress = currentProgress + HOURS_PER_TICK;
|
|
let manaSpent = currentManaSpent + manaPerTick;
|
|
let manaConsumed = manaPerTick;
|
|
let triggeredFreeEnchant = false;
|
|
|
|
let freeEnchantChance = 0;
|
|
for (const [special, chance] of Object.entries(FREE_ENCHANT_CHANCES)) {
|
|
if (hasSpecial(computedEffects, special)) freeEnchantChance += chance;
|
|
}
|
|
|
|
if (freeEnchantChance > 0 && Math.random() < freeEnchantChance) {
|
|
progress = required;
|
|
manaConsumed = 0;
|
|
manaSpent = currentManaSpent;
|
|
triggeredFreeEnchant = true;
|
|
}
|
|
|
|
return { progress, manaSpent, manaConsumed, isComplete: progress >= required, triggeredFreeEnchant };
|
|
}
|
|
|
|
// ─── Enchantment Application ────────────────────────────────────────────────
|
|
|
|
const PURE_ESSENCE_STACK_BONUS = 1.25;
|
|
const PURE_ESSENCE_COST_CAP = 100;
|
|
|
|
export function applyEnchantments(
|
|
instance: EquipmentInstance,
|
|
design: EnchantmentDesign,
|
|
computedEffects: ComputedEffects
|
|
): { updatedInstance: EquipmentInstance; xpGained: number; logMessage: string } {
|
|
const isPureEssenceActive = hasSpecial(computedEffects, SPECIAL_EFFECTS.PURE_ESSENCE);
|
|
|
|
const newEnchantments: AppliedEnchantment[] = design.effects.map(eff => {
|
|
const effectDef = ENCHANTMENT_EFFECTS[eff.effectId];
|
|
const bonusStacks = isPureEssenceActive && effectDef && effectDef.baseCapacityCost < PURE_ESSENCE_COST_CAP;
|
|
return {
|
|
effectId: eff.effectId,
|
|
stacks: bonusStacks ? Math.ceil(eff.stacks * PURE_ESSENCE_STACK_BONUS) : eff.stacks,
|
|
actualCost: eff.capacityCost,
|
|
};
|
|
});
|
|
|
|
const xpGained = calculateEnchantingXP(design.totalCapacityUsed);
|
|
const updatedInstance: EquipmentInstance = {
|
|
...instance,
|
|
enchantments: [...instance.enchantments, ...newEnchantments],
|
|
usedCapacity: instance.usedCapacity + design.totalCapacityUsed,
|
|
};
|
|
|
|
return {
|
|
updatedInstance,
|
|
xpGained,
|
|
logMessage: `✨ Enchantment "${design.name}" applied to ${instance.name}! (+${xpGained} Enchanter XP)`,
|
|
};
|
|
}
|
|
|
|
// ─── Attunement XP Updates ──────────────────────────────────────────────────
|
|
|
|
export function updateEnchanterAttunement(
|
|
attunements: Record<string, AttunementState>,
|
|
xpGained: number
|
|
): Record<string, AttunementState> {
|
|
if (!attunements?.enchanter?.active || xpGained <= 0) return attunements;
|
|
|
|
const enchanterState = attunements.enchanter;
|
|
let newXP = enchanterState.experience + xpGained;
|
|
let newLevel = enchanterState.level;
|
|
|
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
|
if (newXP >= xpNeeded) {
|
|
newXP -= xpNeeded;
|
|
newLevel++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
...attunements,
|
|
enchanter: { ...enchanterState, level: newLevel, experience: newXP },
|
|
};
|
|
}
|
|
|
|
// ─── Application Cancellation ────────────────────────────────────────────────
|
|
|
|
export function cancelApplication() {
|
|
return { logMessage: 'Enchantment application cancelled.' };
|
|
}
|
|
|
|
export function pauseApplication() {
|
|
return { logMessage: 'Enchantment application paused.' };
|
|
}
|
|
|
|
export function resumeApplication() {
|
|
return { logMessage: 'Enchantment application resumed.' };
|
|
}
|
|
|
|
// ─── Progress Calculations ──────────────────────────────────────────────────
|
|
|
|
export function getApplicationManaCostForTick(manaPerHour: number): number {
|
|
return manaPerHour * HOURS_PER_TICK;
|
|
}
|
|
|
|
export function getApplicationRemainingTime(currentProgress: number, required: number): number {
|
|
return Math.max(0, required - currentProgress);
|
|
}
|
|
|
|
export function getApplicationCompletionPercent(currentProgress: number, required: number): number {
|
|
return Math.min(100, Math.floor((currentProgress / required) * 100));
|
|
}
|