Support Emperor special Karama truth commitments
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 18:12:20 +11:00
1 parent b448144c52
commit add8e1d51a
14 files changed
+345 -20

No files matched your search

+24 -2
View File
@@ -1,11 +1,12 @@
import { harvesterAvailable } from './cards';
export const WINDOW_CARD_EFFECTS = ['weather', 'atomics', 'harvester', 'karamaAuction', 'karamaShipment', 'karamaBattleCancel', 'karamaHarkonnenSpecial', 'thumper', 'amal'] as const;
export const WINDOW_CARD_EFFECTS = ['weather', 'atomics', 'harvester', 'karamaAuction', 'karamaShipment', 'karamaBattleCancel', 'karamaHarkonnenSpecial', 'karamaEmperorSpecial', 'thumper', 'amal'] as const;
export type WindowCardEffect = typeof WINDOW_CARD_EFFECTS[number];
export function windowCardNativeEffect(effect: WindowCardEffect): 'weather' | 'atomics' | 'harvester' | 'karama' | 'thumper' | 'amal' {
return effect === 'karamaAuction' || effect === 'karamaShipment' || effect === 'karamaBattleCancel' || effect === 'karamaHarkonnenSpecial' ? 'karama' : effect;
return effect === 'karamaAuction' || effect === 'karamaShipment' || effect === 'karamaBattleCancel' || effect === 'karamaHarkonnenSpecial' || effect === 'karamaEmperorSpecial' ? 'karama' : effect;
}
export type KaramaHarkonnenSpecialSource = { turn: number; owner: string };
export type KaramaEmperorSpecialSource = { turn: number; owner: string };
export type KaramaBattleCancelSource = {
turn: number;
battle: string;
@@ -77,6 +78,16 @@ export function parseKaramaHarkonnenSpecialSource(source: unknown): KaramaHarkon
return { turn: tuple[1], owner: tuple[2] };
}
/** Decode the public owner and turn even after the special power is spent. */
export function parseKaramaEmperorSpecialSource(source: unknown): KaramaEmperorSpecialSource | null {
if (!identifier(source)) return null;
let tuple: unknown;
try { tuple = JSON.parse(source); } catch { return null; }
if (!Array.isArray(tuple) || tuple.length !== 3 || tuple[0] !== 'karamaEmperorSpecial' ||
!whole(tuple[1]) || tuple[1] < 1 || !identifier(tuple[2]) || JSON.stringify(tuple) !== source) return null;
return { turn: tuple[1], owner: tuple[2] };
}
/** Source decoding remains available after the actual grant closes its window. */
export function parseKaramaShipmentSource(source: unknown): KaramaShipmentSource | null {
if (!identifier(source)) return null;
@@ -180,6 +191,17 @@ function globalWindowCardOpportunity(game: WindowCardGame, effect: WindowCardEff
if (owners.length !== 1 || owners[0].specialKaramaUsed === true) return null;
return { effect, turn: game.turn, source: JSON.stringify([effect, game.turn, owners[0].id]) };
}
if (effect === 'karamaEmperorSpecial') {
const players = game.players;
if (game.advanced !== true || game.phase !== 4 || !Array.isArray(players) || !players.length ||
!players.every(player => object(player) && identifier(player.id) &&
typeof player.faction === 'string' && (player.specialKaramaUsed === undefined ||
typeof player.specialKaramaUsed === 'boolean')) ||
new Set(players.map(player => player.id)).size !== players.length) return null;
const owners = players.filter(player => player.faction === 'emperor');
if (owners.length !== 1 || owners[0].specialKaramaUsed === true) return null;
return { effect, turn: game.turn, source: JSON.stringify([effect, game.turn, owners[0].id]) };
}
if (effect === 'karamaBattleCancel') {
const battle = game.battle, response = game.response, players = game.players;
if (game.advanced !== false || game.phase !== 6 || !battle || !identifier(battle.event) ||