Handle advisor conversion after worm rides
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
ad0abfef98
commit
c608d6fea6
15 files changed
+507
-14
No files matched your search
+62
-2
@@ -112,7 +112,7 @@ import { advancedMoritaniLeaderSkillsProfile, selectedDeckLeaderSkillsProfile, h
|
||||
import { bribeReceiptTiming, bribeTimingBlock, bribesCloseBeforeNextMentat, maximumBribe, type BribeOptions } from './bribe-options';
|
||||
import { quoteAtreidesFullPlan } from './atreides-full-plan';
|
||||
import { quoteTleilaxuSpecialKarama } from './tleilaxu-special-karama';
|
||||
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport, type AutomaticAdvisorGamont, type AutomaticAdvisorBattle, type AutomaticAdvisorHomeworldTransport, type AutomaticAdvisorFaceDance, type AutomaticAdvisorStorm, type AutomaticAdvisorRelocation } from './advisor-automatic-flip';
|
||||
import { quoteAutomaticAdvisorFlips, validateAutomaticAdvisorFlip, AutomaticAdvisorFlipError, type AdvisorStance, type AutomaticAdvisorFlipSource, type AutomaticAdvisorTransport, type AutomaticAdvisorGamont, type AutomaticAdvisorBattle, type AutomaticAdvisorHomeworldTransport, type AutomaticAdvisorFaceDance, type AutomaticAdvisorStorm, type AutomaticAdvisorRelocation, type AutomaticAdvisorWormRide } from './advisor-automatic-flip';
|
||||
import { quoteSpicePlacement, stormExposesTerritory, stormSectorAfter, wormConsumesForces } from './disaster-rules';
|
||||
import { isStormCardDistance, type StormCardComponent } from './storm-cards';
|
||||
import { createLeaderSkills, validateLeaderSkills, dealLeaderSkills, chooseLeaderSkill, returnDeadLeaderSkills, offerRevivedLeaderSkill, drawRevivedLeaderSkills, declineRevivedLeaderSkill, LeaderSkillError, type LeaderSkillsState, type LeaderSkillsView } from './leader-skills';
|
||||
@@ -1521,6 +1521,7 @@ export type Game = {
|
||||
signature: string;
|
||||
resume: { kind: 'ambassador'; event: string } | { kind: 'discovery'; child: DiscoveryEntryArrivalChild };
|
||||
};
|
||||
pendingAutomaticAdvisorWormRide?: { receipt: AutomaticAdvisorWormRide; signature: string };
|
||||
pendingAdvisorMatching?: {
|
||||
receipt: AdvisorMatchingSource;
|
||||
signature: string;
|
||||
@@ -28508,6 +28509,12 @@ function currentAutomaticAdvisorFlip(g: Game, response: ResponseWindow) {
|
||||
'The mandatory conversion has lost its original completed relocation.');
|
||||
validateRelocationAdvisorParent(g, parent);
|
||||
}
|
||||
if (source.producer === 'wormRide') {
|
||||
const parent = g.pendingAutomaticAdvisorWormRide;
|
||||
requireRule(parent && JSON.stringify(source.wormRide) === JSON.stringify(parent.receipt),
|
||||
'The mandatory conversion has lost its original completed worm ride.');
|
||||
validateWormRideAdvisorParent(g, parent);
|
||||
}
|
||||
if(source.producer==='spiritualArrival'){
|
||||
const frame=spiritualFrameBody(g);
|
||||
requireRule(frame.stage==='arrival'&&JSON.stringify(frame.source)===JSON.stringify(source.spiritual),
|
||||
@@ -28922,6 +28929,54 @@ function finishAutomaticAdvisorRelocation(g: Game) {
|
||||
completeDiscoveryEntryArrival(g, child);
|
||||
}
|
||||
}
|
||||
/** The ride and any destination collection have already happened. Bind the
|
||||
* untouched worm cursor and the rider's physical pools until arrival resumes. */
|
||||
function wormRideAdvisorSignature(g: Game, receipt: AutomaticAdvisorWormRide) {
|
||||
const rider = getPlayer(g, receipt.player);
|
||||
return JSON.stringify({ receipt, turn:g.turn, phase:g.phase, wormRides:g.wormRides,
|
||||
spiceSequence:g.spiceSequence, spiceResolution:g.spiceResolution,
|
||||
summonedNexusBeforeRides:g.summonedNexusBeforeRides, greatMaker:g.greatMaker,
|
||||
forces:rider.forces, elites:rider.elites, spice:g.spice, riderSpice:rider.spice });
|
||||
}
|
||||
function validateWormRideAdvisorParent(g: Game, parent: NonNullable<Game['pendingAutomaticAdvisorWormRide']>) {
|
||||
requireRule(g.status === 'playing' && g.advanced && g.phase === 1 &&
|
||||
parent.signature === wormRideAdvisorSignature(g, parent.receipt),
|
||||
'The mandatory conversion lost its committed worm ride or original continuation.');
|
||||
}
|
||||
function prepareWormRideAdvisorFlips(g: Game, ride: Omit<AutomaticAdvisorWormRide, 'event'>) {
|
||||
const groups = automaticAdvisorRule(() => quoteAutomaticAdvisorFlips(g))
|
||||
.filter(group => group.territory === ride.origin);
|
||||
if (!groups.length) return false;
|
||||
requireRule(!g.pendingAutomaticAdvisorFlips?.length && !g.pendingAutomaticAdvisorWormRide &&
|
||||
!g.pendingAutomaticAdvisorRelocation && !g.pendingAutomaticAdvisorTransport &&
|
||||
!g.pendingAutomaticAdvisorHomeworldTransport && !g.pendingAutomaticAdvisorGamont &&
|
||||
!g.pendingAutomaticAdvisorBattle && !g.pendingAutomaticAdvisorFaceDance && !g.pendingAutomaticAdvisorStorm,
|
||||
'Finish the original advisor conversion before a worm ride.');
|
||||
const receipt = { event:crypto.randomUUID(), ...ride };
|
||||
g.pendingAutomaticAdvisorWormRide = { receipt, signature:wormRideAdvisorSignature(g,receipt) };
|
||||
for (const group of groups) {
|
||||
const source: AutomaticAdvisorFlipSource = {event:crypto.randomUUID(), owner:group.player,
|
||||
territory:group.territory, turn:g.turn, phase:g.phase, forces:group.forces,
|
||||
producer:'wormRide', wormRide:receipt};
|
||||
getPlayer(g,group.player).advisors![group.territory].aloneFlipHold = source.event;
|
||||
(g.pendingAutomaticAdvisorFlips ??= []).push(source);
|
||||
}
|
||||
openAutomaticAdvisorFlip(g);
|
||||
return true;
|
||||
}
|
||||
function finishAutomaticAdvisorWormRide(g: Game) {
|
||||
const parent = g.pendingAutomaticAdvisorWormRide;
|
||||
if (!parent) return;
|
||||
requireRule(!g.pendingAutomaticAdvisorFlips?.length,
|
||||
'Finish the original worm-ride conversions before arrival.');
|
||||
validateWormRideAdvisorParent(g,parent);
|
||||
delete g.pendingAutomaticAdvisorWormRide;
|
||||
const ride = parent.receipt, rider = getPlayer(g,ride.player);
|
||||
const intruded = intrusion(g,rider,ride.destination,{wormRide:true});
|
||||
if (openTerritoryEntry(g,rider,ride.destination,ride.sector,ride.amount,ride.elite,'wormRide','wormRide') || intruded)
|
||||
return;
|
||||
nextWormRide(g);
|
||||
}
|
||||
function applyAdvisorReleases(g: Game, released: AdvisorRelease[]) {
|
||||
for (const entry of released)
|
||||
delete getPlayer(g, entry.player).advisors?.[entry.territory];
|
||||
@@ -38560,7 +38615,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
}
|
||||
if (automaticAdvisorFlip) {
|
||||
if (canceled)
|
||||
log(g, `${bg.name}'s mandatory conversion in ${territory(to).name} was prevented. The completed ${automaticAdvisorFlip.producer === 'guildTransport' || automaticAdvisorFlip.producer === 'homeworldTransport' ? 'transport and payment stay' : automaticAdvisorFlip.producer === 'gamont' ? 'Trip to Gamont effect and card use stay' : automaticAdvisorFlip.producer === 'battleLosses' ? 'battle losses and costs stay' : automaticAdvisorFlip.producer === 'faceDance' ? 'Face Dance replacement and reveal stay' : automaticAdvisorFlip.producer === 'storm' ? 'current-sector storm casualties stay' : automaticAdvisorFlip.producer === 'specialRelocation' ? 'relocation and any collection stay' : automaticAdvisorFlip.producer === 'spiritualArrival' ? 'free Spiritual force placement and any income stay' : 'movement stays'} committed; this group remains advisors while continuously alone.`,
|
||||
log(g, `${bg.name}'s mandatory conversion in ${territory(to).name} was prevented. The completed ${automaticAdvisorFlip.producer === 'guildTransport' || automaticAdvisorFlip.producer === 'homeworldTransport' ? 'transport and payment stay' : automaticAdvisorFlip.producer === 'gamont' ? 'Trip to Gamont effect and card use stay' : automaticAdvisorFlip.producer === 'battleLosses' ? 'battle losses and costs stay' : automaticAdvisorFlip.producer === 'faceDance' ? 'Face Dance replacement and reveal stay' : automaticAdvisorFlip.producer === 'storm' ? 'current-sector storm casualties stay' : automaticAdvisorFlip.producer === 'specialRelocation' ? 'relocation and any collection stay' : automaticAdvisorFlip.producer === 'wormRide' ? 'worm ride and any collection stay' : automaticAdvisorFlip.producer === 'spiritualArrival' ? 'free Spiritual force placement and any income stay' : 'movement stays'} committed; this group remains advisors while continuously alone.`,
|
||||
{ faction: bg.faction, name: 'Mandatory advisor conversion prevented' });
|
||||
g.pendingAutomaticAdvisorFlips!.shift();
|
||||
if (!g.pendingAutomaticAdvisorFlips!.length) delete g.pendingAutomaticAdvisorFlips;
|
||||
@@ -38572,6 +38627,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
finishAutomaticAdvisorFaceDance(g);
|
||||
finishAutomaticAdvisorStorm(g);
|
||||
finishAutomaticAdvisorRelocation(g);
|
||||
finishAutomaticAdvisorWormRide(g);
|
||||
if(g.pendingSpiritualAdvisor?.stage==='arrival')completeSpiritualAdvisorArrival(g);
|
||||
} else openAutomaticAdvisorFlip(g);
|
||||
} else finishAdvisorReaction(g, response);
|
||||
@@ -41877,6 +41933,8 @@ export function prepareSpecialKaramaIntent(
|
||||
g.phase === 1 && !g.summonedWorm,
|
||||
'Summon Shai-Hulud during Spice Blow and Nexus.',
|
||||
);
|
||||
requireRule(!g.pendingAutomaticAdvisorWormRide,
|
||||
'Resolve the completed worm ride’s advisor conversion before summoning another worm.');
|
||||
const t = stringField(action.territory);
|
||||
requireRule(
|
||||
TERRITORIES.some((x) => x.id === t && x.type === 'sand'),
|
||||
@@ -45700,6 +45758,8 @@ function applyActionInner(
|
||||
log(g, `${p.name}'s Sandmaster collected 1 spice on arriving in ${territory(to).name} by worm. ${sandmaster.before - 1} spice remains in that pile. This ride does not consume normal movement.`,
|
||||
{ faction: p.faction, name: 'Sandmaster collection' });
|
||||
}
|
||||
if (prepareWormRideAdvisorFlips(g,{player:p.id,origin:decision.territory,
|
||||
destination:to,sector,amount:total,elite:totalElite})) return g;
|
||||
const intruded = intrusion(g, p, to, { wormRide: true });
|
||||
if (
|
||||
openTerritoryEntry(
|
||||
|
||||
Reference in new issue
Block a user