Fix upgrade reset bugs: milestone merging and tier-up carryover
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Has been cancelled
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Has been cancelled
- Fixed commitSkillUpgrades() to preserve upgrades from other milestones (selecting L10 upgrades no longer resets L5 selections) - Fixed tierUpSkill() to carry over upgrades from previous tier (tier 2 now starts with tier 1's upgrades intact) - Added summoned golems display to SpireTab with stats and progress bars
This commit is contained in:
@@ -96,7 +96,7 @@ export function SkillsTab({ store }: SkillsTabProps) {
|
|||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
const currentSelections = pendingUpgradeSelections.length > 0 ? pendingUpgradeSelections : alreadySelected;
|
const currentSelections = pendingUpgradeSelections.length > 0 ? pendingUpgradeSelections : alreadySelected;
|
||||||
if (currentSelections.length === 2 && upgradeDialogSkill) {
|
if (currentSelections.length === 2 && upgradeDialogSkill) {
|
||||||
store.commitSkillUpgrades(upgradeDialogSkill, currentSelections);
|
store.commitSkillUpgrades(upgradeDialogSkill, currentSelections, upgradeDialogMilestone);
|
||||||
}
|
}
|
||||||
setPendingUpgradeSelections([]);
|
setPendingUpgradeSelections([]);
|
||||||
setUpgradeDialogSkill(null);
|
setUpgradeDialogSkill(null);
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
import { Swords, BookOpen, ChevronUp, ChevronDown, RotateCcw, X } from 'lucide-react';
|
import { Swords, BookOpen, ChevronUp, ChevronDown, RotateCcw, X, Mountain } from 'lucide-react';
|
||||||
import type { GameStore } from '@/lib/game/types';
|
import type { GameStore } from '@/lib/game/types';
|
||||||
import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constants';
|
import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constants';
|
||||||
|
import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems';
|
||||||
import { fmt, fmtDec, getFloorElement, canAffordSpellCost } from '@/lib/game/store';
|
import { fmt, fmtDec, getFloorElement, canAffordSpellCost } from '@/lib/game/store';
|
||||||
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
|
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
|
||||||
import { formatSpellCost, getSpellCostColor, formatStudyTime } from '@/lib/game/formatting';
|
import { formatSpellCost, getSpellCostColor, formatStudyTime } from '@/lib/game/formatting';
|
||||||
@@ -204,6 +205,58 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Summoned Golems Card */}
|
||||||
|
{store.golemancy.summonedGolems.length > 0 && (
|
||||||
|
<Card className="bg-gray-900/80 border-amber-600/50">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
||||||
|
<Mountain className="w-4 h-4" />
|
||||||
|
Active Golems ({store.golemancy.summonedGolems.length})
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
{store.golemancy.summonedGolems.map((summoned) => {
|
||||||
|
const golemDef = GOLEMS_DEF[summoned.golemId];
|
||||||
|
if (!golemDef) return null;
|
||||||
|
|
||||||
|
const elemColor = ELEMENTS[golemDef.baseManaType]?.color || '#888';
|
||||||
|
const damage = getGolemDamage(summoned.golemId, store.skills);
|
||||||
|
const attackSpeed = getGolemAttackSpeed(summoned.golemId, store.skills);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={summoned.golemId} className="p-2 rounded border border-gray-700 bg-gray-800/30">
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Mountain className="w-4 h-4" style={{ color: elemColor }} />
|
||||||
|
<span className="text-sm font-semibold game-panel-title" style={{ color: elemColor }}>
|
||||||
|
{golemDef.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{golemDef.isAoe && (
|
||||||
|
<Badge variant="outline" className="text-xs">AOE {golemDef.aoeTargets}</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 game-mono">
|
||||||
|
⚔️ {damage} DMG • ⚡ {attackSpeed.toFixed(1)}/hr •
|
||||||
|
🛡️ {Math.floor(golemDef.armorPierce * 100)}% Pierce
|
||||||
|
</div>
|
||||||
|
{/* Attack progress bar when climbing */}
|
||||||
|
{store.currentAction === 'climb' && summoned.attackProgress > 0 && (
|
||||||
|
<div className="space-y-0.5 mt-1">
|
||||||
|
<div className="flex justify-between text-xs text-gray-500">
|
||||||
|
<span>Attack</span>
|
||||||
|
<span>{Math.min(100, (summoned.attackProgress * 100)).toFixed(0)}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={Math.min(100, summoned.attackProgress * 100)} className="h-1.5 bg-gray-700" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Current Study (if any) */}
|
{/* Current Study (if any) */}
|
||||||
{store.currentStudyTarget && (
|
{store.currentStudyTarget && (
|
||||||
<Card className="bg-gray-900/80 border-purple-600/50 lg:col-span-2">
|
<Card className="bg-gray-900/80 border-purple-600/50 lg:col-span-2">
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ interface GameStore extends GameState, CraftingActions {
|
|||||||
addLog: (message: string) => void;
|
addLog: (message: string) => void;
|
||||||
selectSkillUpgrade: (skillId: string, upgradeId: string) => void;
|
selectSkillUpgrade: (skillId: string, upgradeId: string) => void;
|
||||||
deselectSkillUpgrade: (skillId: string, upgradeId: string) => void;
|
deselectSkillUpgrade: (skillId: string, upgradeId: string) => void;
|
||||||
commitSkillUpgrades: (skillId: string, upgradeIds: string[]) => void;
|
commitSkillUpgrades: (skillId: string, upgradeIds: string[], milestone?: 5 | 10) => void;
|
||||||
tierUpSkill: (skillId: string) => void;
|
tierUpSkill: (skillId: string) => void;
|
||||||
|
|
||||||
// Attunement XP and leveling
|
// Attunement XP and leveling
|
||||||
@@ -1725,13 +1725,32 @@ export const useGameStore = create<GameStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
commitSkillUpgrades: (skillId: string, upgradeIds: string[]) => {
|
commitSkillUpgrades: (skillId: string, upgradeIds: string[], milestone?: 5 | 10) => {
|
||||||
set((state) => ({
|
set((state) => {
|
||||||
skillUpgrades: {
|
const currentUpgrades = state.skillUpgrades?.[skillId] || [];
|
||||||
...state.skillUpgrades,
|
|
||||||
[skillId]: upgradeIds,
|
// If milestone is specified, merge by keeping other milestone's upgrades
|
||||||
},
|
let newUpgrades: string[];
|
||||||
}));
|
if (milestone === 5) {
|
||||||
|
// Keep existing L10 upgrades, replace L5 upgrades
|
||||||
|
const existingL10 = currentUpgrades.filter(id => id.includes('_l10'));
|
||||||
|
newUpgrades = [...upgradeIds, ...existingL10];
|
||||||
|
} else if (milestone === 10) {
|
||||||
|
// Keep existing L5 upgrades, replace L10 upgrades
|
||||||
|
const existingL5 = currentUpgrades.filter(id => id.includes('_l5'));
|
||||||
|
newUpgrades = [...existingL5, ...upgradeIds];
|
||||||
|
} else {
|
||||||
|
// No milestone specified (legacy behavior), replace all
|
||||||
|
newUpgrades = upgradeIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
skillUpgrades: {
|
||||||
|
...state.skillUpgrades,
|
||||||
|
[skillId]: newUpgrades,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
tierUpSkill: (skillId: string) => {
|
tierUpSkill: (skillId: string) => {
|
||||||
@@ -1744,6 +1763,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
|
|
||||||
const nextTierSkillId = `${baseSkillId}_t${nextTier}`;
|
const nextTierSkillId = `${baseSkillId}_t${nextTier}`;
|
||||||
const currentLevel = state.skills[skillId] || 0;
|
const currentLevel = state.skills[skillId] || 0;
|
||||||
|
const currentUpgrades = state.skillUpgrades?.[skillId] || [];
|
||||||
|
|
||||||
set({
|
set({
|
||||||
skillTiers: {
|
skillTiers: {
|
||||||
@@ -1757,7 +1777,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
},
|
},
|
||||||
skillUpgrades: {
|
skillUpgrades: {
|
||||||
...state.skillUpgrades,
|
...state.skillUpgrades,
|
||||||
[nextTierSkillId]: [], // Start fresh with upgrades for new tier
|
[nextTierSkillId]: currentUpgrades, // Carry over upgrades from previous tier
|
||||||
},
|
},
|
||||||
log: [`🌟 ${SKILLS_DEF[baseSkillId]?.name || baseSkillId} evolved to Tier ${nextTier}!`, ...state.log.slice(0, 49)],
|
log: [`🌟 ${SKILLS_DEF[baseSkillId]?.name || baseSkillId} evolved to Tier ${nextTier}!`, ...state.log.slice(0, 49)],
|
||||||
});
|
});
|
||||||
|
|||||||
30
worklog.md
30
worklog.md
@@ -633,3 +633,33 @@ Stage Summary:
|
|||||||
- UI for selecting and managing golems
|
- UI for selecting and managing golems
|
||||||
- Documentation updated
|
- Documentation updated
|
||||||
- Lint passes
|
- Lint passes
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 28
|
||||||
|
Agent: Main
|
||||||
|
Task: Fix level upgrade reset loop bug and add golem display to SpireTab
|
||||||
|
|
||||||
|
Work Log:
|
||||||
|
- **Fixed upgrade reset bug in commitSkillUpgrades()**:
|
||||||
|
- Root cause: `commitSkillUpgrades()` was replacing ALL upgrades for a skill instead of merging by milestone
|
||||||
|
- When selecting level 10 upgrades, it would wipe out level 5 selections (and vice versa)
|
||||||
|
- Added optional `milestone` parameter (5 | 10) to the function
|
||||||
|
- When milestone is specified, the function now:
|
||||||
|
- Keeps existing upgrades from OTHER milestones
|
||||||
|
- Only replaces upgrades for the CURRENT milestone
|
||||||
|
- Updated type definition in GameStore interface
|
||||||
|
- Updated SkillsTab.tsx to pass `upgradeDialogMilestone` when committing
|
||||||
|
|
||||||
|
- **Added summoned golems display to SpireTab**:
|
||||||
|
- Imported GOLEMS_DEF and helper functions
|
||||||
|
- Added Mountain icon import
|
||||||
|
- Added "Active Golems" card that appears when golems are summoned
|
||||||
|
- Shows each golem's name, damage, attack speed, and armor pierce
|
||||||
|
- Displays attack progress bar when climbing
|
||||||
|
- AOE badge for golems with area attacks
|
||||||
|
|
||||||
|
Stage Summary:
|
||||||
|
- Level 5 and level 10 upgrades no longer reset each other
|
||||||
|
- Players can safely select upgrades at both milestones
|
||||||
|
- Summoned golems now visible in Spire tab during combat
|
||||||
|
- Lint passes, dev server running
|
||||||
|
|||||||
Reference in New Issue
Block a user