Support Harkonnen special Karama truth commitments
Verify and publish checkpoint container / container (push) Successful in 10m17s
Verify and publish checkpoint container / container (push) Successful in 10m17s
This commit is contained in:
1 parent
15a4f3038a
commit
b448144c52
15 files changed
+449
-35
No files matched your search
+45
-5
@@ -18999,7 +18999,8 @@ function karamaSpendingBlock(g: Game, p: Player, card: Card, purpose?: KullPurpo
|
||||
(required.effect === 'karamaAuction' && purpose === 'auction-free-purchase' ||
|
||||
required.effect === 'karamaShipment' && purpose === 'shipment-price' ||
|
||||
required.effect === 'karamaBattleCancel' && !!g.response &&
|
||||
purpose === karamaUsePurpose(g, { kind: 'cancel', response: g.response })) &&
|
||||
purpose === karamaUsePurpose(g, { kind: 'cancel', response: g.response }) ||
|
||||
required.effect === 'karamaHarkonnenSpecial' && purpose === 'special:harkonnen') &&
|
||||
windowCardOpportunity(g, required.effect)?.source === required.source))
|
||||
return 'Retain this opportunity without a forbidden printed Karama cost.';
|
||||
const kullBlock = kullKaramaBlock(g, p, purpose);
|
||||
@@ -20487,6 +20488,13 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
|
||||
'Retain your promised printed Karama cancellation or another whole-answer completion.');
|
||||
}
|
||||
const used = discard(g, p, card.id);
|
||||
// Spending this physical Karama for a different role must preserve any
|
||||
// still-open Harkonnen special promise before a foreign response can park
|
||||
// the shared reconciler.
|
||||
if (card.kind === 'special' && card.effect === 'karama' && auctionRows.some(row => {
|
||||
const stage = row.windowCards?.karamaHarkonnenSpecial;
|
||||
return stage && !stage.closed && windowCardOpportunity(g, 'karamaHarkonnenSpecial', stage.source)?.source === stage.source;
|
||||
})) guardTurnActionPrintedCardCost(g, p, used);
|
||||
if (auctionWindow && auctionRows.some(row => row.windowCards?.karamaAuction?.source === auctionWindow))
|
||||
guardTurnActionAuctionClosure(g, p.id, auctionRows);
|
||||
if (shipmentWindow && use.kind === 'shipment') {
|
||||
@@ -21592,7 +21600,7 @@ function validatePlanetologistMove(g: Game, p: Player, move: Pick<MovementOrder,
|
||||
skill.leader === planetologistLeader(g, p.id),
|
||||
'This Planetologist movement no longer has its living, uncaptured skilled leader.');
|
||||
requireRule(planetologistMovementModeSupported(g),
|
||||
'Planetologist movement combined with expansion factions is still being implemented.');
|
||||
'Planetologist movement is not yet supported in this game configuration.');
|
||||
requireRule(!(move.ornithopterEvent && move.discoveryFlight),
|
||||
'Choose one Ornithopter flight source for this Planetologist movement.');
|
||||
// Selected composition: the concealed marker moves as one force for either
|
||||
@@ -22376,6 +22384,22 @@ function quotedAmalReady(g: Game, actor: string): boolean {
|
||||
function* nativeWindowCardActions(g: Game, actor: string, effect: WindowCardEffect): Generator<Action> {
|
||||
if (!windowCardOpportunity(g, effect)) return;
|
||||
const player = getPlayer(g, actor);
|
||||
if (effect === 'karamaHarkonnenSpecial') {
|
||||
const opportunity = windowCardOpportunity(g, effect);
|
||||
// The public source identifies the unique native owner; eligibility and
|
||||
// each target's hand size are checked here, without inspecting its faces.
|
||||
if (player.faction !== 'harkonnen' || !opportunity || g.kullPreview || g.pendingKull ||
|
||||
g.semutaPreview || g.semutaRecruitsPreview || g.semutaAdvancedOrdinaryPreview ||
|
||||
g.truthtrance || g.response || g.decision || g.phaseOpening || g.pendingKarama ||
|
||||
g.pendingTreacheryDiscard || g.pendingNullentropy) return;
|
||||
for (const card of player.hand.filter(card => card.kind === 'special' && card.effect === 'karama'))
|
||||
for (const target of g.players.filter(candidate => candidate.id !== actor && candidate.hand.length > 0)) {
|
||||
const action: Action = { type: 'card', mode: 'special', card: card.id, target: target.id, amount: 1 };
|
||||
try { if (prepareSpecialKaramaIntent(g, actor, action).kind === 'harkonnen') yield action; }
|
||||
catch (error) { if (!(error instanceof RuleError)) throw error; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (effect === 'karamaBattleCancel') {
|
||||
for (const card of quotedBattleCancelKaramaCards(g, player)) yield { type: 'card', card, mode: 'cancel' };
|
||||
return;
|
||||
@@ -22459,7 +22483,18 @@ function* turnActionWindowCardSeeds(source: Game, actor: string, requirements: r
|
||||
}
|
||||
for (const action of nativeWindowCardActions(state, actor, required.effect)) {
|
||||
try {
|
||||
const next = applyActionInner(state, actor, action, 'movementPreparation');
|
||||
let next: Game;
|
||||
if (required.effect === 'karamaHarkonnenSpecial') {
|
||||
// An opaque legal witness returns the unseen drawn cards to the
|
||||
// target. It spends the actual card but never samples, copies or
|
||||
// derives a benefit from another player's private hand.
|
||||
next = structuredClone(state);
|
||||
const intent = prepareSpecialKaramaIntent(next, actor, action);
|
||||
requireRule(intent.kind === 'harkonnen', 'Retain the original Harkonnen special opportunity.');
|
||||
const owner = getPlayer(next, actor);
|
||||
discard(next, owner, intent.card);
|
||||
owner.specialKaramaUsed = true;
|
||||
} else next = applyActionInner(state, actor, action, 'movementPreparation');
|
||||
if (required.effect === 'karamaAuction') {
|
||||
const before = getPlayer(state, actor), after = getPlayer(next, actor);
|
||||
const known = before.faction === 'atreides' && state.auction?.peekKnown
|
||||
@@ -34856,11 +34891,11 @@ function currentBattleResolutionQuote(g: Game, canceledVoter?: string) {
|
||||
if (quote.sukGraduate) requireRule(
|
||||
ordinaryLeaderSkillModeSupported(g) &&
|
||||
(nativeExpansionLeaderSkillsProfile(g) || g.players.every((p) => faction(p.faction).expansion === 'base')),
|
||||
'Suk Graduate rescue with expansion factions or other optional modules is still being implemented.');
|
||||
'Suk Graduate rescue is not yet supported in this game configuration.');
|
||||
if (quote.rihani || quote.sandmaster) requireRule(
|
||||
ordinaryLeaderSkillModeSupported(g) &&
|
||||
(nativeExpansionLeaderSkillsProfile(g) || g.players.every((p) => faction(p.faction).expansion === 'base')),
|
||||
'These Leader Skill victory effects with expansion factions or other optional modules are still being integrated.');
|
||||
'These Leader Skill victory effects are not yet supported in this game configuration.');
|
||||
const collection = b.smugglerCollection;
|
||||
const collects = !!collection && !quote.effects?.stunned &&
|
||||
!(collection.player === b.attacker ? quote.leaderDeaths.attacker : quote.leaderDeaths.defender);
|
||||
@@ -42787,7 +42822,12 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
|
||||
} else if (intent.kind === 'harkonnen') {
|
||||
const target = getPlayer(g, intent.target),
|
||||
count = intent.amount;
|
||||
const opportunity = windowCardOpportunity(g, 'karamaHarkonnenSpecial');
|
||||
if (opportunity && card.kind === 'special' && card.effect === 'karama')
|
||||
recordNativeTurnActionWindowCard(g, p.id, 'karamaHarkonnenSpecial', opportunity.source);
|
||||
p.specialKaramaUsed = true;
|
||||
if (liveTurnActionPromises(g.turnActionPromises ?? [], p.id, g.turn).length)
|
||||
guardTurnActionPrintedCardCost(g, p, card);
|
||||
const taken = shuffle(target.hand).slice(0, count);
|
||||
target.hand = target.hand.filter((c) => !taken.some((t) => t.id === c.id));
|
||||
p.hand.push(...taken);
|
||||
|
||||
Reference in new issue
Block a user