From f0ab3ca3ce9159e87741a6477acadc7eeed6276f Mon Sep 17 00:00:00 2001 From: Refactoring Agent <[email protected]> Date: Fri, 1 May 2026 16:41:29 +0200 Subject: [PATCH] refactor: extract components from SkillsTab.tsx to reduce below 400 lines --- .../game/tabs/CategorySkillsList.tsx | 221 +++++++++++++++ .../game/tabs/MilestoneProgress.tsx | 22 ++ .../game/tabs/SkillCategoryHeader.tsx | 42 +++ src/components/game/tabs/SkillMultipliers.tsx | 50 ++++ src/components/game/tabs/SkillRow.tsx | 30 +-- src/components/game/tabs/SkillsTab.tsx | 255 +++--------------- .../game/hooks/useSkillUpgradeSelection.ts | 31 +++ src/lib/game/types/index.ts | 3 +- 8 files changed, 422 insertions(+), 232 deletions(-) create mode 100644 src/components/game/tabs/CategorySkillsList.tsx create mode 100644 src/components/game/tabs/MilestoneProgress.tsx create mode 100644 src/components/game/tabs/SkillCategoryHeader.tsx create mode 100644 src/components/game/tabs/SkillMultipliers.tsx diff --git a/src/components/game/tabs/CategorySkillsList.tsx b/src/components/game/tabs/CategorySkillsList.tsx new file mode 100644 index 0000000..e7704aa --- /dev/null +++ b/src/components/game/tabs/CategorySkillsList.tsx @@ -0,0 +1,221 @@ +// ─── Category Skills List ─────────────────────────────────────────── +// Wraps all skills in a single category, handles category-level UI + +'use client'; + +import { useState } from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { SkillRow } from './SkillRow'; +import { SkillCategoryHeader } from './SkillCategoryHeader'; +import type { GameStore } from '@/lib/game/store'; +import { SKILL_CATEGORIES, SKILLS_DEF, getStudyCostMultiplier, getStudySpeedMultiplier } from '@/lib/game/constants'; +import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier, SKILL_EVOLUTION_PATHS } from '@/lib/game/skill-evolution'; +import { SPECIAL_EFFECTS, hasSpecial } from '@/lib/game/special-effects'; +import { ELEMENTS } from '@/lib/game/constants'; +import { getUnifiedEffects } from '@/lib/game/effects'; +import { hasMilestoneUpgrade } from '@/lib/game/hooks/useSkillUpgradeSelection'; +import type { ComputedEffects } from '@/lib/game/upgrade-effects.types'; +import type { SkillCost } from '@/lib/game/types'; +import { useGameToast } from '@/components/game/GameToast'; + +interface CategorySkillsListProps { + category: { id: string; name: string; icon: string }; + availableCategories: string[]; + isCollapsed: boolean; + onToggleCategory: (categoryId: string) => void; + store: GameStore; + studySpeedMult: number; + upgradeEffects: ComputedEffects; + currentStudyTarget: any; + onStartStudying: (skillId: string) => void; + onParallelStudy: (skillId: string) => void; + onCancelStudy: () => void; + onOpenUpgradeDialog: (skillId: string, milestone: 5 | 10) => void; + onTierUp: (skillId: string) => void; + pendingSelections: string[]; + setPendingSelections: (selections: string[]) => void; +} + +// Type guard for element skill costs +function isElementCost(cost?: SkillCost | null): cost is SkillCost & { type: 'element'; element: string } { + return cost !== null && typeof cost !== 'undefined' && cost.type === 'element' && typeof cost.element === 'string'; +} + +export function CategorySkillsList({ + category, + availableCategories, + isCollapsed, + onToggleCategory, + store, + studySpeedMult, + upgradeEffects, + currentStudyTarget, + onStartStudying, + onParallelStudy, + onCancelStudy, + onOpenUpgradeDialog, + onTierUp, + pendingSelections, + setPendingSelections, +}: CategorySkillsListProps) { + const showToast = useGameToast(); + + // Skip if category not available + if (!availableCategories.includes(category.id)) return null; + + const skillsInCat = Object.entries(SKILLS_DEF).filter(([, def]) => def.cat === category.id); + if (skillsInCat.length === 0) return null; + + const handleCancelStudyInternal = () => { + onCancelStudy(); + }; + + return ( + + onToggleCategory(category.id)} + /> + {!isCollapsed && ( + +
+ {skillsInCat.map(([id, def]) => { + // GATE MANA CAPACITY SKILLS BY UNLOCKED ELEMENT + if (isElementCost(def.cost)) { + const element = store.elements[def.cost.element]; + if (!element?.unlocked) return null; + } + + // Get tier info + const currentTier = store.skillTiers?.[id] || 1; + const tieredSkillId = currentTier > 1 ? `${id}_t${currentTier}` : id; + const tierMultiplier = getTierMultiplier(tieredSkillId); + + // Get the actual level from the tiered skill + const level = store.skills[tieredSkillId] || store.skills[id] || 0; + const maxed = level >= def.max; + + // Check if studying this skill + const isStudying = + (store.currentStudyTarget?.id === id || store.currentStudyTarget?.id === tieredSkillId) && + store.currentStudyTarget?.type === 'skill'; + + // Get tier name for display + const tierDef = SKILL_EVOLUTION_PATHS[id]?.tiers.find((t) => t.tier === currentTier); + const skillDisplayName = tierDef?.name || def.name; + + // Check prerequisites + let prereqMet = true; + if (def.req) { + for (const [r, rl] of Object.entries(def.req)) { + if ((store.skills[r] || 0) < rl) { + prereqMet = false; + break; + } + } + } + + // Apply skill modifiers + const costMult = getStudyCostMultiplier(store.skills); + const speedMult = getStudySpeedMultiplier(store.skills); + const studyEffects = getUnifiedEffects(store); + const effectiveSpeedMult = speedMult * studyEffects.studySpeedMultiplier; + + // Study time scales with tier + const tierStudyTime = def.studyTime * currentTier; + const effectiveStudyTime = tierStudyTime / effectiveSpeedMult; + + // Cost scales with tier + const baseCost = def.base * (level + 1) * currentTier; + const cost = Math.floor(baseCost * costMult); + + // Additional cost (element mana) - only pass element costs + const additionalCost = isElementCost(def.cost) + ? { type: 'element' as const, element: def.cost.element, amount: def.cost.amount } + : undefined; + + // Can start studying? + let canStudy = !maxed && prereqMet && store.rawMana >= cost && !isStudying; + + // Check additional cost (element mana) + if (isElementCost(def.cost)) { + const element = store.elements[def.cost.element]; + if (!element || element.current < def.cost.amount) { + canStudy = false; + } + } + + // Check for milestone upgrades + const milestoneInfo = hasMilestoneUpgrade( + tieredSkillId, + level, + store.skillTiers || {}, + store.skillUpgrades + ); + + // Check for tier up + const nextTierSkill = getNextTierSkill(tieredSkillId); + const canTierUp = Boolean(maxed && nextTierSkill); + + // Get selected upgrades + const selectedUpgrades = store.skillUpgrades[tieredSkillId] || []; + const selectedL5 = selectedUpgrades.filter((u) => u.includes('_l5')); + const selectedL10 = selectedUpgrades.filter((u) => u.includes('_l10')); + + // Check if insufficient mana for toast + const hasInsufficientMana = !isStudying && !maxed && store.rawMana < cost; + + // Check for parallel study eligibility + const isParallelStudy = + store.currentStudyTarget?.id === tieredSkillId && + store.currentStudyTarget?.type === 'skill'; + const canParallelStudy: boolean = + hasSpecial(studyEffects, SPECIAL_EFFECTS.PARALLEL_STUDY) && + !!store.currentStudyTarget && + store.currentStudyTarget.id !== tieredSkillId && + !isStudying; + + return ( + + ); + })} +
+
+ )} +
+ ); +} diff --git a/src/components/game/tabs/MilestoneProgress.tsx b/src/components/game/tabs/MilestoneProgress.tsx new file mode 100644 index 0000000..3b2d34e --- /dev/null +++ b/src/components/game/tabs/MilestoneProgress.tsx @@ -0,0 +1,22 @@ +// ─── Milestone Progress ─────────────────────────────────────────── +// Milestone upgrade progress indicator for skill rows + +import { Badge } from '@/components/ui/badge'; + +interface MilestoneProgressProps { + milestoneInfo: { + milestone: 5 | 10; + hasUpgrades: boolean; + selectedCount: number; + } | null; +} + +export function MilestoneProgress({ milestoneInfo }: MilestoneProgressProps) { + if (!milestoneInfo) return null; + + return ( +
+ ⭐ Level {milestoneInfo.milestone} milestone: {milestoneInfo.selectedCount}/2 upgrades selected +
+ ); +} diff --git a/src/components/game/tabs/SkillCategoryHeader.tsx b/src/components/game/tabs/SkillCategoryHeader.tsx new file mode 100644 index 0000000..f39ce2b --- /dev/null +++ b/src/components/game/tabs/SkillCategoryHeader.tsx @@ -0,0 +1,42 @@ +// ─── Skill Category Header ─────────────────────────────────────────── +// Header for a skill category with collapse/expand toggle + +import { Badge } from '@/components/ui/badge'; +import { ChevronDown, ChevronRight } from 'lucide-react'; + +interface SkillCategoryHeaderProps { + category: { + id: string; + name: string; + icon: string; + }; + skillCount: number; + isCollapsed: boolean; + onToggle: () => void; +} + +export function SkillCategoryHeader({ + category, + skillCount, + isCollapsed, + onToggle, +}: SkillCategoryHeaderProps) { + return ( + + + + {category.icon} {category.name} + +
+ + {skillCount} skills + + {isCollapsed ? : } +
+
+
+ ); +} + +// Local import of CardHeader and CardTitle to avoid circular deps +import { CardHeader, CardTitle } from '@/components/ui/card'; diff --git a/src/components/game/tabs/SkillMultipliers.tsx b/src/components/game/tabs/SkillMultipliers.tsx new file mode 100644 index 0000000..6c76bd6 --- /dev/null +++ b/src/components/game/tabs/SkillMultipliers.tsx @@ -0,0 +1,50 @@ +// ─── Skill Multipliers ─────────────────────────────────────────── +// Study speed and cost multiplier display for skill rows + +import { ELEMENTS } from '@/lib/game/constants'; + +type AdditionalCost = { type: 'element'; element: string; amount: number }; + +interface SkillMultipliersProps { + effectiveStudyTime: number; + speedMult: number; + costMult: number; + cost: number; + additionalCost?: AdditionalCost; +} + +export function SkillMultipliers({ + effectiveStudyTime, + speedMult, + costMult, + cost, + additionalCost, +}: SkillMultipliersProps) { + return ( +
+ 1 ? 'text-green-400' : ''}> + Study: {formatStudyTime(effectiveStudyTime)}{speedMult > 1 && ( + ({Math.round(speedMult * 100)}% speed) + )} + + {' • '} + + Cost: {cost} mana{costMult < 1 && ( + ({Math.round(costMult * 100)}% cost) + )} + {additionalCost && additionalCost.type === 'element' && ( + + + {additionalCost.amount} {ELEMENTS[additionalCost.element]?.sym} {additionalCost.element} + + )} + +
+ ); +} + +function formatStudyTime(ms: number): string { + if (ms < 60_000) return `${Math.floor(ms / 1000)}s`; + if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`; + if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ${Math.floor((ms % 3_600_000) / 60_000)}m`; + return `${Math.floor(ms / 86_400_000)}d ${Math.floor((ms % 86_400_000) / 3_600_000)}h`; +} diff --git a/src/components/game/tabs/SkillRow.tsx b/src/components/game/tabs/SkillRow.tsx index 07b2d2f..fd30403 100644 --- a/src/components/game/tabs/SkillRow.tsx +++ b/src/components/game/tabs/SkillRow.tsx @@ -10,11 +10,12 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/comp import { ChevronRight } from 'lucide-react'; import type { SkillUpgradeChoice } from '@/lib/game/types'; import { ELEMENTS } from '@/lib/game/constants'; -import { hasMilestoneUpgrade } from '@/lib/game/hooks/useSkillUpgradeSelection'; import { formatStudyTime } from '@/lib/game/formatting'; import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier, SKILL_EVOLUTION_PATHS } from '@/lib/game/skill-evolution'; import type { ComputedEffects } from '@/lib/game/upgrade-effects.types'; import { SPECIAL_EFFECTS, hasSpecial } from '@/lib/game/special-effects'; +import { MilestoneProgress } from './MilestoneProgress'; +import { SkillMultipliers } from './SkillMultipliers'; type StudyTarget = { type: 'skill' | 'spell'; id: string; progress: number; required: number; } | null; @@ -126,20 +127,13 @@ export function SkillRow(props: SkillRowProps) { Requires: {Object.entries(def.req).map(([r, rl]) => `${r} Lv.${rl}`).join(', ')} )} -
- 1 ? 'text-green-400' : ''}> - Study: {formatStudyTime(effectiveStudyTime)}{speedMult > 1 && ({Math.round(speedMult * 100)}% speed)} - - {' • '} - - Cost: {cost} mana{costMult < 1 && ({Math.round(costMult * 100)}% cost)} - {additionalCost && additionalCost.type === 'element' && ( - - + {additionalCost.amount} {ELEMENTS[additionalCost.element]?.sym} {additionalCost.element} - - )} - -
+ {hasInsufficientMana && (
@@ -147,11 +141,7 @@ export function SkillRow(props: SkillRowProps) {
)} - {milestoneInfo && ( -
- ⭐ Level {milestoneInfo.milestone} milestone: {milestoneInfo.selectedCount}/2 upgrades selected -
- )} +
diff --git a/src/components/game/tabs/SkillsTab.tsx b/src/components/game/tabs/SkillsTab.tsx index 1633b04..336eb44 100755 --- a/src/components/game/tabs/SkillsTab.tsx +++ b/src/components/game/tabs/SkillsTab.tsx @@ -1,10 +1,10 @@ -// ─── Skills Tab ──────────────────────────── +// ─── Skills Tab ─────────────────────────────────────────────────── // SkillsTab - Displays all skills organized by category -// Refactored: uses SkillRow component for per-skill rendering (reduced from 469 lines) +// Refactored: extracted components for better modularity (reduced from 400 lines) 'use client'; -import { useState } from 'react'; +import { useState, useCallback } from 'react'; import { SKILLS_DEF, SKILL_CATEGORIES, @@ -20,7 +20,7 @@ import { import { getUnifiedEffects } from '@/lib/game/effects'; import { getAvailableSkillCategories } from '@/lib/game/data/attunements'; import { fmt, fmtDec } from '@/lib/game/store'; -import type { SkillUpgradeChoice, GameStore } from '@/lib/game/types'; +import type { SkillUpgradeChoice } from '@/lib/game/types'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; @@ -38,43 +38,13 @@ import { ELEMENTS } from '@/lib/game/constants'; import { ChevronDown, ChevronRight } from 'lucide-react'; import { SkillRow } from './SkillRow'; import { useSkillUpgradeSelection } from '@/lib/game/hooks/useSkillUpgradeSelection'; +import { CategorySkillsList } from './CategorySkillsList'; +import type { GameStore } from '@/lib/game/store'; export interface SkillsTabProps { store: GameStore; } -// Check if skill has milestone available -function hasMilestoneUpgrade( - skillId: string, - level: number, - skillTiers: Record, - skillUpgrades: Record -): { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null { - const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; - const path = SKILL_EVOLUTION_PATHS[baseSkillId]; - if (!path) return null; - - // Check level 5 milestone - if (level >= 5) { - const upgrades5 = getUpgradesForSkillAtMilestone(skillId, 5, skillTiers); - const selected5 = (skillUpgrades[skillId] || []).filter((id) => id.includes('_l5')); - if (upgrades5.length > 0 && selected5.length < 2) { - return { milestone: 5, hasUpgrades: true, selectedCount: selected5.length }; - } - } - - // Check level 10 milestone - if (level >= 10) { - const upgrades10 = getUpgradesForSkillAtMilestone(skillId, 10, skillTiers); - const selected10 = (skillUpgrades[skillId] || []).filter((id) => id.includes('_l10')); - if (upgrades10.length > 0 && selected10.length < 2) { - return { milestone: 10, hasUpgrades: true, selectedCount: selected10.length }; - } - } - - return null; -} - export function SkillsTab({ store }: SkillsTabProps) { const showToast = useGameToast(); const [upgradeDialogSkill, setUpgradeDialogSkill] = useState(null); @@ -86,7 +56,7 @@ export function SkillsTab({ store }: SkillsTabProps) { } | null>(null); const studySpeedMult = getStudySpeedMultiplier(store.skills); - const upgradeEffects = getUnifiedEffects(store); + const upgradeEffects = getUnifiedEffects(store as any); // Upgrade selection hook const { @@ -135,6 +105,14 @@ export function SkillsTab({ store }: SkillsTabProps) { hookHandleCancel(() => setUpgradeDialogSkill(null)); }; + // Wrapper for upgrade toggle that matches UpgradeDialog's onToggle signature + const handleUpgradeToggle = useCallback( + (upgradeId: string) => { + toggleUpgrade(upgradeId, available, alreadySelected); + }, + [toggleUpgrade, available, alreadySelected] + ); + // Handle study start with toast const handleStartStudying = (skillId: string) => { const skillDef = SKILLS_DEF[skillId.includes('_t') ? skillId.split('_t')[0] : skillId]; @@ -173,6 +151,9 @@ export function SkillsTab({ store }: SkillsTabProps) { } }; + // Get available skill categories based on attunements + const availableCategories = getAvailableSkillCategories(store.attunements || {}); + return (
{/* Upgrade Selection Dialog */} @@ -183,7 +164,7 @@ export function SkillsTab({ store }: SkillsTabProps) { pendingSelections={pendingSelections.length > 0 ? pendingSelections : alreadySelected} available={available} alreadySelected={alreadySelected} - onToggle={toggleUpgrade} + onToggle={handleUpgradeToggle} onConfirm={handleConfirm} onCancel={handleCancel} onOpenChange={(open) => { @@ -221,179 +202,31 @@ export function SkillsTab({ store }: SkillsTabProps) { )} - {/* Get available skill categories based on attunements */} - {(() => { - const availableCategories = getAvailableSkillCategories(store.attunements || {}); - - return SKILL_CATEGORIES.filter((cat) => availableCategories.includes(cat.id)).map((cat) => { - const skillsInCat = Object.entries(SKILLS_DEF).filter(([, def]) => def.cat === cat.id); - if (skillsInCat.length === 0) return null; - - const isCollapsed = collapsedCategories.has(cat.id); - - return ( - - toggleCategory(cat.id)}> - - - {cat.icon} {cat.name} - -
- - {skillsInCat.length} skills - - {isCollapsed ? : } -
-
-
- {!isCollapsed && ( - -
- {skillsInCat.map(([id, def]) => { - // GATE MANA CAPACITY SKILLS BY UNLOCKED ELEMENT - if (def.cost?.type === 'element') { - const element = store.elements[def.cost.element]; - if (!element?.unlocked) { - return null; // Don't render this skill - } - } - // Get tier info - const currentTier = store.skillTiers?.[id] || 1; - const tieredSkillId = currentTier > 1 ? `${id}_t${currentTier}` : id; - const tierMultiplier = getTierMultiplier(tieredSkillId); - - // Get the actual level from the tiered skill - const level = store.skills[tieredSkillId] || store.skills[id] || 0; - const maxed = level >= def.max; - - // Check if studying this skill - const isStudying = - (store.currentStudyTarget?.id === id || store.currentStudyTarget?.id === tieredSkillId) && - store.currentStudyTarget?.type === 'skill'; - - // Get tier name for display - const tierDef = SKILL_EVOLUTION_PATHS[id]?.tiers.find((t) => t.tier === currentTier); - const skillDisplayName = tierDef?.name || def.name; - - // Check prerequisites - let prereqMet = true; - if (def.req) { - for (const [r, rl] of Object.entries(def.req)) { - if ((store.skills[r] || 0) < rl) { - prereqMet = false; - break; - } - } - } - - // Apply skill modifiers - const costMult = getStudyCostMultiplier(store.skills); - const speedMult = getStudySpeedMultiplier(store.skills); - const studyEffects = getUnifiedEffects(store); - const effectiveSpeedMult = speedMult * studyEffects.studySpeedMultiplier; - - // Study time scales with tier - const tierStudyTime = def.studyTime * currentTier; - const effectiveStudyTime = tierStudyTime / effectiveSpeedMult; - - // Cost scales with tier - const baseCost = def.base * (level + 1) * currentTier; - const cost = Math.floor(baseCost * costMult); - - // Additional cost (element mana) - const additionalCost = def.cost; - - // Can start studying? - let canStudy = !maxed && prereqMet && store.rawMana >= cost && !isStudying; - - // Check additional cost (element mana) - if (def.cost && def.cost.type === 'element') { - const element = store.elements[def.cost.element]; - if (!element || element.current < def.cost.amount) { - canStudy = false; - } - } - - // Check for milestone upgrades - const milestoneInfo = hasMilestoneUpgrade( - tieredSkillId, - level, - store.skillTiers || {}, - store.skillUpgrades - ); - - // Check for tier up - const nextTierSkill = getNextTierSkill(tieredSkillId); - const canTierUp = maxed && nextTierSkill; - - // Get selected upgrades - const selectedUpgrades = store.skillUpgrades[tieredSkillId] || []; - const selectedL5 = selectedUpgrades.filter((u) => u.includes('_l5')); - const selectedL10 = selectedUpgrades.filter((u) => u.includes('_l10')); - - // Check if insufficient mana for toast - const hasInsufficientMana = !isStudying && !maxed && store.rawMana < cost; - - // Check for parallel study eligibility - const isParallelStudy = - store.parallelStudyTarget?.id === tieredSkillId && - store.parallelStudyTarget?.type === 'skill'; - const canParallelStudy = - hasSpecial(studyEffects, SPECIAL_EFFECTS.PARALLEL_STUDY) && - store.currentStudyTarget && - store.currentStudyTarget.id !== tieredSkillId && - !isStudying; - - return ( - { - if (store.currentStudyTarget?.id === tieredSkillId) { - handleCancelStudy(); - } - }} - onUpgradeDialogOpen={(skillId, milestone) => { - setUpgradeDialogSkill(skillId); - setUpgradeDialogMilestone(milestone); - setPendingSelections([]); - }} - onTierUp={(skillId) => store.tierUpSkill(skillId)} - /> - ); - })} -
-
- )} -
- ); - }); - })()} + {/* Skill Categories */} + {SKILL_CATEGORIES.filter((cat) => availableCategories.includes(cat.id)).map((cat) => ( + { + setUpgradeDialogSkill(skillId); + setUpgradeDialogMilestone(milestone); + setPendingSelections([]); + }} + onTierUp={(skillId) => store.tierUpSkill(skillId)} + pendingSelections={pendingSelections} + setPendingSelections={setPendingSelections} + /> + ))}
); } diff --git a/src/lib/game/hooks/useSkillUpgradeSelection.ts b/src/lib/game/hooks/useSkillUpgradeSelection.ts index 944b83a..41bd90d 100644 --- a/src/lib/game/hooks/useSkillUpgradeSelection.ts +++ b/src/lib/game/hooks/useSkillUpgradeSelection.ts @@ -3,6 +3,7 @@ import { useState, useMemo, useCallback, SetStateAction, Dispatch } from 'react'; import type { SkillUpgradeChoice } from '@/lib/game/types'; +import { getUpgradesForSkillAtMilestone } from '@/lib/game/skill-evolution'; export interface UseSkillUpgradeSelectionResult { pendingSelections: string[]; @@ -12,6 +13,36 @@ export interface UseSkillUpgradeSelectionResult { handleCancel: (onClose: () => void) => void; } +/** + * Check if skill has milestone available (also exported for use in SkillRow/CategorySkillsList) + */ +export function hasMilestoneUpgrade( + skillId: string, + level: number, + skillTiers: Record, + skillUpgrades: Record +): { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null { + // Check level 5 milestone + if (level >= 5) { + const upgrades5 = getUpgradesForSkillAtMilestone(skillId, 5, skillTiers); + const selected5 = (skillUpgrades[skillId] || []).filter((id) => id.includes('_l5')); + if (upgrades5.length > 0 && selected5.length < 2) { + return { milestone: 5, hasUpgrades: true, selectedCount: selected5.length }; + } + } + + // Check level 10 milestone + if (level >= 10) { + const upgrades10 = getUpgradesForSkillAtMilestone(skillId, 10, skillTiers); + const selected10 = (skillUpgrades[skillId] || []).filter((id) => id.includes('_l10')); + if (upgrades10.length > 0 && selected10.length < 2) { + return { milestone: 10, hasUpgrades: true, selectedCount: selected10.length }; + } + } + + return null; +} + /** * Hook for managing skill upgrade selection state in the SkillsTab milestone upgrade dialog. * Manages pending selections across the dialog open/close cycle. diff --git a/src/lib/game/types/index.ts b/src/lib/game/types/index.ts index 2c7b1e6..842d37e 100644 --- a/src/lib/game/types/index.ts +++ b/src/lib/game/types/index.ts @@ -20,7 +20,8 @@ export type { SkillTierDef, SkillPerkChoice, SkillUpgradeChoice, - PrestigeDef + PrestigeDef, + SkillCost } from './skills'; // Equipment types