Files
Dune/game/coalition-battle-preparation.ts
T
Quintine 3ba9087405
Verify and publish checkpoint container / container (push) Successful in 10m53s
Connect staged Advanced classic group alliances
2026-10-29 02:37:23 +11:00

62 lines
2.9 KiB
TypeScript

import { coalitionOf, CoalitionAllianceError, type CoalitionState } from './coalition-alliance';
/** Only public roster and rule-profile data are required to select a side.
* Inspection answers and sealed plans are deliberately absent. */
export type CoalitionBattlePreparationContext = {
status: string;
players: readonly { id: string; faction: string; ally?: string | null; allySinceTurn?: number }[];
coalitionState?: CoalitionState;
advanced?: boolean;
expansions?: readonly unknown[];
homeworlds?: unknown;
nexusCards?: unknown;
leaderSkills?: unknown;
techTokens?: unknown;
strongholdCards?: unknown;
ecazTreachery?: unknown;
discoveryEnabled?: unknown;
};
const classicFactions = new Set(['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen']);
/** Select the owner or a single allied combatant for native Voice or
* Prescience. The unexposed group extension supports bare Basic and Advanced
* classic profiles. Ambiguous group battles fail
* closed. `requireMutualPair` preserves the stricter legacy Truthtrance quote;
* normal battle preparation historically accepted the owner's scalar ally. */
export function coalitionBattleBeneficiary(
game: CoalitionBattlePreparationContext,
ownerId: string,
attacker: string,
defender: string,
requireMutualPair = false,
): string | null {
if (!Array.isArray(game.players) || !ownerId || !attacker || !defender || attacker === defender) return null;
const owner = game.players.find(player => player.id === ownerId);
if (!owner || !game.players.some(player => player.id === attacker) ||
!game.players.some(player => player.id === defender)) return null;
const combatants = [attacker, defender];
if (game.coalitionState && game.status === 'playing' && typeof game.advanced === 'boolean' &&
Array.isArray(game.expansions) && game.expansions.length === 0 &&
!game.homeworlds && !game.nexusCards && !game.leaderSkills && !game.techTokens &&
!game.strongholdCards && !game.ecazTreachery && !game.discoveryEnabled &&
game.players.every(player => classicFactions.has(player.faction))) {
try {
const seats = game.players.map(player => ({id: player.id, ally: player.ally ?? null,
...(player.allySinceTurn !== undefined ? {allySinceTurn: player.allySinceTurn} : {})}));
const group = coalitionOf(seats, game.coalitionState, ownerId);
if (group && group.members.length >= 3) {
const members = combatants.filter(player => group.members.includes(player));
return members.length === 1 ? members[0] : null;
}
} catch (error) {
if (error instanceof CoalitionAllianceError) return null;
throw error;
}
}
if (combatants.includes(owner.id)) return owner.id;
if (!owner.ally || !combatants.includes(owner.ally)) return null;
if (requireMutualPair && !game.players.some(player => player.id === owner.ally && player.ally === owner.id)) return null;
return owner.ally;
}