Compose original mixed E3 and three-family Nexus

This commit is contained in:
Quintine committed 2026-10-07 15:16:37 +11:00
1 parent 6935b8650d
commit 48b2755088
11 files changed
+255 -63

No files matched your search

+2 -2
View File
@@ -1,4 +1,4 @@
import { homeworldLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, noOtherLeaderSkillModules, pairedE3NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile, type LeaderSkillProfile } from './leader-skill-profile';
import { homeworldLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, mixedE3NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, noOtherLeaderSkillModules, pairedE3NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile, type LeaderSkillProfile } from './leader-skill-profile';
import { classicDiscoveryLeaderSkillsProfile } from './discovery-module-profile';
export type BureaucratPaymentKind = 'auction' | 'shipment' | 'bribe' | 'revival';
export type BureaucratPaymentSource = {
@@ -36,7 +36,7 @@ export function bureaucratPaymentSignature(value: object): string {
return JSON.stringify({ ...value, signature: undefined });
}
export function bureaucratPaymentModeSupported(game: LeaderSkillProfile): boolean {
return mixedE1E2NexusLeaderSkillsProfile(game) || pairedE3NexusLeaderSkillsProfile(game) ||
return mixedE3NexusLeaderSkillsProfile(game) || mixedE1E2NexusLeaderSkillsProfile(game) || pairedE3NexusLeaderSkillsProfile(game) ||
(noOtherLeaderSkillModules(game) &&
(!game.expansions.length ||
(game.expansions.length === 1 && game.expansions[0] === 'choam') ||
+14 -2
View File
@@ -1,5 +1,5 @@
import { FACTIONS } from './catalog';
import type { FactionModuleProfile } from './faction-module-profile';
import { mixedE3NexusRosterProfile, type FactionModuleProfile } from './faction-module-profile';
import type { LeaderSkillProfile } from './leader-skill-profile';
/** Public unused-lobby admission only. The original initializer owns physical
@@ -17,7 +17,7 @@ export function homeworldDiscoveryProfile(game: FactionModuleProfile): boolean {
/** The same roster/module contract during play; live Discovery frames are expected. */
export function discoveryModeSupported(game: FactionModuleProfile): boolean {
if (game.nexusCards) return standaloneE3DiscoveryNexusProfile(game) || singleE1E2DiscoveryNexusProfile(game) ||
mixedE1E2DiscoveryNexusProfile(game) || pairedE3DiscoveryNexusProfile(game);
mixedE3DiscoveryNexusProfile(game) || mixedE1E2DiscoveryNexusProfile(game) || pairedE3DiscoveryNexusProfile(game);
if (typeof game.advanced !== 'boolean' || game.discoveryEnabled !== true ||
game.players.length < 2 || game.players.length > 6 ||
(game.techTokens && game.players.length < 3) || game.expansions.length > 2 ||
@@ -236,3 +236,15 @@ export function pairedE3DiscoveryNexusProfile(game: FactionModuleProfile): boole
}
return ecaz && moritani;
}
/** Original mixed E3 or all-three-family Nexus/Discovery without Skills. */
export function mixedE3DiscoveryNexusProfile(game: FactionModuleProfile): boolean {
return game.discoveryEnabled === true && !!game.nexusCards && !game.leaderSkills &&
(!game.techTokens || game.players.length >= 3) &&
(!game.strongholdCards || game.advanced) &&
!game.ecazTreachery && !game.semutaPreview && !game.advancedPreview &&
!game.kullPreview && !game.nexusKullPreview && !game.guildBetrayalPreview &&
!game.richeseBetrayalPreview && !game.nexusIxianReplacementPreview &&
!game.nexusIxianBetrayalPreview && !game.nexusHarkonnenBetrayalPreview &&
mixedE3NexusRosterProfile(game);
}
+34 -22
View File
@@ -1,7 +1,7 @@
import { classicNexusModulesProfile, mixedE1E2NexusProfile, pairedE3NexusProfile, pairedNexusModulesProfile, singleE1E2NexusProfile, standaloneE3NexusProfile } from './nexus-module-profile';
import { classicNexusModulesProfile, mixedE1E2NexusProfile, mixedE3NexusProfile, pairedE3NexusProfile, pairedNexusModulesProfile, singleE1E2NexusProfile, standaloneE3NexusProfile } from './nexus-module-profile';
import { nativeFactionTechProfile } from './faction-module-profile';
import { classicDiscoveryLeaderSkillsProfile, classicDiscoveryNexusProfile, homeworldDiscoveryProfile, discoveryModeSupported, discoveryModuleProfile, mixedE1E2DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
import { advancedMoritaniLeaderSkillsProfile, advancedNativeLeaderSkillsProfile, homeworldLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedE3NexusLeaderSkillsProfile, pairedNexusLeaderSkillsProfile, singleE1E2NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
import { classicDiscoveryLeaderSkillsProfile, classicDiscoveryNexusProfile, homeworldDiscoveryProfile, discoveryModeSupported, discoveryModuleProfile, mixedE1E2DiscoveryNexusProfile, mixedE3DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
import { advancedMoritaniLeaderSkillsProfile, advancedNativeLeaderSkillsProfile, homeworldLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, mixedE1E2NexusLeaderSkillsProfile, mixedE3NexusLeaderSkillsProfile, nativeDiscoveryLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedE3NexusLeaderSkillsProfile, pairedNexusLeaderSkillsProfile, singleE1E2NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
import { bribeTimingBlock, maximumBribe, type BribeOptions } from './bribe-options';
import { quoteSpicePlacement, stormExposesTerritory, stormSectorAfter, wormConsumesForces } from './disaster-rules';
import { isStormCardDistance, type StormCardComponent } from './storm-cards';
@@ -7612,7 +7612,8 @@ function e3StrongholdFactionProfile(g: Game): boolean {
return native !== null && (native !== 'moritani' || !byFaction(g, 'harkonnen'));
}
function moritaniAssassinateModeSupported(g: Game) {
if (standaloneE3NexusProfile(g) || pairedE3NexusProfile(g) || pairedE3NexusLeaderSkillsProfile(g))
if (mixedE3NexusProfile(g) || mixedE3NexusLeaderSkillsProfile(g) ||
standaloneE3NexusProfile(g) || pairedE3NexusProfile(g) || pairedE3NexusLeaderSkillsProfile(g))
return g.advanced && g.players.some(player => player.faction === 'moritani');
if (g.discoveryEnabled || g.discoveries)
return g.advanced && !!g.discoveries &&
@@ -7695,7 +7696,8 @@ function moritaniAssassinateIntegrity(g: Game) {
} else requireRule(!resume && g.decision?.kind !== 'moritaniAssassinate', 'A settled assassination cannot retain its decision.');
if (g.status === 'playing' || g.status === 'finished') {
const retired = state.opportunities.filter(r => r.stage === 'replaced').map(r => r.card!);
const physical = [...(g.traitorReserve ?? []),...g.players.flatMap(p => p.traitors),...retired].sort();
const physical = [...(g.traitorReserve ?? []), ...g.players.flatMap(p =>
[...p.traitors, ...(p.faceDancers ?? []).map(dancer => dancer.leader)]), ...retired].sort();
requireRule(JSON.stringify(physical) === JSON.stringify([...nexusTraitorUniverse(g)].sort()),
'Assassination must preserve every physical Traitor Card in hand, deck or face-up set-aside custody.');
for (const r of state.opportunities.filter(r => r.stage === 'revealed'))
@@ -8574,9 +8576,11 @@ export function initializeNexusGameForAudit(state: Game): Game {
'Enable Nexus cards in a fresh audit lobby first.');
const standalone = standaloneE3NexusProfile(state);
const pairedE3 = pairedE3NexusProfile(state);
const native = standalone || pairedE3 || singleE1E2NexusProfile(state) || mixedE1E2NexusProfile(state);
const mixedE3 = mixedE3NexusProfile(state);
const native = mixedE3 || standalone || pairedE3 || singleE1E2NexusProfile(state) || mixedE1E2NexusProfile(state);
const discovery = classicDiscoveryNexusProfile(state) || standaloneE3DiscoveryNexusProfile(state) ||
singleE1E2DiscoveryNexusProfile(state) || mixedE1E2DiscoveryNexusProfile(state) || pairedE3DiscoveryNexusProfile(state);
mixedE3DiscoveryNexusProfile(state) || singleE1E2DiscoveryNexusProfile(state) ||
mixedE1E2DiscoveryNexusProfile(state) || pairedE3DiscoveryNexusProfile(state);
if (state.discoveryEnabled) {
requireRule(discovery,
'Discovery Nexus requires a fresh supported original classic or native roster and module envelope.');
@@ -8584,8 +8588,8 @@ export function initializeNexusGameForAudit(state: Game): Game {
requireFreshFactionInventory(state);
}
const g = initializeSetupGameForAudit(state, !!state.homeworlds, true, false, discovery, false, false, native);
if ((standalone || pairedE3) && byFaction(g, 'ecaz')) g.ecazOccupyPreview = true;
if ((standalone || pairedE3) && g.advanced && byFaction(g, 'moritani')) initializeMoritaniAssassinateState(g);
if ((mixedE3 || standalone || pairedE3) && byFaction(g, 'ecaz')) g.ecazOccupyPreview = true;
if ((mixedE3 || standalone || pairedE3) && g.advanced && byFaction(g, 'moritani')) initializeMoritaniAssassinateState(g);
return g;
}
/** Fresh audit admission only; ordinary games and public starts never opt in. */
@@ -9378,10 +9382,13 @@ export function initializeCombinedNexusGameForAudit(state: Game, homeworlds = fa
.every(faction => state.players.some(player => player.faction === faction)),
'The combined Nexus sample requires five or six expansion factions and their three decks.');
requireRule(!!state.nexusCards && state.nexusCards.cards === null && state.nexusCards.phase === null &&
!!state.homeworlds === homeworlds && !state.leaderSkills && !state.discoveryEnabled &&
!state.ecazTreachery && !state.techTokens && !state.strongholdCards,
'Enable only the selected Nexus and Homeworld modules in a fresh combined expansion lobby.');
return initializeSetupGameForAudit(state, homeworlds, true, false, false, false, false, true);
!!state.homeworlds === homeworlds && mixedE3NexusProfile(state),
'Enable original Nexus, optional Homeworlds/Discovery/Tech/Advanced Strongholds in a fresh combined expansion lobby.');
const g = initializeSetupGameForAudit(state, homeworlds, true, false,
mixedE3DiscoveryNexusProfile(state), false, false, true);
if (byFaction(g, 'ecaz')) g.ecazOccupyPreview = true;
if (g.advanced && byFaction(g, 'moritani')) initializeMoritaniAssassinateState(g);
return g;
}
/** Offline prototype entry through real Ix setup, including both expansion factions.
@@ -9409,9 +9416,9 @@ export function initializeFactionExpansionsGameForAudit(state: Game): Game {
return initialized;
}
function ecazOccupyCompositionSupported(g: Game): boolean {
if (g.ecazOccupyPreview === true && (standaloneE3NexusProfile(g) || pairedE3NexusProfile(g)) &&
if (g.ecazOccupyPreview === true && (mixedE3NexusProfile(g) || standaloneE3NexusProfile(g) || pairedE3NexusProfile(g)) &&
g.players.some(player => player.faction === 'ecaz')) return true;
if (g.leaderSkills && (standaloneE3NexusLeaderSkillsProfile(g) || pairedE3NexusLeaderSkillsProfile(g)) &&
if (g.leaderSkills && (mixedE3NexusLeaderSkillsProfile(g) || standaloneE3NexusLeaderSkillsProfile(g) || pairedE3NexusLeaderSkillsProfile(g)) &&
g.players.some(p => p.faction === 'ecaz')) return true;
if (g.ecazOccupyPreview === true && g.discoveries && discoveryModeSupported(g) &&
g.players.some(p => p.faction === 'ecaz')) return true;
@@ -9574,13 +9581,15 @@ export function initializeLeaderSkillsGameForAudit(state: Game): Game {
g.leaderSkills = createLeaderSkills(random);
const classicNexus = classicNexusLeaderSkillsProfile(g);
const pairedE3 = pairedE3NexusLeaderSkillsProfile(g);
const mixedE3 = mixedE3NexusLeaderSkillsProfile(g);
const initialized = initializeSetupGameForAudit(g, homeworlds, classicNexus || pairedNexusLeaderSkillsProfile(g) ||
singleE1E2NexusLeaderSkillsProfile(g) || mixedE1E2NexusLeaderSkillsProfile(g) ||
standaloneE3NexusLeaderSkillsProfile(g) || pairedE3, false,
mixedE3 || standaloneE3NexusLeaderSkillsProfile(g) || pairedE3, false,
classicDiscoveryLeaderSkillsProfile(g) || nativeDiscoveryLeaderSkillsProfile(g) || (g.discoveryEnabled === true && classicNexus), true,
g.expansions.length === 1 && g.expansions[0] === 'choam', nativeExpansionLeaderSkillsProfile(g));
if (advancedMoritaniLeaderSkillsProfile(initialized) || (pairedE3 && initialized.advanced)) initializeMoritaniAssassinateState(initialized);
if ((homeworlds || nativeDiscoveryLeaderSkillsProfile(initialized) || pairedE3) && byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
if (advancedMoritaniLeaderSkillsProfile(initialized) || ((mixedE3 || pairedE3) && initialized.advanced &&
byFaction(initialized, 'moritani'))) initializeMoritaniAssassinateState(initialized);
if ((mixedE3 || homeworlds || nativeDiscoveryLeaderSkillsProfile(initialized) || pairedE3) && byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
return initialized;
}
/** Fresh all-fourteen-card classic or supported native skills; no public income toggle. */
@@ -9620,12 +9629,12 @@ function initializeSetupGameForAudit(state: Game, homeworlds: boolean, nexus = f
const nativeTech = factions && !leaderSkills && nativeFactionTechProfile(g);
const discoveryComposition = discovery && (discoveryModuleProfile(g) ||
classicDiscoveryNexusProfile(g) || pairedDiscoveryNexusProfile(g) ||
mixedE1E2DiscoveryNexusProfile(g) || pairedE3DiscoveryNexusProfile(g) ||
mixedE3DiscoveryNexusProfile(g) || mixedE1E2DiscoveryNexusProfile(g) || pairedE3DiscoveryNexusProfile(g) ||
(leaderSkills && (classicDiscoveryLeaderSkillsProfile(g) || nativeDiscoveryLeaderSkillsProfile(g) || classicNexusLeaderSkillsProfile(g))));
const nexusModules = nexus && (classicNexusModulesProfile(g) || pairedNexusModulesProfile(g) ||
singleE1E2NexusProfile(g) || mixedE1E2NexusProfile(g) || pairedE3NexusProfile(g) || standaloneE3NexusProfile(g) ||
mixedE3NexusProfile(g) || singleE1E2NexusProfile(g) || mixedE1E2NexusProfile(g) || pairedE3NexusProfile(g) || standaloneE3NexusProfile(g) ||
(leaderSkills && (classicNexusLeaderSkillsProfile(g) || pairedNexusLeaderSkillsProfile(g) ||
singleE1E2NexusLeaderSkillsProfile(g) || mixedE1E2NexusLeaderSkillsProfile(g) ||
mixedE3NexusLeaderSkillsProfile(g) || singleE1E2NexusLeaderSkillsProfile(g) || mixedE1E2NexusLeaderSkillsProfile(g) ||
pairedE3NexusLeaderSkillsProfile(g) || standaloneE3NexusLeaderSkillsProfile(g))));
requireRule(!g.techTokens || ((skillTech || nativeTech || nexusModules || discoveryComposition) &&
JSON.stringify(g.techTokens) === JSON.stringify(createTechTokens())),
@@ -14710,6 +14719,8 @@ function ambassadorEffectBlock(
return null;
}
function arrivalOverlapModeSupported(g: Game): boolean {
if ((mixedE3NexusLeaderSkillsProfile(g) || mixedE3NexusProfile(g)) &&
byFaction(g, 'ecaz') && byFaction(g, 'moritani')) return true;
return pairedE3NexusLeaderSkillsProfile(g) || pairedE3NexusProfile(g) ||
(!!byFaction(g, 'ecaz') && !!byFaction(g, 'moritani') &&
g.players.every(player => CLASSIC_FACTIONS[player.faction] === true ||
@@ -25771,7 +25782,8 @@ const RICHESE_CUNNING_ROSTER: Partial<Record<FactionId, true>> = {
beneGesserit: true, guild: true, richese: true, choam: true,
};
function richeseCunningModeSupported(g: Game) {
const mixed = mixedE1E2NexusProfile(g) || (!!g.leaderSkills && mixedE1E2NexusLeaderSkillsProfile(g));
const mixed = mixedE3NexusProfile(g) || mixedE1E2NexusProfile(g) ||
(!!g.leaderSkills && (mixedE3NexusLeaderSkillsProfile(g) || mixedE1E2NexusLeaderSkillsProfile(g)));
if (mixed) return !g.sandtrout;
const skills = !!g.leaderSkills && (pairedNexusLeaderSkillsProfile(g) || singleE1E2NexusLeaderSkillsProfile(g));
return pairedDiscoveryNexusProfile(g) || singleE1E2DiscoveryNexusProfile(g) ||
+35
View File
@@ -66,3 +66,38 @@ export function nativeFactionTechProfile(game: FactionModuleProfile): boolean {
return native > 0 && (!e3 || native === 1) &&
!(game.advanced && moritani && harkonnen);
}
export type MixedE3NativeRoster = {
advanced?: boolean;
expansions: readonly string[];
players?: readonly { faction: string }[];
};
/** Selected E3 plus E1 and/or E2, with an actual native from every selected family. */
export function mixedE3NexusRosterProfile(game: MixedE3NativeRoster, skills = false): boolean {
const players = game.players;
if (typeof game.advanced !== 'boolean' || !players || players.length < 2 || players.length > 6 ||
game.expansions.length < 2 || game.expansions.length > 3 || !game.expansions.includes('ecaz'))
return false;
for (let i = 0; i < game.expansions.length; i++) {
const expansion = game.expansions[i];
if (expansion !== 'ecaz' && expansion !== 'ix' && expansion !== 'choam') return false;
for (let j = 0; j < i; j++) if (expansion === game.expansions[j]) return false;
}
let ix = false, choam = false, e3 = false, moritani = false, harkonnen = false;
for (let i = 0; i < players.length; i++) {
const player = players[i];
for (let j = 0; j < i; j++) if (player.faction === players[j].faction) return false;
let family: string | undefined;
for (const faction of FACTIONS)
if (faction.id === player.faction) { family = faction.expansion; break; }
if (family === undefined || (family !== 'base' && !game.expansions.includes(family))) return false;
if (family === 'ix') ix = true;
if (family === 'choam') choam = true;
if (family === 'ecaz') e3 = true;
if (player.faction === 'moritani') moritani = true;
if (player.faction === 'harkonnen') harkonnen = true;
}
return e3 && ix === game.expansions.includes('ix') && choam === game.expansions.includes('choam') &&
!(game.advanced && harkonnen && (skills || moritani));
}
+9 -2
View File
@@ -1,4 +1,5 @@
import { FACTIONS } from './catalog';
import { mixedE3NexusRosterProfile } from './faction-module-profile';
import { classicDiscoveryLeaderSkillsProfile } from './discovery-module-profile';
/** Public configuration only; never gate a skill on a hidden card or random deal. */
@@ -167,6 +168,12 @@ export function pairedE3NexusLeaderSkillsProfile(game: LeaderSkillProfile): bool
return ecaz && moritani;
}
/** Original two/three selected families with native E3, Nexus and optional original modules. */
export function mixedE3NexusLeaderSkillsProfile(game: LeaderSkillProfile): boolean {
return nexusLeaderSkillModulesSupported(game, game.advanced === true, true) &&
mixedE3NexusRosterProfile(game, true);
}
/** Discovery keeps original native roster predicates; only this module envelope
* differs. Live token/entry/Great Maker frames are original consumers. */
function discoveryLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean {
@@ -425,7 +432,7 @@ export function advancedNativeLeaderSkillsProfile(game: LeaderSkillProfile): boo
}
export function nativeExpansionLeaderSkillsProfile(game: LeaderSkillProfile): boolean {
return mixedE1E2NexusLeaderSkillsProfile(game) || pairedE3NexusLeaderSkillsProfile(game) ||
return mixedE3NexusLeaderSkillsProfile(game) || mixedE1E2NexusLeaderSkillsProfile(game) || pairedE3NexusLeaderSkillsProfile(game) ||
basicExpansionLeaderSkillsProfile(game) || advancedNativeLeaderSkillsProfile(game) ||
advancedMoritaniLeaderSkillsProfile(game) || nativeEcazLeaderSkillsProfile(game);
}
@@ -485,7 +492,7 @@ export function ordinaryLeaderSkillModeSupported(game: LeaderSkillProfile): bool
classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) ||
strongholdLeaderSkillsProfile(game) || classicNexusLeaderSkillsProfile(game) ||
singleE1E2NexusLeaderSkillsProfile(game) ||
mixedE1E2NexusLeaderSkillsProfile(game) || pairedE3NexusLeaderSkillsProfile(game) ||
mixedE3NexusLeaderSkillsProfile(game) || mixedE1E2NexusLeaderSkillsProfile(game) || pairedE3NexusLeaderSkillsProfile(game) ||
pairedNexusLeaderSkillsProfile(game) || standaloneE3NexusLeaderSkillsProfile(game) ||
classicDiscoveryLeaderSkillsProfile(game) || nativeDiscoveryLeaderSkillsProfile(game) ||
homeworldLeaderSkillsProfile(game);
+4
View File
@@ -65,6 +65,10 @@ const opponents: readonly FactionId[] = [
'emperor',
'fremen',
'ecaz',
'ixians',
'tleilaxu',
'choam',
'richese',
];
const identifier = (value: unknown): value is string =>
typeof value === 'string' &&
+4 -4
View File
@@ -3,8 +3,8 @@ import type { Game } from './engine';
import { acquireDuke, DUKE_VIDAL_ID, type DukeSource } from './duke-vidal';
import { nexusCardMode } from './nexus-cards';
import { nexusCleanPlayBlocked } from './nexus-play-boundary';
import { pairedE3NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile } from './leader-skill-profile';
import { pairedE3NexusProfile, standaloneE3NexusProfile } from './nexus-module-profile';
import { mixedE3NexusLeaderSkillsProfile, pairedE3NexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile } from './leader-skill-profile';
import { mixedE3NexusProfile, pairedE3NexusProfile, standaloneE3NexusProfile } from './nexus-module-profile';
const SUPPORTED_FACTIONS: Partial<Record<FactionId, true>> = {
ecaz: true, moritani: true, atreides: true, harkonnen: true,
@@ -88,11 +88,11 @@ export function quoteNexusEcazDuke(g: Game, owner: string, automaticPending = fa
}
export function nexusEcazDukeModeSupported(g: Game): boolean {
if ((standaloneE3NexusProfile(g) || pairedE3NexusProfile(g)) &&
if ((mixedE3NexusProfile(g) || standaloneE3NexusProfile(g) || pairedE3NexusProfile(g)) &&
g.players.some(player => player.faction === 'ecaz') &&
!g.sandtrout && !(g.advanced && g.players.some(player => player.faction === 'harkonnen')))
return !!g.nexusCards?.cards && g.players.length >= 3;
if (g.leaderSkills && (standaloneE3NexusLeaderSkillsProfile(g) || pairedE3NexusLeaderSkillsProfile(g)) &&
if (g.leaderSkills && (mixedE3NexusLeaderSkillsProfile(g) || standaloneE3NexusLeaderSkillsProfile(g) || pairedE3NexusLeaderSkillsProfile(g)) &&
g.players.some(player => player.faction === 'ecaz') && !g.sandtrout)
return !!g.nexusCards?.cards && g.players.length >= 3;
return !!g.nexusCards?.cards && g.players.length >= 3 && g.players.length <= 6 &&
+16 -2
View File
@@ -1,6 +1,6 @@
import { FACTIONS } from './catalog';
import type { FactionModuleProfile } from './faction-module-profile';
import { classicDiscoveryNexusProfile, mixedE1E2DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
import { mixedE3NexusRosterProfile, type FactionModuleProfile } from './faction-module-profile';
import { classicDiscoveryNexusProfile, mixedE1E2DiscoveryNexusProfile, mixedE3DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
/** Original classic Nexus modules, optionally Homeworlds; initializer checks undealt components. */
export function classicNexusModulesProfile(game: FactionModuleProfile): boolean {
@@ -172,3 +172,17 @@ export function pairedE3NexusProfile(game: FactionModuleProfile): boolean {
}
return ecaz && moritani;
}
/** Original mixed E3 or all-three-family Nexus without Skills, optionally original modules. */
export function mixedE3NexusProfile(game: FactionModuleProfile): boolean {
if (game.discoveryEnabled || game.discoveries) return mixedE3DiscoveryNexusProfile(game);
return !!game.nexusCards && !game.leaderSkills &&
(!game.techTokens || game.players.length >= 3) &&
(!game.strongholdCards || game.advanced) &&
!game.discoveryEnabled && !game.discoveries && !game.discoveryStash && !game.greatMaker &&
!game.ecazTreachery && !game.semutaPreview && !game.advancedPreview &&
!game.kullPreview && !game.nexusKullPreview && !game.guildBetrayalPreview &&
!game.richeseBetrayalPreview && !game.nexusIxianReplacementPreview &&
!game.nexusIxianBetrayalPreview && !game.nexusHarkonnenBetrayalPreview &&
mixedE3NexusRosterProfile(game);
}