151 lines
4.5 KiB
TypeScript
151 lines
4.5 KiB
TypeScript
import type { Game, Player } from './engine';
|
|
import { eliteRevivalRemaining } from './revival';
|
|
import { classicNexusModulesProfile } from './nexus-module-profile';
|
|
import { classicNexusLeaderSkillsProfile } from './leader-skill-profile';
|
|
|
|
export const EMPEROR_NEXUS_REVIVALS = 3;
|
|
export type EmperorNexusPools = {
|
|
reserves: number;
|
|
tanks: number;
|
|
eliteReserves: number;
|
|
eliteTanks: number;
|
|
eliteRevived: number;
|
|
revived: number;
|
|
freeForcesRevived: number | null;
|
|
spice: number;
|
|
};
|
|
export type NexusEmperorRevival = {
|
|
kind: 'revival';
|
|
event: string;
|
|
owner: string;
|
|
turn: number;
|
|
phase: 4;
|
|
sequence: number;
|
|
faction: Player['faction'];
|
|
advanced: boolean;
|
|
elite: number;
|
|
before: EmperorNexusPools;
|
|
after: EmperorNexusPools;
|
|
signature: string;
|
|
};
|
|
export function emperorNexusPools(p: Player): EmperorNexusPools {
|
|
return {
|
|
reserves: p.reserves,
|
|
tanks: p.tanks,
|
|
eliteReserves: p.elites?.reserves ?? 0,
|
|
eliteTanks: p.elites?.tanks ?? 0,
|
|
eliteRevived: p.elites?.revived ?? 0,
|
|
revived: p.revived,
|
|
freeForcesRevived: p.freeForcesRevived ?? null,
|
|
spice: p.spice,
|
|
};
|
|
}
|
|
export function emperorNexusEvent(
|
|
turn: number,
|
|
phase: number,
|
|
owner: string,
|
|
sequence: number,
|
|
): string {
|
|
return JSON.stringify([
|
|
'nexusEmperorSecretAlly',
|
|
turn,
|
|
phase,
|
|
owner,
|
|
sequence,
|
|
]);
|
|
}
|
|
export function emperorNexusSignature(record: object): string {
|
|
return JSON.stringify({ ...record, signature: undefined });
|
|
}
|
|
export function emperorNexusModeSupported(g: Game): boolean {
|
|
return (
|
|
!g.expansions.length &&
|
|
g.players.every((p) =>
|
|
['atreides', 'harkonnen', 'fremen', 'guild', 'beneGesserit'].includes(
|
|
p.faction,
|
|
),
|
|
) &&
|
|
(classicNexusModulesProfile(g) || (!!g.leaderSkills && classicNexusLeaderSkillsProfile(g)) ||
|
|
(!g.homeworlds &&
|
|
(!g.leaderSkills || classicNexusLeaderSkillsProfile(g)) &&
|
|
!g.discoveryEnabled &&
|
|
!g.discoveries &&
|
|
!g.discoveryStash &&
|
|
!g.greatMaker &&
|
|
((!g.techTokens && !g.strongholdCards) || classicNexusModulesProfile(g) ||
|
|
(!!g.leaderSkills && classicNexusLeaderSkillsProfile(g)))))
|
|
);
|
|
}
|
|
/** A fixed three-counter grant preserves the separate Advanced elite cap. */
|
|
export function emperorNexusRevivalElites(
|
|
p: Player,
|
|
advanced: boolean,
|
|
): number[] {
|
|
const choices: number[] = [];
|
|
for (let elite = 0; elite <= 3; elite++)
|
|
if (
|
|
elite <= eliteRevivalRemaining(p, advanced) &&
|
|
elite <= (p.elites?.tanks ?? 0) &&
|
|
3 - elite <= p.tanks - (p.elites?.tanks ?? 0)
|
|
)
|
|
choices.push(elite);
|
|
return choices;
|
|
}
|
|
export function validateEmperorNexusRevival(
|
|
g: Game,
|
|
r: NexusEmperorRevival,
|
|
): void {
|
|
const p = r && g.players.find((p) => p.id === r.owner);
|
|
const validPools = (pool: EmperorNexusPools) =>
|
|
pool &&
|
|
Object.keys(pool).sort().join(',') ===
|
|
'eliteReserves,eliteRevived,eliteTanks,freeForcesRevived,reserves,revived,spice,tanks' &&
|
|
Object.entries(pool).every(
|
|
([key, n]) =>
|
|
(key === 'freeForcesRevived' && n === null) ||
|
|
(Number.isSafeInteger(n) && n! >= 0),
|
|
) &&
|
|
pool.eliteReserves <= pool.reserves &&
|
|
pool.eliteTanks <= pool.tanks &&
|
|
pool.reserves + pool.tanks <= 20;
|
|
if (
|
|
!p ||
|
|
Object.keys(r).sort().join(',') !==
|
|
'advanced,after,before,elite,event,faction,kind,owner,phase,sequence,signature,turn' ||
|
|
r.kind !== 'revival' ||
|
|
r.phase !== 4 ||
|
|
!Number.isSafeInteger(r.turn) ||
|
|
r.turn < 1 ||
|
|
r.turn > g.turn ||
|
|
r.faction !== p.faction ||
|
|
r.advanced !== g.advanced ||
|
|
!Number.isSafeInteger(r.sequence) ||
|
|
r.sequence < 0 ||
|
|
!Number.isSafeInteger(r.elite) ||
|
|
r.elite < 0 ||
|
|
r.elite > 3 ||
|
|
r.event !== emperorNexusEvent(r.turn, 4, r.owner, r.sequence) ||
|
|
r.signature !== emperorNexusSignature(r) ||
|
|
!validPools(r.before) ||
|
|
!validPools(r.after) ||
|
|
r.before.tanks < 3 ||
|
|
r.elite > r.before.eliteTanks ||
|
|
3 - r.elite > r.before.tanks - r.before.eliteTanks ||
|
|
(g.advanced && r.before.eliteRevived + r.elite > 1) ||
|
|
(p.faction !== 'fremen' &&
|
|
(r.elite || r.before.eliteTanks || r.before.eliteReserves)) ||
|
|
JSON.stringify(r.after) !==
|
|
JSON.stringify({
|
|
...r.before,
|
|
reserves: r.before.reserves + 3,
|
|
tanks: r.before.tanks - 3,
|
|
eliteReserves: r.before.eliteReserves + r.elite,
|
|
eliteTanks: r.before.eliteTanks - r.elite,
|
|
eliteRevived: r.before.eliteRevived + r.elite,
|
|
})
|
|
)
|
|
throw new Error(
|
|
'Emperor Nexus revival has lost its exact original physical return or allowance.',
|
|
);
|
|
}
|