diff --git a/server/game/cards/01_SOR/events/YoureMyOnlyHope.ts b/server/game/cards/01_SOR/events/YoureMyOnlyHope.ts index f99af375d..d50f4c214 100644 --- a/server/game/cards/01_SOR/events/YoureMyOnlyHope.ts +++ b/server/game/cards/01_SOR/events/YoureMyOnlyHope.ts @@ -3,8 +3,6 @@ import { EventCard } from '../../../core/card/EventCard'; import { CostAdjustType } from '../../../core/cost/CostAdjuster'; export default class YoureMyOnlyHope extends EventCard { - protected override readonly overrideNotImplemented: boolean = true; - protected override getImplementationId() { return { id: '3509161777', @@ -15,26 +13,40 @@ export default class YoureMyOnlyHope extends EventCard { public override setupCardAbilities() { this.setEventAbility({ title: 'Look at the top card of your deck', - immediateEffect: AbilityHelper.immediateEffects.lookAt((context) => ({ - target: context.source.controller.getTopCardOfDeck() - })), - ifYouDo: (ifYouDoContext) => ({ - title: ifYouDoContext.source.controller.base.remainingHp <= 5 - ? `Play ${ifYouDoContext.source.controller.getTopCardOfDeck().title} for free` - : `Play ${ifYouDoContext.source.controller.getTopCardOfDeck().title}, it costs 5 less`, - optional: true, - immediateEffect: AbilityHelper.immediateEffects.conditional({ - condition: (context) => context.source.controller.base.remainingHp <= 5, - onTrue: AbilityHelper.immediateEffects.playCardFromOutOfPlay((context) => ({ - target: context.source.controller.getTopCardOfDeck(), - adjustCost: { costAdjustType: CostAdjustType.Free } - })), - onFalse: AbilityHelper.immediateEffects.playCardFromOutOfPlay((context) => ({ - target: context.source.controller.getTopCardOfDeck(), - adjustCost: { costAdjustType: CostAdjustType.Decrease, amount: 5 } - })) - }) - }) + immediateEffect: AbilityHelper.immediateEffects.lookAtAndChooseOption( + (context) => { + const topCardOfDeck = context.source.controller.getTopCardOfDeck(); + const canPlayForFree = context.source.controller.base.remainingHp <= 5; + const leaveOnTopButton = { + text: 'Leave on top', + arg: 'leave', + immediateEffect: AbilityHelper.immediateEffects.noAction({ hasLegalTarget: true }) + }; + const playForFreeButton = { + text: 'Play for free', + arg: 'play-free', + immediateEffect: AbilityHelper.immediateEffects.playCardFromOutOfPlay({ + target: topCardOfDeck, + adjustCost: { costAdjustType: CostAdjustType.Free } + }) + }; + const playForDiscountButton = { + text: 'Play for 5 less', + arg: 'play-discount', + immediateEffect: AbilityHelper.immediateEffects.playCardFromOutOfPlay({ + target: topCardOfDeck, + adjustCost: { costAdjustType: CostAdjustType.Decrease, amount: 5 } + }) + }; + + return { + target: topCardOfDeck, + perCardButtons: canPlayForFree + ? [playForFreeButton, leaveOnTopButton] + : [playForDiscountButton, leaveOnTopButton] + }; + } + ) }); } } diff --git a/test/helpers/CustomMatchers.js b/test/helpers/CustomMatchers.js index d3454bc0a..fc4ba52cb 100644 --- a/test/helpers/CustomMatchers.js +++ b/test/helpers/CustomMatchers.js @@ -873,9 +873,9 @@ var customMatchers = { result.pass = stringArraysEqual(actualButtonsInPrompt, expectedButtonsInPrompt); if (result.pass) { - result.message = `Expected ${player.name} not to have this exact set of "per card" buttons but it did: ${expectedButtonsInPrompt.join(', ')}`; + result.message = `Expected ${player.name} not to have this exact set of enabled "per card" buttons but it did: ${expectedButtonsInPrompt.join(', ')}`; } else { - result.message = `Expected ${player.name} to have this exact set of "per card" buttons: '${expectedButtonsInPrompt.join(', ')}' but it has: '${actualButtonsInPrompt.join(', ')}'`; + result.message = `Expected ${player.name} to have this exact set of enabled "per card" buttons: '${expectedButtonsInPrompt.join(', ')}' but it has: '${actualButtonsInPrompt.join(', ')}'`; } result.message += `\n\n${generatePromptHelpMessage(player.testContext)}`; @@ -898,9 +898,9 @@ var customMatchers = { result.pass = stringArraysEqual(actualButtonsInPrompt, expectedButtonsInPrompt); if (result.pass) { - result.message = `Expected ${player.name} not to have this exact set of "per card" buttons but it did: ${expectedButtonsInPrompt.join(', ')}`; + result.message = `Expected ${player.name} not to have this exact set of disabled "per card" buttons but it did: ${expectedButtonsInPrompt.join(', ')}`; } else { - result.message = `Expected ${player.name} to have this exact set of "per card" buttons: '${expectedButtonsInPrompt.join(', ')}' but it has: '${actualButtonsInPrompt.join(', ')}'`; + result.message = `Expected ${player.name} to have this exact set of disabled "per card" buttons: '${expectedButtonsInPrompt.join(', ')}' but it has: '${actualButtonsInPrompt.join(', ')}'`; } result.message += `\n\n${generatePromptHelpMessage(player.testContext)}`; diff --git a/test/server/cards/01_SOR/events/YoureMyOnlyHope.spec.ts b/test/server/cards/01_SOR/events/YoureMyOnlyHope.spec.ts index ceedb4145..569ae63e6 100644 --- a/test/server/cards/01_SOR/events/YoureMyOnlyHope.spec.ts +++ b/test/server/cards/01_SOR/events/YoureMyOnlyHope.spec.ts @@ -8,64 +8,89 @@ describe('You\'re My Only Hope', function() { leader: 'hera-syndulla#spectre-two', base: { card: 'administrators-tower', damage: 21 }, hand: ['youre-my-only-hope'], - deck: ['atst', 'wampa'], + deck: ['atst'], groundArena: ['isb-agent'] }, player2: { + hand: ['regional-governor'], spaceArena: ['black-one#scourge-of-starkiller-base'] - }, - - // IMPORTANT: this is here for backwards compatibility of older tests, don't use in new code - autoSingleTarget: true + } }); }); - it('shows the top card of the deck and allow to play it', function () { + it('shows the top card of the deck and allows the player to leave it', function () { const { context } = contextRef; - const reset = () => { - context.player1.moveCard(context.youreMyOnlyHope, 'hand'); - context.player2.passAction(); - }; - // Scenario 1: Do nothing context.player1.clickCard(context.youreMyOnlyHope); - // TODO: we need a 'look at' prompt for secretly revealing, currently chat logs go to all players - expect(context.getChatLogs(1)).toContain('You\'re My Only Hope sees AT-ST'); - expect(context.player1).toHavePassAbilityPrompt('Play AT-ST, it costs 5 less'); - context.player1.passAction(); + expect(context.player1).toHaveExactSelectableDisplayPromptCards([context.atst]); + expect(context.player1).toHaveExactDisplayPromptPerCardButtons(['Play for 5 less', 'Leave on top']); + expect(context.getChatLogs(1)[0]).not.toContain(context.atst.title); + + context.player1.clickDisplayCardPromptButton(context.atst.uuid, 'leave'); + expect(context.atst).toBeInZone('deck', context.player1); + }); - reset(); + it('shows the top card of the deck and allows the player to play it for 5 less', function () { + const { context } = contextRef; - // Scenario 2: Play the card for 5 less context.player1.clickCard(context.youreMyOnlyHope); - // TODO: we need a 'look at' prompt for secretly revealing, currently chat logs go to all players - expect(context.getChatLogs(1)).toContain('You\'re My Only Hope sees AT-ST'); - expect(context.player1).toHavePassAbilityPrompt('Play AT-ST, it costs 5 less'); - let exhaustedResourcesBeforeAction = context.player1.exhaustedResourceCount; - context.player1.clickPrompt('Play AT-ST, it costs 5 less'); + expect(context.player1).toHaveExactSelectableDisplayPromptCards([context.atst]); + expect(context.player1).toHaveExactDisplayPromptPerCardButtons(['Play for 5 less', 'Leave on top']); + expect(context.getChatLogs(1)[0]).not.toContain(context.atst.title); + + const exhaustedResourcesBeforeAction = context.player1.exhaustedResourceCount; + context.player1.clickDisplayCardPromptButton(context.atst.uuid, 'play-discount'); + expect(context.player1.exhaustedResourceCount).toBe(exhaustedResourcesBeforeAction + 3); expect(context.atst).toBeInZone('groundArena', context.player1); + }); - reset(); + it('shows the top card of the deck and allows the player to play it for free if their base has 5 or less remaining HP', function () { + const { context } = contextRef; - // Scenario 3: Play the card for free context.player1.clickCard(context.isbAgent); + context.player1.clickCard(context.p2Base); context.player2.clickCard(context.blackOne); + context.player2.clickCard(context.p1Base); + context.player1.clickCard(context.youreMyOnlyHope); - expect(context.getChatLogs(1)).toContain('You\'re My Only Hope sees Wampa'); - expect(context.player1).toHavePassAbilityPrompt('Play Wampa for free'); - exhaustedResourcesBeforeAction = context.player1.exhaustedResourceCount; - context.player1.clickPrompt('Play Wampa for free'); + expect(context.player1).toHaveExactSelectableDisplayPromptCards([context.atst]); + expect(context.player1).toHaveExactDisplayPromptPerCardButtons(['Play for free', 'Leave on top']); + expect(context.getChatLogs(1)[0]).not.toContain(context.atst.title); + + const exhaustedResourcesBeforeAction = context.player1.exhaustedResourceCount; + context.player1.clickDisplayCardPromptButton(context.atst.uuid, 'play-free'); + expect(context.player1.exhaustedResourceCount).toBe(exhaustedResourcesBeforeAction); - expect(context.wampa).toBeInZone('groundArena', context.player1); + expect(context.atst).toBeInZone('groundArena', context.player1); + }); + + it('shows the top card of the deck and disallows the player from playing it if it has been named by the Regional Governor', function () { + const { context } = contextRef; - reset(); + context.player1.passAction(); + context.player2.clickCard(context.regionalGovernor); + context.player2.chooseListOption('AT-ST'); + + context.player1.clickCard(context.youreMyOnlyHope); + + expect(context.player1).toHaveExactSelectableDisplayPromptCards([context.atst]); + expect(context.player1).toHaveExactDisabledDisplayPromptPerCardButtons(['Play for 5 less']); + expect(context.player1).toHaveExactEnabledDisplayPromptPerCardButtons(['Leave on top']); + expect(context.getChatLogs(1)[0]).not.toContain(context.atst.title); + + context.player1.clickDisplayCardPromptButton(context.atst.uuid, 'leave'); + expect(context.atst).toBeInZone('deck', context.player1); + }); + + it('does nothing if the deck is empty', function () { + const { context } = contextRef; + context.player1.moveCard(context.atst, 'discard'); - // Scenario 4: Does not trigger if the deck is empty expect(context.player1.deck.length).toEqual(0); context.player1.clickCard(context.youreMyOnlyHope); expect(context.youreMyOnlyHope).toBeInZone('discard', context.player1); diff --git a/test/server/cards/03_TWI/units/LuxBonteriRenegadeSeparatist.spec.ts b/test/server/cards/03_TWI/units/LuxBonteriRenegadeSeparatist.spec.ts index d206e2de2..b135086b2 100644 --- a/test/server/cards/03_TWI/units/LuxBonteriRenegadeSeparatist.spec.ts +++ b/test/server/cards/03_TWI/units/LuxBonteriRenegadeSeparatist.spec.ts @@ -29,7 +29,7 @@ describe('Lux Bonteri, Renegade Separatist', function () { context.player1.moveCard(context.scoutBikePursuer, 'hand'); context.player2.clickCard(context.youreMyOnlyHope); - context.player2.clickPrompt('Play Ki-Adi-Mundi, it costs 5 less'); + context.player2.clickDisplayCardPromptButton(context.kiadimundi.uuid, 'play-discount'); // player 2 play a unit with decreased cost, nothing happens