diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson index cac126103..d09c7661b 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson @@ -108,8 +108,9 @@ multiplier: -1 } } - cost: [ - { + effect: { + type: CostToEffect + cost: { type: chooseANumber text: Choose how many {raider} tokens to remove from: 0 @@ -120,26 +121,34 @@ } memorize: tokenCount } - { - type: RemoveCultureTokens - culture: raider - select: self - count: { - type: fromMemory - memory: tokenCount - } + effect: { + type: CostToEffect + cost: { + type: RemoveCultureTokens + culture: raider + select: self + count: { + type: fromMemory + memory: tokenCount + } + } + effect: { + type: playCardFromDiscard + select: choose(corsair) + maxDiscount: { + type: ForEachCultureToken + culture: raider + filter: self + multiplier: -1 + } + discount: { + type: fromMemory + memory: tokenCount + multiplier: -1 + } + } } - ] - effect: { - type: playCardFromDiscard - select: choose(corsair) - discount: { - type: ForEachCultureToken - culture: raider - filter: self - multiplier: -1 - } - } + } } ] gametext: When you play this possession, you may add a [raider] token here.
Shadow: Remove X [raider] tokens here to play a corsair from your discard pile; its twilight cost is -X. Discard this possession. diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java index 5dca2595f..bd0368c4f 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java @@ -27,13 +27,16 @@ import java.util.Collection; public class PlayCardFromDiscard implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "player", "select", "on", "discount", "removedTwilight", "optional", "extraEffects", "memorize"); + FieldUtils.validateAllowedFields(effectObject, "select", "on", "discount", "maxDiscount", "removedTwilight", "optional", "extraEffects", "memorize"); final String player = FieldUtils.getString(effectObject.get("player"), "player", "you"); final String select = FieldUtils.getString(effectObject.get("select"), "select"); final String onFilter = FieldUtils.getString(effectObject.get("on"), "on"); + final ValueSource costModifierSource = ValueResolver.resolveEvaluator(effectObject.get("discount"), 0, environment); + final ValueSource maxDiscountSource = effectObject.get("maxDiscount") == null ? costModifierSource : ValueResolver.resolveEvaluator(effectObject.get("maxDiscount"), 0, environment); final int removedTwilight = FieldUtils.getInteger(effectObject.get("removedTwilight"), "removedTwilight", 0); + final boolean optional = FieldUtils.getBoolean(effectObject.get("optional"), "optional", false); final String memorize = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp"); @@ -61,13 +64,13 @@ public class PlayCardFromDiscard implements EffectAppenderProducer { }, (actionContext) -> { final LotroGame game = actionContext.getGame(); - final int costModifier = costModifierSource.getEvaluator(actionContext).evaluateExpression(game, actionContext.getSource()); + final int maxDiscountModifier = maxDiscountSource.getEvaluator(actionContext).evaluateExpression(game, actionContext.getSource()); if (onFilterableSource != null) { final Filterable onFilterable = onFilterableSource.getFilterable(actionContext); - return Filters.and(Filters.playable(actionContext.getGame(), removedTwilight, costModifier, false, false, true), ExtraFilters.attachableTo(actionContext.getGame(), costModifier, onFilterable)); + return Filters.and(Filters.playable(actionContext.getGame(), removedTwilight, maxDiscountModifier, false, false, true), ExtraFilters.attachableTo(actionContext.getGame(), maxDiscountModifier, onFilterable)); } - return Filters.playable(actionContext.getGame(), removedTwilight, costModifier, false, false, true); + return Filters.playable(actionContext.getGame(), removedTwilight, maxDiscountModifier, false, false, true); }, actionContext -> countEvaluator, memorize, player, "Choose card to play from discard", environment)); result.addEffectAppender( diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java index b668c2314..d2f925e9e 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java @@ -17,8 +17,8 @@ public class Card_08_050_Tests return new GenericCardTestHelper( new HashMap<>() {{ - put("card", "8_50"); - // put other cards in here as needed for the test case + put("blacksails", "8_50"); + put("corsair", "8_53"); }}, GenericCardTestHelper.FellowshipSites, GenericCardTestHelper.FOTRFrodo, @@ -43,7 +43,7 @@ public class Card_08_050_Tests var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); + var card = scn.GetFreepsCard("blacksails"); assertEquals("Black Sails of Umbar", card.getBlueprint().getTitle()); assertNull(card.getBlueprint().getSubtitle()); @@ -55,18 +55,228 @@ public class Card_08_050_Tests assertEquals(2, card.getBlueprint().getTwilightCost()); } - // Uncomment any @Test markers below once this is ready to be used - //@Test - public void BlackSailsofUmbarTest1() throws DecisionResultInvalidException, CardNotFoundException { + @Test + public void BlackSailsOnPlayAdds1Token() throws DecisionResultInvalidException, CardNotFoundException { //Pre-game setup var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); - scn.FreepsMoveCardToHand(card); + var blacksails = scn.GetShadowCard("blacksails"); + scn.ShadowMoveCardToHand(blacksails); scn.StartGame(); - scn.FreepsPlayCard(card); + scn.SkipToPhase(Phase.SHADOW); - assertEquals(2, scn.GetTwilight()); + assertTrue(scn.ShadowPlayAvailable(blacksails)); + scn.ShadowPlayCard(blacksails); + + assertEquals(0, scn.GetCultureTokensOn(blacksails)); + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + scn.ShadowAcceptOptionalTrigger(); + assertEquals(1, scn.GetCultureTokensOn(blacksails)); + } + + @Test + public void BlackSailsAbilityCannotActivateIfNoCorsairInDiscard() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardsToTopOfDeck(corsair); + + scn.StartGame(); + scn.SetTwilight(50); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(Zone.DECK, corsair.getZone()); + assertFalse(scn.ShadowActionAvailable(blacksails)); + } + + @Test + public void BlackSailsAbilityCanRemove0TokensToDiscount0() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.SetTwilight(47); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(50, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("0"); + + assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with no discount = 5 + assertEquals(45, scn.GetTwilight()); + } + + @Test + public void BlackSailsAbilityCanRemove1TokensToDiscount1() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.SetTwilight(47); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(50, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("1"); + + assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with a -1 discount = 4 + assertEquals(46, scn.GetTwilight()); + } + + @Test + public void BlackSailsAbilityCanRemove2TokensToDiscount2() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.SetTwilight(47); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(50, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("2"); + + assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with a -2 discount = 3 + assertEquals(47, scn.GetTwilight()); + } + + @Test + public void BlackSailsAbilityCanRemove3TokensToDiscount3() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.SetTwilight(47); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(50, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("3"); + + assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with a -3 discount = 2 + assertEquals(48, scn.GetTwilight()); + } + + @Test + public void BlackSailsAbilityRemovingTooManyTokensCapsAtZeroCost() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.SetTwilight(47); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(50, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("5"); + + assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with a -3 discount = 2, even tho 5 was removed + assertEquals(48, scn.GetTwilight()); + } + + @Test + public void BlackSailsAbilityUsableWhenDiscountRequired() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(3, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("2"); + + assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with a -2 discount = 3 + assertEquals(0, scn.GetTwilight()); + } + + @Test + public void BlackSailsAbilityUsableWhenPlayerFumblesAmounts() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var blacksails = scn.GetShadowCard("blacksails"); + var corsair = scn.GetShadowCard("corsair"); + scn.ShadowMoveCardToSupportArea(blacksails); + scn.ShadowMoveCardToDiscard(corsair); + + scn.StartGame(); + scn.AddTokensToCard(blacksails, 5); + scn.SkipToPhase(Phase.SHADOW); + + assertEquals(3, scn.GetTwilight()); + assertEquals(Zone.DISCARD, corsair.getZone()); + assertTrue(scn.ShadowActionAvailable(blacksails)); + scn.ShadowUseCardAction(blacksails); + assertTrue(scn.ShadowDecisionAvailable("remove")); + scn.ShadowChoose("1"); + + assertEquals(Zone.DISCARD, corsair.getZone()); + //Buccaneer costs 3 + 2 for roaming, with a -1 discount = 4....which is not enough to have played the buccaneer. + assertEquals(3, scn.GetTwilight()); } }