Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/games/smashup/__tests__/newFactionAbilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2210,9 +2210,8 @@ describe('印斯茅斯派系能力', () => {
// 非同名卡(dk2)应放到牌库底
const reorderEvt = result.events.find(e => e.type === SU_EVENTS.DECK_REORDERED);
expect(reorderEvt).toBeDefined();
// 新牌库 = 剩余牌库(dk4)+ 放底的(dk2)
expect(reorderEvt!.payload.deckUids).toEqual(['dk4', 'dk2']);
});
// 新牌库 = 剩余牌库 + 本次翻出来但没进手牌的牌:dk4, dk1, dk3, dk2
expect(reorderEvt!.payload.deckUids).toEqual(['dk4', 'dk1', 'dk3', 'dk2']);

it('牌库顶3张无同名卡时全部放牌库底', () => {
const core = makeState({
Expand Down
110 changes: 96 additions & 14 deletions src/games/smashup/abilities/cthulhu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function cthulhuCompleteTheRitualTrigger(ctx: TriggerContext): SmashUpEvent[] {
);
if (!ritual) continue;

// 1. 将所有随从放回拥有者牌库底
// 1. 将所有随从放回拥有者牌库底;普通随从的附着行动也一并放底
for (const m of base.minions) {
events.push({
type: SU_EVENTS.CARD_TO_DECK_BOTTOM,
Expand All @@ -281,6 +281,20 @@ function cthulhuCompleteTheRitualTrigger(ctx: TriggerContext): SmashUpEvent[] {
},
timestamp: ctx.now,
} as CardToDeckBottomEvent);

// 随从上的附着行动也放回其拥有者牌库底
for (const a of m.attachedActions) {
events.push({
type: SU_EVENTS.CARD_TO_DECK_BOTTOM,
payload: {
cardUid: a.uid,
defId: a.defId,
ownerId: a.ownerId,
reason: 'cthulhu_complete_the_ritual',
},
timestamp: ctx.now,
} as CardToDeckBottomEvent);
}
}

// 2. 将所?ongoing 行动卡放回拥有者牌库底(包括仪式本身)
Expand Down Expand Up @@ -399,11 +413,29 @@ function cthulhuAltarTrigger(ctx: TriggerContext): SmashUpEvent[] {
if (baseIndex === undefined) return events;
const base = ctx.state.bases[baseIndex];
if (!base) return events;
// 原版 altar:每次在这里打随从就给 1 次额外行动
// POD 版:每回合每张 altar 只能触发一次
const usedUids = ctx.state.turnUsedOngoingUids ?? [];
const newUsedUids = [...usedUids];

for (const ongoing of base.ongoingActions) {
if (!matchesDefId(ongoing.defId, 'cthulhu_altar')) continue;
if (ongoing.ownerId !== ctx.playerId) continue;

const isPod = ongoing.defId.endsWith('_pod');
if (isPod && usedUids.includes(ongoing.uid)) continue;

events.push(grantExtraAction(ctx.playerId, 'cthulhu_altar', ctx.now));

if (isPod && !newUsedUids.includes(ongoing.uid)) {
newUsedUids.push(ongoing.uid);
}
}

if (newUsedUids.length !== usedUids.length) {
ctx.state.turnUsedOngoingUids = newUsedUids;
}

return events;
}

Expand Down Expand Up @@ -572,14 +604,22 @@ export function registerCthulhuInteractionHandlers(): void {

const { targetPlayerId, madnessUid } = selected;

// 正常执行:转移疯狂卡
// 正常执行:将这张具体的疯狂卡从当前玩家手牌移到目标玩家手牌
if (!targetPlayerId || !madnessUid) return { state, events: [] };

const events: SmashUpEvent[] = [];
events.push(returnMadnessCard(playerId, madnessUid, 'cthulhu_star_spawn', timestamp));
const drawEvt = drawMadnessCards(targetPlayerId, 1, state.core, 'cthulhu_star_spawn', timestamp);
if (drawEvt) events.push(drawEvt);
return { state, events };

const core = state.core;
const fromPlayer = core.players[playerId];
const toPlayer = core.players[targetPlayerId];
if (!fromPlayer || !toPlayer) return { state, events: [] };

const idx = fromPlayer.hand.findIndex(c => c.uid === madnessUid);
if (idx === -1) return { state, events: [] };

const [card] = fromPlayer.hand.splice(idx, 1);
toPlayer.hand.push(card);

void timestamp; // 时间戳对直接状态修改无影响,这里不产生事件
return { state, events: [] };
});

registerInteractionHandler('special_madness', (state, playerId, value, iData, _random, timestamp) => {
Expand Down Expand Up @@ -626,18 +666,60 @@ export function registerCthulhuInteractionHandlers(): void {
const madnessUids = selectedCards.map(v => v.cardUid).filter(Boolean) as string[];
if (madnessUids.length === 0) return { state, events: [] };
const events: SmashUpEvent[] = [];
const player = state.core.players[playerId];
for (const uid of madnessUids) {
events.push(returnMadnessCard(playerId, uid, 'cthulhu_madness_unleashed', timestamp));

// 1) 丢弃选中的疯狂卡到弃牌堆
events.push({
type: SU_EVENTS.CARDS_DISCARDED,
payload: { playerId, cardUids: madnessUids },
timestamp,
});

// 2) 让玩家选择要使用几次“抽牌+额外行动”的机会(0..N)
const maxOptions = madnessUids.length;
const options: PromptOption<{ count: number }>[] = [];
for (let k = 0; k <= maxOptions; k++) {
options.push({
id: `use-${k}`,
label: `使用 ${k} 次抽牌+额外行动`,
value: { count: k },
displayMode: 'button' as const,
});
}
const drawCount = Math.min(madnessUids.length, player.deck.length);
const interaction = createSimpleChoice<{ count: number }>(
`cthulhu_madness_unleashed_apply_${timestamp}`,
playerId,
'选择要使用几次“抽牌+额外行动”的机会',
options,
{ sourceId: 'cthulhu_madness_unleashed_apply', targetType: 'button' },
);

return { state, events, matchState: queueInteraction(state, interaction) };
});

// 疯狂释放:根据玩家选择的次数执行“抽 1 + 额外行动”
registerInteractionHandler('cthulhu_madness_unleashed_apply', (state, playerId, value, _iData, _random, timestamp) => {
const { count } = value as { count: number };
if (!count || count <= 0) return { state, events: [] };

const player = state.core.players[playerId];
if (!player) return { state, events: [] };

const drawCount = Math.min(count, player.deck.length);
const events: SmashUpEvent[] = [];

if (drawCount > 0) {
const drawnUids = player.deck.slice(0, drawCount).map(c => c.uid);
events.push({ type: SU_EVENTS.CARDS_DRAWN, payload: { playerId, count: drawCount, cardUids: drawnUids }, timestamp } as CardsDrawnEvent);
events.push({
type: SU_EVENTS.CARDS_DRAWN,
payload: { playerId, count: drawCount, cardUids: drawnUids },
timestamp,
} as CardsDrawnEvent);
}
for (let i = 0; i < madnessUids.length; i++) {

for (let i = 0; i < count; i++) {
events.push(grantExtraAction(playerId, 'cthulhu_madness_unleashed', timestamp));
}

return { state, events };
});

Expand Down
2 changes: 2 additions & 0 deletions src/games/smashup/domain/reduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@
pendingPostScoringActions: undefined,
// 清空计分阶段锁定的 eligible 基地列表
scoringEligibleBaseIndices: undefined,
// 清空本回合已使用的持续行动 UID 追踪
turnUsedOngoingUids: undefined,
sleepMarkedPlayers: newSleepMarked?.length ? newSleepMarked : undefined,
players: newPlayers,
};
Expand Down Expand Up @@ -889,7 +891,7 @@
}

case SU_EVENTS.MINION_MOVED: {
const { minionUid, fromBaseIndex, toBaseIndex, reason } = (event as MinionMovedEvent).payload as any;

Check warning on line 894 in src/games/smashup/domain/reduce.ts

View workflow job for this annotation

GitHub Actions / quality-gate

Unexpected any. Specify a different type
const buccaneerPodUsedUids = reason === 'pirate_buccaneer_pod'
? Array.from(new Set([...(state.buccaneerPodUsedUids ?? []), minionUid]))
: state.buccaneerPodUsedUids;
Expand Down Expand Up @@ -976,7 +978,7 @@
}

case SU_EVENTS.MINION_METADATA_UPDATED: {
const { minionUid, baseIndex, metadataUpdate } = (event as any as { payload: { minionUid: string; baseIndex?: number; metadataUpdate: Record<string, unknown> } }).payload;

Check warning on line 981 in src/games/smashup/domain/reduce.ts

View workflow job for this annotation

GitHub Actions / quality-gate

Unexpected any. Specify a different type
const tryUpdateBase = (b: BaseInPlay) => ({
...b,
minions: b.minions.map(m => {
Expand Down
8 changes: 8 additions & 0 deletions src/games/smashup/domain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,14 @@
* 用于实现类似“渗透 POD 天赋”这种“即使牌已离场,压制仍持续到下回合开始”的规则。
*/
suppressedBasesUntilTurnStart?: Array<{ baseIndex: number; suppressorPlayerId: PlayerId }>;

/**
* 本回合已触发过“每回合一次”的持续行动卡 UID 列表。
*
* 用于实现类似 Altar to Cthulhu POD 等“每回合一次触发”的效果。
* 生命周期:在 TURN_STARTED 时清空。
*/
turnUsedOngoingUids?: string[];
}

export interface FactionSelectionState {
Expand All @@ -447,8 +455,8 @@
export function getPlayerPowerOnBase(
base: BaseInPlay,
playerId: PlayerId,
state?: SmashUpCore,

Check warning on line 458 in src/games/smashup/domain/types.ts

View workflow job for this annotation

GitHub Actions / quality-gate

'state' is defined but never used. Allowed unused args must match /^_/u
baseIndex?: number

Check warning on line 459 in src/games/smashup/domain/types.ts

View workflow job for this annotation

GitHub Actions / quality-gate

'baseIndex' is defined but never used. Allowed unused args must match /^_/u
): number {
// 随从级别的力量总和(不包含 ongoing 修正)
const minionPower = base.minions
Expand Down
Loading