Recognize group allies in bribes and deals
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-28 23:57:55 +11:00
1 parent 2fe97ddd1f
commit 6e26ceb216
12 files changed
+318 -29

No files matched your search

+12 -6
View File
@@ -3,6 +3,7 @@
import { useEffect, useRef, useState } from 'react';
import { faction } from '@/game/catalog';
import { publicDealAcceptVotes, publicDealParticipants } from '@/game/public-deals';
import { areCoalitionAllies } from '@/game/coalition-alliance';
import type { Action, GameView } from '@/game/engine';
import { Button } from '@/components/ui/button';
@@ -15,6 +16,13 @@ type DeferredPaymentProjection = GameView & {
deferredDealPaymentOptions?: Record<string, { blocked: string | null }>;
};
/** Alliance membership is public. Scalar links retain legacy pair behavior;
* a staged group uses the same coalition quote as authoritative rules. */
function allied(game: GameView, a: GameView['players'][number], b: GameView['players'][number]): boolean {
return a.ally === b.id || b.ally === a.id ||
!!game.coalitionState && areCoalitionAllies(game.players, game.coalitionState, a.id, b.id);
}
/** Requests are local to the active seat; an owed payment is as important to
* surface as a pending invitation or a request to release an agreement. */
export function publicDealRequests(game: GameView): string[][] {
@@ -71,7 +79,7 @@ export function PublicDealList({ game, act, disabled }: {
const recipient = party && game.me !== deal.from;
const seats = participants.map(id => game.players.find(player => player.id === id));
const eligible = seats.every((player, index) => player && seats.slice(index + 1).every(other =>
other && player.ally !== other.id && other.ally !== player.id));
other && !allied(game, player, other)));
const payment = (deal as unknown as { payment?: DealPayment }).payment;
const paymentStatus = (deal as unknown as { paymentStatus?: DeferredPaymentStatus }).paymentStatus;
const finalAcceptance = recipient && !accepted.includes(game.me) && accepted.length === participants.length - 1;
@@ -141,11 +149,9 @@ export function PublicDeals({ game, act, busy }: {
const me = game.players.find(player => player.id === game.me);
const targets = game.players.filter(player => me && player.id !== me.id && !player.bot);
const selectedPlayers = targets.filter(player => selected.includes(player.id));
const allied = (a: typeof game.players[number], b: typeof game.players[number]) =>
a.ally === b.id || b.ally === a.id;
const legalSelection = !!me && selected.length > 0 && selected.length === selectedPlayers.length &&
selectedPlayers.every((player, index) => !allied(me, player) &&
selectedPlayers.slice(index + 1).every(other => !allied(player, other)));
selectedPlayers.every((player, index) => !allied(game, me, player) &&
selectedPlayers.slice(index + 1).every(other => !allied(game, player, other)));
const parties = me ? [me, ...selectedPlayers] : [];
const amount = Number(paymentAmount);
const dueTurn = Number(paymentDueTurn);
@@ -174,7 +180,7 @@ export function PublicDeals({ game, act, busy }: {
<legend>Deal recipients</legend>
{targets.map(player => {
const checked = selected.includes(player.id);
const conflicts = !!me && (allied(me, player) || selectedPlayers.some(other => other.id !== player.id && allied(other, player)));
const conflicts = !!me && (allied(game, me, player) || selectedPlayers.some(other => other.id !== player.id && allied(game, other, player)));
return <label key={player.id} className="checkbox-row">
<input type="checkbox" checked={checked} disabled={disabled || (!checked && conflicts)}
onChange={event => {