'use client'; import { useMemo, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Input } from '@/components/ui/input'; import { Sparkles, Heart, Zap, Star, Shield, Flame, Droplet, Wind, Mountain, Sun, Moon, Leaf, Skull, Brain, Link, Wind as Force, Droplets, TreeDeciduous, Hourglass, Gem, CircleDot, Circle, Sword, Wand2, ShieldCheck, TrendingUp, Clock, Crown } from 'lucide-react'; import type { GameState, FamiliarInstance, FamiliarDef, FamiliarAbilityType } from '@/lib/game/types'; import { FAMILIARS_DEF, getFamiliarXpRequired, getFamiliarAbilityValue } from '@/lib/game/data/familiars'; import { ELEMENTS } from '@/lib/game/constants'; // Element icon mapping const ELEMENT_ICONS: Record = { fire: Flame, water: Droplet, air: Wind, earth: Mountain, light: Sun, dark: Moon, life: Leaf, death: Skull, mental: Brain, transference: Link, force: Force, blood: Droplets, metal: Shield, wood: TreeDeciduous, sand: Hourglass, crystal: Gem, stellar: Star, void: CircleDot, raw: Circle, }; // Rarity colors const RARITY_COLORS: Record = { common: 'text-gray-400 border-gray-600', uncommon: 'text-green-400 border-green-600', rare: 'text-blue-400 border-blue-600', epic: 'text-purple-400 border-purple-600', legendary: 'text-amber-400 border-amber-600', }; const RARITY_BG: Record = { common: 'bg-gray-900/50', uncommon: 'bg-green-900/20', rare: 'bg-blue-900/20', epic: 'bg-purple-900/20', legendary: 'bg-amber-900/20', }; // Role icons const ROLE_ICONS: Record = { combat: Sword, mana: Sparkles, support: Heart, guardian: ShieldCheck, }; // Ability type icons const ABILITY_ICONS: Record = { damageBonus: Sword, manaRegen: Sparkles, autoGather: Zap, critChance: Star, castSpeed: Clock, manaShield: Shield, elementalBonus: Flame, lifeSteal: Heart, bonusGold: TrendingUp, autoConvert: Wand2, thorns: ShieldCheck, }; interface FamiliarTabProps { store: GameState & { setActiveFamiliar: (index: number, active: boolean) => void; setFamiliarNickname: (index: number, nickname: string) => void; summonFamiliar: (familiarId: string) => void; upgradeFamiliarAbility: (index: number, abilityType: FamiliarAbilityType) => void; getActiveFamiliarBonuses: () => ReturnType['getActiveFamiliarBonuses'] extends () => infer R ? R : never; getAvailableFamiliars: () => string[]; }; } export function FamiliarTab({ store }: FamiliarTabProps) { const [selectedFamiliar, setSelectedFamiliar] = useState(null); const [nicknameInput, setNicknameInput] = useState(''); const familiars = store.familiars; const activeFamiliarSlots = store.activeFamiliarSlots; const activeCount = familiars.filter(f => f.active).length; const availableFamiliars = store.getAvailableFamiliars(); const familiarBonuses = store.getActiveFamiliarBonuses(); // Format XP display const formatXp = (current: number, level: number) => { const required = getFamiliarXpRequired(level); return `${current}/${required}`; }; // Get familiar definition const getFamiliarDef = (instance: FamiliarInstance): FamiliarDef | undefined => { return FAMILIARS_DEF[instance.familiarId]; }; // Render a single familiar card const renderFamiliarCard = (instance: FamiliarInstance, index: number) => { const def = getFamiliarDef(instance); if (!def) return null; const ElementIcon = ELEMENT_ICONS[def.element] || Circle; const RoleIcon = ROLE_ICONS[def.role] || Sparkles; const xpRequired = getFamiliarXpRequired(instance.level); const xpPercent = Math.min(100, (instance.experience / xpRequired) * 100); const bondPercent = instance.bond; const isSelected = selectedFamiliar === index; return ( setSelectedFamiliar(isSelected ? null : index)} >
{instance.nickname || def.name}
Lv.{instance.level} {instance.active && ( Active )}
{def.rarity}
{/* XP Bar */}
XP {formatXp(instance.experience, instance.level)}
{/* Bond Bar */}
Bond {bondPercent.toFixed(0)}%
{/* Abilities Preview */}
{instance.abilities.slice(0, 3).map(ability => { const abilityDef = def.abilities.find(a => a.type === ability.type); if (!abilityDef) return null; const AbilityIcon = ABILITY_ICONS[ability.type] || Zap; const value = getFamiliarAbilityValue(abilityDef, instance.level, ability.level); return ( {ability.type === 'damageBonus' || ability.type === 'elementalBonus' || ability.type === 'castSpeed' || ability.type === 'critChance' || ability.type === 'lifeSteal' || ability.type === 'thorns' || ability.type === 'bonusGold' ? `+${value.toFixed(1)}%` : `+${value.toFixed(1)}`}

{abilityDef.desc}

Level {ability.level}/10

); })}
); }; // Render selected familiar details const renderFamiliarDetails = () => { if (selectedFamiliar === null || selectedFamiliar >= familiars.length) return null; const instance = familiars[selectedFamiliar]; const def = getFamiliarDef(instance); if (!def) return null; const ElementIcon = ELEMENT_ICONS[def.element] || Circle; return (
Familiar Details
{/* Name and nickname */}

