// Your seat along the bottom, as 无名杀's mobile layout: your general's portrait card at the left (name, faction, HP, // identity, judgement area), your equipment beside it as five slots, your hand across the middle, your skills as // buttons at the right. Which skills and equipment can be used now is the upstream's own logic (PlayerCardLarge and // EquipColumn, followed here): their actions, activeWhen and variants, and distribute picking equipment. import type { ReactNode } from 'react'; import type { GameView, Json } from '../../../src/engine/types'; import type { SkillActionDef } from '../../../src/client/skillActionRegistry'; import type { HpChangeNumber } from '../../../src/client/hooks/useAnimationState'; import { useGameView } from '../../../src/client/components/GameViewCtx'; import { isActiveAction, isFreePlayWindow, skillActionVariants } from '../../../src/client/utils/gameViewHelpers'; import { EQUIPMENT_SKILL_NAMES } from '../../../src/client/components/gameViewConstants'; import { LORD_SKILLS } from '../../../src/engine/data/character-meta'; import { DEFAULT_SKILLS as ENGINE_DEFAULT_SKILLS } from '../../../src/engine/atoms/选将'; import { getSkillDescription } from '../../../src/engine/skills/lifecycle'; import { useSkillDescReady } from '../../../src/client/hooks/useSkillDescReady'; import { displaySkillName } from '../../../src/client/utils/skillDisplay'; import { Portrait, Identity, SLOTS, seatState, equipName } from './Seat'; import { isRed } from './Card'; const DEFAULT_SKILLS = new Set(ENGINE_DEFAULT_SKILLS); type Player = GameView['players'][number]; interface Props { player: Player; index: number; isCurrent: boolean; phase: string; spectating: boolean; onSkillAction: (action: SkillActionDef, extraParams?: Record) => void; onEquipClick: (cardId: string) => void; distCandidates: Set | null; distSelected: Set; isDistributeActive: boolean; targetable: boolean; selected: boolean; onTarget: () => void; damaged?: number; healed?: number; hpChange?: HpChangeNumber; timer: ReactNode; hand: ReactNode; } export function Me(props: Props) { const { view, canOperate, skillActions } = useGameView(); useSkillDescReady(); const { player: p, index, isCurrent, phase, spectating, onSkillAction, onEquipClick, distCandidates, distSelected, isDistributeActive, targetable, selected, onTarget, damaged, healed, hpChange, timer, hand } = props; const actionCtx = { view, perspectiveIdx: index }; // Skills (PlayerCardLarge): the active ones as buttons, those of the free play window that can't be used now greyed, // the rest as labels. const isLord = p.identity === '主公'; const visible = p.skills.filter((s) => !DEFAULT_SKILLS.has(s) && !EQUIPMENT_SKILL_NAMES.has(s) && (isLord || !LORD_SKILLS.has(s))); const triggerable = skillActions.filter( (a) => a.prompt.type === 'confirm' || a.prompt.type === 'chooseOption' || a.prompt.type === 'choosePlayer' || a.prompt.type === 'selectTarget' || (a.prompt.type === 'useCardAndTarget' && !!a.transform) || (a.prompt.type === 'useCard' && !!a.transform) || a.prompt.type === 'distribute', ); const isSkillActive = (a: SkillActionDef) => canOperate && isActiveAction(a, actionCtx); const inFreePlayWindow = canOperate && isFreePlayWindow({ isMyTurn: isCurrent, phase: view.phase, pending: view.pending }); const skills = visible.flatMap((s) => { const btns = triggerable.filter((a) => a.skillId === s); const desc = getSkillDescription(s) ?? btns[0]?.prompt.title; const display = displaySkillName(s); const variants = btns.flatMap((a) => skillActionVariants(a).map((v) => ({ ...v, action: a, label: v.label || (btns.length > 1 ? a.label || a.actionType : '') }))); const active = variants.filter((v) => isSkillActive(v.action)); if (active.length) return active.map((v) => ( )); if (btns[0] && inFreePlayWindow) return [ , ]; return [ {display} , ]; }); // Equipment (EquipColumn): an equipment skill that can be used now, or a card distribute may pick, is clickable. const activeEquip = new Map(); for (const a of skillActions) if (EQUIPMENT_SKILL_NAMES.has(a.skillId) && canOperate && isActiveAction(a, actionCtx)) activeEquip.set(a.skillId, a); const equips = SLOTS.map(([slot]) => { const id = p.equipment[slot]; const c = id ? view.cardMap[id] : undefined; if (!id) return
; const skill = isDistributeActive ? undefined : activeEquip.get(c?.name ?? ''); const cand = isDistributeActive && !!distCandidates?.has(id); const desc = c && EQUIPMENT_SKILL_NAMES.has(c.name) ? getSkillDescription(c.name) : c?.description; return (
onSkillAction(skill) : cand ? () => onEquipClick(id) : undefined} title={`${c?.name ?? id}(${slot})${desc ? `\n${desc}` : ''}`} > {c ? equipName(slot, c.name) : id} {c && ( {c.suit} {c.rank} )}
); }); return (
{equips}
{timer} {hand}
{skills}
{spectating ? `旁观 · ${p.name}` : p.name}
); }