// The 确定 / 取消 / 结束回合 buttons, as 无名杀 shows them above your hand. Which buttons show, when they are enabled and // what they do is the upstream's CenterActionBar (src/client/components/CenterActionBar.tsx), condition for condition; // only the labels are 无名杀's: 确定 for 出牌, 打出, 使用 and 确认弃牌, 取消 for 不回应 and 取消选择. import type { PendingView } from '../../../src/engine/types'; import type { PlayInteractionResult } from '../../../src/client/hooks/usePlayInteraction'; import { useGameView } from '../../../src/client/components/GameViewCtx'; import { displaySkillName } from '../../../src/client/utils/skillDisplay'; interface Props { play: PlayInteractionResult; pending: PendingView | null; isRespondPending: boolean; respondCandidateCount: number | null; showCancelSelection: boolean; showEndTurn: boolean; isDiscardPhase: boolean; isPerspectiveAwaiting: boolean; discardMin: number; discardMax: number; } type Btn = { key: string; label: string; kind: 'ok' | 'cancel' | 'end' | 'plain'; onClick: () => void; disabled?: boolean; title?: string }; export function ActionBar({ play, pending, isRespondPending, respondCandidateCount, showCancelSelection, showEndTurn, isDiscardPhase, isPerspectiveAwaiting, discardMin, discardMax }: Props) { const { canOperate } = useGameView(); const { selectedCardId, selectedTarget, selectedForDiscard, transformMode, transformWrapperName, distributeMode, activeDistribute, isDistributeActive, distSelected, distAllocations, distTargetName, selectedActive, playButtonState, transformSubmit, selectedRespondCardId, respondTargetReady, altActions, handlePlayCard, handleSkillAction, handleTransformPlay, isRespondTransformContext, handleRespond, handlePlayRespond, handleEndTurn, handleConfirmDiscard, handleDiscardSelectAll, handleTransformSelectAll, handleTransformInvert, handleDistSubmit, handleDistClear, handleDistSelectAll, handleDistInvert, cancelSelection, setDistributeMode, } = play; const b: Btn[] = []; const weapon = pending?.prompt?.type === 'useCardAndTarget'; if (isRespondPending) { if (respondCandidateCount !== 0) b.push({ key: 'respond', label: '确定', kind: 'ok', onClick: handlePlayRespond, disabled: !selectedRespondCardId || !respondTargetReady }); b.push({ key: 'skip', label: weapon ? '交出武器' : '取消', kind: 'cancel', onClick: () => handleRespond(), title: respondCandidateCount === 0 ? '没有可回应的牌' : undefined }); } if (canOperate && transformMode && transformMode.minCards > 1) { const ids = transformMode.selectedCardIds; b.push({ key: 't-all', label: '全选', kind: 'plain', onClick: handleTransformSelectAll, disabled: ids.length >= transformMode.maxCards }); b.push({ key: 't-inv', label: '反选', kind: 'plain', onClick: handleTransformInvert, disabled: ids.length === 0 }); } if (canOperate && (selectedActive || isRespondTransformContext) && transformMode && transformMode.minCards > 1) { const ok = transformSubmit?.canSubmit ?? false; const needsTarget = transformSubmit?.needsTarget ?? false; b.push({ key: 't-go', label: '确定', kind: 'ok', onClick: () => ok && handleTransformPlay(needsTarget ? selectedTarget! : ''), disabled: !ok, title: `使用${transformWrapperName ?? transformMode.wrapperName}` }); } if (canOperate && (selectedActive || isRespondTransformContext) && transformMode?.minCards === 1 && selectedCardId) { if (isRespondTransformContext) b.push({ key: 't-one', label: '确定', kind: 'ok', onClick: () => handleTransformPlay(''), title: `使用${transformWrapperName ?? transformMode.wrapperName}` }); else b.push({ key: 't-one', label: '确定', kind: 'ok', onClick: () => selectedTarget && handleTransformPlay(selectedTarget), disabled: !selectedTarget, title: `使用${transformWrapperName ?? transformMode.wrapperName}` }); } if (canOperate && selectedActive && !transformMode && selectedCardId && playButtonState) b.push({ key: 'play', label: '确定', kind: 'ok', onClick: handlePlayCard, disabled: !playButtonState.canPlay, title: `出牌${playButtonState.targetLabel}` }); if (canOperate && !transformMode && selectedCardId) for (const a of altActions) b.push({ key: `${a.skillId}:${a.actionType}`, label: displaySkillName(a.label), kind: 'plain', onClick: () => handleSkillAction(a) }); if (!transformMode && showCancelSelection) b.push({ key: 'cancel', label: '取消', kind: 'cancel', onClick: cancelSelection }); if (showEndTurn) b.push({ key: 'end', label: '结束回合', kind: 'end', onClick: handleEndTurn }); if (canOperate && isDiscardPhase && isPerspectiveAwaiting) { const n = selectedForDiscard.length; b.push({ key: 'discard', label: '确定', kind: 'ok', onClick: handleConfirmDiscard, disabled: n < discardMin || n > discardMax, title: `弃置 ${n}/${discardMin}` }); b.push({ key: 'd-all', label: '全选', kind: 'plain', onClick: handleDiscardSelectAll, disabled: n >= discardMax }); } if (canOperate && isDistributeActive && activeDistribute) { const mode = activeDistribute.prompt.mode ?? 'allocate'; const minTotal = activeDistribute.prompt.minTotal ?? 1; const maxTotal = activeDistribute.prompt.maxTotal ?? 99; const ok = mode === 'select' ? distSelected.size >= minTotal && distSelected.size <= maxTotal : activeDistribute.externalTargetSelection ? distSelected.size >= minTotal && distSelected.size <= maxTotal && !!distTargetName : distAllocations.flatMap((a) => a.cardIds).length >= minTotal; if (mode === 'select') { b.push({ key: 'x-all', label: '全选', kind: 'plain', onClick: handleDistSelectAll, disabled: distSelected.size >= activeDistribute.cardIds.length }); b.push({ key: 'x-inv', label: '反选', kind: 'plain', onClick: handleDistInvert, disabled: distSelected.size === 0 }); } b.push({ key: 'x-clear', label: '重选', kind: 'plain', onClick: handleDistClear, disabled: distSelected.size === 0 && distAllocations.length === 0 }); b.push({ key: 'x-go', label: '确定', kind: 'ok', onClick: handleDistSubmit, disabled: !ok, title: activeDistribute.prompt.title }); if (distributeMode) b.push({ key: 'x-cancel', label: '取消', kind: 'cancel', onClick: () => setDistributeMode(null) }); } if (!b.length) return null; return (
{b.map(({ key, label, kind, onClick, disabled, title }) => ( ))}
); }