7dd9ad5b92
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s
- #236: Fix Climb the Spire React #185 infinite loop - removed redundant set() in processCombatTick that caused double Zustand writes per tick - #235: Add enchanting design/prepare/apply tick handlers - extracted to pipelines/enchanting-tick.ts - #235: Fix startApplying not setting currentAction to 'enchant' - #243: Guard discipline store against undefined activeIds/processedPerks from corrupted persisted state - #245: Change Plasma symbol from ⚡ (conflicts with Lightning) to 🔴 - #241: Fix combat store maxFloorReached desync - initialize to 0, reset on exitSpireMode - #239: Fix EffectSelector not rendering when unlockedEffects is empty (fresh game) - Created pipelines/enchanting-tick.ts to keep gameStore.ts under 400 lines
164 lines
5.3 KiB
TypeScript
164 lines
5.3 KiB
TypeScript
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
|
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects';
|
|
import type { DesignEffect, EquipmentInstance, EquipmentCategory } from '@/lib/game/types';
|
|
import { calculateDesignCapacityCost as calcCapacityCost, calculateDesignTime as calcDesignTime } from '@/lib/game/crafting-design';
|
|
|
|
/**
|
|
* Get available effects for selected equipment type (only unlocked ones)
|
|
* Requirement (task3 bug #7): Show incompatible enchantments in greyed-out "Unavailable" section
|
|
*/
|
|
export function getAvailableEffects(
|
|
selectedEquipmentType: string | null,
|
|
unlockedEffects: string[]
|
|
) {
|
|
if (!selectedEquipmentType) return [];
|
|
const type = EQUIPMENT_TYPES[selectedEquipmentType];
|
|
if (!type) return [];
|
|
|
|
return Object.values(ENCHANTMENT_EFFECTS).filter(
|
|
effect =>
|
|
effect.allowedEquipmentCategories.includes(type.category) &&
|
|
(unlockedEffects.length === 0 || unlockedEffects.includes(effect.id))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get incompatible effects (unlocked but not for this equipment type)
|
|
*/
|
|
export function getIncompatibleEffects(
|
|
selectedEquipmentType: string | null,
|
|
unlockedEffects: string[]
|
|
) {
|
|
if (!selectedEquipmentType) return [];
|
|
const type = EQUIPMENT_TYPES[selectedEquipmentType];
|
|
if (!type) return [];
|
|
|
|
return Object.values(ENCHANTMENT_EFFECTS).filter(
|
|
effect =>
|
|
!effect.allowedEquipmentCategories.includes(type.category) &&
|
|
unlockedEffects.includes(effect.id)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get equipment types that the player actually owns (has instances of)
|
|
* This ensures enchantment compatibility is based on owned items, not just blueprints
|
|
*/
|
|
export function getOwnedEquipmentTypes(equipmentInstances: Record<string, EquipmentInstance>) {
|
|
// Get all unique equipment type IDs from owned instances
|
|
const ownedEquipmentTypeIds = new Set<string>();
|
|
|
|
// Check all equipment instances the player owns
|
|
for (const instance of Object.values(equipmentInstances || {})) {
|
|
ownedEquipmentTypeIds.add(instance.typeId);
|
|
}
|
|
|
|
// Filter EQUIPMENT_TYPES to only include types the player owns
|
|
return Object.values(EQUIPMENT_TYPES).filter(type => ownedEquipmentTypeIds.has(type.id));
|
|
}
|
|
|
|
/**
|
|
* Get the reason why an effect is incompatible
|
|
*/
|
|
export function getIncompatibilityReason(
|
|
effect: { id: string; name: string; description: string; allowedEquipmentCategories: EquipmentCategory[] },
|
|
selectedEquipmentType: string | null
|
|
): string {
|
|
if (!selectedEquipmentType) return 'No equipment selected';
|
|
const type = EQUIPMENT_TYPES[selectedEquipmentType];
|
|
if (!type) return 'Unknown equipment type';
|
|
|
|
// Check what categories this effect is allowed for
|
|
const allowedCategories = effect.allowedEquipmentCategories;
|
|
const equipmentCategory = type.category;
|
|
|
|
if (allowedCategories.includes(equipmentCategory)) {
|
|
return 'Compatible';
|
|
}
|
|
|
|
// Provide specific reasons
|
|
if (allowedCategories.includes('weapon' as EquipmentCategory) && equipmentCategory !== 'sword' && equipmentCategory !== 'caster' && equipmentCategory !== 'catalyst') {
|
|
return `Requires a weapon (${allowedCategories.filter(c => ['sword', 'caster', 'catalyst'].includes(c)).join(', ')})`;
|
|
}
|
|
|
|
return `Requires ${allowedCategories.join(' or ')} equipment`;
|
|
}
|
|
|
|
/**
|
|
* Calculate total capacity cost for current design
|
|
* Delegates to canonical calculateDesignCapacityCost from crafting-design
|
|
*/
|
|
export function calculateDesignCapacityCost(
|
|
selectedEffects: DesignEffect[],
|
|
efficiencyBonus: number
|
|
): number {
|
|
return calcCapacityCost(selectedEffects, efficiencyBonus);
|
|
}
|
|
|
|
/**
|
|
* Get capacity limit for selected equipment type
|
|
*/
|
|
export function getEquipmentCapacity(selectedEquipmentType: string | null): number {
|
|
return selectedEquipmentType ? EQUIPMENT_TYPES[selectedEquipmentType]?.baseCapacity || 0 : 0;
|
|
}
|
|
|
|
/**
|
|
* Calculate design time
|
|
* Delegates to canonical calculateDesignTime from crafting-design
|
|
*/
|
|
export function calculateDesignTime(selectedEffects: DesignEffect[]): number {
|
|
return calcDesignTime(selectedEffects);
|
|
}
|
|
|
|
/**
|
|
* Add effect to design
|
|
*/
|
|
export function addEffectToDesign(
|
|
effectId: string,
|
|
selectedEffects: DesignEffect[],
|
|
efficiencyBonus: number,
|
|
setSelectedEffects: (effects: DesignEffect[]) => void
|
|
) {
|
|
const existing = selectedEffects.find(e => e.effectId === effectId);
|
|
const effectDef = ENCHANTMENT_EFFECTS[effectId];
|
|
if (!effectDef) return;
|
|
|
|
if (existing) {
|
|
if (existing.stacks < effectDef.maxStacks) {
|
|
setSelectedEffects(selectedEffects.map(e =>
|
|
e.effectId === effectId
|
|
? { ...e, stacks: e.stacks + 1 }
|
|
: e
|
|
));
|
|
}
|
|
} else {
|
|
setSelectedEffects([...selectedEffects, {
|
|
effectId,
|
|
stacks: 1,
|
|
capacityCost: calculateEffectCapacityCost(effectId, 1, efficiencyBonus),
|
|
}]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove effect from design
|
|
*/
|
|
export function removeEffectFromDesign(
|
|
effectId: string,
|
|
selectedEffects: DesignEffect[],
|
|
setSelectedEffects: (effects: DesignEffect[]) => void
|
|
) {
|
|
const existing = selectedEffects.find(e => e.effectId === effectId);
|
|
if (!existing) return;
|
|
|
|
if (existing.stacks > 1) {
|
|
setSelectedEffects(selectedEffects.map(e =>
|
|
e.effectId === effectId
|
|
? { ...e, stacks: e.stacks - 1 }
|
|
: e
|
|
));
|
|
} else {
|
|
setSelectedEffects(selectedEffects.filter(e => e.effectId !== effectId));
|
|
}
|
|
}
|