Support group Guild-rate reserve shipments
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-28 23:27:49 +11:00
1 parent 1eeeb32566
commit b89c225d5b
9 files changed
+297 -26

No files matched your search

+24 -11
View File
@@ -623,6 +623,7 @@ import { IxStartingCardError, ixStartingCardPoolSize, ixStartingCardDealPlan } f
import { ecazDukeRevivalBlock, quoteEcazDukeRevival } from './ecaz-duke-revival';
import {
reserveShipmentCost,
groupGuildShipmentRate,
guildShipmentCost,
guildShipmentIncome,
shipmentPaymentBounds,
@@ -16047,8 +16048,8 @@ function applyCoalitionTransition(g: Game, result: CoalitionTransition) {
}
syncCoalitionEscrow(g, before);
}
/** Initial staged group-funding scope: ordinary Basic planet shipments, with
* Guild outside the group. Guild-allied rate and other purchases remain separate. */
/** Staged group-funding scope: ordinary Basic planet shipments and bids;
* independent purchased rates and special transactions remain separate. */
function coalitionFundingAvailable(g: Game, p: Player, committedBid = false): boolean {
if (!g.coalitionEscrow || !g.coalitionState || g.status !== 'playing' || g.advanced || ![3, 5].includes(g.phase) ||
g.expansions.length || g.homeworlds || g.nexusCards || g.leaderSkills || g.techTokens ||
@@ -16057,8 +16058,10 @@ function coalitionFundingAvailable(g: Game, p: Player, committedBid = false): bo
(!committedBid && (g.bidPromises?.length || g.phaseBidPromises?.length || g.turnActionPromises?.length ||
g.revivalPromises?.length || g.bribePromises?.length))) return false;
const group = coalitionRule(() => coalitionOf(g.players, g.coalitionState, p.id));
const guild = byFaction(g, 'guild');
return group?.members.length === 3 && (g.phase === 3 || !guild || !group.members.includes(guild.id));
return group?.members.length === 3;
}
function guildGroupAlly(g: Game, p: Player): boolean {
return coalitionRule(() => groupGuildShipmentRate(g.players, p.id, g.coalitionState));
}
function coalitionShipmentQuote(g: Game, p: Player, cost: number, contributions: CoalitionContribution[]) {
requireRule(g.phase === 5 && coalitionFundingAvailable(g, p), 'Group funding is unavailable for this shipment.');
@@ -38028,7 +38031,8 @@ function ordinaryGuildRateFullCost(g: Game, shipment: PendingShipment): number |
const pricedAmount = shipment.noField ? 1 + (shipment.noFieldReserves ?? 0)
: shipment.alliedNoField || shipment.nexusEvent ? 1
: shipment.amount - (shipment.smuggler ? 1 : 0);
if (!guild || (p.id !== guild.id && !(p.ally === guild.id && guild.ally === p.id)) ||
if (!guild || (p.id !== guild.id && !(p.ally === guild.id && guild.ally === p.id) &&
!guildGroupAlly(g, p)) ||
g.karamaShipping?.player === p.id ||
(g.guildRateBlocked?.turn === g.turn && g.guildRateBlocked.player === p.id) ||
shipment.source || shipment.guildSecretEvent || shipment.richesePair)
@@ -38132,10 +38136,16 @@ function finishGuildRateResponse(g: Game, response: ResponseWindow, canceled: bo
g.guildRateBlocked = { turn: g.turn, player: shipper.id };
const bounds = shipmentPaymentBounds(fullCost, shipper.spice,
aidFor(g, shipper)?.amount ?? 0);
let groupFunded = true;
if (shipment.coalitionContributions !== undefined) {
try { coalitionShipmentQuote(g, shipper, fullCost, shipment.coalitionContributions); }
catch (error) { if (!(error instanceof RuleError)) throw error; groupFunded = false; }
}
// A loan supplier approved the original whole price, not an increased gift.
const supplierApprovalChanges = shipment.alliedNoField &&
shipment.alliedNoField.payer === shipment.alliedNoField.owner;
if (supplierApprovalChanges || shipment.allyPayment < bounds.minimum || shipment.allyPayment > bounds.maximum) {
if (supplierApprovalChanges || (shipment.coalitionContributions !== undefined ? !groupFunded
: shipment.allyPayment < bounds.minimum || shipment.allyPayment > bounds.maximum)) {
if (cunning) {
delete cunning.shipment;
saveGuildCunning(g, cunning);
@@ -38680,7 +38690,10 @@ function commitShipment(g: Game, shipment: PendingShipment, settlement?: {recipi
const guild = byFaction(g, 'guild');
const groupShares = shipment.coalitionContributions;
const groupTotal = groupShares?.reduce((sum, share) => sum + share.amount, 0) ?? 0;
const guildPayment = groupShares ? (guild ? cost : 0) : guildShipmentIncome({
const groupContributors = groupShares ? [{payer:p.id, amount:cost - groupTotal},
...groupShares.map(share => ({payer:share.donor, amount:share.amount}))]
.filter(share => share.payer !== guild?.id && share.amount > 0) : null;
const guildPayment = groupContributors ? groupContributors.reduce((sum, share) => sum + share.amount, 0) : guildShipmentIncome({
guild: guild?.id,
shipper: p.id,
ally: p.ally,
@@ -38690,9 +38703,8 @@ function commitShipment(g: Game, shipment: PendingShipment, settlement?: {recipi
!!(shipment.alliedNoField && alliedNoFieldPricing(g, p).bankOnly),
});
if (!settlement?.recipient && guild && guildPayment > 0) {
const named = groupShares ? [{payer:p.id, amount:cost - groupTotal},
...groupShares.map(share => ({payer:share.donor, amount:share.amount}))]
: [{ payer: p.id, amount: p.id === guild.id ? 0 : cost - allyPayment },
const named = groupContributors ??
[{ payer: p.id, amount: p.id === guild.id ? 0 : cost - allyPayment },
...(allyPayment && p.ally !== guild.id ? [{ payer: p.ally!, amount: allyPayment }] : [])];
g.response = guildPaymentResponse(g, guild.id,
groupShares ? named.map(share => share.amount) : shipmentIncomeContributions(g, p, cost, allyPayment),
@@ -39292,8 +39304,9 @@ function richesePairIntegrity(g: Game) {
}
}
function shipmentHalfRate(g: Game, p: Player) {
const guild = byFaction(g, 'guild');
return g.karamaShipping?.player === p.id ||
((p.faction === 'guild' || byFaction(g,'guild')?.id === p.ally) &&
((p.faction === 'guild' || guild?.id === p.ally || guildGroupAlly(g, p)) &&
!(g.guildRateBlocked?.turn === g.turn && g.guildRateBlocked.player === p.id));
}
function nexusRicheseSignature(record: NonNullable<Game['nexusRicheseHistory']>[number]) {