import { motion } from 'motion/react'; import type { CSSProperties, ReactNode } from 'react'; import { formatAttributeList, resolveAttributeSchema, resolveCharacterAttributeProfile } from '../data/attributeResolver'; import { type CharacterEquipmentItem, type CharacterInventoryItem, getCharacterEquipment, getCharacterMaxHp, getCharacterMaxMana, getInventoryItems, } from '../data/characterPresets'; import { getResourceLabelsForWorld } from '../services/customWorldPresentation'; import { AnimationState, type Character, type CharacterSkillDefinition, type CustomWorldProfile, type WorldType } from '../types'; import { CHROME_ICONS, getNineSliceStyle, type NineSliceTexture, UI_CHROME } from '../uiAssets'; import { CharacterAnimator } from './CharacterAnimator'; import { PixelIcon } from './PixelIcon'; interface CharacterDetailModalProps { character: Character | null; worldType: WorldType | null; customWorldProfile?: CustomWorldProfile | null; subtitle?: string; onClose: () => void; } const SKILL_STYLE_LABELS: Record = { burst: '爆发', steady: '稳定', mobility: '机动', finisher: '终结', projectile: '远程', }; function getGenderLabel(gender: Character['gender']) { if (gender === 'female') return '女'; if (gender === 'male') return '男'; return '未知'; } function getCharacterDetailSpriteStyle(character: Character, scale = 1.36) { const groundOffset = character.groundOffsetY ?? 22; const translateY = Math.max(10, Math.round((groundOffset - 22) * 0.34)); return { transform: `translateY(${translateY}px) scale(${scale})`, transformOrigin: 'center bottom', } satisfies CSSProperties; } function Section({ title, chrome = UI_CHROME.panel, children, }: { title: string; chrome?: NineSliceTexture; children: ReactNode; }) { return (
{title}
{children}
); } function StatPill({ label, value, tone, }: { label: string; value: string; tone: 'neutral' | 'hp' | 'mp'; }) { const toneClassName = tone === 'hp' ? 'border-rose-400/20 bg-rose-500/10 text-rose-100' : tone === 'mp' ? 'border-sky-400/20 bg-sky-500/10 text-sky-100' : 'border-white/10 bg-black/20 text-zinc-200'; return (
{label}
{value}
); } function EquipmentGrid({ items }: { items: CharacterEquipmentItem[] }) { return (
{items.map(item => (
{item.slot}
{item.item}
{item.rarity}
))}
); } function InventoryGrid({ items }: { items: CharacterInventoryItem[] }) { return (
{items.map(item => (
{item.category}
{item.name}
数量 x{item.quantity}
))}
); } function SkillList({ skills, resourceLabels, }: { skills: CharacterSkillDefinition[]; resourceLabels: ReturnType; }) { return (
{skills.map(skill => (
{skill.name}
{SKILL_STYLE_LABELS[skill.style]}
{resourceLabels.damage} {skill.damage}
{resourceLabels.manaCost} {skill.manaCost}
{resourceLabels.cooldown} {skill.cooldownTurns}
{resourceLabels.range} {skill.range}
))}
); } export function CharacterDetailModal({ character, worldType, customWorldProfile = null, subtitle = '初始伙伴', onClose, }: CharacterDetailModalProps) { if (!character) { return null; } const opening = worldType ? character.adventureOpenings[worldType] : null; const equipment = getCharacterEquipment(character); const inventory = getInventoryItems(character, worldType); const attributeSchema = resolveAttributeSchema(worldType, customWorldProfile); const attributeProfile = resolveCharacterAttributeProfile(character, worldType, customWorldProfile); const attributeRows = formatAttributeList(attributeProfile, attributeSchema); const resourceLabels = getResourceLabelsForWorld(worldType); return ( event.stopPropagation()} >
{character.name}
{subtitle}
候选人
{character.name}
{character.title} 性别: {getGenderLabel(character.gender)}

{character.description}

{attributeRows.map(({ slot, value }) => (
{slot.name}
{slot.definition}
{value}
))}
{opening && (
原因
{opening.reason}
目标
{opening.goal}
)}
{character.backstory}
{character.personality}
); }