Support staged group bid funding
Verify and publish checkpoint container / container (push) Successful in 10m27s
Verify and publish checkpoint container / container (push) Successful in 10m27s
This commit is contained in:
1 parent
34aa64cdef
commit
1eeeb32566
12 files changed
+426
-37
No files matched your search
@@ -4,7 +4,7 @@ import { useId, useState } from 'react';
|
||||
|
||||
/** Only the viewer's own pledges and contributions offered to that viewer. */
|
||||
export type CoalitionFundingView = {
|
||||
targets: readonly { id: string; name: string; pledged: number; maximum: number }[];
|
||||
targets: readonly { id: string; name: string; pledged: number; maximum: number; minimum?: number }[];
|
||||
incoming: readonly { donor: string; name: string; amount: number }[];
|
||||
blocked: string | null;
|
||||
};
|
||||
@@ -35,13 +35,15 @@ export function coalitionPledgeAction(
|
||||
): CoalitionPledgeAction | null {
|
||||
if (busy || blocked || !/^(0|[1-9]\d*)$/.test(raw)) return null;
|
||||
const amount = Number(raw);
|
||||
if (!Number.isSafeInteger(amount) || amount > safeMaximum(target.maximum)) return null;
|
||||
if (!Number.isSafeInteger(amount) || amount < safeMaximum(target.minimum ?? 0) ||
|
||||
amount > safeMaximum(target.maximum)) return null;
|
||||
return { type: 'coalitionPledge', recipient: target.id, amount };
|
||||
}
|
||||
|
||||
export function CoalitionFunding({ view, busy = false, act, onContributionsChange }: {
|
||||
export function CoalitionFunding({ view, busy = false, act, onContributionsChange, purpose = 'shipment' }: {
|
||||
view: CoalitionFundingView;
|
||||
busy?: boolean;
|
||||
purpose?: 'bid' | 'shipment';
|
||||
act: (action: CoalitionPledgeAction) => void;
|
||||
onContributionsChange?: (contributions: CoalitionFundingContribution[]) => void;
|
||||
}) {
|
||||
@@ -66,9 +68,11 @@ export function CoalitionFunding({ view, busy = false, act, onContributionsChang
|
||||
const raw = pledges[target.id] ?? String(target.pledged);
|
||||
const action = coalitionPledgeAction(target, raw, view.blocked, busy);
|
||||
const inputId = `${id}-pledge-${index}`;
|
||||
const minimum = safeMaximum(target.minimum ?? 0);
|
||||
return <div key={target.id} className="space-y-2">
|
||||
<label htmlFor={inputId}>Pledge to {target.name} · {target.pledged} spice currently set aside</label>
|
||||
<input id={inputId} type="number" min={0} max={safeMaximum(target.maximum)} step={1}
|
||||
{minimum > 0 && <p>The current high bid commits {minimum} spice of this pledge.</p>}
|
||||
<input id={inputId} type="number" min={minimum} max={safeMaximum(target.maximum)} step={1}
|
||||
inputMode="numeric" value={raw} disabled={busy || !!view.blocked}
|
||||
onChange={event => setPledges(previous => ({ ...previous, [target.id]: event.target.value }))} />
|
||||
<button type="button" className="game-action" disabled={!action}
|
||||
@@ -84,7 +88,7 @@ export function CoalitionFunding({ view, busy = false, act, onContributionsChang
|
||||
return <div key={entry.donor} className="space-y-2">
|
||||
<p>{entry.name}: {entry.amount} spice</p>
|
||||
{onContributionsChange && <>
|
||||
<label htmlFor={inputId}>Use from {entry.name} for your next shipment</label>
|
||||
<label htmlFor={inputId}>Use from {entry.name} for your next {purpose}</label>
|
||||
<input id={inputId} type="number" min={0} max={maximum} step={1}
|
||||
inputMode="numeric" value={value} disabled={busy || !!view.blocked}
|
||||
onChange={event => {
|
||||
|
||||
@@ -453,11 +453,6 @@ export function GameTable({
|
||||
? bidDraft.value
|
||||
: String(minimumBid);
|
||||
const bidAmount = integerInputValue(bidText);
|
||||
const maximumBid = me.hand?.some((c) =>
|
||||
canUseAsKaramaRole(g, me, c),
|
||||
)
|
||||
? Number.MAX_SAFE_INTEGER
|
||||
: (me.spice ?? 0) + g.aid.available;
|
||||
const [eliteAmount, setEliteAmount] = useState(0);
|
||||
const [homeworldDraft, setHomeworldDraft] = useState<{
|
||||
key: string;
|
||||
@@ -782,13 +777,16 @@ export function GameTable({
|
||||
if (useGuildSecret && shipmentCost === null && !g.nexusGuildSecretAlly?.blocked) shipmentProblems.push('Choose a current Guild Secret Ally shipment.');
|
||||
if (useRicheseShipment && shipmentCost === null && !g.nexusRichese?.blocked)
|
||||
shipmentProblems.push('Choose a current Richese Secret Ally shipment of up to five physical forces.');
|
||||
const coalitionFundingKey = JSON.stringify(g.coalitionFunding?.incoming.map(({donor, amount}) => [donor, amount]) ?? []);
|
||||
const coalitionFundingKey = JSON.stringify([g.code, g.turn, g.phase,
|
||||
g.coalitionFunding?.incoming.map(({donor, amount}) => [donor, amount]) ?? []]);
|
||||
const currentCoalitionShares = g.coalitionFunding && coalitionShares.key === coalitionFundingKey
|
||||
? coalitionFundingContributions(g.coalitionFunding.incoming,
|
||||
Object.fromEntries(coalitionShares.shares.map(share => [share.donor, share.amount]))) : [];
|
||||
const groupShipmentShare = currentCoalitionShares.reduce((sum, share) => sum + share.amount, 0);
|
||||
const availableShipmentAid = g.coalitionFunding
|
||||
? g.coalitionFunding.incoming.reduce((sum, share) => sum + share.amount, 0) : g.aid.available;
|
||||
const maximumBid = me.hand?.some((c) => canUseAsKaramaRole(g, me, c))
|
||||
? Number.MAX_SAFE_INTEGER : (me.spice ?? 0) + (g.phase === 3 && g.coalitionFunding ? groupShipmentShare : g.aid.available);
|
||||
const selectedAllyPayment =
|
||||
shipmentCost === null
|
||||
? 0
|
||||
@@ -3890,7 +3888,7 @@ export function GameTable({
|
||||
{actionButton(
|
||||
'Pay with spice',
|
||||
{ type: 'decision', karama: false },
|
||||
(g.auction?.bid ?? 0) > (me.spice ?? 0) + g.aid.available,
|
||||
(g.auction?.bid ?? 0) > (me.spice ?? 0) + g.aid.available + (g.auction?.coalitionPayment ?? 0),
|
||||
)}
|
||||
{me.hand
|
||||
?.filter((card) => canSpendKarama(card, 'auction-free-purchase'))
|
||||
@@ -4237,7 +4235,8 @@ export function GameTable({
|
||||
<ChoamWorthless game={g} act={act} busy={busy} />
|
||||
<ChoamBattleFunding game={g} act={act} busy={busy} />
|
||||
{g.coalitionFunding && <CoalitionFunding view={g.coalitionFunding} busy={busy} act={act}
|
||||
{...(shipmentAvailable ? {onContributionsChange:(shares:CoalitionFundingContribution[]) =>
|
||||
purpose={g.phase === 3 ? 'bid' : 'shipment'}
|
||||
{...((shipmentAvailable || g.phase === 3 && g.auction?.active === me.id) ? {onContributionsChange:(shares:CoalitionFundingContribution[]) =>
|
||||
setCoalitionShares({key:coalitionFundingKey, shares})} : {})} />}
|
||||
{me.ally && [3, 5].includes(g.phase) && (
|
||||
<details className="alliance-funding">
|
||||
@@ -4494,11 +4493,15 @@ export function GameTable({
|
||||
type: 'bid',
|
||||
amount: bidAmount,
|
||||
...payment,
|
||||
...(g.coalitionFunding && currentCoalitionShares.length
|
||||
? {allyPayment:0, coalitionContributions:currentCoalitionShares} : {}),
|
||||
},
|
||||
!!normalAuctionHandBlock(me.hand?.length ?? me.handCount ?? me.handLimit, me.handLimit) ||
|
||||
!Number.isSafeInteger(bidAmount) ||
|
||||
bidAmount < minimumBid ||
|
||||
bidAmount > maximumBid,
|
||||
bidAmount > maximumBid ||
|
||||
!!(g.coalitionFunding && currentCoalitionShares.length &&
|
||||
(groupShipmentShare > bidAmount || bidAmount - groupShipmentShare > (me.spice ?? 0))),
|
||||
)}
|
||||
{actionButton('Pass this bid', { type: 'passBid' })}
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user