Allow chosen Reinforcements reserve costs
Verify and publish checkpoint container / container (push) Successful in 11m11s
Verify and publish checkpoint container / container (push) Successful in 11m11s
This commit is contained in:
1 parent
9dd386ff7a
commit
1cf90f1328
16 files changed
+419
-44
No files matched your search
+24
-13
@@ -776,7 +776,7 @@ import {
|
||||
type SpiceCard,
|
||||
} from './cards';
|
||||
import { ecazTreacheryCards, ecazTreacheryDefinition } from './ecaz-cards';
|
||||
import { REINFORCEMENTS_CARD, isReinforcements, quoteReinforcements, ReinforcementsError } from './reinforcements';
|
||||
import { REINFORCEMENTS_CARD, isReinforcements, quoteReinforcements, reinforcementsEliteOptions, ReinforcementsError } from './reinforcements';
|
||||
import { quoteReinforcementsHomeworldCost, reinforcementsHomeworldSources,
|
||||
reinforcementsBattleForces } from './reinforcements-homeworld';
|
||||
import {
|
||||
@@ -873,12 +873,14 @@ export type Plan = {
|
||||
support: number;
|
||||
allyPayment?: number;
|
||||
kwisatz?: boolean;
|
||||
/** Explicit number of special reserve counters in the three-counter fee. */
|
||||
reinforcementsElite?: number;
|
||||
/** Exact native reserve fee sources; only a Homeworld Reinforcements plan. */
|
||||
reinforcementsSources?: NativeReserveSelections;
|
||||
};
|
||||
export type PlanField = Exclude<
|
||||
keyof Plan,
|
||||
'support' | 'kwisatz' | 'allyPayment' | 'bankerSpice' | 'reinforcementsSources' | 'planetologistSpecial'
|
||||
'support' | 'kwisatz' | 'allyPayment' | 'bankerSpice' | 'reinforcementsSources' | 'reinforcementsElite' | 'planetologistSpecial'
|
||||
>;
|
||||
export type DiplomatDefenseReceipt = DiplomatDefenseQuote & {
|
||||
event: string;
|
||||
@@ -10814,7 +10816,7 @@ function planCombatForces(g: Game, p: Player, opponent: Player,
|
||||
if (!home || home.native !== p.id) return forces;
|
||||
const plan = g.battle!.plans[p.id];
|
||||
const cost = reserveCost ?? (plan && usesNativeBattleSpecial(plan, REINFORCEMENTS_CARD)
|
||||
? currentReinforcementsCost(g, p, plan.reinforcementsSources) : undefined);
|
||||
? currentReinforcementsCost(g, p, plan.reinforcementsSources, plan.reinforcementsElite) : undefined);
|
||||
return reinforcementsBattleForces(forces, cost?.sources?.[home.id]);
|
||||
}
|
||||
function takeBattleLosses(g: Game, p: Player, t: string, losses: Casualties) {
|
||||
@@ -18717,8 +18719,10 @@ function ecazTreacheryIntegrity(g: Game) {
|
||||
[plan.weapon, plan.defense].every(id => id === null || !!cardOf(owner, id)) &&
|
||||
validBattleSlotPair(cardOf(owner, plan.weapon), cardOf(owner, plan.defense), true),
|
||||
'The sealed Reinforcements plan lost its physical card or legal slot.');
|
||||
currentReinforcementsCost(g, owner, plan.reinforcementsSources);
|
||||
currentReinforcementsCost(g, owner, plan.reinforcementsSources, plan.reinforcementsElite);
|
||||
}
|
||||
requireRule(plan.reinforcementsElite === undefined || usesNativeBattleSpecial(plan, REINFORCEMENTS_CARD),
|
||||
'A typed reserve fee requires the physical Reinforcements card.');
|
||||
requireRule(plan.reinforcementsSources === undefined ||
|
||||
(g.homeworlds && usesNativeBattleSpecial(plan, REINFORCEMENTS_CARD)),
|
||||
'Native reserve fee sources require the physical Reinforcements card in a Homeworld plan.');
|
||||
@@ -18799,7 +18803,7 @@ type ReinforcementsCost = ReturnType<typeof quoteReinforcements> & {
|
||||
native?: ReturnType<typeof quoteNativeReserveWithdrawal>;
|
||||
};
|
||||
function currentReinforcementsCost(g: Game, p: Player,
|
||||
selections?: NativeReserveSelections): ReinforcementsCost {
|
||||
selections?: NativeReserveSelections, selectedElite?: number): ReinforcementsCost {
|
||||
requireRule(reinforcementsModeSupported(g),
|
||||
'Reinforcements requires the independent Ecaz card variant in a supported native faction composition.');
|
||||
const combinedBlock = combinedCardBattleBlock(g);
|
||||
@@ -18812,11 +18816,11 @@ function currentReinforcementsCost(g: Game, p: Player,
|
||||
requireRule(g.homeworlds.custody, 'Reinforcements needs its original native reserve custody.');
|
||||
// Richese materializes ordinary primary-World reserves before this fee.
|
||||
if (materializes)
|
||||
quoteReinforcements(p.reserves - materializes - (p.elites?.reserves ?? 0), p.elites?.reserves ?? 0);
|
||||
quoteReinforcements(p.reserves - materializes - (p.elites?.reserves ?? 0), p.elites?.reserves ?? 0, selectedElite);
|
||||
return homeworldRule(() => quoteReinforcementsHomeworldCost(
|
||||
homeworldContext(g), g.homeworlds!.custody!, p.id, selections));
|
||||
homeworldContext(g), g.homeworlds!.custody!, p.id, selections, selectedElite));
|
||||
}
|
||||
return quoteReinforcements(p.reserves - materializes - (p.elites?.reserves ?? 0), p.elites?.reserves ?? 0);
|
||||
return quoteReinforcements(p.reserves - materializes - (p.elites?.reserves ?? 0), p.elites?.reserves ?? 0, selectedElite);
|
||||
} catch (error) {
|
||||
if (error instanceof ReinforcementsError) throw new RuleError(error.message);
|
||||
throw error;
|
||||
@@ -18842,9 +18846,11 @@ function reinforcementsPreview(g: Game, p: Player) {
|
||||
blocked = materializes
|
||||
? 'Your No-Field reveals before Reinforcements; three own reserves must remain after materialization.'
|
||||
: 'Reinforcements needs three physical forces in your reserves.';
|
||||
return { blocked, normal, elite,
|
||||
const eliteOptions = blocked ? [] : reinforcementsEliteOptions(
|
||||
reservesAfterReveal - (p.elites?.reserves ?? 0), p.elites?.reserves ?? 0);
|
||||
return { blocked, normal, elite, eliteOptions,
|
||||
...(!blocked && g.homeworlds?.custody ? { homeworldSources: homeworldRule(() =>
|
||||
reinforcementsHomeworldSources(homeworldContext(g), g.homeworlds!.custody!, p.id)) } : {}),
|
||||
eliteOptions.flatMap(choice => reinforcementsHomeworldSources(homeworldContext(g), g.homeworlds!.custody!, p.id, choice))) } : {}),
|
||||
...(noFieldAtCombatLocation(p,battle.territory)
|
||||
? { noField: { materializes, reservesAfterReveal } } : {}) };
|
||||
}
|
||||
@@ -31181,8 +31187,12 @@ function validatePlan(
|
||||
const selectedReinforcements = usesNativeBattleSpecial(input, REINFORCEMENTS_CARD);
|
||||
requireRule(input.reinforcementsSources === undefined || selectedReinforcements,
|
||||
'Native reserve fee sources require the physical Reinforcements card.');
|
||||
requireRule(input.reinforcementsElite === undefined || selectedReinforcements &&
|
||||
typeof input.reinforcementsElite === 'number',
|
||||
'A typed reserve fee requires the physical Reinforcements card.');
|
||||
const reserveCost = selectedReinforcements
|
||||
? currentReinforcementsCost(g, p, input.reinforcementsSources as NativeReserveSelections | undefined)
|
||||
? currentReinforcementsCost(g, p, input.reinforcementsSources as NativeReserveSelections | undefined,
|
||||
input.reinforcementsElite as number | undefined)
|
||||
: undefined;
|
||||
const forces = planCombatForces(g, p, opponent, reserveCost);
|
||||
const profile = ecazBattleProfile(g);
|
||||
@@ -31215,6 +31225,7 @@ function validatePlan(
|
||||
...(input.planetologistSpecial === true ? { planetologistSpecial: true as const } : {}),
|
||||
...(allyPayment ? { allyPayment } : {}),
|
||||
...(reserveCost?.sources ? { reinforcementsSources: structuredClone(reserveCost.sources) } : {}),
|
||||
...(input.reinforcementsElite !== undefined ? { reinforcementsElite: input.reinforcementsElite as number } : {}),
|
||||
dial,
|
||||
leader:
|
||||
typeof input.leader === 'string' && input.leader ? input.leader : null,
|
||||
@@ -33951,7 +33962,7 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
const reduced = casualtyCommitment.forces.temporaryElite !== sardaukar.receipt.count;
|
||||
const plan = b.plans[winner.id];
|
||||
const fee = reduced && usesNativeBattleSpecial(plan,REINFORCEMENTS_CARD)
|
||||
? currentReinforcementsCost(g,winner,plan.reinforcementsSources).sources?.[b.territory] : undefined;
|
||||
? currentReinforcementsCost(g,winner,plan.reinforcementsSources,plan.reinforcementsElite).sources?.[b.territory] : undefined;
|
||||
requireRule(!reduced || (fee && g.homeworlds && homeworldBattleLocation(g,b.territory)?.native === winner.id),
|
||||
'Reduced temporary Sardaukar require the original native Reinforcements fee.');
|
||||
sardaukar.casualties = {...structuredClone(casualtyCommitment),
|
||||
@@ -33979,7 +33990,7 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
}
|
||||
for (const cost of quote.reinforcements ?? []) {
|
||||
const player = getPlayer(g, cost.player);
|
||||
const current = currentReinforcementsCost(g, player, b.plans[player.id].reinforcementsSources);
|
||||
const current = currentReinforcementsCost(g, player, b.plans[player.id].reinforcementsSources, b.plans[player.id].reinforcementsElite);
|
||||
requireRule(cost.normal === current.normal && cost.elite === current.elite,
|
||||
'Reinforcements lost its original physical reserve cost.');
|
||||
if (current.native) {
|
||||
|
||||
Reference in new issue
Block a user