// ─── Crafting Design System ───────────────────────────────────────────────── // Design system functions: calculateDesignTime, capacity cost, XP, etc. import type { EnchantmentDesign, DesignEffect, AppliedEnchantment, DesignProgress } from './types'; import type { ComputedEffects } from './effects/upgrade-effects.types'; import { calculateEnchantingXP } from './data/attunements'; import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects'; import { hasSpecial, SPECIAL_EFFECTS } from './effects/special-effects'; import { HOURS_PER_TICK } from './constants'; import { EQUIPMENT_TYPES } from './data/equipment'; // Progress per tick expressed as a fraction of HOURS_PER_TICK const DESIGN_PROGRESS_PER_TICK = HOURS_PER_TICK; const HASTY_ENCHANTER_BONUS_MULTIPLIER = 0.25; // ─── Design Creation & Calculation ────────────────────────────────────────── export function validateDesignEffects( effects: DesignEffect[], equipmentTypeId: string, enchantingLevel: number ): { valid: boolean; reason?: string } { if (enchantingLevel < 1) return { valid: false, reason: 'Requires enchanting skill level 1' }; const equipType = EQUIPMENT_TYPES[equipmentTypeId]; if (!equipType || !equipType.category) return { valid: false, reason: 'Invalid equipment type or category' }; for (const eff of effects) { const effectDef = ENCHANTMENT_EFFECTS[eff.effectId]; if (!effectDef) return { valid: false, reason: `Unknown effect: ${eff.effectId}` }; if (!effectDef.allowedEquipmentCategories.includes(equipType.category)) { return { valid: false, reason: `Effect ${eff.effectId} not allowed on ${equipType.category}` }; } if (eff.stacks > effectDef.maxStacks) return { valid: false, reason: `Stacks exceed maximum for ${eff.effectId}` }; } return { valid: true }; } export function createEnchantmentDesign( name: string, equipmentType: string, effects: DesignEffect[], efficiencyBonus: number = 0 ): EnchantmentDesign { return { id: `design_${Date.now()}`, name, equipmentType, effects, totalCapacityUsed: calculateDesignCapacityCost(effects, efficiencyBonus), designTime: calculateDesignTime(effects), created: Date.now(), }; } // ─── Capacity & Time Calculations ─────────────────────────────────────────── export function calculateDesignCapacityCost(effects: DesignEffect[], efficiencyBonus: number = 0): number { return effects.reduce((total, eff) => total + calculateEffectCapacityCost(eff.effectId, eff.stacks, efficiencyBonus), 0); } export function calculateTotalCapacityCost(design: EnchantmentDesign): number { return design.totalCapacityUsed; } export function calculateDesignTime(effects: DesignEffect[]): number { let time = 1; for (const eff of effects) { if (ENCHANTMENT_EFFECTS[eff.effectId]) time += 0.5 * eff.stacks; } return time; } export function getDesignTimeWithHaste( effects: DesignEffect[], isRepeatDesign: boolean, computedEffects: ComputedEffects ): number { const time = calculateDesignTime(effects); return isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER) ? time * 0.75 : time; } // ─── XP & Progression ─────────────────────────────────────────────────────── export function calculateEnchantingXpFromDesign(design: EnchantmentDesign): number { return calculateEnchantingXP(design.totalCapacityUsed); } export function calculateXpFromInstanceEnchantments( instance: { enchantments: AppliedEnchantment[] } ): number { let totalXp = 0; for (const ench of instance.enchantments) { const effectDef = ENCHANTMENT_EFFECTS[ench.effectId]; totalXp += calculateEnchantingXP((effectDef?.baseCapacityCost || 0) * ench.stacks); } return totalXp; } // ─── Progress Calculations ────────────────────────────────────────────────── export interface DesignProgressUpdate { progress: number; required: number; isComplete: boolean; timeBonus: number; } const INSTANT_DESIGN_CHANCE = 0.10; export function calculateDesignProgress( currentProgress: number, required: number, computedEffects: ComputedEffects, isRepeatDesign: boolean ): DesignProgressUpdate { let progress = currentProgress + DESIGN_PROGRESS_PER_TICK; let timeBonus = 0; if (isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) { timeBonus = DESIGN_PROGRESS_PER_TICK * HASTY_ENCHANTER_BONUS_MULTIPLIER; progress += timeBonus; } if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < INSTANT_DESIGN_CHANCE) { progress = required; } return { progress, required, isComplete: progress >= required, timeBonus }; } export function calculateSecondDesignProgress( currentProgress: number, required: number, computedEffects: ComputedEffects, isRepeatDesign: boolean ): DesignProgressUpdate { return calculateDesignProgress(currentProgress, required, computedEffects, isRepeatDesign); } export function isSecondDesignSlotAvailable( designProgress: DesignProgress | null, designProgress2: DesignProgress | null, hasEnchantMastery: boolean ): boolean { if (!designProgress && !designProgress2) return true; if (!designProgress && designProgress2) return false; return !!(designProgress && !designProgress2 && hasEnchantMastery); } // ─── Auto-save Completed Design ──────────────────────────────────────────── export function createCompletedDesignFromProgress( progressData: { designId: string; name: string; equipmentType: string; effects: DesignEffect[]; required: number; }, efficiencyBonus: number = 0 ): EnchantmentDesign { return { id: progressData.designId, name: progressData.name, equipmentType: progressData.equipmentType, effects: progressData.effects, totalCapacityUsed: calculateDesignCapacityCost(progressData.effects, efficiencyBonus), designTime: progressData.required, created: Date.now(), }; } // ─── Design Management ────────────────────────────────────────────────────── export interface DesignWithCapacityInfo { design: EnchantmentDesign; fitsInEquipment: boolean; availableCapacity: number; } export function filterDesignsByEquipment( designs: EnchantmentDesign[], equipment: { instanceId: string; totalCapacity: number; usedCapacity: number } | null ): DesignWithCapacityInfo[] { if (!equipment) return []; const availableCapacity = equipment.totalCapacity - equipment.usedCapacity; return designs.map(design => ({ design, fitsInEquipment: (equipment.usedCapacity || 0) + design.totalCapacityUsed <= equipment.totalCapacity, availableCapacity, })); }