{def.name}

{instance.nickname && (

"{instance.nickname}"

)}
{/* Nickname input */}
setNicknameInput(e.target.value)} placeholder="Set nickname..." className="h-8 text-sm bg-gray-800 border-gray-600" />
{/* Description */}
{def.desc}
{/* Stats */}
Level: {instance.level}/100
Bond: {instance.bond.toFixed(0)}%
Role: {def.role}
Element: {def.element}
{/* Abilities */}

Abilities

{instance.abilities.map(ability => { const abilityDef = def.abilities.find(a => a.type === ability.type); if (!abilityDef) return null; const AbilityIcon = ABILITY_ICONS[ability.type] || Zap; const value = getFamiliarAbilityValue(abilityDef, instance.level, ability.level); const upgradeCost = ability.level * 100; const canUpgrade = instance.experience >= upgradeCost && ability.level < 10; return (
{ability.type.replace(/([A-Z])/g, ' $1').trim()}
Lv.{ability.level}/10

{abilityDef.desc}

Current: +{value.toFixed(2)} {ability.type === 'damageBonus' || ability.type === 'elementalBonus' || ability.type === 'castSpeed' || ability.type === 'critChance' || ability.type === 'lifeSteal' || ability.type === 'thorns' || ability.type === 'bonusGold' ? '%' : ''} {ability.level < 10 && ( )}
); })}
{/* Activate/Deactivate */} {/* Flavor text */} {def.flavorText && (

"{def.flavorText}"

)}
); }; // Render summonable familiars const renderSummonableFamiliars = () => { if (availableFamiliars.length === 0) return null; return ( Available to Summon ({availableFamiliars.length})
{availableFamiliars.map(familiarId => { const def = FAMILIARS_DEF[familiarId]; if (!def) return null; const ElementIcon = ELEMENT_ICONS[def.element] || Circle; const RoleIcon = ROLE_ICONS[def.role] || Sparkles; return (
{def.name}
{def.role}
); })}
); }; // Render active bonuses const renderActiveBonuses = () => { const hasBonuses = Object.entries(familiarBonuses).some(([key, value]) => { if (key === 'damageMultiplier' || key === 'castSpeedMultiplier' || key === 'elementalDamageMultiplier' || key === 'insightMultiplier') { return value > 1; } return value > 0; }); if (!hasBonuses) return null; return ( Active Familiar Bonuses
{familiarBonuses.damageMultiplier > 1 && (
+{((familiarBonuses.damageMultiplier - 1) * 100).toFixed(0)}% DMG
)} {familiarBonuses.manaRegenBonus > 0 && (
+{familiarBonuses.manaRegenBonus.toFixed(1)} regen
)} {familiarBonuses.autoGatherRate > 0 && (
+{familiarBonuses.autoGatherRate.toFixed(1)}/hr gather
)} {familiarBonuses.critChanceBonus > 0 && (
+{familiarBonuses.critChanceBonus.toFixed(1)}% crit
)} {familiarBonuses.castSpeedMultiplier > 1 && (
+{((familiarBonuses.castSpeedMultiplier - 1) * 100).toFixed(0)}% speed
)} {familiarBonuses.elementalDamageMultiplier > 1 && (
+{((familiarBonuses.elementalDamageMultiplier - 1) * 100).toFixed(0)}% elem
)} {familiarBonuses.lifeStealPercent > 0 && (
+{familiarBonuses.lifeStealPercent.toFixed(0)}% lifesteal
)} {familiarBonuses.insightMultiplier > 1 && (
+{((familiarBonuses.insightMultiplier - 1) * 100).toFixed(0)}% insight
)}
); }; return (
{/* Owned Familiars */}
Your Familiars ({familiars.length})
Active Slots: {activeCount}/{activeFamiliarSlots}
{familiars.length > 0 ? (
{familiars.map((instance, index) => renderFamiliarCard(instance, index))}
) : (
No familiars yet. Progress through the game to summon companions!
)}
{/* Active Bonuses */} {renderActiveBonuses()} {/* Selected Familiar Details */} {renderFamiliarDetails()} {/* Summonable Familiars */} {renderSummonableFamiliars()} {/* Familiar Guide */} Familiar Guide

Acquiring Familiars

Familiars become available to summon as you progress through floors, gather mana, and sign pacts with guardians. Higher rarity familiars are unlocked later.

Leveling & Bond

Active familiars gain XP from combat, gathering, and time. Higher bond increases their power and XP gain. Upgrade abilities using XP to boost their effects.

Roles

Combat - Damage and crit bonuses
Mana - Regeneration and auto-gathering
Support - Speed and utility
Guardian - Defense and shields

Active Slots

You can have 1 familiar active by default. Upgrade through prestige to unlock more active slots for stacking bonuses.

); }