Allow Basic bribes during selected pending decisions
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
171bc3a636
commit
72f6ec2b12
11 files changed
+435
-29
No files matched your search
+59
-20
@@ -109,7 +109,7 @@ import { PhaseBidPromiseError, phaseBidPromiseModeSupported, livePhaseBidPromise
|
||||
type PhaseBidPromise } from './turn-bid-promises';
|
||||
import { classicDiscoveryLeaderSkillsProfile, classicDiscoveryNexusProfile, homeworldDiscoveryProfile, discoveryModeSupported, discoveryModuleProfile, mixedE1E2DiscoveryNexusProfile, mixedE3DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
|
||||
import { advancedMoritaniLeaderSkillsProfile, selectedDeckLeaderSkillsProfile, homeworldLeaderSkillsProfile, independentTreacheryLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, guildBetrayalNexusLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, mixedE3NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeEcazLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedE3LeaderSkillsProfile, pairedE3NexusLeaderSkillsProfile, pairedNexusLeaderSkillsProfile, singleE1E2NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
|
||||
import { bribeTimingBlock, bribesCloseBeforeNextMentat, maximumBribe, type BribeOptions } from './bribe-options';
|
||||
import { bribeReceiptTiming, bribeTimingBlock, bribesCloseBeforeNextMentat, maximumBribe, type BribeOptions } from './bribe-options';
|
||||
import { quoteAtreidesFullPlan } from './atreides-full-plan';
|
||||
import { quoteTleilaxuSpecialKarama } from './tleilaxu-special-karama';
|
||||
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport, type AutomaticAdvisorGamont, type AutomaticAdvisorBattle, type AutomaticAdvisorHomeworldTransport, type AutomaticAdvisorFaceDance, type AutomaticAdvisorStorm, type AutomaticAdvisorRelocation } from './advisor-automatic-flip';
|
||||
@@ -17299,8 +17299,38 @@ function bribePaymentPreservesPromises(g: Game, p: Player, recipient: string, am
|
||||
? !!turnActionCompletion(trial, p.id) : !hasBribePromises(trial, p.id) || !!bribePromiseCompletion(trial, actor);
|
||||
} catch (error) { if (error instanceof BribeExpressionPromiseError || error instanceof BribePromiseError || error instanceof RuleError) return false; throw error; }
|
||||
}
|
||||
/** Selected Basic parents accept a direct payment without replacing or
|
||||
* advancing their pending state. Existing whole-promise planners retain their
|
||||
* original timing boundary until those compositions are implemented. */
|
||||
function basicBribeInterrupt(g: Game, p: Player): boolean {
|
||||
if (g.status !== 'playing' || g.advanced || g.expansions.length !== 1 ||
|
||||
g.inflation?.side === 'double' || g.techTokens || g.strongholdCards || g.leaderSkills ||
|
||||
g.homeworlds || g.nexusCards || g.discoveryEnabled || g.sandtrout || g.coalitionState ||
|
||||
g.ecazTreachery || g.semutaPreview || g.semutaRecruitsPreview || g.truthtrance || g.response ||
|
||||
g.pendingKarama || g.pendingKull || g.pendingTreacheryDiscard || g.pendingNullentropy ||
|
||||
g.bureaucratPayments?.pending || g.battle || g.pendingRicheseGift || g.pendingShipment ||
|
||||
g.pendingGuildTransport || g.pendingArrivalOverlap || g.pendingAmbassador || g.pendingEmperorAuctionGift ||
|
||||
liveShipmentPromises(g.shipmentPromises ?? [], p.id, g.turn).length ||
|
||||
hasMovementPromises(g, p.id) || hasBribePromises(g, p.id) ||
|
||||
liveRevivalPromises(g.revivalPromises ?? [], p.id, g.turn).length ||
|
||||
liveLeaderRevivalPromises(g.leaderRevivalPromises ?? [], p.id, g.turn).length ||
|
||||
liveRevivalExpressionPromises(g.revivalExpressionPromises ?? [], p.id, g.turn).length ||
|
||||
liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn).length ||
|
||||
livePhaseBidPromises(g.phaseBidPromises ?? [], p.id, g.turn).length ||
|
||||
(g.pledgePromises ?? []).some(row => row.player === p.id && row.turn === g.turn && !row.fulfilled && !row.released) ||
|
||||
(g.deals ?? []).some(row => row.status === 'active' && row.payment?.payer === p.id && row.paymentStatus === 'pending')) return false;
|
||||
const classic = ['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'];
|
||||
if (g.expansions[0] === 'ix') return !!g.phaseOpening && !!g.amalWindowOpen && !g.decision &&
|
||||
g.players.every(seat => [...classic, 'ixians', 'tleilaxu'].includes(seat.faction));
|
||||
const extortion = g.moritaniExtortion, decision = g.decision;
|
||||
return g.expansions[0] === 'ecaz' && !g.phaseOpening && g.phase === 8 &&
|
||||
g.players.every(seat => [...classic, 'moritani'].includes(seat.faction)) &&
|
||||
extortion?.stage === 'payment' && decision?.kind === 'moritaniExtortion' &&
|
||||
decision.event === extortionEvent(extortion) && decision.player === extortion.queue[extortion.cursor];
|
||||
}
|
||||
function projectedBribes(g: Game, p: Player): BribeOptions {
|
||||
const available = Math.max(0, uncommittedSpice(g, p)), blocked = bribeTimingBlock(g);
|
||||
const available = Math.max(0, uncommittedSpice(g, p)),
|
||||
blocked = basicBribeInterrupt(g, p) ? null : bribeTimingBlock(g);
|
||||
const constrained = liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn).length > 0 || hasBribePromises(g, p.id) ||
|
||||
g.battle?.truthPromises?.some(promise => promise.player === p.id && !promise.released && !g.battle?.revealed && !g.battle?.plans[p.id]) ||
|
||||
liveShipmentPromises(g.shipmentPromises ?? [], p.id, g.turn).length ||
|
||||
@@ -17308,7 +17338,7 @@ function projectedBribes(g: Game, p: Player): BribeOptions {
|
||||
liveRevivalPromises(g.revivalPromises ?? [], p.id, g.turn).some(promise => promise.answer) ||
|
||||
liveLeaderRevivalPromises(g.leaderRevivalPromises ?? [], p.id, g.turn).some(promise => promise.answer) ||
|
||||
liveRevivalExpressionPromises(g.revivalExpressionPromises ?? [], p.id, g.turn).some(promise => promise.answer);
|
||||
return { available, incoming: p.bribes, blocked,
|
||||
return { available, incoming: p.bribes, receiptTiming: bribeReceiptTiming(g), blocked,
|
||||
targets: g.players.filter(target => target.id !== p.id && !bribeAllied(g, p, target.id)).map(target => {
|
||||
let limit = !blocked && constrained ? maximumBribe(available,
|
||||
amount => bribePaymentPreservesPromises(g, p, target.id, amount)) : available;
|
||||
@@ -42205,6 +42235,7 @@ function applyActionWithInitiator(state: Game, id: string, action: Action, initi
|
||||
if (action?.type === 'dealPay') {
|
||||
requireRule(state.status === 'playing' && Object.keys(action).sort().join(',') === 'deal,type' &&
|
||||
typeof action.deal === 'string', 'Choose your pending deal payment.');
|
||||
requireRule(!bribeTimingBlock(state), 'Finish the current decision or response before paying a deal.');
|
||||
const g = structuredClone(state), index = (g.deals ?? []).findIndex(deal => deal.id === action.deal);
|
||||
requireRule(index >= 0, 'This deal is no longer available.');
|
||||
const current = g.deals![index], payment = current.payment;
|
||||
@@ -42275,6 +42306,7 @@ function applyActionWithInitiator(state: Game, id: string, action: Action, initi
|
||||
// The payer consented to these immutable terms. Run the native action
|
||||
// on the same cloned state so a failed payment also rolls back consent.
|
||||
try {
|
||||
requireRule(!bribeTimingBlock(g), 'Finish the current decision or response before paying a deal.');
|
||||
requireRule(!getPlayer(g, next.payment.payer).autopilot, 'The payer is not controlling this seat.');
|
||||
return applyActionWithInitiator(g, next.payment.payer, {
|
||||
type: 'bribe', target: next.payment.recipient, amount: next.payment.amount,
|
||||
@@ -43009,6 +43041,25 @@ export function normalizeAutomaticGame(state: Game): Game {
|
||||
return g;
|
||||
}
|
||||
|
||||
function payOrdinaryBribe(g: Game, p: Player, action: Action): void {
|
||||
const blocked = bribeTimingBlock(g);
|
||||
requireRule(!blocked || basicBribeInterrupt(g, p), blocked ?? 'Bribes are unavailable.');
|
||||
const target = getPlayer(g, stringField(action.target));
|
||||
requireRule(
|
||||
target.id !== p.id && !bribeAllied(g, p, target.id),
|
||||
'Bribes are between different, non-allied factions.',
|
||||
);
|
||||
const n = integer(action.amount, 1, uncommittedSpice(g, p), 'Spice');
|
||||
checkBureaucratContributors(g,target.id,[{payer:p.id,amount:n}]);
|
||||
recordBribePayment(g, p.id, target.id, n);
|
||||
p.spice -= n;
|
||||
log(
|
||||
g,
|
||||
`${p.name} paid a ${n}-spice bribe to ${target.name}; its recipient share becomes spendable ${bribeReceiptTiming(g) === 'nextTurn' ? 'at the start of next turn’s Mentat Pause' : 'at the next Mentat Pause opening'}.`,
|
||||
);
|
||||
const source = stampBureaucratPayment(g,'bribe',p.id,target.id,n).bureaucratPayment;
|
||||
if (!offerBureaucratPayment(g,source,{kind:'bribe'})) target.bribes += n;
|
||||
}
|
||||
function applyActionInner(
|
||||
state: Game,
|
||||
id: string,
|
||||
@@ -43553,6 +43604,10 @@ function applyActionInner(
|
||||
decideBiddingEnd(g, p, action);
|
||||
return g;
|
||||
}
|
||||
if (t === 'bribe' && basicBribeInterrupt(g, p)) {
|
||||
payOrdinaryBribe(g, p, action);
|
||||
return g;
|
||||
}
|
||||
if (t === 'emperorGift' && g.phaseOpening && !g.response) {
|
||||
const quote = emperorPhaseOpeningGiftQuote(g, p);
|
||||
requireRule(quote && Object.keys(action).sort().join(',') === 'amount,type',
|
||||
@@ -45834,23 +45889,7 @@ function applyActionInner(
|
||||
return g;
|
||||
}
|
||||
if (t === 'bribe') {
|
||||
const blocked = bribeTimingBlock(g);
|
||||
requireRule(!blocked, blocked ?? 'Bribes are unavailable.');
|
||||
const target = getPlayer(g, stringField(action.target));
|
||||
requireRule(
|
||||
target.id !== id && !bribeAllied(g, p, target.id),
|
||||
'Bribes are between different, non-allied factions.',
|
||||
);
|
||||
const n = integer(action.amount, 1, uncommittedSpice(g, p), 'Spice');
|
||||
checkBureaucratContributors(g,target.id,[{payer:p.id,amount:n}]);
|
||||
recordBribePayment(g, p.id, target.id, n);
|
||||
p.spice -= n;
|
||||
log(
|
||||
g,
|
||||
`${p.name} paid a ${n}-spice bribe to ${target.name}; its recipient share becomes spendable ${g.phase === 8 ? 'at the start of next turn’s Mentat Pause' : 'at the next Mentat Pause opening'}.`,
|
||||
);
|
||||
const source = stampBureaucratPayment(g,'bribe',p.id,target.id,n).bureaucratPayment;
|
||||
if (!offerBureaucratPayment(g,source,{kind:'bribe'})) target.bribes += n;
|
||||
payOrdinaryBribe(g, p, action);
|
||||
return g;
|
||||
}
|
||||
if (t === 'stormDial') {
|
||||
|
||||
Reference in new issue
Block a user