refactor: cleanup codebase — remove hydration guards, extract constants, fix bugs
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m20s

This commit is contained in:
2026-05-26 11:20:36 +02:00
parent 5c64bb00fa
commit b402b8f56e
23 changed files with 579 additions and 979 deletions
+44 -66
View File
@@ -6,67 +6,55 @@ 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 ──────────────────────────────────────────
// Validate effects for a design against equipment category
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' };
}
if (enchantingLevel < 1) return { valid: false, reason: 'Requires enchanting skill level 1' };
const equipType = EQUIPMENT_TYPES[equipmentTypeId];
if (!equipType) {
return { valid: false, reason: 'Invalid equipment type' };
}
const category = equipType.category;
if (!category) {
return { valid: false, reason: 'Invalid equipment category' };
}
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(category)) {
return { valid: false, reason: `Effect ${eff.effectId} not allowed on ${category}` };
}
if (eff.stacks > effectDef.maxStacks) {
return { valid: false, reason: `Stacks exceed maximum for ${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 };
}
// Create an enchantment design from validated inputs
export function createEnchantmentDesign(
name: string,
equipmentType: string,
effects: DesignEffect[],
efficiencyBonus: number = 0
): EnchantmentDesign {
const totalCapacityUsed = calculateDesignCapacityCost(effects, efficiencyBonus);
const designTime = calculateDesignTime(effects);
return {
id: `design_${Date.now()}`,
name,
equipmentType,
effects,
totalCapacityUsed,
designTime,
totalCapacityUsed: calculateDesignCapacityCost(effects, efficiencyBonus),
designTime: calculateDesignTime(effects),
created: Date.now(),
};
}
// ─── Capacity Cost Calculation ──────────────────────────────────────────────
// ─── 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);
@@ -76,6 +64,25 @@ 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 {
@@ -88,37 +95,11 @@ export function calculateXpFromInstanceEnchantments(
let totalXp = 0;
for (const ench of instance.enchantments) {
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
const baseCost = effectDef?.baseCapacityCost || 0;
totalXp += calculateEnchantingXP(baseCost * ench.stacks);
totalXp += calculateEnchantingXP((effectDef?.baseCapacityCost || 0) * ench.stacks);
}
return totalXp;
}
// ─── Design Time Calculations ──────────────────────────────────────────────
export function calculateDesignTime(effects: DesignEffect[]): number {
let time = 1;
for (const eff of effects) {
const effectDef = ENCHANTMENT_EFFECTS[eff.effectId];
if (effectDef) {
time += 0.5 * eff.stacks;
}
}
return time;
}
export function getDesignTimeWithHaste(
effects: DesignEffect[],
isRepeatDesign: boolean,
computedEffects: ComputedEffects
): number {
let time = calculateDesignTime(effects);
if (isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) {
time *= 0.75;
}
return time;
}
// ─── Progress Calculations ──────────────────────────────────────────────────
export interface DesignProgressUpdate {
@@ -128,21 +109,23 @@ export interface DesignProgressUpdate {
timeBonus: number;
}
const INSTANT_DESIGN_CHANCE = 0.10;
export function calculateDesignProgress(
currentProgress: number,
required: number,
computedEffects: ComputedEffects,
isRepeatDesign: boolean
): DesignProgressUpdate {
let progress = currentProgress + 0.04;
let progress = currentProgress + DESIGN_PROGRESS_PER_TICK;
let timeBonus = 0;
if (isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) {
timeBonus = 0.04 * 0.25;
timeBonus = DESIGN_PROGRESS_PER_TICK * HASTY_ENCHANTER_BONUS_MULTIPLIER;
progress += timeBonus;
}
if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < 0.10) {
if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < INSTANT_DESIGN_CHANCE) {
progress = required;
}
@@ -165,8 +148,7 @@ export function isSecondDesignSlotAvailable(
): boolean {
if (!designProgress && !designProgress2) return true;
if (!designProgress && designProgress2) return false;
if (designProgress && !designProgress2 && hasEnchantMastery) return true;
return false;
return !!(designProgress && !designProgress2 && hasEnchantMastery);
}
// ─── Auto-save Completed Design ────────────────────────────────────────────
@@ -181,13 +163,12 @@ export function createCompletedDesignFromProgress(
},
efficiencyBonus: number = 0
): EnchantmentDesign {
const totalCapacityCost = calculateDesignCapacityCost(progressData.effects, efficiencyBonus);
return {
id: progressData.designId,
name: progressData.name,
equipmentType: progressData.equipmentType,
effects: progressData.effects,
totalCapacityUsed: totalCapacityCost,
totalCapacityUsed: calculateDesignCapacityCost(progressData.effects, efficiencyBonus),
designTime: progressData.required,
created: Date.now(),
};
@@ -206,13 +187,10 @@ export function filterDesignsByEquipment(
equipment: { instanceId: string; totalCapacity: number; usedCapacity: number } | null
): DesignWithCapacityInfo[] {
if (!equipment) return [];
const availableCapacity = equipment.totalCapacity - equipment.usedCapacity;
return designs.map(design => ({
design,
fitsInEquipment: designFitsInEquipment(design, equipment),
availableCapacity: equipment.totalCapacity - equipment.usedCapacity,
fitsInEquipment: (equipment.usedCapacity || 0) + design.totalCapacityUsed <= equipment.totalCapacity,
availableCapacity,
}));
}
function designFitsInEquipment(design: EnchantmentDesign, instance: { usedCapacity: number; totalCapacity: number }): boolean {
return (instance.usedCapacity || 0) + design.totalCapacityUsed <= instance.totalCapacity;
}