Prototype conditional future battle Truthtrance
Verify and publish checkpoint container / container (push) Failing after 11m11s
Verify and publish checkpoint container / container (push) Failing after 11m11s
This commit is contained in:
1 parent
71009d73e3
commit
02695aea7c
15 files changed
+721
-8
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import { createTwoPlayerControllers, validateTwoPlayerControllersLobby, validateTwoPlayerControllersComplete,
|
||||
addTwoPlayerControlledSeat, TwoPlayerControllersError, type TwoPlayerControllers } from './two-player-controllers';
|
||||
import { createFutureBattleLeader, bindFutureBattleLeader, settleFutureBattleLeader, releaseFutureBattleLeader,
|
||||
expireFutureBattleLeader, type FutureBattleLeaderReceipt } from './future-battle-leader';
|
||||
import { createFactionDraft, validateFactionDraft, currentFactionDrafter, chooseFaction,
|
||||
FactionDraftError, type FactionDraft } from './faction-draft';
|
||||
import { createFactionSwap, validateFactionSwap, answerFactionSwap, cancelFactionSwap,
|
||||
@@ -682,6 +684,7 @@ import {
|
||||
type TruthQueueEntry,
|
||||
type TruthRecord,
|
||||
type TruthAnswer,
|
||||
type TruthQuestion,
|
||||
type MixedMovementClaim,
|
||||
} from './truthtrance';
|
||||
import type { MixedShipmentExpression } from './mixed-shipment-question';
|
||||
@@ -2319,6 +2322,7 @@ export type Game = {
|
||||
};
|
||||
truthtrance?: TruthWindow | null;
|
||||
truthHistory?: TruthRecord[];
|
||||
futureBattleLeaderPromises?: FutureBattleLeaderReceipt[];
|
||||
travelPromises?: TravelPromise[];
|
||||
turnActionPromises?: TurnActionPromise[];
|
||||
/** Actual accepted printed-role uses; old games need no baseline retrofit. */
|
||||
@@ -7550,6 +7554,7 @@ function truthtranceDiscardSignature(g: Game, next: TruthDiscardContinuation) {
|
||||
...(['travel', 'turnActions'].includes(next.record.question.kind) ? { movementExpressionPromises: g.movementExpressionPromises } : {}),
|
||||
...(g.turnActionPromises ? { turnActionPromises: g.turnActionPromises, bribePromises: g.bribePromises,
|
||||
bribeExpressionPromises: g.bribeExpressionPromises, bribePromiseSequence: g.bribePromiseSequence } : {}),
|
||||
...(next.record.question.kind === 'futureBattleLeader' ? {futureBattleLeaderPromises: g.futureBattleLeaderPromises} : {}),
|
||||
...(next.record.question.kind === 'mixedTraitorCall' ? { mixedTraitorQuestions: g.mixedTraitorQuestions } : {}),
|
||||
...(next.record.question.kind === 'mixedBattlePlan' ? { mixedBattleQuestions: g.mixedBattleQuestions } : {}),
|
||||
...(['allianceExpression', 'mixedAlliance'].includes(next.record.question.kind) ? {
|
||||
@@ -8622,6 +8627,13 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
}
|
||||
const q = record.question,
|
||||
promise = c.promise;
|
||||
if (q.kind === 'futureBattleLeader') {
|
||||
const binding = g.futureBattleLeaderPromises?.at(-1);
|
||||
requireRule(binding && binding.turn === g.turn && binding.asker === record.asker &&
|
||||
binding.player === q.target && binding.leader === q.leader &&
|
||||
binding.answer === (record.answer === 'yes') && binding.status === 'waiting' && !binding.battle,
|
||||
'The completed Truthtrance lost its conditional battle answer.');
|
||||
}
|
||||
const expectedShipment = q.kind === 'shipment' || q.kind === 'mixedShipment';
|
||||
const expectedBattle =
|
||||
q.kind === 'battlePlan' &&
|
||||
@@ -31674,6 +31686,61 @@ function battleTruthAnswers(
|
||||
answers.push(answer ? 'yes' : 'no');
|
||||
return answers.length ? answers : ['unknown'];
|
||||
}
|
||||
type FutureBattleLeaderQuestion = Extract<TruthQuestion, {kind: 'futureBattleLeader'}>;
|
||||
function futureBattleLeaderTruthAnswers(g: Game, p: Player, q: FutureBattleLeaderQuestion): TruthAnswer[] {
|
||||
const otherPromises: {player: string; turn?: number; fulfilled?: boolean; released?: boolean}[] = [
|
||||
...(g.travelPromises ?? []), ...(g.shipmentPromises ?? []), ...(g.movementPromises ?? []),
|
||||
...(g.movementExpressionPromises ?? []), ...(g.bribePromises ?? []), ...(g.bribeExpressionPromises ?? []),
|
||||
...(g.revivalPromises ?? []), ...(g.leaderRevivalPromises ?? []), ...(g.revivalExpressionPromises ?? []),
|
||||
...(g.bidPromises ?? []), ...(g.phaseBidPromises ?? []), ...(g.richeseBidPromises ?? []),
|
||||
...(g.earlyRicheseBidPromises ?? []), ...(g.alliancePromises ?? []), ...(g.allianceExpressionPromises ?? []),
|
||||
];
|
||||
if (q.turn !== g.turn || q.target !== p.id || g.phase !== 5 ||
|
||||
(g.futureBattleLeaderPromises ?? []).some(row => row.turn === g.turn && row.player === p.id &&
|
||||
(row.status === 'waiting' || row.status === 'bound')) ||
|
||||
otherPromises.some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
|
||||
liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn).length) return ['unknown'];
|
||||
const available = p.leaders.filter(leader => nativeAvailable(leader) && !leader.usedAt && leader.faction === p.faction);
|
||||
const result: TruthAnswer[] = [];
|
||||
if (available.some(leader => leader.id === q.leader)) result.push('yes');
|
||||
if (available.some(leader => leader.id !== q.leader) || p.hand.some(card => card.kind === 'hero')) result.push('no');
|
||||
return result.length ? result : ['unknown'];
|
||||
}
|
||||
function bindFutureBattleLeaderTruth(g: Game, p: Player, q: FutureBattleLeaderQuestion, answer: boolean, asker: string) {
|
||||
requireRule(futureBattleLeaderTruthAnswers(g, p, q).includes(answer ? 'yes' : 'no'),
|
||||
'Choose an answer supported by your own available leaders.');
|
||||
(g.futureBattleLeaderPromises ??= []).push(createFutureBattleLeader(
|
||||
{ turn: g.turn, asker, player: p.id, leader: q.leader, answer }, leaders(p.faction).map(leader => leader.id)));
|
||||
}
|
||||
function bindFutureBattleLeaderQuestions(g: Game) {
|
||||
const battle = g.battle;
|
||||
if (!battle?.event || !g.futureBattleLeaderPromises) return;
|
||||
g.futureBattleLeaderPromises = g.futureBattleLeaderPromises.map(receipt => {
|
||||
const bound = bindFutureBattleLeader(receipt, {turn: g.turn, event: battle.event!, attacker: battle.attacker,
|
||||
defender: battle.defender, native: true, plansOpen: !battle.revealed});
|
||||
if (bound !== receipt) {
|
||||
const claim: PlanClaim = {kind: 'leader', leader: bound.leader};
|
||||
bindBattleTruth(g, getPlayer(g, bound.player), claim, bound.answer, bound.asker);
|
||||
}
|
||||
return bound;
|
||||
});
|
||||
}
|
||||
function reconcileFutureBattleLeaderQuestions(g: Game) {
|
||||
const battle = g.battle;
|
||||
if (!g.futureBattleLeaderPromises) return;
|
||||
g.futureBattleLeaderPromises = g.futureBattleLeaderPromises.map(receipt => {
|
||||
if (receipt.status === 'waiting' && (g.turn > receipt.turn ||
|
||||
receipt.turn === g.turn && (g.phase > 6 || g.status !== 'playing')))
|
||||
return expireFutureBattleLeader(receipt, {turn: receipt.turn, battlePhaseEnded: true});
|
||||
if (receipt.status !== 'bound' || !battle || battle.event !== receipt.battle) return receipt;
|
||||
const bound = battle.truthPromises?.find(row => row.player === receipt.player && row.asker === receipt.asker &&
|
||||
row.answer === receipt.answer && row.claim.kind === 'leader' && row.claim.leader === receipt.leader);
|
||||
if (bound?.released) return releaseFutureBattleLeader(receipt, {turn: receipt.turn, battle: receipt.battle});
|
||||
if (battle.revealed && bound) return settleFutureBattleLeader(receipt, {turn: receipt.turn,
|
||||
battle: receipt.battle!, player: receipt.player, revealed: true, leader: battle.plans[receipt.player].leader});
|
||||
return receipt;
|
||||
});
|
||||
}
|
||||
function bindBattleTruth(
|
||||
g: Game,
|
||||
p: Player,
|
||||
@@ -40911,6 +40978,7 @@ function applyActionWithInitiator(state: Game, id: string, action: Action, initi
|
||||
finishLeaderSkillCustody(g, state);
|
||||
leaderSkillsIntegrity(g);
|
||||
reconcileBattlePromises(g, { actor: id, action });
|
||||
reconcileFutureBattleLeaderQuestions(g);
|
||||
reconcileShipmentPromises(g, { actor: id, action, phase: state.phase });
|
||||
reconcileBidPromises(g, { actor: id, action });
|
||||
reconcilePhaseBids(g, { actor: id, action });
|
||||
@@ -41575,6 +41643,8 @@ function applyActionInner(
|
||||
bindMixedBattle: bindMixedBattleTruth,
|
||||
battleAnswers: battleTruthAnswers,
|
||||
bindBattle: bindBattleTruth,
|
||||
futureBattleLeaderAnswers: futureBattleLeaderTruthAnswers,
|
||||
bindFutureBattleLeader: bindFutureBattleLeaderTruth,
|
||||
cardBlock: transferCardBlock,
|
||||
})
|
||||
)
|
||||
@@ -45038,6 +45108,7 @@ function applyActionInner(
|
||||
revealed: false,
|
||||
traitorCalls: {},
|
||||
};
|
||||
bindFutureBattleLeaderQuestions(g);
|
||||
if (combined) {
|
||||
g.battle.ecazOccupy = { event: battleEvent, ecaz: ecaz!.id, ally: ally!.id, lead: null, canceled: false };
|
||||
const opponent = [choice.attacker, choice.defender].find(id => id !== ecaz!.id && id !== ally!.id)!;
|
||||
@@ -45200,6 +45271,7 @@ function applyActionInner(
|
||||
b.revealed = true;
|
||||
recordTurnActionRevealedPlans(g);
|
||||
reconcileMixedBattleQuestions(g);
|
||||
reconcileFutureBattleLeaderQuestions(g);
|
||||
for (const plan of Object.values(b.plans)) {
|
||||
const l = g.players
|
||||
.flatMap((p) => p.leaders)
|
||||
@@ -45717,6 +45789,17 @@ export function viewGame(state: Game, id: string, now?: number) {
|
||||
g.truthtrance.question.kind === 'battlePlan'
|
||||
? battleTruthAnswers(g, me, g.truthtrance.question.claim)
|
||||
: null,
|
||||
truthFutureBattleLeaderAnswers: g.truthtrance?.stage === 'answer' &&
|
||||
g.truthtrance.question?.target === id && g.truthtrance.question.kind === 'futureBattleLeader'
|
||||
? futureBattleLeaderTruthAnswers(g, me, g.truthtrance.question) : null,
|
||||
truthFutureBattleLeaderTargets: g.status === 'playing' && !g.advanced && g.phase === 5 &&
|
||||
!g.expansions.length && !g.techTokens && !g.strongholdCards && !g.nexusCards && !g.homeworlds &&
|
||||
!g.leaderSkills && !g.discoveryEnabled && !g.ecazTreachery && !g.battle && !g.response &&
|
||||
!g.decision && !g.phaseOpening && !g.pendingKarama && !g.pendingTreacheryDiscard
|
||||
? g.players.filter(player => player.id !== id && !(g.futureBattleLeaderPromises ?? []).some(row =>
|
||||
row.turn === g.turn && row.player === player.id && (row.status === 'waiting' || row.status === 'bound')))
|
||||
.map(player => player.id) : [],
|
||||
futureBattleLeaderPromises: (g.futureBattleLeaderPromises ?? []).filter(row => row.player === id),
|
||||
bidPromiseLot: (() => { const lot = bidOpportunity(g); return lot ? { turn: lot.turn, lot: lot.lot } : null; })(),
|
||||
karamaAuctionCards: quotedAuctionKaramaCards(g, me),
|
||||
karamaShipmentCards: quotedShipmentKaramaCards(g, me),
|
||||
|
||||
Reference in new issue
Block a user