Connect Truthtrance promises to printed Hajr and Ghola plays
This commit is contained in:
1 parent
f548039cd8
commit
c93b693137
17 files changed
+1554
-104
No files matched your search
@@ -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 the current battle with this turn’s alliances, bids, Revival, payments, shipment and ground movements</option>
|
||||
<option value="turnActions">Combine Hajr/Ghola plays, the current battle and this turn’s other actions</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 current battle, Traitor declaration, alliance, ordinary bid, Revival, payment, shipment or ground-movement opportunity this turn.
|
||||
{!g.truthTurnActionTargets.includes(target) && <p className="notice">Choose a player with supported printed card plays, current battle, Traitor declaration, alliance, ordinary bid, Revival, payment, shipment or ground-movement opportunities 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>
|
||||
</>
|
||||
|
||||
@@ -39,7 +39,7 @@ const currentBattle = (game: GameView, target: string, kind: 'battlePlan' | 'tra
|
||||
const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
prefix: 'turn-action',
|
||||
legend: 'Current battle and this-turn action conditions, with optional current facts',
|
||||
actionLabel: 'battle, alliance, bid, Revival, payment, shipment or ground-movement condition',
|
||||
actionLabel: 'printed card play, 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.
|
||||
@@ -56,6 +56,10 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
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.
|
||||
Printed-card conditions ask you to play Hajr or Tleilaxu Ghola in its printed role after this answer this turn.
|
||||
An earlier play, discard, sale, Karama role or recovery alone does not count; an actual later replay after Box recovery does.
|
||||
Hajr counts when played even if its extra movement is unused. Tleilaxu Ghola is a card play, separate from ordinary Revival.
|
||||
Hajr conditions close at your own Movement turn end; Tleilaxu Ghola conditions close at the actual turn end.
|
||||
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.
|
||||
@@ -93,6 +97,7 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
const kind = event.currentTarget.value;
|
||||
if (kind === value.kind) return;
|
||||
const minimum = 'minimum' in value ? value.minimum : 1;
|
||||
if (kind === 'cardPlay') onChange({ kind, effect: 'hajr' });
|
||||
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 });
|
||||
@@ -104,6 +109,7 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
if (kind === 'ship' || kind === 'move') onChange({ kind,
|
||||
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="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>
|
||||
@@ -114,7 +120,17 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
<option value="ship">Ship from reserves</option>
|
||||
<option value="move">Make ground movement</option>
|
||||
</select>
|
||||
{value.kind === 'battlePlan' || value.kind === 'traitorCall' ? <>
|
||||
{value.kind === 'cardPlay' ? <>
|
||||
<label htmlFor={`${id}-effect`}>Printed card role after this answer</label>
|
||||
<select id={`${id}-effect`} value={value.effect} disabled={disabled}
|
||||
aria-invalid={value.effect !== 'hajr' && value.effect !== 'ghola'} onChange={event => {
|
||||
const effect = event.currentTarget.value;
|
||||
if (effect === 'hajr' || effect === 'ghola') onChange({ kind: 'cardPlay', effect });
|
||||
}}>
|
||||
<option value="hajr">Play Hajr in its printed role after this answer this turn</option>
|
||||
<option value="ghola">Play Tleilaxu Ghola in its printed role after this answer this turn</option>
|
||||
</select>
|
||||
</> : 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}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { bribeAction } from '@/game/bribe-options';
|
||||
import { alliancePromiseModeSupported } from '@/game/alliance-promises';
|
||||
import { nativePromiseScopeSupported } from '@/game/native-promise-scope';
|
||||
import { quoteNexusAlliance } from '@/game/nexus-alliance';
|
||||
import { ordinaryCardAvailability } from '@/game/card-availability';
|
||||
import { gholaForcePreparations } from '@/game/ghola-force-preparation';
|
||||
import { truthQuestionText, type TruthAnswer } from '@/game/truthtrance';
|
||||
import { CHEAP_HERO_TRAITOR } from '@/game/traitors';
|
||||
import { Button } from './ui/button';
|
||||
@@ -43,8 +45,37 @@ 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 heldCard = cardOwner?.hand?.find(card => card.id === action.card);
|
||||
const cardSignature = heldCard && JSON.stringify({ id: heldCard.id, kind: heldCard.kind, effect: heldCard.effect });
|
||||
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);
|
||||
return JSON.stringify({ moved: player?.moved, movesAllowed: player?.movesAllowed,
|
||||
hajrPlays: player?.hajrPlays, available: player?.hajrAvailable });
|
||||
};
|
||||
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 &&
|
||||
me.hand?.find(card => card.id === action.card) === heldCard &&
|
||||
JSON.stringify({ id: heldCard.id, kind: heldCard.kind, effect: heldCard.effect }) === cardSignature;
|
||||
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;
|
||||
const gholaSource = ownCard && !!heldCard && heldCard.kind === 'special' && heldCard.effect === 'ghola' && action.mode === undefined &&
|
||||
!!ghola && current.ghola === ghola && JSON.stringify(current.ghola) === gholaSignature &&
|
||||
ghola.available && ghola.cards.includes(heldCard.id);
|
||||
const forceChoices = gholaSource && !!heldCard && !!me && !!ghola && action.leader === undefined && typeof action.amount === 'number' &&
|
||||
Number.isSafeInteger(action.amount) && action.amount >= 1 && action.amount <= 5 &&
|
||||
Number.isSafeInteger(ghola.maxForces) && ghola.maxForces >= 0 && ghola.maxForces <= 5
|
||||
? [...gholaForcePreparations({ cards: [heldCard.id], tanks: me.tanks, eliteTanks: me.elites?.tanks ?? 0,
|
||||
maxForces: ghola.maxForces, eliteRemaining: ghola.eliteRemaining })].filter(choice => choice.amount === action.amount)
|
||||
: [];
|
||||
const printedGhola = gholaSource && !!ghola && (action.leader !== undefined
|
||||
? 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 genericCard = ownCard && !!heldCard && heldCard.effect !== 'ghola' && heldCard.effect !== 'hajr';
|
||||
const options = current.bribeOptions;
|
||||
const target = action.type === 'bribe' && typeof action.target === 'string'
|
||||
? current.players.find(player => player.id === action.target) : undefined;
|
||||
@@ -88,18 +119,18 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
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 battlePreparation = genericCard && 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)
|
||||
const opportunity = payment || paidBox || printedHajr || printedGhola || plan || battlePreparation || declaration || nexusWindow && (ownedAlliance || action.type === 'ready') || (phase === 2
|
||||
? !current.ready.includes(actor) && (['charity', 'ready'].includes(action.type) || genericCard)
|
||||
: phase === 3
|
||||
? invoice || nativePass || requiredBid || action.type === 'passBid' && !current.richeseAuction && currentAuction && current.auction?.active === actor
|
||||
: phase === 4
|
||||
? !current.ready.includes(actor) && revivalTurn && ['revive', 'reviveLeader', 'reviveKwisatz', 'acceptLeaderRevival', 'ready', 'card'].includes(action.type)
|
||||
? !current.ready.includes(actor) && revivalTurn && (['revive', 'reviveLeader', 'reviveKwisatz', 'acceptLeaderRevival', 'ready'].includes(action.type) || genericCard)
|
||||
: phase === 5
|
||||
? current.active === actor && (action.type === 'guildShip' && action.from === 'reserves' ||
|
||||
['ship', 'move', 'endMovement', 'card', 'prepareAdvisors', 'revealNoField'].includes(action.type))
|
||||
['ship', 'move', 'endMovement', 'prepareAdvisors', 'revealNoField'].includes(action.type) || genericCard)
|
||||
: (phase === 7 || phase === 8) &&
|
||||
action.type === 'ready' && !current.ready.includes(actor));
|
||||
return !props.busy && props.act === act && current === game && nativePromiseScopeSupported(current) && current.status === 'playing' &&
|
||||
@@ -116,8 +147,14 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
: typeof action.amount === 'number' ? action.amount : 0;
|
||||
const destination = game.combatLocations.find(location => location.id === action.territory)?.name ?? 'the quoted destination';
|
||||
const recipient = game.players.find(player => player.id === action.target)?.name ?? 'the quoted recipient';
|
||||
const gholaLeader = action.leader === 'kwisatz' ? 'Kwisatz Haderach'
|
||||
: ghola?.leaders.find(leader => leader.id === action.leader)?.name ?? 'the quoted leader';
|
||||
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 === 'card' && heldCard?.effect === 'hajr' ? 'Play Hajr in its printed role while honoring your whole answer'
|
||||
: 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 === '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
|
||||
@@ -188,12 +225,17 @@ export function TurnActionPromises(props: TurnActionProps) {
|
||||
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.
|
||||
Printed-card conditions count actual future Hajr or Tleilaxu Ghola plays in their printed role after the answer this turn.
|
||||
Earlier plays, discards, sales, Karama roles and recovery alone do not count; an actual later Box replay does.
|
||||
Hajr counts even if its extra movement is unused and closes at your own Movement turn end.
|
||||
Tleilaxu Ghola is separate from ordinary Revival and remains open until the actual turn ends.
|
||||
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.
|
||||
Shipment and movement close at your own Movement turn end; ordinary payments remain possible until the actual turn ends.
|
||||
A known Inflation Double opening can close payments earlier without closing Ghola.
|
||||
Original costs, timing, cards, preparation and action rights still apply. This claim grants no extra action.</p>
|
||||
{live && <p>{game.phase === 8 ? 'Complete any remaining ordinary payment before the actual turn ends. Late recipients collect next turn.'
|
||||
{live && <p>{game.phase === 8 ? 'Honor remaining Ghola or payment conditions before this turn ends. Late payment recipients collect next turn.'
|
||||
: game.phase < 5 ? 'Your Movement turn is still ahead. Preserve the needed forces, spice and cards and complete any offered native preparation.'
|
||||
: 'Use the current native controls to honor your whole answer. Payment conditions can remain after your Movement turn ends.'}</p>}
|
||||
{live && <TurnActionGuideButton {...props} />}
|
||||
@@ -226,8 +268,11 @@ export function TurnActionAnswers(props: TurnActionProps) {
|
||||
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.
|
||||
Hajr and Tleilaxu Ghola conditions require actual later printed-role plays, not earlier play, disposal or recovery alone.
|
||||
Hajr closes at your own Movement turn end even if its extra move is unused; Ghola closes at the actual turn end.
|
||||
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.
|
||||
A known Inflation Double opening can close payments earlier without closing Ghola.
|
||||
Original rights, preparation, timing and costs still apply. Only your whole answer is shared.</p>
|
||||
{answers.map(answer => {
|
||||
const action: Action = { type: 'truthAnswer', answer };
|
||||
|
||||
Reference in new issue
Block a user