Support group Fremen worm protection
Verify and publish checkpoint container / container (push) Successful in 10m58s
Verify and publish checkpoint container / container (push) Successful in 10m58s
This commit is contained in:
1 parent
6e26ceb216
commit
40c9651b80
12 files changed
+479
-24
No files matched your search
+77
-13
@@ -754,6 +754,9 @@ import { isAlliedShipmentVisit, makeBasicAlliedShipment, quoteBasicAlliedShipmen
|
||||
import { answerCoalitionAdmission, beginCoalitionAdmission, coalitionAllies, coalitionGroups, coalitionOf,
|
||||
leaveCoalition, CoalitionAllianceError, type CoalitionState, type CoalitionTransition } from './coalition-alliance';
|
||||
import { coalitionBattleBeneficiary } from './coalition-battle-preparation';
|
||||
import { createCoalitionWormProtection, currentCoalitionWormAlly, decideCoalitionWormProtection,
|
||||
resolveCoalitionWormProtection, protectedCoalitionWormAllies, validateCoalitionWormProtection,
|
||||
CoalitionWormProtectionError, type CoalitionWormProtectionFrame } from './coalition-worm-protection';
|
||||
import { reconcileCoalitionEscrow, setCoalitionPledge, type CoalitionEscrowMembership,
|
||||
type CoalitionEscrowState } from './coalition-escrow';
|
||||
import { CoalitionFundingError, quoteCoalitionFunding, settleCoalitionFunding,
|
||||
@@ -1187,7 +1190,7 @@ export type Decision =
|
||||
resume: 'storm' | 'shipment';
|
||||
}
|
||||
| { kind: 'wormPlacement'; player: string }
|
||||
| { kind: 'wormProtection'; player: string; territory: string; ally: string }
|
||||
| { kind: 'wormProtection'; player: string; territory: string; ally: string; event?: string }
|
||||
| { kind: 'nexusFremenProtection'; player: string; event: string }
|
||||
| { kind: 'nexusFremenRideBetrayal'; player: string; event: string }
|
||||
| { kind: 'discoveryDiscard'; player: string; event: string }
|
||||
@@ -1247,6 +1250,7 @@ export type ResponseWindow = {
|
||||
/** Saved exact ordinary Guild-rate response; not the independent Karama rate. */
|
||||
guildRateEvent?: string;
|
||||
guildTransportEvent?: string;
|
||||
coalitionWormEvent?: string;
|
||||
advisorResume?: 'preparation' | 'wormRide' | 'declaration' | 'ambassador' | 'discoveryEntry' | 'planetologistGather';
|
||||
advisorGatherMove?: { turn: number; move: number; order: MovementOrder };
|
||||
discoveryEntry?: DiscoveryEntryArrivalChild;
|
||||
@@ -1446,6 +1450,7 @@ export type Game = {
|
||||
/** Unexposed group-alliance integration state; absent in existing games. */
|
||||
coalitionState?: CoalitionState;
|
||||
coalitionEscrow?: CoalitionEscrowState;
|
||||
coalitionWormProtection?: CoalitionWormProtectionFrame;
|
||||
/** Public written agreements. Their prose is honored by players, not parsed. */
|
||||
deals?: PublicDeal[];
|
||||
/** A human-to-human lobby offer, pending the recipient's consent. */
|
||||
@@ -13706,7 +13711,7 @@ function drawSpice(g: Game) {
|
||||
function devour(
|
||||
g: Game,
|
||||
t: string,
|
||||
protectedAlly?: string,
|
||||
protectedAlly?: string | readonly string[],
|
||||
protectFremen = true,
|
||||
) {
|
||||
disasterPreflight(() =>
|
||||
@@ -13722,6 +13727,27 @@ function devour(
|
||||
g.wormRides.push(t);
|
||||
log(g, `Shai-Hulud appeared in ${territory(t).name}.`);
|
||||
}
|
||||
function coalitionWormRule<T>(operation: () => T): T {
|
||||
try { return operation(); }
|
||||
catch (error) { if (error instanceof CoalitionWormProtectionError) throw new RuleError(error.message); throw error; }
|
||||
}
|
||||
function advanceCoalitionWormProtection(g: Game) {
|
||||
const frame = g.coalitionWormProtection;
|
||||
requireRule(frame, 'The original group worm protection is missing.');
|
||||
const ally = coalitionWormRule(() => currentCoalitionWormAlly(g, frame));
|
||||
if (ally) {
|
||||
requireRule(frame.stage === 'decision', 'Finish the current worm protection response first.');
|
||||
g.decision = {kind:'wormProtection', player:frame.owner, territory:frame.territory, ally, event:frame.event};
|
||||
} else wormSurvival(g, frame.territory);
|
||||
}
|
||||
function finishCoalitionWorm(g: Game, t: string, protectFremen: boolean) {
|
||||
const frame = g.coalitionWormProtection;
|
||||
requireRule(frame && frame.territory === t, 'The original group worm occurrence changed.');
|
||||
const protectedAllies = coalitionWormRule(() => protectedCoalitionWormAllies(g, frame));
|
||||
devour(g, t, protectedAllies, protectFremen);
|
||||
delete g.coalitionWormProtection;
|
||||
afterWorm(g);
|
||||
}
|
||||
function beginWorm(g: Game, t: string, origin: 'natural' | 'additional' | 'summoned' = 'natural') {
|
||||
const fremen = byFaction(g, 'fremen');
|
||||
if (!fremen && g.nexusCards?.cards && g.players.some(p =>
|
||||
@@ -13773,7 +13799,12 @@ function beginWorm(g: Game, t: string, origin: 'natural' | 'additional' | 'summo
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fremen?.ally && at(getPlayer(g, fremen.ally), t)) {
|
||||
const group = fremen && coalitionWormRule(() => createCoalitionWormProtection(g, fremen.id, t, crypto.randomUUID()));
|
||||
if (group) {
|
||||
requireRule(!g.coalitionWormProtection, 'Finish the previous group worm before another appearance.');
|
||||
g.coalitionWormProtection = group;
|
||||
advanceCoalitionWormProtection(g);
|
||||
} else if (fremen?.ally && at(getPlayer(g, fremen.ally), t)) {
|
||||
g.decision = {
|
||||
kind: 'wormProtection',
|
||||
player: fremen.id,
|
||||
@@ -13892,11 +13923,12 @@ function wormSurvival(g: Game, t: string, protectedAlly?: string) {
|
||||
owner: fremen.id,
|
||||
location: t,
|
||||
recipient: protectedAlly,
|
||||
...(g.coalitionWormProtection ? {coalitionWormEvent:g.coalitionWormProtection.event} : {}),
|
||||
passed: [],
|
||||
};
|
||||
} else {
|
||||
devour(g, t, protectedAlly);
|
||||
afterWorm(g);
|
||||
if (g.coalitionWormProtection) finishCoalitionWorm(g, t, true);
|
||||
else { devour(g, t, protectedAlly); afterWorm(g); }
|
||||
}
|
||||
}
|
||||
function afterWorm(g: Game) {
|
||||
@@ -30893,6 +30925,22 @@ function atomicsIntegrity(g: Game) {
|
||||
'Atomics Aftermath has lost its revealed token, original targets or hand-limit custody.');
|
||||
}
|
||||
function marketGholaIntegrity(g: Game) {
|
||||
const groupWorm = g.coalitionWormProtection;
|
||||
if (groupWorm) {
|
||||
coalitionWormRule(() => validateCoalitionWormProtection(g, groupWorm));
|
||||
const pending = g.pendingKarama?.use.kind === 'cancel' ? g.pendingKarama.use.response : null;
|
||||
const response = g.response ?? pending;
|
||||
if (groupWorm.stage === 'decision') {
|
||||
const decision = g.decision;
|
||||
requireRule(decision?.kind === 'wormProtection' && decision.event === groupWorm.event &&
|
||||
decision.player === groupWorm.owner && decision.territory === groupWorm.territory &&
|
||||
decision.ally === coalitionWormRule(() => currentCoalitionWormAlly(g, groupWorm)),
|
||||
'The group worm has lost its current Fremen decision.');
|
||||
} else requireRule(response?.coalitionWormEvent === groupWorm.event && response.owner === groupWorm.owner &&
|
||||
response.location === groupWorm.territory && (groupWorm.stage === 'response'
|
||||
? response.kind === 'wormAllyProtection' && response.recipient === coalitionWormRule(() => currentCoalitionWormAlly(g, groupWorm))
|
||||
: response.kind === 'wormSurvival'), 'The group worm has lost its original response.');
|
||||
}
|
||||
saphoCyclicIntegrity(g);
|
||||
battleOrderIntegrity(g);
|
||||
saphoAggressorIntegrity(g);
|
||||
@@ -37059,14 +37107,20 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
continueSpice(g);
|
||||
} else beginWorm(g, response.location!, 'additional');
|
||||
} else if (response.kind === 'wormAllyProtection') {
|
||||
wormSurvival(
|
||||
g,
|
||||
response.location!,
|
||||
canceled ? undefined : response.recipient,
|
||||
);
|
||||
if (response.coalitionWormEvent) {
|
||||
const frame = g.coalitionWormProtection;
|
||||
requireRule(frame && response.location === frame.territory && response.recipient,
|
||||
'The original group worm response is missing its ally.');
|
||||
g.coalitionWormProtection = coalitionWormRule(() => resolveCoalitionWormProtection(g, frame,
|
||||
response.owner, response.coalitionWormEvent!, response.recipient!, canceled));
|
||||
advanceCoalitionWormProtection(g);
|
||||
} else wormSurvival(g, response.location!, canceled ? undefined : response.recipient);
|
||||
} else if (response.kind === 'wormSurvival') {
|
||||
devour(g, response.location!, response.recipient, !canceled);
|
||||
afterWorm(g);
|
||||
if (response.coalitionWormEvent) {
|
||||
requireRule(g.coalitionWormProtection?.event === response.coalitionWormEvent,
|
||||
'The original group worm survival response is missing.');
|
||||
finishCoalitionWorm(g, response.location!, !canceled);
|
||||
} else { devour(g, response.location!, response.recipient, !canceled); afterWorm(g); }
|
||||
} else if (response.kind === 'stormProtection') {
|
||||
if (response.resume === 'storm') {
|
||||
g.stormResolution!.protected = !canceled;
|
||||
@@ -43640,7 +43694,17 @@ function applyActionInner(
|
||||
typeof action.accept === 'boolean',
|
||||
'Choose whether to protect your ally.',
|
||||
);
|
||||
if (action.accept) {
|
||||
if (decision.event) {
|
||||
const frame = g.coalitionWormProtection;
|
||||
requireRule(frame && frame.territory === decision.territory && action.event === decision.event &&
|
||||
action.ally === decision.ally && Object.keys(action).sort().join(',') === 'accept,ally,event,type',
|
||||
'Choose the original group worm event and threatened ally.');
|
||||
g.coalitionWormProtection = coalitionWormRule(() => decideCoalitionWormProtection(g, frame, id,
|
||||
decision.event!, decision.ally, action.accept as boolean));
|
||||
if (action.accept) g.response = {kind:'wormAllyProtection', owner:id, location:decision.territory,
|
||||
recipient:decision.ally, coalitionWormEvent:decision.event, passed:[]};
|
||||
else advanceCoalitionWormProtection(g);
|
||||
} else if (action.accept) {
|
||||
g.response = {
|
||||
kind: 'wormAllyProtection',
|
||||
owner: id,
|
||||
|
||||
Reference in new issue
Block a user