Files
Dune/game/homeworld-revival-deployment.ts

140 lines
4.6 KiB
TypeScript

import type { Game } from './engine';
import {
homeworldForceGroups,
HomeworldCustodyError,
type HomeworldCustody,
type HomeworldCustodyContext,
} from './homeworld-custody';
import { homeworldContext } from './homeworld-game';
import { quoteNativeReserveDeposit } from './homeworld-native-reserves';
import { homeworldPopulations } from './homeworld-population';
export type HomeworldRevivalDeploymentContext = Pick<
Game,
'advanced' | 'players' | 'homeworlds'
>;
export type HomeworldRevivalDeploymentSource =
| 'normal'
| 'emperorExtra'
| 'ghola'
| 'nexusEmperor'
| 'nexusTleilaxu';
export type HomeworldRevivalDeploymentGroup = {
amount: number;
elite: number;
free: number;
};
export type HomeworldRevivalDeploymentQuote = {
kind: 'fedaykin' | 'tleilax';
normal: number;
elite: number;
beforePopulation: number;
afterPopulation: number;
blocked: string | null;
};
function physicalWorlds(
context: HomeworldCustodyContext,
custody: HomeworldCustody,
) {
return homeworldForceGroups(context, custody)
.map((home) => ({
id: home.id,
native: home.native,
forces: Object.entries(home.forces)
.filter(([, forces]) => forces.normal + forces.elite > 0)
.map(([player, forces]) => [player, forces.normal, forces.elite])
.sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
}))
.sort((a, b) => a.id.localeCompare(b.id));
}
/** Quote the eligible subset of one actual typed native revival deposit.
* Timing, destinations, splitting and durable completion belong to the caller.
* Provisional first version: the high-threshold placement ability is evaluated
* on the population *resulting* from this return, so the redeployment that
* itself reaches the high side qualifies. This is a visibly provisional
* composition policy, not a located publisher timing ruling. */
export function quoteHomeworldRevivalDeployment(
before: HomeworldRevivalDeploymentContext,
after: HomeworldRevivalDeploymentContext,
player: string,
source: HomeworldRevivalDeploymentSource,
group: HomeworldRevivalDeploymentGroup,
): HomeworldRevivalDeploymentQuote | null {
if (
!['normal', 'emperorExtra', 'ghola', 'nexusTleilaxu', 'nexusEmperor'].includes(source) ||
!group ||
typeof group !== 'object' ||
Array.isArray(group) ||
!Number.isSafeInteger(group.amount) ||
group.amount <= 0 ||
group.amount > 20 ||
!Number.isSafeInteger(group.elite) ||
group.elite < 0 ||
group.elite > group.amount ||
!Number.isSafeInteger(group.free) ||
group.free < 0 ||
group.free > group.amount ||
(source === 'nexusEmperor' &&
(group.amount !== 3 || group.free !== 3))
)
throw new HomeworldCustodyError(
'Revival deployment needs a valid source and exact newly returned physical group.',
);
if (!before.homeworlds?.custody && !after.homeworlds?.custody) return null;
if (
!before.homeworlds?.custody ||
!after.homeworlds?.custody ||
before.advanced !== after.advanced
)
throw new HomeworldCustodyError(
'The Homeworld module cannot change during a revival deposit.',
);
const beforeContext = homeworldContext(before);
const afterContext = homeworldContext(after);
const expected = quoteNativeReserveDeposit(
beforeContext,
before.homeworlds.custody,
player,
{ normal: group.amount - group.elite, elite: group.elite },
);
if (
JSON.stringify(
physicalWorlds(
{ ...beforeContext, players: expected.players },
expected.state,
),
) !== JSON.stringify(physicalWorlds(afterContext, after.homeworlds.custody))
)
throw new HomeworldCustodyError(
'The revival deployment does not match its original typed native reserve deposit.',
);
const owner = beforeContext.players.find((seat) => seat.id === player)!;
const kind =
owner.faction === 'fremen' && group.elite > 0
? 'fedaykin'
: owner.faction === 'tleilaxu' && source === 'normal' && group.free > 0
? 'tleilax'
: null;
if (!kind) return null;
const card = kind === 'fedaykin' ? 'southern_hemisphere' : 'tleilax';
const prior = homeworldPopulations(
beforeContext,
before.homeworlds.custody,
).find((home) => home.native === player && home.card === card)!;
const current = homeworldPopulations(
afterContext,
after.homeworlds.custody,
).find((home) => home.native === player && home.card === card)!;
if (current.side !== 'high') return null;
return {
kind,
normal: kind === 'tleilax' ? group.free : 0,
elite: kind === 'fedaykin' ? group.elite : 0,
beforePopulation: prior.population,
afterPopulation: current.population,
blocked: null,
};
}