197 lines
7.2 KiB
TypeScript
197 lines
7.2 KiB
TypeScript
// ─── 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,
|
|
}));
|
|
}
|