Bind Truthtrance Karama promises to original auction lots
This commit is contained in:
1 parent
e88f871385
commit
fd0b2f0fa5
18 files changed
+1128
-68
No files matched your search
@@ -604,6 +604,7 @@ export function Truthtrance({
|
||||
disabled={busy || !g.truthTurnActionTargets.includes(target)} onChange={setTurnActions} />
|
||||
{!g.truthTurnActionTargets.includes(target) && <p className="notice">Choose a player with supported native Voice or Prescience, printed card plays, current battle, Traitor declaration, alliance, ordinary bid, Revival, payment, shipment or ground-movement opportunities this turn.
|
||||
Weather Control, Family Atomics and Harvester refer only to their original current Storm or fresh spice-blow window.
|
||||
Printed Karama auction use refers only to the current ordinary lot, through immediate purchase or its winning invoice.
|
||||
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>
|
||||
</>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { MixedActionExpression } from '@/game/mixed-action-question';
|
||||
import { parseBattleActionClaim } from '@/game/mixed-battle-question';
|
||||
import { battlePowerOf, nativeBattlePowerOpportunity, type BattlePowerClaim } from '@/game/battle-power-question';
|
||||
import { battleCardLabel, VOICE_KINDS } from '@/game/battle-cards';
|
||||
import { windowCardOpportunity, type WindowCardEffect } from '@/game/window-card-play';
|
||||
import { WINDOW_CARD_EFFECTS, windowCardOpportunity, type WindowCardEffect } from '@/game/window-card-play';
|
||||
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';
|
||||
@@ -47,8 +47,8 @@ const newPowerClaim = (game: GameView, target: string): BattlePowerClaim =>
|
||||
nativeBattlePowerOpportunity(game, target, 'voice') ? { kind: 'voice', use: true }
|
||||
: nativeBattlePowerOpportunity(game, target, 'prescience') ? { kind: 'prescience', use: true }
|
||||
: { kind: 'voice', use: true };
|
||||
const windowEffects: WindowCardEffect[] = ['weather', 'atomics', 'harvester'];
|
||||
const windowRoleNames: Record<WindowCardEffect, string> = { weather: 'Weather Control', atomics: 'Family Atomics', harvester: 'Harvester' };
|
||||
const windowRoleNames: Record<WindowCardEffect, string> = { weather: 'Weather Control', atomics: 'Family Atomics', harvester: 'Harvester',
|
||||
karamaAuction: 'Spend a printed Karama for this current ordinary lot' };
|
||||
const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
prefix: 'turn-action',
|
||||
legend: 'Current battle and this-turn action conditions, with optional current facts',
|
||||
@@ -80,6 +80,9 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
Weather Control, Family Atomics and Harvester conditions count an actual accepted future printed-role play after this answer
|
||||
in the original open Storm or fresh spice-blow window. Earlier plays, disposal, other roles and recovery alone do not count.
|
||||
They do not ask for a selected storm distance or resulting spice amount. Each condition closes only when its original native window closes.
|
||||
A current-lot Karama condition requires spending a printed Karama for this original ordinary lot, through immediate purchase
|
||||
or its winning invoice. Other Karama roles, Worthless conversion and a successful bid alone do not count.
|
||||
Recovery alone does not count. This condition closes with the original ordinary lot.
|
||||
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.
|
||||
@@ -115,7 +118,7 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
if (action.kind === 'windowCardPlay') {
|
||||
const opportunity = windowCardOpportunity(game, action.effect);
|
||||
if (!opportunity || opportunity.turn !== game.turn || action.source !== opportunity.source)
|
||||
throw new Error('Choose the original currently open Storm or fresh spice-blow window for this printed role.');
|
||||
throw new Error('Choose the original currently open Storm, fresh spice-blow or ordinary-lot window for this printed role.');
|
||||
}
|
||||
return action;
|
||||
},
|
||||
@@ -131,7 +134,7 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
const minimum = 'minimum' in value ? value.minimum : 1;
|
||||
if (kind === 'cardPlay') onChange({ kind, effect: 'hajr' });
|
||||
if (kind === 'windowCardPlay') {
|
||||
const effect = windowEffects.find(effect => windowCardOpportunity(game, effect)) ?? 'weather';
|
||||
const effect = WINDOW_CARD_EFFECTS.find(effect => windowCardOpportunity(game, effect)) ?? 'weather';
|
||||
onChange({ kind, effect, source: windowCardOpportunity(game, effect)?.source ?? '' });
|
||||
}
|
||||
if (kind === 'battlePower') {
|
||||
@@ -150,7 +153,7 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
territory: value.kind === 'ship' || value.kind === 'move' ? value.territory : 'carthag', minimum });
|
||||
}}>
|
||||
<option value="cardPlay">Play Hajr or Tleilaxu Ghola in its printed role</option>
|
||||
<option value="windowCardPlay">Play Weather Control, Family Atomics or Harvester in this window</option>
|
||||
<option value="windowCardPlay">Use a printed card in this original Storm, spice-blow or ordinary-lot window</option>
|
||||
<option value="battlePower">Declare native Voice or Prescience in this battle</option>
|
||||
<option value="battlePlan">Use an actual current battle-plan choice</option>
|
||||
<option value="traitorCall">Make the current Traitor declaration</option>
|
||||
@@ -174,14 +177,14 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
</select>
|
||||
</> : value.kind === 'windowCardPlay' ? <>
|
||||
<label htmlFor={`${id}-window-effect`}>Printed role in the original window</label>
|
||||
<select id={`${id}-window-effect`} value={windowEffects.includes(value.effect) ? value.effect : ''}
|
||||
disabled={disabled} aria-invalid={!windowEffects.includes(value.effect)} onChange={event => {
|
||||
<select id={`${id}-window-effect`} value={WINDOW_CARD_EFFECTS.includes(value.effect) ? value.effect : ''}
|
||||
disabled={disabled} aria-invalid={!WINDOW_CARD_EFFECTS.includes(value.effect)} onChange={event => {
|
||||
const effect = event.currentTarget.value;
|
||||
if (effect === 'weather' || effect === 'atomics' || effect === 'harvester')
|
||||
if (effect === 'weather' || effect === 'atomics' || effect === 'harvester' || effect === 'karamaAuction')
|
||||
onChange({ kind: 'windowCardPlay', effect, source: windowCardOpportunity(game, effect)?.source ?? '' });
|
||||
}}>
|
||||
<option value="">Choose a printed role</option>
|
||||
{windowEffects.map(effect => <option key={effect} value={effect}>{windowRoleNames[effect]}</option>)}
|
||||
{WINDOW_CARD_EFFECTS.map(effect => <option key={effect} value={effect}>{windowRoleNames[effect]}</option>)}
|
||||
</select>
|
||||
<label htmlFor={`${id}-window-source`}>Original native window</label>
|
||||
<select id={`${id}-window-source`} value={currentWindow?.source === value.source ? value.source : ''}
|
||||
@@ -193,7 +196,8 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
<option value="">Choose the original open window</option>
|
||||
{currentWindow && <option value={currentWindow.source}>{value.effect === 'harvester'
|
||||
? `Turn ${currentWindow.turn} · Fresh spice blow in ${game.combatLocations.find(location => location.id === game.spiceWindow?.territory)?.name ?? game.spiceWindow?.territory}`
|
||||
: `Turn ${currentWindow.turn} · Before this Storm movement`}</option>}
|
||||
: value.effect === 'karamaAuction' ? `Turn ${currentWindow.turn} · Ordinary lot ${game.bidPromiseLot?.lot}`
|
||||
: `Turn ${currentWindow.turn} · Before this Storm movement`}</option>}
|
||||
</select>
|
||||
{!currentWindow && <p className="fine">This printed role has no current original open native window.</p>}
|
||||
</> : value.kind === 'battlePower' ? <>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ordinaryCardAvailability } from '@/game/card-availability';
|
||||
import { gholaForcePreparations } from '@/game/ghola-force-preparation';
|
||||
import { nativeBattlePowerOpportunity, type NativeBattlePower } from '@/game/battle-power-question';
|
||||
import { battleCardLabel, VOICE_KINDS } from '@/game/battle-cards';
|
||||
import { windowCardOpportunity, type WindowCardEffect } from '@/game/window-card-play';
|
||||
import { WINDOW_CARD_EFFECTS, windowCardNativeEffect, windowCardOpportunity, type WindowCardEffect } from '@/game/window-card-play';
|
||||
import type { TurnActionLeaf } from '@/game/turn-action-promises';
|
||||
import type { MixedActionExpression } from '@/game/mixed-action-question';
|
||||
import { truthQuestionText, type TruthAnswer } from '@/game/truthtrance';
|
||||
@@ -26,10 +26,11 @@ const publicBattleContext = (game: GameView) => game.battle && ({
|
||||
preparation: game.battle.preparation,
|
||||
traitorVoters: game.battle.traitorVoters, traitorSubmitted: game.battle.traitorSubmitted,
|
||||
});
|
||||
const isWindowEffect = (effect: unknown): effect is WindowCardEffect => effect === 'weather' || effect === 'atomics' || effect === 'harvester';
|
||||
const windowRoleNames: Record<WindowCardEffect, string> = { weather: 'Weather Control', atomics: 'Family Atomics', harvester: 'Harvester' };
|
||||
const namesWindow = (mixed: MixedActionExpression<TurnActionLeaf>, effect: WindowCardEffect, source: string): boolean =>
|
||||
mixed.kind === 'action' ? mixed.action.kind === 'windowCardPlay' && mixed.action.effect === effect && mixed.action.source === source
|
||||
const isWindowEffect = (effect: unknown): effect is Exclude<WindowCardEffect, 'karamaAuction'> => effect === 'weather' || effect === 'atomics' || effect === 'harvester';
|
||||
const windowRoleNames: Record<WindowCardEffect, string> = { weather: 'Weather Control', atomics: 'Family Atomics', harvester: 'Harvester',
|
||||
karamaAuction: 'printed Karama for this ordinary lot' };
|
||||
const namesWindow = (mixed: MixedActionExpression<TurnActionLeaf>, effect: WindowCardEffect, source?: string): boolean =>
|
||||
mixed.kind === 'action' ? mixed.action.kind === 'windowCardPlay' && mixed.action.effect === effect && (source === undefined || mixed.action.source === source)
|
||||
: mixed.kind !== 'fact' && mixed.terms.some(term => namesWindow(term, effect, source));
|
||||
|
||||
/** One projected native packet; no client branch selection or completion solver. */
|
||||
@@ -61,27 +62,38 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
const nexus = game.alliancePromiseNexus, nexusOpen = game.nexus, nexusSpiceWindow = game.spiceWindow;
|
||||
const nexusSignature = JSON.stringify({ ...publicAllianceContext(game), ready: game.ready });
|
||||
const box = game.nullentropy, search = box?.search;
|
||||
const cardOwner = action.type === 'card' ? game.players.find(player => player.id === actor) : undefined;
|
||||
const windowPromise = game.turnActionPromises.find(promise => promise.event === event && promise.player === actor && promise.turn === turn);
|
||||
const windowQuestion = windowPromise && JSON.stringify(windowPromise.mixed);
|
||||
const currentAuctionWindow = windowCardOpportunity(game, 'karamaAuction');
|
||||
const auctionRoleNamed = !!windowPromise && !!currentAuctionWindow && namesWindow(windowPromise.mixed, 'karamaAuction', currentAuctionWindow.source);
|
||||
const auctionPurchaseAction = action.type === 'card' && action.mode === 'purchase';
|
||||
const printedInvoiceAction = action.type === 'decision' && action.karama === true && auctionRoleNamed;
|
||||
const cardOwner = action.type === 'card' || printedInvoiceAction ? game.players.find(player => player.id === actor) : undefined;
|
||||
const heldCard = cardOwner?.hand?.find(card => card.id === action.card);
|
||||
const cardSignature = heldCard && JSON.stringify({ id: heldCard.id, kind: heldCard.kind, effect: heldCard.effect });
|
||||
const windowEffect = isWindowEffect(heldCard?.effect) ? heldCard.effect : null;
|
||||
const windowEffect = (auctionPurchaseAction || printedInvoiceAction) && heldCard?.effect === windowCardNativeEffect('karamaAuction')
|
||||
? 'karamaAuction' : isWindowEffect(heldCard?.effect) ? heldCard.effect : null;
|
||||
const cardWindow = windowEffect ? windowCardOpportunity(game, windowEffect) : null;
|
||||
const earlyWindowReadyAction = action.type === 'ready' && (phase === 0 || phase === 1);
|
||||
const windowPromise = action.type === 'card' || earlyWindowReadyAction
|
||||
? game.turnActionPromises.find(promise => promise.event === event && promise.player === actor && promise.turn === turn) : null;
|
||||
const windowQuestion = windowPromise && JSON.stringify(windowPromise.mixed);
|
||||
const publicWindowState = (current: GameView) => JSON.stringify({
|
||||
opportunity: windowEffect ? windowCardOpportunity(current, windowEffect)
|
||||
: (['weather', 'atomics', 'harvester'] as const).map(effect => windowCardOpportunity(current, effect)),
|
||||
: WINDOW_CARD_EFFECTS.map(effect => windowCardOpportunity(current, effect)),
|
||||
stormOpen: current.stormCardWindowOpen,
|
||||
stormPending: current.stormPending, wallDestroyed: current.shieldWallDestroyed,
|
||||
blow: current.spiceWindow,
|
||||
...(current.phase === 3 ? { auction: auctionSource(current) } : {}),
|
||||
...(windowEffect === 'atomics' ? { forces: current.players.find(player => player.id === actor)?.forces,
|
||||
advisors: current.players.find(player => player.id === actor)?.advisors } : {}),
|
||||
});
|
||||
const windowSignature = windowEffect || heldCard?.effect === 'nullentropyBox' || earlyWindowReadyAction ? publicWindowState(game) : null;
|
||||
const originalBlow = windowEffect === 'harvester' || earlyWindowReadyAction && phase === 1 ? game.spiceWindow : null;
|
||||
const earlyBoxSignature = box && JSON.stringify({ card: box.card.id, blocked: box.blocked, search: box.search?.event });
|
||||
const auctionCards = game.karamaAuctionCards, auctionCardsSignature = JSON.stringify(auctionCards);
|
||||
const auctionHandSource = (current: GameView) => {
|
||||
const player = current.players.find(player => player.id === actor);
|
||||
return JSON.stringify({ hand: player?.hand?.map(card => ({ id: card.id, kind: card.kind, effect: card.effect })), limit: player?.handLimit });
|
||||
};
|
||||
const auctionHandSignature = windowEffect === 'karamaAuction' ? auctionHandSource(game) : null;
|
||||
const ghola = heldCard?.effect === 'ghola' ? game.ghola : null, gholaSignature = JSON.stringify(ghola);
|
||||
const hajrSource = (current: GameView) => {
|
||||
const player = current.players.find(player => player.id === actor);
|
||||
@@ -91,9 +103,13 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
const hajrSignature = heldCard?.effect === 'hajr' ? hajrSource(game) : null;
|
||||
const canAct = () => {
|
||||
const current = props.game, me = current.players.find(player => player.id === actor);
|
||||
const ownCard = action.type === 'card' && !!heldCard && !!me && me === cardOwner &&
|
||||
const ownPhysicalCard = (action.type === 'card' || printedInvoiceAction) && !!heldCard && !!me && me === cardOwner &&
|
||||
me.hand?.find(card => card.id === action.card) === heldCard &&
|
||||
JSON.stringify({ id: heldCard.id, kind: heldCard.kind, effect: heldCard.effect }) === cardSignature;
|
||||
const ownCard = action.type === 'card' && ownPhysicalCard;
|
||||
const currentAuction = current.auction === auction && auctionSource(current) === auctionSignature && !!auction;
|
||||
const ownInvoiceSource = phase === 3 && current.decision === decision && current.decision?.kind === 'auctionPayment' &&
|
||||
current.decision.player === actor && !current.richeseAuction && currentAuction && current.auction?.bidder === actor;
|
||||
const printedHajr = ownCard && !!heldCard && !!me && heldCard.kind === 'special' && heldCard.effect === 'hajr' && action.mode === undefined &&
|
||||
phase === 5 && current.active === actor && me.hajrAvailable === true && hajrSource(current) === hajrSignature &&
|
||||
ordinaryCardAvailability(current, heldCard.id)?.available === true;
|
||||
@@ -110,22 +126,31 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
? typeof action.leader === 'string' && (action.leader === 'kwisatz' ? ghola.kwisatz : ghola.leaders.some(leader => leader.id === action.leader))
|
||||
: action.elite === undefined ? forceChoices.length === 1 : forceChoices.some(choice => choice.elite === action.elite));
|
||||
const windowPacket = Object.keys(action).every(key => ['type', 'card', 'turnActionPromiseEvent', 'turnActionPromisePhase',
|
||||
...(windowEffect === 'weather' ? ['amount'] : [])].includes(key)) &&
|
||||
...(windowEffect === 'weather' ? ['amount'] : windowEffect === 'karamaAuction' ? ['mode'] : [])].includes(key)) &&
|
||||
(windowEffect !== 'weather' || typeof action.amount === 'number' && Number.isSafeInteger(action.amount) && action.amount >= 0 && action.amount <= 10);
|
||||
const printedWindow = ownCard && !!heldCard && heldCard.kind === 'special' && !!windowEffect && !!cardWindow &&
|
||||
action.mode === undefined && windowPacket && !!windowPromise &&
|
||||
const printedWindowSource = ownPhysicalCard && !!heldCard && heldCard.kind === 'special' && !!windowEffect && !!cardWindow &&
|
||||
heldCard.effect === windowCardNativeEffect(windowEffect) && !!windowPromise &&
|
||||
current.turnActionPromises.includes(windowPromise) && JSON.stringify(windowPromise.mixed) === windowQuestion &&
|
||||
namesWindow(windowPromise.mixed, windowEffect, cardWindow.source) &&
|
||||
(windowEffect !== 'harvester' || current.spiceWindow === originalBlow) && publicWindowState(current) === windowSignature &&
|
||||
ordinaryCardAvailability(current, heldCard.id, windowEffect === 'weather' ? { amount: action.amount } : undefined)?.available === true;
|
||||
const genericCard = ownCard && !!heldCard && heldCard.effect !== 'ghola' && heldCard.effect !== 'hajr' && !isWindowEffect(heldCard.effect);
|
||||
const earlyWindowBox = (phase === 0 || phase === 1) && genericCard && heldCard?.effect === 'nullentropyBox' &&
|
||||
(windowEffect !== 'harvester' || current.spiceWindow === originalBlow) && publicWindowState(current) === windowSignature;
|
||||
const nativeAuctionCard = windowEffect === 'karamaAuction' && !!heldCard && currentAuction &&
|
||||
Array.isArray(current.karamaAuctionCards) &&
|
||||
current.karamaAuctionCards === auctionCards && JSON.stringify(current.karamaAuctionCards) === auctionCardsSignature &&
|
||||
current.karamaAuctionCards.includes(heldCard.id) && auctionHandSource(current) === auctionHandSignature;
|
||||
const printedWindow = action.type === 'card' && printedWindowSource && !!heldCard && windowPacket &&
|
||||
(windowEffect === 'karamaAuction' ? action.mode === 'purchase' && nativeAuctionCard && !!me && !current.decision &&
|
||||
Number.isSafeInteger(me.handLimit) && Array.isArray(me.hand) && me.hand.length < me.handLimit
|
||||
: action.mode === undefined && ordinaryCardAvailability(current, heldCard.id, windowEffect === 'weather' ? { amount: action.amount } : undefined)?.available === true);
|
||||
const printedAuctionInvoice = printedInvoiceAction && printedWindowSource && nativeAuctionCard &&
|
||||
Object.keys(action).every(key => ['type', 'card', 'karama', 'turnActionPromiseEvent', 'turnActionPromisePhase'].includes(key));
|
||||
const genericCard = ownCard && action.type === 'card' && !auctionPurchaseAction && !!heldCard && heldCard.effect !== 'ghola' && heldCard.effect !== 'hajr' && !isWindowEffect(heldCard.effect);
|
||||
const earlyWindowBox = (phase === 0 || phase === 1 || phase === 3) && genericCard && heldCard?.effect === 'nullentropyBox' &&
|
||||
action.mode === undefined && Object.keys(action).every(key => ['type', 'card', 'turnActionPromiseEvent', 'turnActionPromisePhase'].includes(key)) &&
|
||||
!!box && current.nullentropy === box &&
|
||||
JSON.stringify({ card: box.card.id, blocked: box.blocked, search: box.search?.event }) === earlyBoxSignature &&
|
||||
!box.blocked && !box.search && box.card.id === heldCard.id &&
|
||||
!!windowPromise && current.turnActionPromises.includes(windowPromise) && JSON.stringify(windowPromise.mixed) === windowQuestion &&
|
||||
(['weather', 'atomics', 'harvester'] as const).some(effect => {
|
||||
WINDOW_CARD_EFFECTS.some(effect => {
|
||||
const opportunity = windowCardOpportunity(current, effect);
|
||||
return !!opportunity && namesWindow(windowPromise.mixed, effect, opportunity.source);
|
||||
}) && publicWindowState(current) === windowSignature;
|
||||
@@ -155,11 +180,9 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
current.decision?.kind === 'nullentropy' && current.nullentropy === box && current.nullentropy?.search === search &&
|
||||
current.decision.player === actor && !!current.nullentropy?.search &&
|
||||
current.nullentropy.search.event === action.event && !current.nullentropy.blocked;
|
||||
const currentAuction = current.auction === auction && auctionSource(current) === auctionSignature && !!auction;
|
||||
const invoice = phase === 3 && action.type === 'decision' && typeof action.karama === 'boolean' &&
|
||||
(!action.karama || typeof action.card === 'string') &&
|
||||
current.decision === decision && current.decision?.kind === 'auctionPayment' && current.decision.player === actor &&
|
||||
!current.richeseAuction && currentAuction && current.auction?.bidder === actor;
|
||||
ownInvoiceSource && (!printedInvoiceAction || printedAuctionInvoice);
|
||||
const nativePass = phase === 3 && action.type === 'richeseBid' && current.richeseAuction === native &&
|
||||
nativeSource(current) === nativeSignature && native && native.event === action.event && !native.outcome &&
|
||||
native.eligible.includes(actor) && (native.method === 'silent'
|
||||
@@ -213,7 +236,7 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
action.turnActionPromiseEvent === event && action.turnActionPromisePhase === phase && JSON.stringify(action) === signature && opportunity &&
|
||||
current.turnActionPromises.some(promise => promise.event === event && promise.player === actor && promise.turn === turn &&
|
||||
!promise.fulfilled && !promise.released) &&
|
||||
!!me && !me.autopilot && !current.truthtrance && !current.response && (!current.decision || paidBox || invoice) &&
|
||||
!!me && !me.autopilot && !current.truthtrance && !current.response && (!current.decision || paidBox || invoice || earlyWindowBox && ownInvoiceSource) &&
|
||||
!current.phaseOpening && !current.automaticContinuationPending && !current.roomControl?.paused && !current.roomControl?.closed;
|
||||
};
|
||||
const amount = action.forces && typeof action.forces === 'object'
|
||||
@@ -229,6 +252,7 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
: action.type === 'card' && heldCard?.effect === 'ghola' ? action.leader !== undefined
|
||||
? `Play Tleilaxu Ghola to return ${gholaLeader} while honoring your whole answer`
|
||||
: `Play Tleilaxu Ghola to return ${amount} physical forces while honoring your whole answer`
|
||||
: action.type === 'card' && windowEffect === 'karamaAuction' ? 'Spend a printed Karama to purchase this ordinary lot while honoring your whole answer'
|
||||
: action.type === 'card' && windowEffect ? `Play ${windowRoleNames[windowEffect]} in its original native window${windowEffect === 'weather' ? ` with storm distance ${amount}` : ''} while honoring your whole answer`
|
||||
: action.type === 'battlePlan' ? 'Fill the quoted battle plan for review before sealing'
|
||||
: action.type === 'voice' ? `Declare native Voice: opponent ${action.must ? 'must' : 'cannot'} play ${battleCardLabel(String(action.kind))}`
|
||||
@@ -256,7 +280,8 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
: action.type === 'move' ? `Move ${amount} physical forces to ${destination}${sector} while honoring your whole answer`
|
||||
: action.type === 'endMovement' ? 'Finish movement while preserving any later payment conditions'
|
||||
: action.type === 'decision' ? game.decision?.kind === 'auctionPayment'
|
||||
? action.karama === true ? 'Use the quoted Karama for the current auction payment while preserving your whole answer'
|
||||
? action.karama === true ? printedInvoiceAction ? 'Spend the quoted printed Karama on this ordinary-lot winning invoice while preserving your whole answer'
|
||||
: 'Use the quoted Karama for the current auction payment while preserving your whole answer'
|
||||
: 'Pay the current auction price while preserving your whole answer'
|
||||
: 'Complete your paid Box search while preserving your whole answer'
|
||||
: action.type === 'prepareAdvisors' ? `Prepare advisors in ${destination} while preserving your whole answer`
|
||||
@@ -314,6 +339,9 @@ export function TurnActionPromises(props: TurnActionProps) {
|
||||
Weather Control, Family Atomics and Harvester conditions count an actual accepted future printed-role play after the answer
|
||||
in the original open Storm or fresh spice-blow window. Earlier plays, disposal, other roles and recovery alone do not count.
|
||||
They do not promise a selected storm distance or resulting spice amount. Each condition closes only when its original native window closes.
|
||||
A current-lot Karama condition requires spending a printed Karama for this original ordinary lot, through immediate purchase
|
||||
or its winning invoice. Other Karama roles, Worthless conversion and a successful bid alone do not count.
|
||||
Recovery alone does not count. This condition closes with the original ordinary lot.
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user