Enforce Atomics shipment ban across transport
Verify and publish checkpoint container / container (push) Successful in 10m7s
Verify and publish checkpoint container / container (push) Successful in 10m7s
This commit is contained in:
1 parent
4feb418488
commit
0f8e0b4153
17 files changed
+477
-19
No files matched your search
+27
-5
@@ -7911,7 +7911,7 @@ function tupileDiscardSignature(g: Game, c: TupileDiscardContinuation) {
|
||||
function treacheryDiscardIntegrity(g: Game) {
|
||||
const discovery = g.discoveryEnabled === true && !!g.discoveries && discoveryModeSupported(g);
|
||||
requireRule(g.semutaRecruitsPreview === undefined || g.semutaRecruitsPreview === true &&
|
||||
basicIndependentSemutaProfile(g) && !g.semutaPreview,
|
||||
basicIndependentRecruitsSemutaProfile(g) && !g.semutaPreview,
|
||||
'This Recruits Semuta preview no longer matches its original Basic profile.');
|
||||
requireRule(g.semutaPreview === undefined || g.semutaPreview === true &&
|
||||
g.expansions.includes('choam') && !!byFaction(g, 'richese') &&
|
||||
@@ -8130,7 +8130,7 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
);
|
||||
requireRule(
|
||||
c.effect === 'recruits'
|
||||
? g.semutaRecruitsPreview === true && basicIndependentSemutaProfile(g) &&
|
||||
? g.semutaRecruitsPreview === true && basicIndependentRecruitsSemutaProfile(g) &&
|
||||
g.phase === 4 && g.recruits?.turn === g.turn && g.recruits.player === c.player &&
|
||||
g.recruits.card === c.card
|
||||
: c.effect === 'hajr'
|
||||
@@ -9469,7 +9469,7 @@ function semutaOfferSupported(
|
||||
entries: FreshDiscardBatch['entries'],
|
||||
) {
|
||||
if (continuation.kind === 'ordinaryCardDiscard' && continuation.effect === 'recruits')
|
||||
return g.semutaRecruitsPreview === true && basicIndependentSemutaProfile(g) &&
|
||||
return g.semutaRecruitsPreview === true && basicIndependentRecruitsSemutaProfile(g) &&
|
||||
g.status === 'playing' && g.phase === 4 && g.recruits?.turn === g.turn &&
|
||||
g.recruits.player === continuation.player && g.recruits.card === continuation.card &&
|
||||
entries.length === 1 && entries[0].publicFace && entries[0].discardedBy === continuation.player &&
|
||||
@@ -13183,6 +13183,20 @@ function basicIndependentSemutaProfile(g: Game): boolean {
|
||||
!g.discoveryEnabled && !g.discoveries && !g.discoveryStash && !g.greatMaker &&
|
||||
!g.kullPreview && !g.nexusKullPreview && !g.ecazOccupyPreview && !g.moritaniAssassinatePreview;
|
||||
}
|
||||
/** Recruits' completed Revival discard has no faction-specific continuation.
|
||||
* Its separate admission does not broaden other independent-card reactions. */
|
||||
function basicIndependentRecruitsSemutaProfile(g: Game): boolean {
|
||||
return g.advanced === false && g.ecazTreachery === true &&
|
||||
g.expansions.includes('choam') && !!byFaction(g, 'richese') &&
|
||||
g.expansions.every(id => ['ix', 'choam', 'ecaz'].includes(id)) &&
|
||||
new Set(g.expansions).size === g.expansions.length &&
|
||||
g.players.length >= 2 && g.players.length <= 6 &&
|
||||
g.players.every(p => faction(p.faction).expansion === 'base' ||
|
||||
g.expansions.includes(faction(p.faction).expansion)) &&
|
||||
!g.homeworlds && !g.nexusCards && !g.leaderSkills && !g.techTokens && !g.strongholdCards &&
|
||||
!g.discoveryEnabled && !g.discoveries && !g.discoveryStash && !g.greatMaker &&
|
||||
!g.kullPreview && !g.nexusKullPreview && !g.moritaniAssassinatePreview;
|
||||
}
|
||||
/** Bare native Basic starts share the original deck/roster validation. Optional
|
||||
* modules retain their own explicit Semuta envelopes; saved games are not upgraded. */
|
||||
function bareBasicSemutaProfile(g: Game): boolean {
|
||||
@@ -13396,7 +13410,7 @@ export function initializeEcazTreacheryGameForAudit(state: Game): Game {
|
||||
requireFreshFactionInventory(g);
|
||||
}
|
||||
const initialized = initializeSetupGameForAudit(g, homeworlds, independentNexusProfile(g), false, discovery, false, false, true, true);
|
||||
if (basicIndependentSemutaProfile(initialized)) initialized.semutaRecruitsPreview = true;
|
||||
if (basicIndependentRecruitsSemutaProfile(initialized)) initialized.semutaRecruitsPreview = true;
|
||||
if (byFaction(initialized, 'ecaz')) initialized.ecazOccupyPreview = true;
|
||||
if (moritaniAssassinateModeSupported(initialized)) initializeMoritaniAssassinateState(initialized);
|
||||
return initialized;
|
||||
@@ -38024,6 +38038,8 @@ function quoteJunctionDeclaration(g: Game, p: Player, declaration: JunctionTrans
|
||||
const advisors = arrival ? arrivalAsAdvisor(g, p, arrival.territory,
|
||||
quote.originKind === 'arrakis' ? quote.origin : undefined) : false;
|
||||
if (arrival) {
|
||||
requireRule(!atomicsShipmentBlocked(g.moritaniAtomics, arrival.territory),
|
||||
'Atomics Aftermath prevents shipment into this territory.');
|
||||
requireRule(arrival.territory !== MOBILE_STRONGHOLD || p.faction === 'ixians',
|
||||
'Only Ixians may ship directly into the mobile stronghold.');
|
||||
requireRule(advisors || sourceLock !== g.turn ||
|
||||
@@ -39088,6 +39104,8 @@ function validateGuildTransportForCommit(
|
||||
'New advisors cannot become fighters this turn.');
|
||||
requireRule(!frame.noField || to === 'reserves' || !to.startsWith('homeworld:'),
|
||||
'A concealed No-Field cannot be placed on a Homeworld.');
|
||||
requireRule(to === 'reserves' || !atomicsShipmentBlocked(g.moritaniAtomics, to),
|
||||
'Atomics Aftermath prevents shipment into this territory.');
|
||||
if (to !== 'reserves') allowedEntry(g, p, to, sector, false, advisors);
|
||||
const halfRate = !!guildSecretEvent || g.karamaShipping?.player === p.id ||
|
||||
!(g.guildRateBlocked?.turn === g.turn && g.guildRateBlocked.player === p.id);
|
||||
@@ -39112,6 +39130,8 @@ function commitGuildTransport(
|
||||
) {
|
||||
requireRule(!g.guildBetrayalPreview || !guildSecretEvent,
|
||||
'Guild Betrayal native transport does not combine with a Guild Secret Ally producer.');
|
||||
requireRule(frame.to === 'reserves' || !atomicsShipmentBlocked(g.moritaniAtomics, frame.to),
|
||||
'Atomics Aftermath prevents shipment into this territory.');
|
||||
if (!guildSecretEvent || frame.noField) validateGuildTransportForCommit(g, p, frame, fromReserves, guildSecretEvent);
|
||||
if (!settlement && beginGuildBetrayal(g, {source: 'guildTransport', frame, fromReserves})) return;
|
||||
const { origin, group, eliteGroup, to, sector, amount, elite, cost, allyPayment, advisors } = frame;
|
||||
@@ -42643,7 +42663,7 @@ function applyActionInner(
|
||||
`${p.name} played Recruits. Current free revival rates are doubled and the ordinary force revival limit is seven for this turn.`,
|
||||
{ faction: p.faction, name: 'Recruits' },
|
||||
);
|
||||
if (execution === 'live' && g.semutaRecruitsPreview && basicIndependentSemutaProfile(g))
|
||||
if (execution === 'live' && g.semutaRecruitsPreview && basicIndependentRecruitsSemutaProfile(g))
|
||||
stageOrdinaryCardDiscard(g, p, card);
|
||||
return g;
|
||||
}
|
||||
@@ -45976,6 +45996,8 @@ function applyActionInner(
|
||||
);
|
||||
const to = stringField(action.territory ?? 'reserves');
|
||||
const sector = integer(action.sector ?? 0, 0, 18);
|
||||
requireRule(to === 'reserves' || !atomicsShipmentBlocked(g.moritaniAtomics, to),
|
||||
'Atomics Aftermath prevents shipment into this territory.');
|
||||
requireRule(fromReserves || to === 'reserves' || to !== origin, 'Cross-shipment requires a different destination territory.');
|
||||
requireRule(
|
||||
to !== 'reserves' || p.faction === 'guild' || !!guildSecretEvent,
|
||||
|
||||
Reference in new issue
Block a user