Connect printed high Ecaz Homeworld alliance victory
This commit is contained in:
1 parent
cfa0fe3952
commit
17bc4537c3
14 files changed
+878
-8
No files matched your search
@@ -0,0 +1,156 @@
|
||||
import type { BoardContext, BoardSeat } from './board-resolution-quote';
|
||||
import type { FactionId } from './catalog';
|
||||
import type { Game } from './engine';
|
||||
import { HOMEWORLD_CARDS } from './homeworld-cards';
|
||||
import { homeworldForceGroups } from './homeworld-custody';
|
||||
import { homeworldPopulations } from './homeworld-population';
|
||||
import { validateHomeworldOccupationHistory } from './homeworld-occupation-history';
|
||||
import {
|
||||
quoteStableHomeworldOccupation,
|
||||
type StableHomeworldOccupationContext,
|
||||
} from './homeworld-stable-occupation';
|
||||
import { strongholdProgress, VictoryProgressError } from './victory-progress';
|
||||
|
||||
export type EcazHomeworldVictoryContext = Omit<BoardContext, 'players'> &
|
||||
Pick<Game, 'turn' | 'homeworlds' | 'homeworldOccupationHistory' | 'homeworldOccupationPreview'> & {
|
||||
homeworldEcazVictoryPreview?: true;
|
||||
players: readonly (BoardSeat & {
|
||||
reserves: number;
|
||||
elites?: { reserves: number } | null;
|
||||
})[];
|
||||
};
|
||||
export type EcazHomeworldVictoryProgress = {
|
||||
owner: string;
|
||||
members: string[];
|
||||
population: number;
|
||||
minimum: number;
|
||||
high: boolean;
|
||||
jointStrongholds: string[];
|
||||
foreignHomeworlds: {
|
||||
world: string;
|
||||
card: string;
|
||||
native: string;
|
||||
faction: FactionId;
|
||||
holder: string;
|
||||
}[];
|
||||
distinctNativeFactions: FactionId[];
|
||||
qualifies: boolean;
|
||||
blocked: string | null;
|
||||
};
|
||||
|
||||
/** Printed High Ecaz victory, separate from ordinary points and occupied Duke
|
||||
* benefits. Foreign holdings require the existing original source authority AND
|
||||
* the entitled holder's current physical presence; a departed garrison is never
|
||||
* a victory holding. Advanced p.22 retained occupation is otherwise respected.
|
||||
* This returns public facts only, never an income receipt or a winning action. */
|
||||
export function ecazHomeworldVictoryProgress(
|
||||
g: EcazHomeworldVictoryContext,
|
||||
): EcazHomeworldVictoryProgress | null {
|
||||
// Original BoardContext callers are not opted in and need no reserve data.
|
||||
if (g.homeworldEcazVictoryPreview === undefined) return null;
|
||||
try {
|
||||
if (g.homeworldEcazVictoryPreview !== true || g.homeworldOccupationPreview !== true)
|
||||
throw new VictoryProgressError('Ecaz Homeworld victory requires the fresh occupation preview.');
|
||||
if (!Number.isSafeInteger(g.turn) || g.turn < 1)
|
||||
throw new VictoryProgressError('Ecaz Homeworld victory requires the current positive turn.');
|
||||
const board: BoardContext = {
|
||||
advanced: g.advanced,
|
||||
ecazOccupyPreview: g.ecazOccupyPreview,
|
||||
storm: g.storm,
|
||||
order: g.order,
|
||||
players: g.players,
|
||||
mobileStronghold: g.mobileStronghold,
|
||||
discoveries: g.discoveries,
|
||||
};
|
||||
// This owns alliance validation, advisor settling, public No-Field presence,
|
||||
// storm/contested stronghold and HMS semantics. Tech sets are not joint sites.
|
||||
const ordinary = strongholdProgress(board).progress;
|
||||
const ecaz = g.players.find(player => player.faction === 'ecaz');
|
||||
if (!ecaz)
|
||||
throw new VictoryProgressError('Fresh Ecaz Homeworld victory requires native Ecaz.');
|
||||
const state = g.homeworlds;
|
||||
if (!state || typeof state !== 'object' || Array.isArray(state) ||
|
||||
Object.keys(state).some(key => !['custody', 'historyVersion'].includes(key)) ||
|
||||
(state.historyVersion !== undefined && state.historyVersion !== 1) || !state.custody)
|
||||
throw new VictoryProgressError('Ecaz Homeworld victory requires valid native Homeworld custody.');
|
||||
const context = {
|
||||
advanced: g.advanced,
|
||||
players: g.players.map(player => ({
|
||||
id: player.id, faction: player.faction, reserves: player.reserves,
|
||||
eliteReserves: player.elites?.reserves ?? 0,
|
||||
})),
|
||||
};
|
||||
const worlds = homeworldForceGroups(context, state.custody);
|
||||
const history = g.homeworldOccupationHistory;
|
||||
const originalHistory = state.historyVersion === 1 && history !== undefined;
|
||||
if (originalHistory) validateHomeworldOccupationHistory(history, context, g.turn);
|
||||
const population = homeworldPopulations(context, state.custody)
|
||||
.find(home => home.native === ecaz.id && home.card === 'ecaz')!;
|
||||
const minimum = HOMEWORLD_CARDS.find(card => card.id === 'ecaz')!.high.reserves.min;
|
||||
const progress = ordinary.find(row => row.player === ecaz.id)!;
|
||||
const members = progress.members;
|
||||
const foreignHomeworlds: EcazHomeworldVictoryProgress['foreignHomeworlds'] = [];
|
||||
const possible = new Set<FactionId>();
|
||||
const blockers: string[] = [];
|
||||
// The stable authority's legacy Game signature is broader than its actual
|
||||
// public inputs. Project explicitly rather than passing private Player data.
|
||||
const publicOccupation = {
|
||||
advanced: g.advanced, turn: g.turn, homeworlds: state,
|
||||
homeworldOccupationPreview: g.homeworldOccupationPreview,
|
||||
homeworldOccupationHistory: g.homeworldOccupationHistory,
|
||||
players: g.players.map(player => ({
|
||||
id: player.id, faction: player.faction, ally: player.ally,
|
||||
reserves: player.reserves,
|
||||
elites: player.elites ? { reserves: player.elites.reserves } : undefined,
|
||||
})),
|
||||
} as StableHomeworldOccupationContext;
|
||||
for (const world of worlds) {
|
||||
// Ecaz and its ally's own native worlds are not "other factions".
|
||||
if (members.includes(world.native)) continue;
|
||||
const faction = context.players.find(player => player.id === world.native)!.faction;
|
||||
const card = HOMEWORLD_CARDS.find(card => world.secondary
|
||||
? card.id === 'salusa_secundus'
|
||||
: card.faction === faction && card.id !== 'salusa_secundus')!;
|
||||
const quote = originalHistory
|
||||
? quoteStableHomeworldOccupation(publicOccupation, card.id)
|
||||
: { entitlement: null, blocked: 'Ecaz Homeworld holdings require the original fresh occupation history.' };
|
||||
const alliancePresent = members.some(member => {
|
||||
const forces = world.forces[member];
|
||||
return forces && forces.normal + forces.elite > 0;
|
||||
});
|
||||
if (quote.blocked) {
|
||||
// Pending facts can block a possible win, but cannot manufacture a
|
||||
// holding; unrelated occupiers and empty departed worlds cannot block it.
|
||||
if (alliancePresent) {
|
||||
possible.add(faction);
|
||||
blockers.push(quote.blocked);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const holding = quote.entitlement;
|
||||
if (!holding || !members.includes(holding.occupier)) continue;
|
||||
const present = world.forces[holding.occupier];
|
||||
if (!present || present.normal + present.elite === 0) continue;
|
||||
foreignHomeworlds.push({
|
||||
world: world.id, card: card.id, native: world.native,
|
||||
faction, holder: holding.occupier,
|
||||
});
|
||||
possible.add(faction);
|
||||
}
|
||||
const distinctNativeFactions = [...new Set(foreignHomeworlds.map(home => home.faction))];
|
||||
const high = population.side === 'high';
|
||||
const jointStrongholds = progress.jointlyOccupied;
|
||||
const blocked = blockers.length > 0 && possible.size >= 2 && distinctNativeFactions.length < 2
|
||||
? blockers[0] : null;
|
||||
return {
|
||||
owner: ecaz.id, members: [...members], population: population.population,
|
||||
minimum, high, jointStrongholds: [...jointStrongholds], foreignHomeworlds,
|
||||
distinctNativeFactions,
|
||||
qualifies: high && jointStrongholds.length > 0 && distinctNativeFactions.length >= 2,
|
||||
blocked,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof VictoryProgressError) throw error;
|
||||
throw new VictoryProgressError(error instanceof Error ? error.message : 'Invalid public Ecaz Homeworld victory state.');
|
||||
}
|
||||
}
|
||||
+17
-3
@@ -99,6 +99,7 @@ import { createOccupiedPercentageState, quoteOccupiedPercentageSource, allocateO
|
||||
import { quoteOccupiedBiddingAuthority, requireOccupiedBiddingController, requireOccupiedBiddingBonusRecipient } from './homeworld-occupied-bidding';
|
||||
import { quoteStableHomeworldOccupation } from './homeworld-stable-occupation';
|
||||
import { createOccupiedTupileState, syncOccupiedTupileLease, quoteOccupiedTupileAuthority, quoteOccupiedTupileCleanup, completeOccupiedTupileCleanup, validateOccupiedTupileState, type OccupiedTupileState } from './homeworld-occupied-tupile';
|
||||
import { ecazHomeworldVictoryProgress, type EcazHomeworldVictoryProgress } from './ecaz-homeworld-victory';
|
||||
import { quoteGrummanCollection, quoteGrummanCollectionAction } from './grumman-collection';
|
||||
import { grummanCollectionSignature, validateGrummanCollection, type GrummanCollection } from './grumman-collection-return';
|
||||
import { ambassadorPhaseAllowed } from './ambassador-phase';
|
||||
@@ -1893,6 +1894,8 @@ export type Game = {
|
||||
homeworldOccupationHistory?: HomeworldOccupationHistory;
|
||||
/** Fresh development occupation entry only; never inferred for an older Homeworld save. */
|
||||
homeworldOccupationPreview?: true;
|
||||
/** Fresh printed Ecaz high-card victory, separate from ordinary stronghold points. */
|
||||
homeworldEcazVictoryPreview?: true;
|
||||
/** Fresh Tupile capacity entry only; older occupation games are not converted. */
|
||||
homeworldTupilePreview?: true;
|
||||
homeworldTupileState?: OccupiedTupileState;
|
||||
@@ -8548,6 +8551,7 @@ export function initializeHomeworldOccupationGameForAudit(state: Game): Game {
|
||||
g.homeworldTupileState = createOccupiedTupileState();
|
||||
g.pendingHomeworldTupileCleanup = null;
|
||||
}
|
||||
if (byFaction(g, 'ecaz')) g.homeworldEcazVictoryPreview = true;
|
||||
return g;
|
||||
}
|
||||
/** Offline-only seam for the independent Nexus module; public starts remain gated. */
|
||||
@@ -18354,6 +18358,9 @@ function homeworldHistoryIntegrity(g: Game) {
|
||||
homeworldOccupiedIncomeIntegrity(g);
|
||||
occupiedPercentageIntegrity(g);
|
||||
tupileSlotsIntegrity(g);
|
||||
requireRule(!g.homeworldEcazVictoryPreview ||
|
||||
g.homeworldOccupationPreview === true && !!g.homeworlds && !!byFaction(g, 'ecaz'),
|
||||
'The printed Ecaz Homeworld victory requires its original fresh native entry.');
|
||||
}
|
||||
function homeworldOccupiedIncomeIntegrity(g: Game) {
|
||||
const pending = g.pendingHomeworldOccupiedIncome, history = g.homeworldOccupiedIncomeHistory;
|
||||
@@ -18880,7 +18887,10 @@ function victory(g: Game, quote: VictoryQuote = currentVictoryQuote(g)) {
|
||||
row.members.length === g.winner.length &&
|
||||
row.members.every((id) => g.winner.includes(id)),
|
||||
);
|
||||
const fremen = g.turn === 10 && !progress.some((row) => row.qualifies)
|
||||
const homeworld = quote.ecazHomeworld &&
|
||||
quote.ecazHomeworld.members.length === g.winner.length &&
|
||||
quote.ecazHomeworld.members.every(id => g.winner.includes(id)) ? quote.ecazHomeworld : null;
|
||||
const fremen = !quote.ecazHomeworld && g.turn === 10 && !progress.some((row) => row.qualifies)
|
||||
? fremenSpecialVictory(g) : null;
|
||||
const fremenWon = fremen?.qualifies && fremen.members.length === g.winner.length &&
|
||||
fremen.members.every((id) => g.winner.includes(id));
|
||||
@@ -18889,7 +18899,7 @@ function victory(g: Game, quote: VictoryQuote = currentVictoryQuote(g)) {
|
||||
: '';
|
||||
log(
|
||||
g,
|
||||
`${g.winner.map((id) => faction(getPlayer(g, id).faction).name).join(' and ')} won the game.${occupy ? ` Ecaz Occupy: both allies occupy ${occupy.jointlyOccupied.map((id) => territory(id).name).join(', ')} without opposing fighters, meeting the three-stronghold alliance target.` : ''}${specialReason}`,
|
||||
`${g.winner.map((id) => faction(getPlayer(g, id).faction).name).join(' and ')} won the game.${occupy ? ` Ecaz Occupy: both allies occupy ${occupy.jointlyOccupied.map((id) => territory(id).name).join(', ')} without opposing fighters, meeting the three-stronghold alliance target.` : ''}${homeworld ? ' High Ecaz Homeworld victory: the alliance jointly holds a stronghold and occupies Homeworlds of two other native factions.' : ''}${specialReason}`,
|
||||
);
|
||||
} else if (g.status === 'finished')
|
||||
log(g, 'The tenth turn ended without a winner.');
|
||||
@@ -23092,7 +23102,7 @@ function currentRicheseCancellationQuote(g: Game, response: ResponseWindow) {
|
||||
}
|
||||
function currentVictoryQuote(g: Game) {
|
||||
try {
|
||||
return quoteVictory(g);
|
||||
return quoteVictory(g, g.homeworldEcazVictoryPreview ? g : undefined);
|
||||
} catch (error) {
|
||||
if (error instanceof VictoryQuoteError) throw new RuleError(error.message);
|
||||
throw error;
|
||||
@@ -31456,10 +31466,12 @@ export function viewGame(state: Game, id: string) {
|
||||
const me = getPlayer(g, id);
|
||||
let victoryProgress: StrongholdProgress[] = [];
|
||||
let fremenVictory: FremenVictoryProgress | null = null;
|
||||
let ecazHomeworldVictory: EcazHomeworldVictoryProgress | null = null;
|
||||
if (g.status === 'playing' || g.status === 'finished') {
|
||||
try {
|
||||
victoryProgress = strongholdProgress(state).progress;
|
||||
fremenVictory = fremenSpecialVictory(state);
|
||||
if (g.homeworldEcazVictoryPreview) ecazHomeworldVictory = ecazHomeworldVictoryProgress(state);
|
||||
} catch (error) {
|
||||
// A malformed restored board has no reliable preview. Authoritative
|
||||
// victory still rejects it through its strict quote.
|
||||
@@ -31556,6 +31568,7 @@ export function viewGame(state: Game, id: string) {
|
||||
spiceBankerIncomePreview: g.spiceBankerIncomePreview === true,
|
||||
homeworldOccupationPreview: g.homeworldOccupationPreview === true,
|
||||
homeworldTupilePreview: g.homeworldTupilePreview === true,
|
||||
homeworldEcazVictoryPreview: g.homeworldEcazVictoryPreview === true,
|
||||
spiceBankerIncome: g.spiceBankerIncomePreview
|
||||
? bankerIncomeRule(() => projectBankerIncome(g.spiceBankerIncome!, bankerIncomeContext(g))) : null,
|
||||
bribeOptions: projectedBribes(g, me),
|
||||
@@ -31933,6 +31946,7 @@ export function viewGame(state: Game, id: string) {
|
||||
winner: g.winner,
|
||||
victoryProgress,
|
||||
fremenVictory,
|
||||
ecazHomeworldVictory,
|
||||
shieldWallDestroyed: g.shieldWallDestroyed,
|
||||
stormDialers: g.stormDialers,
|
||||
stormSubmitted: Object.keys(g.stormDials),
|
||||
|
||||
@@ -3123,6 +3123,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
steps: [
|
||||
'Victory is checked when the Mentat pause begins. A faction needs three strongholds, or four when allied or playing a two-player game. Allies combine distinct qualifying strongholds.',
|
||||
'Ecaz and its reciprocal ally can also win by jointly occupying three strongholds: both factions need non-advisor presence in each, without opposing fighters. The ordinary four-stronghold alliance route remains available. Occupy cancellation does not remove this victory condition. A concealed No-Field supplies public presence; its secret value is not revealed by this check.',
|
||||
'The separately marked fresh native Ecaz Homeworld preview has another printed route: native Ecaz must currently have at least seven reserves, the reciprocal alliance must jointly hold one actual uncontested stronghold, and it must occupy worlds of two other native factions. Two Emperor worlds count as one faction. This joins normal winners before a correct BG prediction or final-turn fallback; it adds no ordinary stronghold points.',
|
||||
'Opposing forces may survive together in a stronghold covered by storm. That contested stronghold does not count for either side. A sole occupant still counts it even under storm.',
|
||||
'A correct Bene Gesserit faction-and-turn prediction replaces a normal win, including an allied win, with a Bene Gesserit solo win. Guild and Fremen special wins cannot be predicted.',
|
||||
'On turn ten, a normal stronghold victory comes first. Otherwise, check Fremen’s special condition, then award the game to Guild and its ally if present. If Guild is absent, Fremen and its ally win even when the usual Fremen special condition fails.',
|
||||
@@ -3851,6 +3852,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Proved Wallach IX occupation protects the occupier and ally from native Voice; Tleilax prevents native Face Dancers against them; Grumman prevents revealing Terror on their entry. Occupied Salusa removes Sardaukar advantage without changing starred physical identity. These printed effects cannot be restored or canceled with Karama. Other occupied powers and full Homeworld acceptance remain open.',
|
||||
'Caladan shares the admitted original Bidding inspection with the occupier, not its ally; native inspection can still be prevented independently. Ix gives the occupier the actual private drawn pool and selection, not unrelated setup or Technology. Richese sets its own method/direction before the occupier chooses only the physical cache card; seller, payment and other decisions stay native. Giedi gives one original purchase bonus to the occupier or its reciprocal ally with real receiver capacity, even when Harkonnen’s purchased card fills its hand.',
|
||||
'The separately marked fresh Tupile entry grants one hand slot to the proved occupier and reciprocal ally. Every server-derived hand limit includes it, so Harkonnen can hold nine without receiving a tenth bonus. After a proved loss, each old holder privately selects exactly its excess original eligible cards to return to the normal limit; committed cards remain protected. Original phase opening and remaining movement resume without another shipment or payment. Basic unknown lifecycle blocks rather than guesses cleanup. Advanced departure can restore CHOAM’s low intelligence without clearing its previous answers or used factions.',
|
||||
'The fresh native Ecaz high-card preview shows a separate prospective conjunction: native seven-plus population, at least one actual jointly held stronghold and proved current alliance holdings of two other native factions’ Homeworlds. This does not change the ordinary four-stronghold or three-joint Ecaz targets. Original Mentat decides the win before prediction/fallback; source uncertainty remains blocked. Duke authority and the Ecaz-only revival ruling are separate.',
|
||||
'The card collection below is available for reference. Starting a Homeworld game remains disabled while shipment, battles, occupation, economy and faction interactions are being completed.',
|
||||
],
|
||||
example:
|
||||
@@ -3901,6 +3903,9 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'game/homeworld-occupied-bidding.ts',
|
||||
'game/homeworld-occupied-defenses.ts',
|
||||
'game/homeworld-occupied-tupile.ts',
|
||||
'game/ecaz-homeworld-victory.ts',
|
||||
'game/victory-quote.ts',
|
||||
'components/victory-progress.tsx',
|
||||
'game/tupile-intelligence.ts',
|
||||
'game/tupile-intelligence-answer.ts',
|
||||
'game/tupile-intelligence-state.ts',
|
||||
@@ -4053,6 +4058,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'tests/homeworld-occupied-private-lots.test.ts',
|
||||
'tests/homeworld-occupied-tupile.test.ts',
|
||||
'tests/homeworld-occupied-tupile-runtime.test.ts',
|
||||
'tests/ecaz-homeworld-victory.test.ts',
|
||||
'tests/ecaz-homeworld-victory-runtime.test.ts',
|
||||
'tests/grumman-collection.test.ts',
|
||||
'tests/grumman-collection-engine.test.ts',
|
||||
'tests/grumman-collection-controls.test.ts',
|
||||
|
||||
+24
-2
@@ -21,6 +21,7 @@ import {
|
||||
strongholdControllers,
|
||||
type StrongholdState,
|
||||
} from './stronghold-cards';
|
||||
import { ecazHomeworldVictoryProgress, type EcazHomeworldVictoryContext, type EcazHomeworldVictoryProgress } from './ecaz-homeworld-victory';
|
||||
|
||||
export class VictoryQuoteError extends Error {}
|
||||
function requireVictory(
|
||||
@@ -46,12 +47,14 @@ export type VictoryQuote = {
|
||||
status: 'playing' | 'finished';
|
||||
/** Exact final ownership when settlement is due; null means no settlement. */
|
||||
strongholds: StrongholdState | null;
|
||||
/** Qualifying printed source before any private BG prediction replacement. */
|
||||
ecazHomeworld: EcazHomeworldVictoryProgress | null;
|
||||
};
|
||||
|
||||
/** Internal server quote. Prediction is private input, never a returned field.
|
||||
* This preserves the existing victory order, including the distinct final-turn
|
||||
* Fremen/Guild fallbacks. It does not advance a turn or settle other phase work. */
|
||||
export function quoteVictory(g: VictoryContext): VictoryQuote {
|
||||
export function quoteVictory(g: VictoryContext, ecazContext?: EcazHomeworldVictoryContext): VictoryQuote {
|
||||
requireVictory(
|
||||
(g.status === 'playing' || g.status === 'finished') &&
|
||||
whole(g.turn) &&
|
||||
@@ -154,12 +157,31 @@ export function quoteVictory(g: VictoryContext): VictoryQuote {
|
||||
ids.some((id) => fighterCount(player(id), t.id) > 0) &&
|
||||
players.every((p) => ids.includes(p.id) || fighterCount(p, t.id) === 0),
|
||||
).length;
|
||||
let ecazHomeworld: EcazHomeworldVictoryProgress | null = null;
|
||||
if (ecazContext) {
|
||||
const context = ecazContext;
|
||||
requireVictory(context.homeworldEcazVictoryPreview === true && context.turn === g.turn &&
|
||||
context.advanced === g.advanced && context.storm === g.storm && context.players === g.players &&
|
||||
context.order === g.order && context.mobileStronghold === g.mobileStronghold &&
|
||||
context.discoveries === g.discoveries,
|
||||
'The Ecaz Homeworld source must belong to this original victory board.');
|
||||
try {
|
||||
const row = ecazHomeworldVictoryProgress(context);
|
||||
requireVictory(!row?.blocked || !row.high || !row.jointStrongholds.length,
|
||||
row?.blocked ?? 'The Ecaz Homeworld victory source is unresolved.');
|
||||
if (row?.qualifies) ecazHomeworld = row;
|
||||
} catch (error) {
|
||||
if (error instanceof VictoryProgressError) throw new VictoryQuoteError(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
let winner = [...g.winner];
|
||||
const predicted = () =>
|
||||
bg?.prediction?.turn === g.turn &&
|
||||
winner.some((id) => player(id).faction === bg.prediction!.faction);
|
||||
for (const row of progress)
|
||||
if (row.qualifies) winner = [...new Set([...winner, ...row.members])];
|
||||
if (ecazHomeworld) winner = [...new Set([...winner, ...ecazHomeworld.members])];
|
||||
if (predicted()) winner = [bg!.id];
|
||||
let status = g.status;
|
||||
if (!winner.length && g.turn === 10) {
|
||||
@@ -209,5 +231,5 @@ export function quoteVictory(g: VictoryContext): VictoryQuote {
|
||||
throw new VictoryQuoteError((error as Error).message);
|
||||
}
|
||||
}
|
||||
return { released, winner, status, strongholds };
|
||||
return { released, winner, status, strongholds, ecazHomeworld };
|
||||
}
|
||||
Reference in new issue
Block a user