'use client'; import { useId } from 'react'; import { HelpTip } from './help-tip'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { TRUTH_CARD_NAMES } from '@/game/truthtrance-card-names'; import { planClaimText, type PlanClaim } from '@/game/battle-promises'; import { reinforcementsSourceLabel } from './reinforcements-sources'; import { CHEAP_HERO_TRAITOR } from '@/game/traitors'; import type { Action, GameView, Plan } from '@/game/engine'; export function BattleClaimFields({ game: g, value: c, onChange, }: { game: GameView; value: PlanClaim; onChange: (c: PlanClaim) => void; }) { const amountId = useId(); const initial = (kind: string): PlanClaim => kind === 'dial' || kind === 'support' ? { kind, compare: 'eq', value: 0 } : kind === 'weapon' || kind === 'defense' ? { kind, name: null } : kind === 'leader' ? { kind, leader: g.allLeaders[0].id } : { kind: 'kwisatz', use: true }; return ( <> {(c.kind === 'dial' || c.kind === 'support') && ( <> )} {(c.kind === 'weapon' || c.kind === 'defense') && ( )} {c.kind === 'leader' && ( )} {c.kind === 'kwisatz' && ( )} ); } export function BattlePromises(props: { game: GameView; fill: (plan: Plan) => void; act: (action: Action) => void; busy: boolean; }) { const { game: g, fill, act } = props; const b = g.battle; if (!b) return null; const mixedRows = (g.mixedBattlePromises ?? []).filter(promise => promise.player === g.me && promise.turn === g.turn && promise.battle === b.event && promise.territory === b.territory && !promise.fulfilled && !promise.released); if (!b.truthPromises.length && !b.compliantPreparation && (!b.compliantPlan || !mixedRows.length)) return null; const me = g.players.find((p) => p.id === g.me)!; const name = (id: string | null) => id === null ? 'None' : (me.hand?.find((c) => c.id === id)?.name ?? g.allLeaders.find((l) => l.id === id)?.name ?? id); const plan = b.compliantPlan; const preparation = b.compliantPreparation; const nextPreparation = preparation?.actions[0]; const actor = g.me, turn = g.turn, event = b.event, territory = b.territory; const attacker = b.attacker, defender = b.defender; const mixed = mixedRows .map(promise => ({ promise, event: promise.event, asker: promise.asker, answer: promise.answer, mixed: promise.mixed, signature: JSON.stringify(promise.mixed) })); const mixedAssistance = mixed.length > 0; const planSignature = JSON.stringify(plan), preparationSignature = JSON.stringify(preparation); // Native completion owns feasibility. For mixed assistance, a saved callback // also has to belong to this exact live question and current private guide. const canUseMixedAssistance = () => !mixedAssistance || ( !props.busy && props.game === g && g.status === 'playing' && g.phase === 6 && g.me === actor && g.turn === turn && g.battle === b && typeof event === 'string' && b.event === event && b.territory === territory && b.attacker === attacker && b.defender === defender && [attacker, defender].includes(actor) && !b.revealed && !b.submitted.includes(actor) && mixed.length > 0 && mixed.every(context => g.mixedBattlePromises.includes(context.promise) && context.promise.event === context.event && context.promise.asker === context.asker && context.promise.answer === context.answer && context.promise.mixed === context.mixed && JSON.stringify(context.promise.mixed) === context.signature && context.promise.player === actor && context.promise.turn === turn && context.promise.battle === event && context.promise.territory === territory && !context.promise.fulfilled && !context.promise.released) && g.mixedBattlePromises.filter(promise => promise.player === actor && promise.turn === turn && promise.battle === event && promise.territory === territory && !promise.fulfilled && !promise.released).length === mixed.length && !g.truthtrance && !g.response && !g.decision && !g.phaseOpening && !g.automaticContinuationPending && !g.roomControl?.paused && !g.roomControl?.closed && !!me && g.players.find(player => player.id === actor) === me && !me.autopilot ); const canPrepare = () => !props.busy && !g.truthtrance && !g.response && !g.decision && !g.phaseOpening && canUseMixedAssistance() && (!mixedAssistance || b.compliantPreparation === preparation && b.compliantPreparation?.actions[0] === nextPreparation && JSON.stringify(preparation) === preparationSignature && props.act === act); const canFill = () => !props.busy && canUseMixedAssistance() && (!mixedAssistance || b.compliantPlan === plan && !b.compliantPreparation && JSON.stringify(plan) === planSignature && props.fill === fill); return (

{b.truthPromises.length ? 'Truthtrance commitments' : 'Preparing your committed plan'}{' '}

{b.compliantPreparation && (

Preparation · private

{b.compliantPreparation.waitingForIncome && (

Revival income must resolve before this plan can be funded.

)} {b.compliantPreparation.actions.map((action, index) => (

{index + 1}.{' '} {action.type === 'nexusSuboids' ? 'Use Ixian Cunning to give Suboids full strength without spice support' : action.type === 'nexusSardaukar' ? 'Declare Emperor Cunning to count five ordinary forces as Sardaukar for this battle' : action.mode === 'special' ? `Spend Karama and discard ${(action.cards as string[]).map(name).join(', ')} to gain ${(action.cards as string[]).length * 3} spice` : typeof action.leader === 'string' ? `Play Ghola to revive ${action.leader === 'kwisatz' ? 'Kwisatz Haderach' : name(action.leader)}` : `Play Ghola to revive ${Number(action.amount)} force${Number(action.amount) === 1 ? '' : 's'}${Number(action.elite) ? `, including ${Number(action.elite)} elite` : ''}`} .

))}

{b.compliantPreparation.actions.length ? 'The next card is still in your hand. Complete the preparation and any response before reviewing and sealing the plan.' : 'The revival is complete. Resolve its income response before reviewing and sealing the plan.'}{' '} Other legal ways to keep your answer remain available.

{b.compliantPreparation.actions[0] && (!mixedAssistance || mixed.length > 0 && !b.revealed && !b.submitted.includes(actor)) && ( )}
)} {plan && (!mixedAssistance || mixed.length > 0 && !b.revealed && !b.submitted.includes(actor)) && (
Review a compliant plan · private

Dial {plan.dial} · Support {plan.support} {!!plan.allyPayment && ` · CHOAM pays ${plan.allyPayment}`}
Leader: {name(plan.leader)}
Weapon: {name(plan.weapon)}
Defense: {name(plan.defense)}
Kwisatz: {plan.kwisatz ? 'Included' : 'Not included'}

{plan.reinforcementsSources && (

Native reserve fee: {reinforcementsSourceLabel(g, plan.reinforcementsSources)}

)} {plan.planetologistSpecial && (

Planetologist alternate Special: +2 if the leader survives; no normal card effect.

)}

These choices satisfy your current commitments. Review and change the other elements before sealing your plan.

)}
); }