Plan ordinary promises across one native Charity claim

This commit is contained in:
Quintine committed 2026-10-27 05:52:48 +11:00
1 parent b81823bbec
commit 38738eabac
15 files changed
+831 -96

No files matched your search

+25 -9
View File
@@ -15,31 +15,45 @@ export function BribePromiseGuideButton(props: BribePromiseProps) {
if (!guide) return null;
const actor = game.me, turn = game.turn, phase = game.phase, action = guide.action;
const event = action.bribePromiseEvent, signature = JSON.stringify(action);
const send = props.send, charity = game.charity;
const charityOwner = action.type === 'charity' ? game.players.find(player => player.id === actor) : undefined;
const charitySource = (current: GameView) => {
const owner = current.players.find(player => player.id === actor);
return JSON.stringify({ quote: current.charity, claimed: owner?.charityClaimed, spice: owner?.spice });
};
const charitySignature = action.type === 'charity' ? charitySource(game) : null;
const canAct = () => {
const current = props.game, me = current.players.find(player => player.id === current.me), options = current.bribeOptions;
const target = typeof action.target === 'string' ? current.players.find(player => player.id === action.target) : undefined;
const limit = options.targets.find(recipient => recipient.id === action.target);
return !props.busy && current === game && bribePromiseModeSupported(current) && current.status === 'playing' &&
const payment = action.type === 'bribe' && !!me && !!target && target.id !== actor && target.id !== me.ally &&
options.blocked === null && Number.isSafeInteger(options.available) &&
typeof action.amount === 'number' && action.amount <= options.available &&
!!limit && Number.isSafeInteger(limit.maximum) && action.amount <= limit.maximum &&
!!bribeAction(options, target.id, action.amount);
const claim = action.type === 'charity' && phase === 2 && !!me && me === charityOwner &&
Object.keys(action).every(key => ['type', 'bribePromiseEvent'].includes(key)) &&
!current.ready.includes(actor) && me.charityClaimed === false && current.charity === charity &&
charitySource(current) === charitySignature && current.charity.incomePending === false &&
Number.isSafeInteger(current.charity.amount) && current.charity.amount > 0;
return !props.busy && props.send === send && current === game && bribePromiseModeSupported(current) && current.status === 'playing' &&
Number.isSafeInteger(current.phase) && current.phase >= 0 && current.phase <= 8 && current.phase === phase &&
current.turn === turn && current.me === actor &&
current.bribePromiseGuide === guide && current.bribePromiseGuide.action === action && JSON.stringify(action) === signature &&
action.type === 'bribe' && typeof event === 'string' &&
typeof event === 'string' &&
(liveBribePromises(current.bribePromises, actor, turn).some(promise => promise.event === event) ||
liveBribeExpressionPromises(current.bribeExpressionPromises, actor, turn).some(promise => promise.event === event) ||
current.mixedBribePromises.some(promise => promise.event === event && promise.player === actor && promise.turn === turn &&
!promise.fulfilled && !promise.released)) &&
!!me && !me.autopilot && !!target && target.id !== actor && target.id !== me.ally &&
options.blocked === null && Number.isSafeInteger(options.available) &&
typeof action.amount === 'number' && action.amount <= options.available &&
!!limit && Number.isSafeInteger(limit.maximum) && action.amount <= limit.maximum &&
!!bribeAction(options, target.id, action.amount) &&
!!me && !me.autopilot && (payment || claim) &&
!current.truthtrance && !current.response && !current.decision && !current.phaseOpening &&
!current.automaticContinuationPending && !current.roomControl?.paused && !current.roomControl?.closed;
};
const recipient = game.players.find(player => player.id === action.target)?.name ?? 'the quoted recipient';
return <Button className="game-action min-h-11 whitespace-normal" data-testid="bribe-promise-guide"
disabled={!canAct()} onClick={() => { if (canAct()) props.send(action); }}>
Pay {typeof action.amount === 'number' ? action.amount : 'the quoted'} spice to {recipient} while honoring your answers
disabled={!canAct()} onClick={() => { if (canAct()) send(action); }}>
{action.type === 'charity' ? `Claim ${game.charity.amount} Charity spice to continue your payments`
: `Pay ${typeof action.amount === 'number' ? action.amount : 'the quoted'} spice to ${recipient} while honoring your answers`}
</Button>;
}
@@ -64,6 +78,8 @@ export function BribePromises(props: BribePromiseProps) {
The sender pays immediately. The recipient collects the payment at the start of Mentat Pause.
Payments made during Mentat Pause wait until the start of next turn&apos;s Mentat Pause.
Ordinary bribes can be paid out of turn when the current bribe controls permit them; original timing and payment rules still apply.
A legal timeline may use payments now, one remaining Charity claim and later installments.
The future claim is not current spendable spice; only actual payments add to the recorded total.
{' '}<a href="/rules#truthtrance-bribe">Bribe promise rules</a>.
</p>
{liveBribePromises(own, props.game.me, props.game.turn).length > 0 && <BribePromiseGuideButton {...props} />}