'use client'; import { TERRITORIES } from '@/game/board'; import type { GameView } from '@/game/engine'; import type { MixedActionExpression } from '@/game/mixed-action-question'; import { parseTurnActionLeaf, TURN_ACTION_MAX_DEPTH, TURN_ACTION_MAX_LEAVES, type TurnActionLeaf } from '@/game/turn-action-promises'; import { MixedActionFields, mixedActionInputError, type MixedActionConfig } from './mixed-action-fields'; import { Input } from './ui/input'; type TurnActionExpression = MixedActionExpression; const recipients = (game: GameView, target: string) => { const player = game.players.find(player => player.id === target); return player ? game.players.filter(recipient => recipient.id !== target && recipient.id !== player.ally) : []; }; /** Public faction names only; casualties, custody and native eligibility stay * on the server. Advanced Atreides may name their Kwisatz Haderach. */ const leaders = (game: GameView, target: string) => { const faction = game.players.find(player => player.id === target)?.faction; return [...game.allLeaders.filter(leader => !!faction && leader.faction === faction), ...(game.advanced && faction === 'atreides' ? [{ id: 'kwisatz', name: 'Kwisatz Haderach' }] : [])]; }; const currentLot = (game: GameView) => game.bidPromiseLot?.turn === game.turn ? game.bidPromiseLot : null; const config: MixedActionConfig = { prefix: 'turn-action', legend: 'Future bids, Revival, payments, shipment and ground movements, with optional current facts', actionLabel: 'bid, Revival, payment, shipment or ground-movement condition', actionTiming: 'Future ordinary bid, Revival, payment, reserve shipment or ground move · this turn', actionHelp: <>Ordinary payment conditions sum future real bribes to the same recipient after the answer this turn. Earlier payments, gifts and ally aid do not count. The sender pays immediately; the recipient collects at the next Mentat opening. Payments made during Mentat Pause wait until next turn's Mentat opening; collection never counts twice. Ordinary bid conditions use the highest accepted future bid declaration in the named current lot, never the sum of smaller bids. A qualifying bid counts even if another player wins; it does not promise a purchase. Winning still requires the original payment. Ordinary Revival conditions count cumulative future normal and elite physical force returns, with each counter counted once, or an actual future return of a named native leader. Smaller ordinary force returns add together. Earlier returns, requests alone, Ghola, foreign leaders and special card returns do not count. Shipment conditions concern the single future original reserve shipment after the answer. Ground-movement conditions use the maximum physical forces in one actual future move to that destination; smaller moves never add together. Bid conditions close with their named ordinary lot; Revival conditions close with your native Revival opportunity. Shipment and movement close at your own Movement turn end, while payment conditions last until the actual turn ends. AND requires every condition; OR keeps legal alternatives. No makes the whole expression false, not every condition separately. Original costs, timing, cards, preparation and action rights still apply; this claim grants no extra action., fallbackError: 'Choose a future ordinary bid, Revival, payment, reserve shipment or ground-movement condition, with optional current facts.', maxLeaves: TURN_ACTION_MAX_LEAVES, maxDepth: TURN_ACTION_MAX_DEPTH, requireFact: false, newAction: () => ({ kind: 'ship', territory: 'carthag', minimum: 1 }), parseAction: (input, game, target) => { const action = parseTurnActionLeaf(input); if (action.kind === 'payment' && !recipients(game, target).some(recipient => recipient.id === action.recipient)) throw new Error('Choose a public non-allied recipient for the future ordinary payment.'); if (action.kind === 'bid' && currentLot(game)?.lot !== action.lot) throw new Error('Choose the current public ordinary lot for the future bid.'); if (action.kind === 'leader' && !leaders(game, target).some(leader => leader.id === action.leader)) throw new Error('Choose a public native leader for the respondent’s faction.'); return action; }, renderAction: ({ value, id, game, target, disabled, onChange }) => { const choices = recipients(game, target), leaderChoices = leaders(game, target), lot = currentLot(game); return <> {value.kind === 'bid' ? <> {!lot &&

No public ordinary lot is currently available. Special auctions do not count.

} : value.kind === 'forces' ? <> : value.kind === 'leader' ? <> : value.kind === 'payment' ? <> : <> } {value.kind !== 'leader' && <> 20} onChange={event => onChange({ ...value, minimum: event.currentTarget.valueAsNumber })} /> } ; }, }; /** Public schema choices only; the native engine owns eligibility and funding. */ export function turnActionInputError(value: unknown, game: GameView, target: string): string | null { return mixedActionInputError(value, game, target, config); } export function TurnActionFields(props: { value: TurnActionExpression; game: GameView; target: string; disabled: boolean; onChange: (expression: TurnActionExpression) => void; }) { return MixedActionFields({ ...props, config }); }