Combine current battle decisions with turn commitments
This commit is contained in:
1 parent
1a79fd3131
commit
f76c8dfb93
15 files changed
+1380
-66
No files matched your search
+23
-26
@@ -261,7 +261,7 @@ import {
|
||||
territory,
|
||||
splitLocation,
|
||||
} from '@/game/board';
|
||||
import type { Action, GameView } from '@/game/engine';
|
||||
import type { Action, GameView, Plan } from '@/game/engine';
|
||||
import { PLAYER_CIRCLE_SECTORS } from '@/game/player-positions';
|
||||
import { SeatAutopilot } from './seat-autopilot';
|
||||
import {
|
||||
@@ -570,6 +570,26 @@ export function GameTable({
|
||||
});
|
||||
const [aidAmount, setAidAmount] = useState(0);
|
||||
const [allyPayment, setAllyPayment] = useState('');
|
||||
const fillBattlePlan = (plan: Plan) => {
|
||||
setDial(plan.dial);
|
||||
setSupport(plan.support);
|
||||
setBankerSpice(plan.bankerSpice ?? 0);
|
||||
setAllyPayment(plan.allyPayment === undefined ? '' : String(plan.allyPayment));
|
||||
setLeader(plan.leader ?? '');
|
||||
setWeapon(plan.weapon ?? '');
|
||||
setDefense(plan.defense ?? '');
|
||||
setPlanetologistDraft({
|
||||
key: `${g.battle?.event ?? ''}:${plan.leader ?? ''}:${plan.weapon ?? ''}`,
|
||||
enabled: plan.planetologistSpecial === true,
|
||||
});
|
||||
setKwisatz(!!plan.kwisatz);
|
||||
if (plan.reinforcementsSources) {
|
||||
const chosen = JSON.stringify(plan.reinforcementsSources);
|
||||
const index = reinforcementsSourceChoices?.findIndex(choice => JSON.stringify(choice) === chosen);
|
||||
if (index !== undefined && index >= 0)
|
||||
setReinforcementsSourceDraft({ key: reinforcementsSourceKey, index });
|
||||
}
|
||||
};
|
||||
const [combineSectors, setCombineSectors] = useState(false);
|
||||
const [moveNoField, setMoveNoField] = useState(false);
|
||||
const [moveForces, setMoveForces] = useState<Record<string, number>>({});
|
||||
@@ -2067,7 +2087,7 @@ export function GameTable({
|
||||
{g.status === 'playing' && <MixedBattlePromises game={g} />}
|
||||
{g.status === 'playing' && <MixedTraitorPromises game={g} />}
|
||||
{g.status === 'playing' && <TravelPromises game={g} busy={busy} act={act} />}
|
||||
{g.status === 'playing' && <TurnActionPromises game={g} busy={busy} act={act} />}
|
||||
{g.status === 'playing' && <TurnActionPromises game={g} busy={busy} act={act} fillPlan={fillBattlePlan} />}
|
||||
{g.guildBetrayalReaction ? (
|
||||
<NexusGuildBetrayal game={g} act={act} busy={reactionBusy} />
|
||||
) : g.richeseBetrayalReaction ? (
|
||||
@@ -5311,30 +5331,7 @@ export function GameTable({
|
||||
act={act}
|
||||
game={g}
|
||||
busy={busy}
|
||||
fill={(plan) => {
|
||||
setDial(plan.dial);
|
||||
setSupport(plan.support);
|
||||
setBankerSpice(plan.bankerSpice ?? 0);
|
||||
setAllyPayment(
|
||||
plan.allyPayment === undefined
|
||||
? ''
|
||||
: String(plan.allyPayment),
|
||||
);
|
||||
setLeader(plan.leader ?? '');
|
||||
setWeapon(plan.weapon ?? '');
|
||||
setDefense(plan.defense ?? '');
|
||||
setPlanetologistDraft({
|
||||
key: `${g.battle?.event ?? ''}:${plan.leader ?? ''}:${plan.weapon ?? ''}`,
|
||||
enabled: plan.planetologistSpecial === true,
|
||||
});
|
||||
setKwisatz(!!plan.kwisatz);
|
||||
if (plan.reinforcementsSources) {
|
||||
const chosen = JSON.stringify(plan.reinforcementsSources);
|
||||
const index = reinforcementsSourceChoices?.findIndex(choice => JSON.stringify(choice) === chosen);
|
||||
if (index !== undefined && index >= 0)
|
||||
setReinforcementsSourceDraft({ key: reinforcementsSourceKey, index });
|
||||
}
|
||||
}}
|
||||
fill={fillBattlePlan}
|
||||
/>
|
||||
)}
|
||||
{g.battle.preLeader && !g.battle.preLeader.closed ? (
|
||||
|
||||
@@ -581,7 +581,7 @@ export function Truthtrance({
|
||||
<option value="mixedBid">Combine current facts and ordinary bids</option>
|
||||
<option value="traitorCall">Bind this battle’s Traitor declaration</option>
|
||||
<option value="mixedTraitorCall">Combine current facts and this Traitor declaration</option>
|
||||
<option value="turnActions">Combine this Nexus’s alliances with this turn’s bids, Revival, payments, shipment and ground movements</option>
|
||||
<option value="turnActions">Combine the current battle with this turn’s alliances, bids, Revival, payments, shipment and ground movements</option>
|
||||
<option value="travel">Combine this turn’s shipment and ground movements</option>
|
||||
<option value="shipment">
|
||||
Bind a shipment from reserves
|
||||
@@ -602,7 +602,7 @@ export function Truthtrance({
|
||||
<>
|
||||
<TurnActionFields value={turnActions} game={g} target={target}
|
||||
disabled={busy || !g.truthTurnActionTargets.includes(target)} onChange={setTurnActions} />
|
||||
{!g.truthTurnActionTargets.includes(target) && <p className="notice">Choose a player with a supported alliance, ordinary bid, Revival, payment, shipment or ground-movement opportunity this turn.
|
||||
{!g.truthTurnActionTargets.includes(target) && <p className="notice">Choose a player with a supported current battle, Traitor declaration, alliance, ordinary bid, Revival, payment, shipment or ground-movement opportunity this turn.
|
||||
Ordinary payments may remain available after their Shipment and Movement opportunity ends.</p>}
|
||||
<p className="notice" aria-label="Public turn-action question preview">{turnActionsError ?? truthQuestionText(question, leaderName, locationName, name)}</p>
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import { TERRITORIES } from '@/game/board';
|
||||
import type { GameView } from '@/game/engine';
|
||||
import type { MixedActionExpression } from '@/game/mixed-action-question';
|
||||
import { parseBattleActionClaim } from '@/game/mixed-battle-question';
|
||||
import { parseTurnActionLeaf, TURN_ACTION_MAX_DEPTH, TURN_ACTION_MAX_LEAVES, type TurnActionLeaf } from '@/game/turn-action-promises';
|
||||
import { BattleClaimFields } from './battle-promises';
|
||||
import { MixedActionFields, mixedActionInputError, type MixedActionConfig } from './mixed-action-fields';
|
||||
import { Input } from './ui/input';
|
||||
|
||||
@@ -25,11 +27,20 @@ const partners = (game: GameView, target: string) => game.players.some(player =>
|
||||
const currentNexus = (game: GameView, target: string) => game.status === 'playing' && game.phase === 1 && game.nexus && !game.spiceWindow &&
|
||||
game.alliancePromiseNexus?.turn === game.turn && game.truthMixedAllianceTargets.includes(target) &&
|
||||
!game.response && !game.decision && !game.phaseOpening && !game.automaticContinuationPending;
|
||||
const currentBattle = (game: GameView, target: string, kind: 'battlePlan' | 'traitorCall') => {
|
||||
const battle = game.battle;
|
||||
if (game.status !== 'playing' || game.phase !== 6 || !battle || !battle.event || game.response || game.decision ||
|
||||
game.phaseOpening || game.automaticContinuationPending) return null;
|
||||
if (kind === 'battlePlan') return game.truthMixedBattleTargets.includes(target) &&
|
||||
[battle.attacker, battle.defender].includes(target) ? battle : null;
|
||||
return battle.revealed && game.truthMixedTraitorTargets.includes(target) && battle.traitorVoters.includes(target) &&
|
||||
!battle.traitorSubmitted.includes(target) ? battle : null;
|
||||
};
|
||||
const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
prefix: 'turn-action',
|
||||
legend: 'Future alliances, bids, Revival, payments, shipment and ground movements, with optional current facts',
|
||||
actionLabel: 'alliance, bid, Revival, payment, shipment or ground-movement condition',
|
||||
actionTiming: 'Future alliance, ordinary bid, Revival, payment, reserve shipment or ground move · this turn',
|
||||
legend: 'Current battle and this-turn action conditions, with optional current facts',
|
||||
actionLabel: 'battle, alliance, bid, Revival, payment, shipment or ground-movement condition',
|
||||
actionTiming: 'Current battle or future native action · 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.
|
||||
@@ -41,13 +52,17 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
Alliance conditions concern actual final membership when this current Nexus closes. Offers alone are not alliances.
|
||||
A remaining alliance condition settles at the actual close; forming an alliance does not fulfill it early.
|
||||
No can allow temporary membership followed by a break before the close. You cannot promise another player's consent.
|
||||
Battle-plan conditions concern one actual native plan in this current battle, including a plan already sealed or revealed.
|
||||
An open plan commitment settles when both plans are revealed, not merely when your own plan is sealed.
|
||||
Traitor conditions concern one original Reveal or decline declaration in the current revealed battle.
|
||||
The declaration counts before a later Karama or betrayal response can prevent its effect.
|
||||
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 alliance, ordinary bid, Revival, payment, reserve shipment or ground-movement condition, with optional current facts.',
|
||||
fallbackError: 'Choose a current battle or future native action condition, with optional current facts.',
|
||||
maxLeaves: TURN_ACTION_MAX_LEAVES,
|
||||
maxDepth: TURN_ACTION_MAX_DEPTH,
|
||||
requireFact: false,
|
||||
@@ -62,6 +77,12 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
throw new Error('Choose a public native leader for the respondent’s faction.');
|
||||
if (action.kind === 'alliance' && (!currentNexus(game, target) || !partners(game, target).some(partner => partner.id === action.partner)))
|
||||
throw new Error('Choose a public other partner during the current open Nexus.');
|
||||
if (action.kind === 'battlePlan' || action.kind === 'traitorCall') {
|
||||
const battle = currentBattle(game, target, action.kind);
|
||||
if (!battle || action.battle !== battle.event || action.territory !== battle.territory)
|
||||
throw new Error('Choose this player’s current native battle or unsubmitted Traitor declaration.');
|
||||
if (action.kind === 'battlePlan') parseBattleActionClaim(action.claim, game.allLeaders.map(leader => leader.id));
|
||||
}
|
||||
return action;
|
||||
},
|
||||
renderAction: ({ value, id, game, target, disabled, onChange }) => {
|
||||
@@ -71,7 +92,10 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
<select id={`${id}-action`} value={value.kind} disabled={disabled} onChange={event => {
|
||||
const kind = event.currentTarget.value;
|
||||
if (kind === value.kind) return;
|
||||
const minimum = value.kind === 'leader' || value.kind === 'alliance' ? 1 : value.minimum;
|
||||
const minimum = 'minimum' in value ? value.minimum : 1;
|
||||
if (kind === 'battlePlan') onChange({ kind, battle: game.battle?.event ?? '', territory: game.battle?.territory ?? '',
|
||||
claim: { kind: 'dial', compare: 'eq', value: 0 } });
|
||||
if (kind === 'traitorCall') onChange({ kind, battle: game.battle?.event ?? '', territory: game.battle?.territory ?? '', call: true });
|
||||
if (kind === 'alliance') onChange({ kind, partner: '' });
|
||||
if (kind === 'payment') onChange({ kind, recipient: '', minimum });
|
||||
if (kind === 'bid') onChange({ kind, lot: lot?.lot ?? NaN, minimum });
|
||||
@@ -80,6 +104,8 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
if (kind === 'ship' || kind === 'move') onChange({ kind,
|
||||
territory: value.kind === 'ship' || value.kind === 'move' ? value.territory : 'carthag', minimum });
|
||||
}}>
|
||||
<option value="battlePlan">Use an actual current battle-plan choice</option>
|
||||
<option value="traitorCall">Make the current Traitor declaration</option>
|
||||
<option value="alliance">End this Nexus allied with a named partner</option>
|
||||
<option value="bid">Place an ordinary-lot bid</option>
|
||||
<option value="forces">Revive ordinary physical forces</option>
|
||||
@@ -88,7 +114,34 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
<option value="ship">Ship from reserves</option>
|
||||
<option value="move">Make ground movement</option>
|
||||
</select>
|
||||
{value.kind === 'alliance' ? <>
|
||||
{value.kind === 'battlePlan' || value.kind === 'traitorCall' ? <>
|
||||
<label htmlFor={`${id}-battle`}>Current battle</label>
|
||||
<select id={`${id}-battle`} value={game.battle?.event === value.battle && game.battle.territory === value.territory ? value.battle ?? '' : ''}
|
||||
disabled={disabled} aria-invalid={!currentBattle(game, target, value.kind) || game.battle?.event !== value.battle || game.battle?.territory !== value.territory}
|
||||
onChange={event => {
|
||||
const selected = event.currentTarget.value;
|
||||
if (selected === '') onChange({ ...value, battle: '', territory: '' });
|
||||
else if (game.battle && selected === game.battle.event)
|
||||
onChange({ ...value, battle: game.battle.event, territory: game.battle.territory });
|
||||
}}>
|
||||
<option value="">Choose the current battle</option>
|
||||
{game.battle?.event && <option value={game.battle.event}>{game.combatLocations.find(location => location.id === game.battle?.territory)?.name ?? game.battle.territory}</option>}
|
||||
</select>
|
||||
{value.kind === 'battlePlan' ? <fieldset disabled={disabled} data-turn-action-plan-fields={id}>
|
||||
<BattleClaimFields game={game} value={value.claim} onChange={claim => {
|
||||
if (!disabled && !('terms' in claim)) onChange({ ...value, claim });
|
||||
}} />
|
||||
</fieldset> : <>
|
||||
<label htmlFor={`${id}-call`}>Original Traitor declaration</label>
|
||||
<select id={`${id}-call`} value={value.call ? 'reveal' : 'decline'} disabled={disabled}
|
||||
aria-invalid={typeof value.call !== 'boolean'} onChange={event => {
|
||||
if (event.currentTarget.value === 'reveal') onChange({ ...value, call: true });
|
||||
if (event.currentTarget.value === 'decline') onChange({ ...value, call: false });
|
||||
}}>
|
||||
<option value="reveal">Reveal traitor</option><option value="decline">Decline traitor</option>
|
||||
</select>
|
||||
</>}
|
||||
</> : value.kind === 'alliance' ? <>
|
||||
<label htmlFor={`${id}-partner`}>Final Nexus alliance partner</label>
|
||||
<select id={`${id}-partner`} value={partnerChoices.some(partner => partner.id === value.partner) ? value.partner : ''}
|
||||
disabled={disabled} aria-invalid={!partnerChoices.some(partner => partner.id === value.partner)} onChange={event => {
|
||||
@@ -151,7 +204,7 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
{TERRITORIES.map(destination => <option key={destination.id} value={destination.id}>{destination.name}</option>)}
|
||||
</select>
|
||||
</>}
|
||||
{value.kind !== 'leader' && value.kind !== 'alliance' && <>
|
||||
{'minimum' in value && <>
|
||||
<label htmlFor={`${id}-minimum`}>{value.kind === 'payment' ? 'Minimum future ordinary payments in spice'
|
||||
: value.kind === 'bid' ? 'Minimum future accepted bid in spice'
|
||||
: value.kind === 'forces' ? 'Minimum future ordinary force returns'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { Action, GameView } from '@/game/engine';
|
||||
import type { Action, GameView, Plan } from '@/game/engine';
|
||||
import { bribeAction } from '@/game/bribe-options';
|
||||
import { alliancePromiseModeSupported } from '@/game/alliance-promises';
|
||||
import { nativePromiseScopeSupported } from '@/game/native-promise-scope';
|
||||
@@ -9,16 +9,23 @@ import { truthQuestionText, type TruthAnswer } from '@/game/truthtrance';
|
||||
import { CHEAP_HERO_TRAITOR } from '@/game/traitors';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
type TurnActionProps = { game: GameView; busy: boolean; act: (action: Action) => void };
|
||||
type TurnActionProps = { game: GameView; busy: boolean; act: (action: Action) => void; fillPlan?: (plan: Plan) => void };
|
||||
const publicAllianceContext = (game: GameView) => ({
|
||||
players: game.players.map(player => ({ id: player.id, ally: player.ally })), offers: game.allianceOffers,
|
||||
});
|
||||
const publicBattleContext = (game: GameView) => game.battle && ({
|
||||
event: game.battle.event, territory: game.battle.territory, attacker: game.battle.attacker, defender: game.battle.defender,
|
||||
revealed: game.battle.revealed, submitted: game.battle.submitted,
|
||||
traitorVoters: game.battle.traitorVoters, traitorSubmitted: game.battle.traitorSubmitted,
|
||||
});
|
||||
|
||||
/** One projected native packet; no client branch selection or completion solver. */
|
||||
export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
const game = props.game, guide = game.turnActionGuide;
|
||||
if (!guide) return null;
|
||||
const actor = game.me, turn = game.turn, phase = game.phase, event = guide.event, action = guide.action;
|
||||
const act = props.act, fillPlan = props.fillPlan, battle = game.battle;
|
||||
const battleSignature = JSON.stringify(publicBattleContext(game));
|
||||
const signature = JSON.stringify(action), auction = game.auction, decision = game.decision;
|
||||
const native = game.richeseAuction, order = game.revivalOrder, revival = game.revival;
|
||||
const auctionSource = (current: GameView) => current.auction && JSON.stringify({
|
||||
@@ -77,7 +84,14 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
try { ownedAlliance = !!quoteNexusAlliance({ ...publicAllianceContext(current), actor, target: action.target }).formed; }
|
||||
catch { ownedAlliance = false; }
|
||||
}
|
||||
const opportunity = payment || paidBox || nexusWindow && (ownedAlliance || action.type === 'ready') || (phase === 2
|
||||
const currentBattle = phase === 6 && !!battle && current.battle === battle &&
|
||||
JSON.stringify(publicBattleContext(current)) === battleSignature;
|
||||
const planWindow = !!battle && currentBattle && !battle.revealed && [battle.attacker, battle.defender].includes(actor) && !battle.submitted.includes(actor);
|
||||
const plan = action.type === 'battlePlan' && planWindow && !!fillPlan && props.fillPlan === fillPlan;
|
||||
const battlePreparation = action.type === 'card' && planWindow;
|
||||
const declaration = action.type === 'traitorCall' && typeof action.call === 'boolean' && !!battle && currentBattle && battle.revealed &&
|
||||
battle.traitorVoters.includes(actor) && !battle.traitorSubmitted.includes(actor);
|
||||
const opportunity = payment || paidBox || plan || battlePreparation || declaration || nexusWindow && (ownedAlliance || action.type === 'ready') || (phase === 2
|
||||
? !current.ready.includes(actor) && ['charity', 'ready', 'card'].includes(action.type)
|
||||
: phase === 3
|
||||
? invoice || nativePass || requiredBid || action.type === 'passBid' && !current.richeseAuction && currentAuction && current.auction?.active === actor
|
||||
@@ -88,7 +102,7 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
['ship', 'move', 'endMovement', 'card', 'prepareAdvisors', 'revealNoField'].includes(action.type))
|
||||
: (phase === 7 || phase === 8) &&
|
||||
action.type === 'ready' && !current.ready.includes(actor));
|
||||
return !props.busy && current === game && nativePromiseScopeSupported(current) && current.status === 'playing' &&
|
||||
return !props.busy && props.act === act && current === game && nativePromiseScopeSupported(current) && current.status === 'playing' &&
|
||||
Number.isSafeInteger(phase) && phase >= 0 && phase <= 8 && current.phase === phase && current.turn === turn && current.me === actor &&
|
||||
current.turnActionGuide === guide && guide.event === event && guide.action === action &&
|
||||
action.turnActionPromiseEvent === event && action.turnActionPromisePhase === phase && JSON.stringify(action) === signature && opportunity &&
|
||||
@@ -104,6 +118,8 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
const recipient = game.players.find(player => player.id === action.target)?.name ?? 'the quoted recipient';
|
||||
const sector = typeof action.sector === 'number' ? `, sector ${action.sector}` : '';
|
||||
const label = action.type === 'bribe' ? `Pay ${amount} spice to ${recipient} while honoring your whole answer`
|
||||
: action.type === 'battlePlan' ? 'Fill the quoted battle plan for review before sealing'
|
||||
: action.type === 'traitorCall' ? action.call ? 'Reveal traitor while honoring your whole answer' : 'Decline traitor while honoring your whole answer'
|
||||
: action.type === 'alliance' ? action.target === null
|
||||
? game.players.find(player => player.id === actor)?.ally ? 'Break your current alliance while preserving your whole answer'
|
||||
: 'Withdraw your alliance offer while preserving your whole answer'
|
||||
@@ -131,7 +147,11 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
: action.type === 'revealNoField' ? 'Reveal your No-Field while preserving your whole answer'
|
||||
: 'Use the offered native card preparation while preserving your whole answer';
|
||||
return <Button className="game-action min-h-11 whitespace-normal" data-testid="turn-action-promise-guide"
|
||||
disabled={!canAct()} onClick={() => { if (canAct()) props.act(action); }}>{label}</Button>;
|
||||
disabled={!canAct()} onClick={() => {
|
||||
if (!canAct()) return;
|
||||
if (action.type === 'battlePlan') fillPlan?.(action as Action & Plan);
|
||||
else act(action);
|
||||
}}>{label}</Button>;
|
||||
}
|
||||
|
||||
/** Own original whole questions and aggregate status only. */
|
||||
@@ -140,7 +160,7 @@ export function TurnActionPromises(props: TurnActionProps) {
|
||||
if (!own.length) return null;
|
||||
const live = own.some(promise => promise.turn === game.turn && !promise.fulfilled && !promise.released);
|
||||
return <section className="notice space-y-3" aria-label="Your Truthtrance turn-action promises" id="turn-action-promise-guidance">
|
||||
<h3>Your alliance, bid, Revival, payment, shipment and movement promises</h3>
|
||||
<h3>Your battle and turn-action promises</h3>
|
||||
<ul>{own.map(promise => <li key={promise.event}>
|
||||
Turn {promise.turn}: You answered {promise.answer ? 'Yes' : 'No'} to {truthQuestionText({ kind: 'turnActions', target: promise.player,
|
||||
turn: promise.turn, mixed: promise.mixed }, id => id === 'kwisatz' ? 'Kwisatz Haderach' : id === CHEAP_HERO_TRAITOR ? 'Cheap Hero / Heroine'
|
||||
@@ -165,6 +185,9 @@ export function TurnActionPromises(props: TurnActionProps) {
|
||||
Alliance conditions concern final actual membership at this Nexus close. Offers alone are not alliances.
|
||||
A remaining alliance condition settles only at the actual close; forming an alliance does not fulfill it early.
|
||||
No can allow temporary membership followed by a break before the close. Neither answer promises another player’s consent.
|
||||
Battle conditions describe your actual plan in the named current battle. An open plan condition settles when both plans are revealed;
|
||||
sealing your own plan does not finish it. A plan already fixed can decide its condition at the answer.
|
||||
Traitor conditions describe your original Reveal or decline declaration, before any later Karama or betrayal prevention.
|
||||
Shipment conditions concern one future original reserve shipment. Ground-movement conditions count the maximum physical forces in one actual future move,
|
||||
never the sum of smaller moves. Earlier shipment and movement do not count.
|
||||
Bid conditions close with their named ordinary lot; Revival conditions close with your native Revival opportunity.
|
||||
@@ -194,13 +217,15 @@ export function TurnActionAnswers(props: TurnActionProps) {
|
||||
!!me && !me.autopilot && !current.response && !current.decision && !current.phaseOpening &&
|
||||
!current.automaticContinuationPending && !current.roomControl?.paused && !current.roomControl?.closed;
|
||||
};
|
||||
return <section aria-label="Answer the whole alliance, bid, Revival, payment, shipment and movement question" className="space-y-3">
|
||||
return <section aria-label="Answer the whole battle and turn-action question" className="space-y-3">
|
||||
<p className="notice">Answer the whole expression. Current facts freeze now; OR keeps legal alternatives and No keeps the whole expression false.
|
||||
Future real ordinary payments add together per recipient. Only one future original reserve shipment and actual future ground-movement groups count.
|
||||
Ordinary bids use the maximum future accepted declaration in the named current lot; ordinary force returns add together,
|
||||
while named leaders must actually return through ordinary native Revival.
|
||||
Alliance conditions use actual final membership when the current Nexus closes; offers alone do not count.
|
||||
A remaining alliance condition stays open through temporary membership until that close. Neither answer promises another player’s consent.
|
||||
Current battle-plan conditions use the actual native plan; an open condition settles when both plans are revealed.
|
||||
Traitor conditions use the original Reveal or decline declaration, before later Karama or betrayal prevention.
|
||||
Bids close with their lot; Revival closes with your native Revival opportunity.
|
||||
Shipment and movement close at your own Movement turn end; payment conditions last until the actual turn ends.
|
||||
Original rights, preparation, timing and costs still apply. Only your whole answer is shared.</p>
|
||||
|
||||
Reference in new issue
Block a user