Support earlier combined shipment and movement promises
This commit is contained in:
1 parent
d4e82c74b1
commit
8dbec05844
18 files changed
+1321
-46
No files matched your search
@@ -13,21 +13,58 @@ export function TravelPromiseGuideButton(props: TravelProps) {
|
||||
const game = props.game, guide = game.travelPromiseGuide;
|
||||
if (!guide) return null;
|
||||
const actor = game.me, turn = game.turn, phase = game.phase, event = guide.event, action = guide.action;
|
||||
const signature = JSON.stringify(action);
|
||||
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({
|
||||
bid: current.auction.bid, bidder: current.auction.bidder, active: current.auction.active,
|
||||
remaining: current.auction.remaining, allyPayment: current.auction.allyPayment,
|
||||
});
|
||||
const nativeSource = (current: GameView) => current.richeseAuction && JSON.stringify({
|
||||
event: current.richeseAuction.event, method: current.richeseAuction.method,
|
||||
eligible: current.richeseAuction.eligible, submitted: current.richeseAuction.submitted,
|
||||
active: current.richeseAuction.active, outcome: current.richeseAuction.outcome,
|
||||
bid: current.richeseAuction.bid, bidder: current.richeseAuction.bidder,
|
||||
passed: current.richeseAuction.passed, acted: current.richeseAuction.acted,
|
||||
});
|
||||
const auctionSignature = auctionSource(game), nativeSignature = nativeSource(game), orderSignature = JSON.stringify(order);
|
||||
const box = game.nullentropy, search = box?.search;
|
||||
const canAct = () => {
|
||||
const current = props.game, me = current.players.find(player => player.id === actor);
|
||||
const paidBox = action.type === 'decision' && current.decision?.kind === 'nullentropy' &&
|
||||
const paidBox = action.type === 'decision' && current.decision === decision &&
|
||||
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 opportunity = paidBox || action.type === 'guildShip' && action.from === 'reserves' ||
|
||||
['ship', 'move', 'endMovement', 'card', 'prepareAdvisors', 'revealNoField'].includes(action.type);
|
||||
const currentAuction = current.auction === auction && auctionSource(current) === auctionSignature && !!auction;
|
||||
const payment = 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;
|
||||
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'
|
||||
? !native.submitted.includes(actor) && action.amount === 0
|
||||
: native.active === actor && action.amount === null) && action.allyPayment === 0;
|
||||
const requiredBid = phase === 3 && action.type === 'bid' && !current.richeseAuction && currentAuction &&
|
||||
current.auction?.active === actor && typeof action.amount === 'number' && Number.isSafeInteger(action.amount) &&
|
||||
action.amount > 0;
|
||||
const revivalTurn = current.revival === revival && !current.revival.pending && !current.revival.orderBlocked &&
|
||||
current.revivalOrder === order && JSON.stringify(current.revivalOrder) === orderSignature &&
|
||||
(!order || order.turn === turn && order.remaining[0] === actor && current.active === actor);
|
||||
const opportunity = paidBox || (phase === 2
|
||||
? !current.ready.includes(actor) && ['charity', 'ready', 'card'].includes(action.type)
|
||||
: phase === 3
|
||||
? payment || 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)
|
||||
: phase === 5 && current.active === actor && (action.type === 'guildShip' && action.from === 'reserves' ||
|
||||
['ship', 'move', 'endMovement', 'card', 'prepareAdvisors', 'revealNoField'].includes(action.type)));
|
||||
return !props.busy && current === game && nativePromiseScopeSupported(current) && current.status === 'playing' &&
|
||||
phase === 5 && current.phase === phase && current.turn === turn && current.me === actor && current.active === actor &&
|
||||
Number.isSafeInteger(phase) && phase >= 0 && phase <= 5 && current.phase === phase && current.turn === turn && current.me === actor &&
|
||||
current.travelPromiseGuide === guide && guide.event === event && guide.action === action &&
|
||||
action.travelPromiseEvent === event && JSON.stringify(action) === signature && opportunity &&
|
||||
action.travelPromiseEvent === event && action.travelPromisePhase === phase && JSON.stringify(action) === signature && opportunity &&
|
||||
current.travelPromises.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) &&
|
||||
!!me && !me.autopilot && !current.truthtrance && !current.response && (!current.decision || paidBox || payment) &&
|
||||
!current.phaseOpening && !current.automaticContinuationPending && !current.roomControl?.paused && !current.roomControl?.closed;
|
||||
};
|
||||
const amount = action.forces && typeof action.forces === 'object'
|
||||
@@ -35,10 +72,27 @@ export function TravelPromiseGuideButton(props: TravelProps) {
|
||||
: typeof action.amount === 'number' ? action.amount : 0;
|
||||
const destination = game.combatLocations.find(location => location.id === action.territory)?.name ?? 'the quoted destination';
|
||||
const sector = typeof action.sector === 'number' ? `, sector ${action.sector}` : '';
|
||||
const label = action.type === 'ship' || action.type === 'guildShip' ? `Ship ${amount} physical forces from reserves to ${destination}${sector}${action.type === 'guildShip' ? ' using Guild transport' : ''} while honoring your whole answer`
|
||||
// A projected own face supplies the label only; the native guide owns legality.
|
||||
const paymentCard = action.type === 'decision' && action.karama === true
|
||||
? game.players.find(player => player.id === actor)?.hand?.find(card => card.id === action.card) : undefined;
|
||||
const label = action.type === 'charity' ? 'Claim Charity for your later shipment and movement'
|
||||
: action.type === 'ready' ? 'Finish this preparation phase while preserving your whole answer'
|
||||
: action.type === 'passBid' ? 'Pass this bid and preserve later shipment and movement'
|
||||
: action.type === 'bid' ? `Place the required bid of ${amount} while preserving later shipment and movement`
|
||||
: action.type === 'richeseBid' ? action.amount === 0
|
||||
? 'Submit zero in this Silent auction and preserve later shipment and movement'
|
||||
: 'Pass this Richese bid and preserve later shipment and movement'
|
||||
: action.type === 'revive' ? `Revive ${amount} forces for your later shipment and movement`
|
||||
: action.type === 'reviveLeader' ? 'Revive the quoted leader for your later shipment and movement'
|
||||
: action.type === 'acceptLeaderRevival' ? 'Accept the quoted leader revival for your later shipment and movement'
|
||||
: action.type === 'reviveKwisatz' ? 'Revive your Kwisatz Haderach for your later shipment and movement'
|
||||
: action.type === 'ship' || action.type === 'guildShip' ? `Ship ${amount} physical forces from reserves to ${destination}${sector}${action.type === 'guildShip' ? ' using Guild transport' : ''} while honoring your whole answer`
|
||||
: action.type === 'move' ? `Move ${amount} physical forces to ${destination}${sector} while honoring your whole answer`
|
||||
: action.type === 'endMovement' ? 'Finish movement while honoring your whole answer'
|
||||
: action.type === 'decision' ? 'Complete your paid Box search for your whole answer'
|
||||
: action.type === 'decision' ? game.decision?.kind === 'auctionPayment'
|
||||
? action.karama === true ? `Use ${paymentCard?.name ?? '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 for your whole answer'
|
||||
: action.type === 'prepareAdvisors' ? `Prepare advisors in ${destination} for your whole answer`
|
||||
: action.type === 'revealNoField' ? 'Reveal your No-Field to prepare physical forces for your whole answer'
|
||||
: 'Use the offered native card preparation for your whole answer';
|
||||
@@ -71,8 +125,9 @@ export function TravelPromises(props: TravelProps) {
|
||||
Earlier actions do not count. A shipment can prepare a move; each action counts only for its own conditions.
|
||||
Original shipment rights, movement opportunities, cards, preparation and costs still apply.
|
||||
This claim grants no extra shipment or movement.</p>
|
||||
{live && game.active !== game.me && <p>Your Movement turn is still ahead. Keep the needed forces, spice and cards available;
|
||||
native controls appear on your turn.</p>}
|
||||
{live && (game.phase < 5 || game.active !== game.me) && <p>Your Movement turn is still ahead. Keep the needed forces, spice and cards available;
|
||||
movement controls appear on your turn.
|
||||
{game.phase < 5 && ' Complete any offered current preparation below before confirming readiness.'}</p>}
|
||||
{live && <TravelPromiseGuideButton {...props} />}
|
||||
</section>;
|
||||
}
|
||||
@@ -85,7 +140,8 @@ export function TravelAnswers(props: TravelProps) {
|
||||
const signature = JSON.stringify(question), sourceSignature = JSON.stringify(source);
|
||||
const canAnswer = (answer: TruthAnswer) => {
|
||||
const current = props.game, me = current.players.find(player => player.id === actor);
|
||||
return !props.busy && current === game && current.status === 'playing' && phase === 5 && current.phase === phase &&
|
||||
return !props.busy && current === game && current.status === 'playing' &&
|
||||
Number.isSafeInteger(phase) && phase >= 0 && phase <= 5 && current.phase === phase &&
|
||||
current.turn === turn && current.me === actor && question.turn === turn &&
|
||||
current.truthtrance === window && window.stage === 'answer' && window.question === question && question.target === actor &&
|
||||
JSON.stringify(question) === signature && !!source && window.queue[0] === source && JSON.stringify(source) === sourceSignature &&
|
||||
|
||||
@@ -589,7 +589,7 @@ export function Truthtrance({
|
||||
<>
|
||||
<TravelClaimFields value={travel} game={g} target={target}
|
||||
disabled={busy || !g.truthTravelTargets.includes(target)} onChange={setTravel} />
|
||||
{!g.truthTravelTargets.includes(target) && <p className="notice">Choose an active or unused native Shipment and Movement opportunity in this phase.</p>}
|
||||
{!g.truthTravelTargets.includes(target) && <p className="notice">Choose this turn’s upcoming, active or unused native Shipment and Movement opportunity.</p>}
|
||||
<p className="notice" aria-label="Public travel question preview">{travelError ?? truthQuestionText(question, leaderName, locationName, name)}</p>
|
||||
</>
|
||||
) : kind === 'mixedTraitorCall' ? (
|
||||
|
||||
Reference in new issue
Block a user