Route Advanced Emperor allied auction funding
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
3ba9087405
commit
599af2475d
19 files changed
+883
-35
No files matched your search
+79
-18
@@ -1,5 +1,6 @@
|
||||
import { createTwoPlayerControllers, validateTwoPlayerControllersLobby, validateTwoPlayerControllersComplete,
|
||||
addTwoPlayerControlledSeat, TwoPlayerControllersError, type TwoPlayerControllers } from './two-player-controllers';
|
||||
import { routeAuctionPayment, type AuctionPaymentLeg } from './auction-payment-routing';
|
||||
import { createFutureBattleLeader, bindFutureBattleLeader, settleFutureBattleLeader, releaseFutureBattleLeader,
|
||||
expireFutureBattleLeader, futureBattleLeaderTiming, futureBattleLeaderWindow, type FutureBattleLeaderReceipt } from './future-battle-leader';
|
||||
import { createFactionDraft, validateFactionDraft, currentFactionDrafter, chooseFaction,
|
||||
@@ -2098,6 +2099,8 @@ export type Game = {
|
||||
recipient?: 'bank';
|
||||
/** Actual Richese donor share of another buyer's lot; paid to Emperor or bank. */
|
||||
richeseContribution?: number;
|
||||
/** New Advanced invoices preserve each actual destination through responses. */
|
||||
paymentRouting?: AuctionPaymentLeg[];
|
||||
} | null;
|
||||
pendingAmbassador?: {
|
||||
/** Fresh entries retain their actual arrival source; older frames omit it. */
|
||||
@@ -11366,7 +11369,8 @@ function spiceBankerIncomeIntegrity(g: Game): void {
|
||||
const payment = carrier?.spiceBankerIncomePayment;
|
||||
if (!payment) {
|
||||
requireRule(!carrier || ('winner' in carrier ? carrier.free ||
|
||||
(carrier.recipient !== 'bank' && auctionEmperorIncomeAmount(carrier) === 0) :
|
||||
(carrier.recipient !== 'bank' && auctionEmperorIncomeAmount(carrier) === 0 &&
|
||||
!carrier.paymentRouting?.some(leg => leg.recipient === 'bank')) :
|
||||
carrier.kind !== 'guildIncome' &&
|
||||
!(carrier.kind === 'revivalIncome' && carrier.recipient !== carrier.owner && (carrier.amount ?? 0) > 1)),
|
||||
'The original paid transaction lost its native bank invoice.');
|
||||
@@ -11379,6 +11383,13 @@ function spiceBankerIncomeIntegrity(g: Game): void {
|
||||
new Set(payment.legs.map(leg => leg.payer)).size === payment.legs.length,
|
||||
'The original paid bank transaction changed its event or contribution legs.');
|
||||
if (carrier && 'winner' in carrier) {
|
||||
if (carrier.paymentRouting) {
|
||||
const expected = carrier.paymentRouting.filter(leg => leg.recipient !== 'seller')
|
||||
.map(leg => ({payer:leg.payer, amount:leg.amount, recipient:leg.recipient === 'emperor' ? 'player' : 'bank'}));
|
||||
requireRule(payment.kind === 'auction' && !carrier.free && JSON.stringify(payment.legs) === JSON.stringify(expected),
|
||||
'The deferred bank invoice no longer matches its original auction payer legs.');
|
||||
continue;
|
||||
}
|
||||
const amount = carrier.recipient === 'bank' ? carrier.amount : auctionEmperorIncomeAmount(carrier);
|
||||
const contributionOnly = carrier.origin !== 'normal' && carrier.seller !== carrier.winner && carrier.recipient !== 'bank';
|
||||
requireRule(payment.kind === 'auction' && !carrier.free &&
|
||||
@@ -11590,7 +11601,18 @@ function bureaucratPaymentIntegrity(g: Game) {
|
||||
requireRule(g.leaderSkills && bureaucratPaymentModeSupported(g) && carrier.bureaucratPaymentEvent === source.event,
|
||||
'The saved Bureaucrat payment lost its module or independent event.');
|
||||
validateBureaucratSource(g,source);
|
||||
if ('winner' in carrier) requireRule(source.kind === 'auction' && !carrier.free &&
|
||||
if ('winner' in carrier && carrier.paymentRouting) {
|
||||
const sellerIncome = carrier.origin !== 'normal' && carrier.seller !== carrier.winner;
|
||||
const shares = carrier.paymentRouting.filter(leg => leg.recipient === (sellerIncome ? 'seller' : 'emperor'))
|
||||
.map(leg => ({payer:leg.payer,amount:leg.amount}));
|
||||
requireRule(source.kind === 'auction' && !carrier.free &&
|
||||
source.payee === (sellerIncome ? carrier.seller : byFaction(g,'emperor')?.id) &&
|
||||
source.amount === shares.reduce((sum,share) => sum + share.amount,0) &&
|
||||
shares.some(share => share.payer === source.payer) &&
|
||||
(!source.contributions || JSON.stringify(source.contributions) === JSON.stringify(shares)) &&
|
||||
(sellerIncome ? source.richeseSaleAmount === carrier.amount : source.richeseSaleAmount === undefined),
|
||||
'The Bureaucrat source no longer matches its paid auction sale.');
|
||||
} else if ('winner' in carrier) requireRule(source.kind === 'auction' && !carrier.free &&
|
||||
(source.richeseSaleAmount !== undefined
|
||||
? g.advanced && carrier.origin !== 'normal' && carrier.seller !== carrier.winner &&
|
||||
(carrier.richeseContribution ?? 0) > 0 && source.richeseSaleAmount === carrier.amount &&
|
||||
@@ -15414,15 +15436,25 @@ function settleRicheseSoldLot(g: Game, quote: Extract<RicheseSettlementQuote,{ki
|
||||
if (!confirmed && beginNexusEmperorSellerPurchase(g, quote, bank)) return;
|
||||
const lot = g.richeseAuction!, owner = getPlayer(g,lot.owner);
|
||||
const winner = getPlayer(g, quote.winner), card = quote.card;
|
||||
const emperor = byFaction(g, 'emperor');
|
||||
const paymentRouting = !free && g.advanced && emperor && winner.ally === emperor.id && quote.allyPayment > 0
|
||||
? routeAuctionPayment({advanced:true, free:false, price:quote.amount, buyer:winner.id,
|
||||
emperor:emperor.id, seller:owner.id, bankOnly:bank, contributions:[
|
||||
...(quote.ownPayment > 0 ? [{payer:winner.id,amount:quote.ownPayment}] : []),
|
||||
{payer:emperor.id,amount:quote.allyPayment},
|
||||
]}) : undefined;
|
||||
const richeseContribution = !free && g.advanced && winner.id !== owner.id && winner.ally === owner.id
|
||||
? quote.allyPayment : 0;
|
||||
const bankAmount = free ? 0 : bank || winner.id === owner.id ? quote.amount : richeseContribution;
|
||||
const emperor = byFaction(g, 'emperor');
|
||||
const incomePayment = g.spiceBankerIncomePreview && bankAmount > 0 ? bankerIncomePayment(g, 'auction',
|
||||
const routedBankLegs = paymentRouting?.filter(leg => leg.recipient !== 'seller').map(leg => ({
|
||||
payer:leg.payer, amount:leg.amount, recipient:leg.recipient === 'emperor' ? 'player' as const : 'bank' as const,
|
||||
}));
|
||||
const incomePayment = g.spiceBankerIncomePreview && (routedBankLegs?.length || bankAmount > 0) ? bankerIncomePayment(g, 'auction',
|
||||
routedBankLegs ?? (
|
||||
bank || winner.id === owner.id
|
||||
? [{payer:winner.id,amount:quote.ownPayment,recipient:bank || !emperor ? 'bank' : 'player'},
|
||||
...(quote.allyPayment ? [{payer:winner.ally!,amount:quote.allyPayment,recipient:bank || !emperor ? 'bank' as const : 'player' as const}] : [])]
|
||||
: [{payer:owner.id,amount:richeseContribution,recipient:emperor ? 'player' : 'bank'}]) : undefined;
|
||||
: [{payer:owner.id,amount:richeseContribution,recipient:emperor ? 'player' : 'bank'}])) : undefined;
|
||||
if (!free) payWithAlly(g, winner, quote.amount, quote.allyPayment);
|
||||
g.richeseFunding = {};
|
||||
const retained = lot.source === 'blackMarket' && winner.id === owner.id;
|
||||
@@ -15434,10 +15466,14 @@ function settleRicheseSoldLot(g: Game, quote: Extract<RicheseSettlementQuote,{ki
|
||||
}
|
||||
g.currentAuctionSale = {
|
||||
...(g.homeworldOccupationPreview ? { occupiedEvent: lot.event,
|
||||
occupiedContributions: free ? [] : [{ payer: winner.id, amount: quote.ownPayment },
|
||||
...(quote.allyPayment ? [{ payer: winner.ally!, amount: quote.allyPayment }] : [])] } : {}),
|
||||
occupiedContributions: free ? [] : paymentRouting
|
||||
? paymentRouting.filter(leg => leg.recipient === (winner.id === owner.id ? 'emperor' : 'seller'))
|
||||
.map(({payer,amount}) => ({payer,amount}))
|
||||
: [{ payer: winner.id, amount: quote.ownPayment },
|
||||
...(quote.allyPayment ? [{ payer: winner.ally!, amount: quote.allyPayment }] : [])] } : {}),
|
||||
...(richeseContribution ? {richeseContribution} : {}),
|
||||
...(incomePayment ? {spiceBankerIncomePayment:incomePayment} : {}),
|
||||
...(paymentRouting ? {paymentRouting} : {}),
|
||||
winner: winner.id,
|
||||
amount: quote.amount,
|
||||
free,
|
||||
@@ -15446,7 +15482,8 @@ function settleRicheseSoldLot(g: Game, quote: Extract<RicheseSettlementQuote,{ki
|
||||
...(bank ? {recipient:'bank' as const} : {}),
|
||||
...(!free ? stampAuctionBureaucrat(g,winner,quote.amount,quote.allyPayment,owner.id) : {}),
|
||||
};
|
||||
if (bank || !emperor) awardBankerIncome(g, incomePayment);
|
||||
if (bank || !emperor || paymentRouting && auctionEmperorIncomeAmount(g.currentAuctionSale) === 0)
|
||||
awardBankerIncome(g, incomePayment);
|
||||
log(
|
||||
g,
|
||||
`${winner.name} ${retained ? 'retained their own Black Market card under provisional paid self-purchase' : `bought ${lot.source === 'cache' ? card.name : 'the concealed Black Market card'}`} for ${quote.amount} spice.${free ? ' Emperor Nexus retained the proven price; no seller or bank payment occurred.' : ' The funded bid was paid automatically.'}`,
|
||||
@@ -16318,9 +16355,16 @@ function coalitionFundingAvailable(g: Game, p: Player, committedBid = false): bo
|
||||
if (!group || group.members.length < 3) return false;
|
||||
const groupHasUnsettled = (records: readonly {player: string; fulfilled?: boolean; released?: boolean}[] | undefined) =>
|
||||
!!records?.some(record => group.members.includes(record.player) && !record.fulfilled && !record.released);
|
||||
// A current No bribe answer only constrains later ordinary bribes. Moving
|
||||
// spice into alliance escrow or paying a bid/shipment does not satisfy it.
|
||||
// Positive and stale answers still need their funding preserved separately.
|
||||
const groupHasFundedBribe = (records: readonly {player: string; turn: number; answer: boolean;
|
||||
fulfilled?: boolean; released?: boolean}[] | undefined) => !!records?.some(record =>
|
||||
group.members.includes(record.player) && !record.fulfilled && !record.released &&
|
||||
(record.turn !== g.turn || record.answer));
|
||||
return committedBid || ![g.bidPromises, g.phaseBidPromises, g.turnActionPromises,
|
||||
g.revivalPromises, g.leaderRevivalPromises, g.revivalExpressionPromises,
|
||||
g.bribePromises, g.bribeExpressionPromises].some(groupHasUnsettled);
|
||||
g.revivalPromises, g.leaderRevivalPromises, g.revivalExpressionPromises].some(groupHasUnsettled) &&
|
||||
!groupHasFundedBribe(g.bribePromises) && !groupHasFundedBribe(g.bribeExpressionPromises);
|
||||
}
|
||||
function coalitionEmperorGroup(g: Game, p: Player) {
|
||||
if (!g.coalitionState || p.faction !== 'emperor' || g.status !== 'playing' ||
|
||||
@@ -17037,9 +17081,11 @@ function payWithAlly(g: Game, p: Player, cost: number, allyPayment: number) {
|
||||
if (credit) credit.amount -= allyPayment;
|
||||
}
|
||||
function auctionBureaucratParties(g:Game,p:Player,amount:number,allyPayment:number,seller?:string) {
|
||||
const split = g.advanced && allyPayment > 0 && seller !== undefined && seller !== p.id && p.ally === seller;
|
||||
const emperorBank = g.advanced && allyPayment > 0 && p.ally === byFaction(g,'emperor')?.id;
|
||||
const split = g.advanced && allyPayment > 0 && seller !== undefined && seller !== p.id &&
|
||||
(p.ally === seller || emperorBank);
|
||||
return {payee:seller && seller !== p.id ? seller : byFaction(g,'emperor')?.id,
|
||||
shares:(split ? [{payer:p.id,amount:amount-allyPayment}]
|
||||
shares:(split || emperorBank ? [{payer:p.id,amount:amount-allyPayment}]
|
||||
: [{payer:p.id,amount:amount-allyPayment},...(allyPayment ? [{payer:p.ally!,amount:allyPayment}] : [])]).filter(share => share.amount > 0),
|
||||
richeseSaleAmount:split ? amount : undefined};
|
||||
}
|
||||
@@ -18085,6 +18131,17 @@ function auctionNext(g: Game) {
|
||||
function settleAuction(g: Game, free = false, automatic = false, emperorNexus = false) {
|
||||
const a = g.auction!;
|
||||
const winner = getPlayer(g, a.bidder!);
|
||||
const emperor = byFaction(g, 'emperor');
|
||||
const donors = a.coalitionContributions ?? (a.allyPayment && winner.ally
|
||||
? [{donor:winner.ally, amount:a.allyPayment}] : []);
|
||||
const donorTotal = donors.reduce((sum, leg) => sum + leg.amount, 0);
|
||||
const paymentRouting = !free && g.advanced && emperor &&
|
||||
donors.some(leg => leg.donor === emperor.id && leg.amount > 0)
|
||||
? routeAuctionPayment({advanced:true, free:false, price:a.bid, buyer:winner.id,
|
||||
emperor:emperor.id, seller:null, contributions:[
|
||||
...(a.bid > donorTotal ? [{payer:winner.id, amount:a.bid-donorTotal}] : []),
|
||||
...donors.map(leg => ({payer:leg.donor, amount:leg.amount})),
|
||||
]}) : undefined;
|
||||
if (free) delete a.coalitionContributions;
|
||||
else if (a.coalitionContributions !== undefined) {
|
||||
coalitionBidPaymentQuote(g, winner, a.bid, a.coalitionContributions, true);
|
||||
@@ -18096,18 +18153,21 @@ function settleAuction(g: Game, free = false, automatic = false, emperorNexus =
|
||||
g.coalitionEscrow = {...g.coalitionEscrow!, pledges:result.pledges};
|
||||
} else payWithAlly(g, winner, a.bid, a.allyPayment ?? 0);
|
||||
winner.hand.push(a.cards[a.index]);
|
||||
const emperor = byFaction(g, 'emperor');
|
||||
const paysEmperor = !!emperor && emperor.id !== winner.id;
|
||||
const incomePayment = !free && g.spiceBankerIncomePreview ? bankerIncomePayment(g, 'auction', [
|
||||
const incomePayment = !free && g.spiceBankerIncomePreview ? bankerIncomePayment(g, 'auction', paymentRouting
|
||||
? paymentRouting.map(leg => ({payer:leg.payer,amount:leg.amount,
|
||||
recipient:leg.recipient === 'emperor' ? 'player' as const : 'bank' as const})) : [
|
||||
{payer: winner.id, amount: a.bid - (a.allyPayment ?? 0), recipient: paysEmperor ? 'player' : 'bank'},
|
||||
...(a.allyPayment ? [{payer: winner.ally!, amount: a.allyPayment,
|
||||
recipient: paysEmperor ? 'player' as const : 'bank' as const}] : []),
|
||||
]) : undefined;
|
||||
g.currentAuctionSale = {
|
||||
...(g.homeworldOccupationPreview ? { occupiedEvent: crypto.randomUUID(),
|
||||
occupiedContributions: [{ payer: winner.id, amount: free ? 0 : a.bid - (a.allyPayment ?? 0) },
|
||||
...(!free && a.allyPayment ? [{ payer: winner.ally!, amount: a.allyPayment }] : [])] } : {}),
|
||||
occupiedContributions: paymentRouting ? paymentRouting.filter(leg => leg.recipient === 'emperor')
|
||||
.map(({payer,amount}) => ({payer,amount})) : [{ payer: winner.id, amount: free ? 0 : a.bid - (a.allyPayment ?? 0) },
|
||||
...(!free && a.allyPayment ? [{ payer: winner.ally!, amount: a.allyPayment }] : [])] } : {}),
|
||||
...(incomePayment ? {spiceBankerIncomePayment: incomePayment} : {}),
|
||||
...(paymentRouting ? {paymentRouting} : {}),
|
||||
winner: winner.id,
|
||||
amount: a.bid,
|
||||
free,
|
||||
@@ -18115,7 +18175,7 @@ function settleAuction(g: Game, free = false, automatic = false, emperorNexus =
|
||||
seller: null,
|
||||
...(!free ? stampAuctionBureaucrat(g,winner,a.bid,a.allyPayment ?? 0) : {}),
|
||||
};
|
||||
if (!paysEmperor) awardBankerIncome(g, incomePayment);
|
||||
if (!paysEmperor || auctionEmperorIncomeAmount(g.currentAuctionSale) === 0) awardBankerIncome(g, incomePayment);
|
||||
log(
|
||||
g,
|
||||
`${faction(winner.faction).name} won a treachery card for ${emperorNexus ? `the Emperor Nexus card after proving ${a.bid} spice` : free ? 'a Karama' : `${a.bid} spice`}.${automatic ? ' The declared spice payment was the only available payment method.' : ''}`,
|
||||
@@ -18205,7 +18265,8 @@ function commitAuctionContinuation(g: Game, quote: AuctionContinuationQuote,bure
|
||||
const sale = g.currentAuctionSale!, received = step.amount - (bureaucratDiversion ?? 0);
|
||||
const source: OccupiedPercentageSource = { kind: 'richese-income', event: crypto.randomUUID(),
|
||||
native: seller.id, amount: received, payment: { id: sale.occupiedEvent!,
|
||||
binding: occupiedProducerProof(g, 'richese-income'), paid: sale.amount,
|
||||
binding: occupiedProducerProof(g, 'richese-income'),
|
||||
paid: sale.paymentRouting ? sale.occupiedContributions!.reduce((sum,leg) => sum + leg.amount,0) : sale.amount,
|
||||
contributions: sale.occupiedContributions!, recipient: seller.id, received } };
|
||||
if (admitOccupiedPercentage(g, source, received, { kind: 'auction', quote: { ...quote, steps: [] } })) return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user