import { AnimatePresence, motion } from 'motion/react'; import { useEffect, useMemo, useState } from 'react'; import { getCharacterById } from '../data/characterPresets'; import { MAX_COMPANIONS } from '../data/npcInteractions'; import { Character, CompanionState } from '../types'; import { CHROME_ICONS, getNineSliceStyle, UI_CHROME } from '../uiAssets'; import { PixelIcon } from './PixelIcon'; interface CompanionCampModalProps { isOpen: boolean; playerCharacter: Character | null; companions: CompanionState[]; roster: CompanionState[]; inBattle: boolean; onClose: () => void; onBenchCompanion: (npcId: string) => void; onActivateCompanion: (npcId: string, swapNpcId?: string | null) => void; } type CompanionCardData = { companion: CompanionState; character: Character; }; function StatusPill({ label, value }: { label: string; value: string }) { return (
{label} {value}
); } function buildCampMoments( playerCharacter: Character | null, activeCompanions: CompanionCardData[], reserveCompanions: CompanionCardData[], ) { if (!playerCharacter) { return ['营地尚未准备完毕。']; } const moments: string[] = []; if (activeCompanions.length === 0 && reserveCompanions.length === 0) { moments.push(`${playerCharacter.name}独自坐在营火旁,暂时还没有固定同行者。`); } if (activeCompanions.length >= 2) { const firstCompanion = activeCompanions[0]; const secondCompanion = activeCompanions[1]; if (firstCompanion && secondCompanion) { moments.push(`${firstCompanion.character.name}和${secondCompanion.character.name}正低声商量下一段路怎么走。`); } } const trustedCompanion = activeCompanions.find(item => item.companion.joinedAtAffinity >= 70); if (trustedCompanion) { moments.push(`${trustedCompanion.character.name}熟练地清点补给,看起来已经像能交托后背的同伴了。`); } if (reserveCompanions.length > 0) { const reserveCompanion = reserveCompanions[0]; if (reserveCompanion) { moments.push(`${reserveCompanion.character.name}正在营地里待命,随时都能重新归队。`); } } if (moments.length === 0) { moments.push(`${playerCharacter.name}环视营地,确认众人都已经各就各位。`); } return moments.slice(0, 3); } export function CompanionCampModal({ isOpen, playerCharacter, companions, roster, inBattle, onClose, onBenchCompanion, onActivateCompanion, }: CompanionCampModalProps) { const [selectedSwapNpcId, setSelectedSwapNpcId] = useState(null); const activeCompanionCards = useMemo( () => companions .map(companion => { const character = getCharacterById(companion.characterId); return character ? { companion, character } : null; }) .filter(Boolean) as CompanionCardData[], [companions], ); const reserveCompanionCards = useMemo( () => roster .map(companion => { const character = getCharacterById(companion.characterId); return character ? { companion, character } : null; }) .filter(Boolean) as CompanionCardData[], [roster], ); const campMoments = useMemo( () => buildCampMoments(playerCharacter, activeCompanionCards, reserveCompanionCards), [activeCompanionCards, playerCharacter, reserveCompanionCards], ); useEffect(() => { if (!isOpen) return; if (companions.length >= MAX_COMPANIONS) { setSelectedSwapNpcId(companions[0]?.npcId ?? null); return; } setSelectedSwapNpcId(null); }, [companions, isOpen]); return ( {isOpen && ( event.stopPropagation()} >
营地编组
{playerCharacter ? `${playerCharacter.name} / 出战 ${companions.length}/${MAX_COMPANIONS}` : '队伍调度'}
当前队伍
可直接把同行者转入后备,或先选定替换位,再让后备成员归队。
{inBattle && (
战斗中无法调整编组。
)}
{activeCompanionCards.length > 0 ? activeCompanionCards.map(({ companion, character }) => { const selectedForSwap = selectedSwapNpcId === companion.npcId; return (
{character.name}
{character.name}
{character.title}
); }) : (
当前没有已出战的同行者。
)}
后备队伍
后备同行者会在营地待命,随时可以重新召回。
{reserveCompanionCards.length > 0 ? reserveCompanionCards.map(({ companion, character }) => { const needsSwap = companions.length >= MAX_COMPANIONS; return (
{character.name}
{character.name}
{character.title}
); }) : (
当前还没有后备同行者。
)}
营地气氛
{campMoments.map((moment, index) => (
{moment}
))}
)}
); }