Fix crafting tab crash, update attunement XP system
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 33s
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 33s
- Add missing EquipmentCraftingProgress interface to types.ts - Fix CraftingTab to accept store prop like other tabs - Update attunement XP calculation: 1 XP per 10 capacity used (min 1) - Change XP requirements: 1000 for lv2, 2500 for lv3, doubling thereafter - Grant Enchanter XP when enchantments are applied - Add equipmentCraftingProgress to GameState and persist config
This commit is contained in:
@@ -17,7 +17,7 @@ import { ENCHANTMENT_EFFECTS, type EnchantmentEffectDef, calculateEffectCapacity
|
|||||||
import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes';
|
import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recipes';
|
||||||
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
||||||
import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
import type { EquipmentInstance, EnchantmentDesign, DesignEffect, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
||||||
import { fmt } from '@/lib/game/store';
|
import { fmt, type GameStore } from '@/lib/game/store';
|
||||||
|
|
||||||
// Slot display names
|
// Slot display names
|
||||||
const SLOT_NAMES: Record<EquipmentSlot, string> = {
|
const SLOT_NAMES: Record<EquipmentSlot, string> = {
|
||||||
@@ -31,76 +31,38 @@ const SLOT_NAMES: Record<EquipmentSlot, string> = {
|
|||||||
accessory2: 'Accessory 2',
|
accessory2: 'Accessory 2',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface CraftingTabProps {
|
export interface CraftingTabProps {
|
||||||
// Equipment state
|
store: GameStore;
|
||||||
equippedInstances: Record<string, string | null>;
|
|
||||||
equipmentInstances: Record<string, EquipmentInstance>;
|
|
||||||
enchantmentDesigns: EnchantmentDesign[];
|
|
||||||
|
|
||||||
// Progress states
|
|
||||||
designProgress: { designId: string; progress: number; required: number } | null;
|
|
||||||
preparationProgress: { equipmentInstanceId: string; progress: number; required: number; manaCostPaid: number } | null;
|
|
||||||
applicationProgress: { equipmentInstanceId: string; designId: string; progress: number; required: number; manaPerHour: number; paused: boolean; manaSpent: number } | null;
|
|
||||||
equipmentCraftingProgress: EquipmentCraftingProgress | null;
|
|
||||||
|
|
||||||
// Player state
|
|
||||||
rawMana: number;
|
|
||||||
skills: Record<string, number>;
|
|
||||||
currentAction: string;
|
|
||||||
unlockedEffects: string[]; // Effect IDs that have been researched
|
|
||||||
|
|
||||||
// Loot inventory
|
|
||||||
lootInventory: LootInventory;
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
startDesigningEnchantment: (name: string, equipmentTypeId: string, effects: DesignEffect[]) => boolean;
|
|
||||||
cancelDesign: () => void;
|
|
||||||
saveDesign: (design: EnchantmentDesign) => void;
|
|
||||||
deleteDesign: (designId: string) => void;
|
|
||||||
startPreparing: (equipmentInstanceId: string) => boolean;
|
|
||||||
cancelPreparation: () => void;
|
|
||||||
startApplying: (equipmentInstanceId: string, designId: string) => boolean;
|
|
||||||
pauseApplication: () => void;
|
|
||||||
resumeApplication: () => void;
|
|
||||||
cancelApplication: () => void;
|
|
||||||
disenchantEquipment: (instanceId: string) => void;
|
|
||||||
getAvailableCapacity: (instanceId: string) => number;
|
|
||||||
|
|
||||||
// Equipment crafting actions
|
|
||||||
startCraftingEquipment: (blueprintId: string) => boolean;
|
|
||||||
cancelEquipmentCrafting: () => void;
|
|
||||||
deleteMaterial: (materialId: string, amount: number) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CraftingTab({
|
export function CraftingTab({ store }: CraftingTabProps) {
|
||||||
equippedInstances,
|
const equippedInstances = store.equippedInstances;
|
||||||
equipmentInstances,
|
const equipmentInstances = store.equipmentInstances;
|
||||||
enchantmentDesigns,
|
const enchantmentDesigns = store.enchantmentDesigns;
|
||||||
designProgress,
|
const designProgress = store.designProgress;
|
||||||
preparationProgress,
|
const preparationProgress = store.preparationProgress;
|
||||||
applicationProgress,
|
const applicationProgress = store.applicationProgress;
|
||||||
equipmentCraftingProgress,
|
const equipmentCraftingProgress = store.equipmentCraftingProgress;
|
||||||
rawMana,
|
const rawMana = store.rawMana;
|
||||||
skills,
|
const skills = store.skills;
|
||||||
currentAction,
|
const currentAction = store.currentAction;
|
||||||
unlockedEffects,
|
const unlockedEffects = store.unlockedEffects;
|
||||||
lootInventory,
|
const lootInventory = store.lootInventory;
|
||||||
startDesigningEnchantment,
|
const startDesigningEnchantment = store.startDesigningEnchantment;
|
||||||
cancelDesign,
|
const cancelDesign = store.cancelDesign;
|
||||||
saveDesign,
|
const saveDesign = store.saveDesign;
|
||||||
deleteDesign,
|
const deleteDesign = store.deleteDesign;
|
||||||
startPreparing,
|
const startPreparing = store.startPreparing;
|
||||||
cancelPreparation,
|
const cancelPreparation = store.cancelPreparation;
|
||||||
startApplying,
|
const startApplying = store.startApplying;
|
||||||
pauseApplication,
|
const pauseApplication = store.pauseApplication;
|
||||||
resumeApplication,
|
const resumeApplication = store.resumeApplication;
|
||||||
cancelApplication,
|
const cancelApplication = store.cancelApplication;
|
||||||
disenchantEquipment,
|
const disenchantEquipment = store.disenchantEquipment;
|
||||||
getAvailableCapacity,
|
const getAvailableCapacity = store.getAvailableCapacity;
|
||||||
startCraftingEquipment,
|
const startCraftingEquipment = store.startCraftingEquipment;
|
||||||
cancelEquipmentCrafting,
|
const cancelEquipmentCrafting = store.cancelEquipmentCrafting;
|
||||||
deleteMaterial,
|
const deleteMaterial = store.deleteMaterial;
|
||||||
}: CraftingTabProps) {
|
|
||||||
const [craftingStage, setCraftingStage] = useState<'design' | 'prepare' | 'apply' | 'craft'>('craft');
|
const [craftingStage, setCraftingStage] = useState<'design' | 'prepare' | 'apply' | 'craft'>('craft');
|
||||||
const [selectedEquipmentType, setSelectedEquipmentType] = useState<string | null>(null);
|
const [selectedEquipmentType, setSelectedEquipmentType] = useState<string | null>(null);
|
||||||
const [selectedEquipmentInstance, setSelectedEquipmentInstance] = useState<string | null>(null);
|
const [selectedEquipmentInstance, setSelectedEquipmentInstance] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// ─── Crafting Store Slice ─────────────────────────────────────────────────────────
|
// ─── Crafting Store Slice ─────────────────────────────────────────────────────────
|
||||||
// Handles equipment and enchantment system: design, prepare, apply stages
|
// Handles equipment and enchantment system: design, prepare, apply stages
|
||||||
|
|
||||||
import type { GameState, EquipmentInstance, AppliedEnchantment, EnchantmentDesign, DesignEffect, EquipmentSlot, EquipmentCraftingProgress, LootInventory } from './types';
|
import type { GameState, EquipmentInstance, AppliedEnchantment, EnchantmentDesign, DesignEffect, EquipmentSlot, EquipmentCraftingProgress, LootInventory, AttunementState } from './types';
|
||||||
import { EQUIPMENT_TYPES, type EquipmentCategory } from './data/equipment';
|
import { EQUIPMENT_TYPES, type EquipmentCategory } from './data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||||
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
||||||
import { SPELLS_DEF } from './constants';
|
import { SPELLS_DEF } from './constants';
|
||||||
|
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||||
|
|
||||||
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -687,11 +688,42 @@ export function processCraftingTick(
|
|||||||
actualCost: eff.capacityCost,
|
actualCost: eff.capacityCost,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Calculate and grant attunement XP to enchanter
|
||||||
|
const xpGained = calculateEnchantingXP(design.totalCapacityUsed);
|
||||||
|
let newAttunements = state.attunements;
|
||||||
|
|
||||||
|
if (state.attunements.enchanter?.active && xpGained > 0) {
|
||||||
|
const enchanterState = state.attunements.enchanter;
|
||||||
|
let newXP = enchanterState.experience + xpGained;
|
||||||
|
let newLevel = enchanterState.level;
|
||||||
|
|
||||||
|
// Check for level ups
|
||||||
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
||||||
|
if (newXP >= xpNeeded) {
|
||||||
|
newXP -= xpNeeded;
|
||||||
|
newLevel++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newAttunements = {
|
||||||
|
...state.attunements,
|
||||||
|
enchanter: {
|
||||||
|
...enchanterState,
|
||||||
|
level: newLevel,
|
||||||
|
experience: newXP,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
updates = {
|
updates = {
|
||||||
...updates,
|
...updates,
|
||||||
rawMana: rawMana - manaCost,
|
rawMana: rawMana - manaCost,
|
||||||
applicationProgress: null,
|
applicationProgress: null,
|
||||||
currentAction: 'meditate',
|
currentAction: 'meditate',
|
||||||
|
attunements: newAttunements,
|
||||||
equipmentInstances: {
|
equipmentInstances: {
|
||||||
...state.equipmentInstances,
|
...state.equipmentInstances,
|
||||||
[app.equipmentInstanceId]: {
|
[app.equipmentInstanceId]: {
|
||||||
@@ -700,7 +732,7 @@ export function processCraftingTick(
|
|||||||
usedCapacity: instance.usedCapacity + design.totalCapacityUsed,
|
usedCapacity: instance.usedCapacity + design.totalCapacityUsed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
log: [`✨ Enchantment "${design.name}" applied to ${instance.name}!`, ...log],
|
log: [`✨ Enchantment "${design.name}" applied to ${instance.name}! (+${xpGained} Enchanter XP)`, ...log],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -111,10 +111,22 @@ export function getAttunementConversionRate(attunementId: string, level: number)
|
|||||||
|
|
||||||
// XP required for attunement level
|
// XP required for attunement level
|
||||||
export function getAttunementXPForLevel(level: number): number {
|
export function getAttunementXPForLevel(level: number): number {
|
||||||
// Level 2: 100 XP, Level 3: 300 XP, Level 4: 900 XP, etc.
|
// New scaling:
|
||||||
// Exponential: 100 * (3 ^ (level - 2))
|
// Level 2: 1000 XP
|
||||||
|
// Level 3: 2500 XP
|
||||||
|
// Level 4: 5000 XP
|
||||||
|
// Level 5: 10000 XP
|
||||||
|
// etc. (each level requires 2x the previous, starting from 1000)
|
||||||
if (level <= 1) return 0;
|
if (level <= 1) return 0;
|
||||||
return Math.floor(100 * Math.pow(3, level - 2));
|
if (level === 2) return 1000;
|
||||||
|
// For level 3+: 1000 * 2.5^(level-2), but rounded nicely
|
||||||
|
return Math.floor(1000 * Math.pow(2, level - 2) * (level >= 3 ? 1.25 : 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate XP gained from enchanting based on capacity used
|
||||||
|
export function calculateEnchantingXP(capacityUsed: number): number {
|
||||||
|
// 1 XP per 10 capacity used, floored, minimum 1
|
||||||
|
return Math.max(1, Math.floor(capacityUsed / 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max attunement level
|
// Max attunement level
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
|||||||
designProgress: null,
|
designProgress: null,
|
||||||
preparationProgress: null,
|
preparationProgress: null,
|
||||||
applicationProgress: null,
|
applicationProgress: null,
|
||||||
|
equipmentCraftingProgress: null,
|
||||||
unlockedEffects: [...BASE_UNLOCKED_EFFECTS], // Start with mana bolt only
|
unlockedEffects: [...BASE_UNLOCKED_EFFECTS], // Start with mana bolt only
|
||||||
equipmentSpellStates: [],
|
equipmentSpellStates: [],
|
||||||
|
|
||||||
@@ -1875,6 +1876,8 @@ export const useGameStore = create<GameStore>()(
|
|||||||
designProgress: state.designProgress,
|
designProgress: state.designProgress,
|
||||||
preparationProgress: state.preparationProgress,
|
preparationProgress: state.preparationProgress,
|
||||||
applicationProgress: state.applicationProgress,
|
applicationProgress: state.applicationProgress,
|
||||||
|
equipmentCraftingProgress: state.equipmentCraftingProgress,
|
||||||
|
unlockedEffects: state.unlockedEffects,
|
||||||
// Loot inventory
|
// Loot inventory
|
||||||
lootInventory: state.lootInventory,
|
lootInventory: state.lootInventory,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -232,6 +232,15 @@ export interface ApplicationProgress {
|
|||||||
manaSpent: number; // Total mana spent so far
|
manaSpent: number; // Total mana spent so far
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equipment crafting progress (from blueprints)
|
||||||
|
export interface EquipmentCraftingProgress {
|
||||||
|
blueprintId: string;
|
||||||
|
equipmentTypeId: string;
|
||||||
|
progress: number; // Hours spent crafting
|
||||||
|
required: number; // Total hours needed
|
||||||
|
manaSpent: number; // Total mana spent so far
|
||||||
|
}
|
||||||
|
|
||||||
// Equipment spell state (for multi-spell casting)
|
// Equipment spell state (for multi-spell casting)
|
||||||
export interface EquipmentSpellState {
|
export interface EquipmentSpellState {
|
||||||
spellId: string;
|
spellId: string;
|
||||||
@@ -362,6 +371,7 @@ export interface GameState {
|
|||||||
designProgress: DesignProgress | null;
|
designProgress: DesignProgress | null;
|
||||||
preparationProgress: PreparationProgress | null;
|
preparationProgress: PreparationProgress | null;
|
||||||
applicationProgress: ApplicationProgress | null;
|
applicationProgress: ApplicationProgress | null;
|
||||||
|
equipmentCraftingProgress: EquipmentCraftingProgress | null;
|
||||||
|
|
||||||
// Unlocked enchantment effects for designing
|
// Unlocked enchantment effects for designing
|
||||||
unlockedEffects: string[]; // Effect IDs that have been researched
|
unlockedEffects: string[]; // Effect IDs that have been researched
|
||||||
|
|||||||
Reference in New Issue
Block a user