diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set02/set02-Moria.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set02/set02-Moria.hjson index 1a0f6db2f..347ad74e1 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set02/set02-Moria.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set02/set02-Moria.hjson @@ -1046,6 +1046,15 @@ memory: revealedCards multiplier: -2 } + //As a play-from-hand effect, this has to be checked for playability before + // the event resolves, even tho it's an effect and not a cost. Thus we have to + // provide the maximum theoretical discount so we can play this card even if + // there is less twilight in the pool than the Balrog costs. + maxDiscount: { + type: ForEachInHand + filter: culture(moria),orc + multiplier: -2 + } } } gametext: Shadow: Reveal any number of [moria] Orcs from your hand to play The Balrog. Its twilight cost is -2 for each Orc revealed. diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromHand.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromHand.java index cf15169c9..c290202c5 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromHand.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromHand.java @@ -24,11 +24,12 @@ import java.util.Collection; public class PlayCardFromHand implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "select", "on", "discount", "removedTwilight", "ignoreInDeadPile", "ignoreRoamingPenalty", "memorize"); + FieldUtils.validateAllowedFields(effectObject, "select", "on", "discount", "maxDiscount", "removedTwilight", "ignoreInDeadPile", "ignoreRoamingPenalty", "memorize"); 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 ignoreInDeadPile = FieldUtils.getBoolean(effectObject.get("ignoreInDeadPile"), "ignoreInDeadPile", false); final boolean ignoreRoamingPenalty = FieldUtils.getBoolean(effectObject.get("ignoreRoamingPenalty"), "ignoreRoamingPenalty", false); @@ -50,6 +51,15 @@ public class PlayCardFromHand implements EffectAppenderProducer { } return Filters.playable(game, removedTwilight, costModifier, ignoreRoamingPenalty, ignoreInDeadPile, true); }, + (actionContext) -> { + final LotroGame game = actionContext.getGame(); + final int maxDiscountModifier = maxDiscountSource.getEvaluator(actionContext).evaluateExpression(game, actionContext.getSource()); + if (onFilterableSource != null) { + final Filterable onFilterable = onFilterableSource.getFilterable(actionContext); + return Filters.and(Filters.playable(game, maxDiscountModifier, ignoreRoamingPenalty, ignoreInDeadPile), ExtraFilters.attachableTo(game, maxDiscountModifier, onFilterable)); + } + return Filters.playable(game, removedTwilight, maxDiscountModifier, ignoreRoamingPenalty, ignoreInDeadPile, true); + }, actionContext -> new ConstantEvaluator(1), memorize, "you", "you", "Choose card to play from hand", false, environment)); result.addEffectAppender( new DelayedAppender() { diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java index 197c11706..cc2642523 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java @@ -111,14 +111,18 @@ public class CardResolver { } public static EffectAppender resolveCardsInHand(String type, ValueSource countSource, String memory, String choicePlayer, String handPlayer, String choiceText, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - return resolveCardsInHand(type, null, countSource, memory, choicePlayer, handPlayer, choiceText, false, environment); + return resolveCardsInHand(type, null, null, countSource, memory, choicePlayer, handPlayer, choiceText, false, environment); } public static EffectAppender resolveCardsInHand(String type, ValueSource countSource, String memory, String choicePlayer, String handPlayer, String choiceText, boolean showMatchingOnly, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - return resolveCardsInHand(type, null, countSource, memory, choicePlayer, handPlayer, choiceText, showMatchingOnly, environment); + return resolveCardsInHand(type, null, null, countSource, memory, choicePlayer, handPlayer, choiceText, showMatchingOnly, environment); } - public static EffectAppender resolveCardsInHand(String type, FilterableSource additionalFilter, ValueSource countSource, String memory, String choicePlayer, String handPlayer, String choiceText, boolean showMatchingOnly, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + public static EffectAppender resolveCardsInHand(String type, FilterableSource choiceFilter, ValueSource countSource, String memory, String choicePlayer, String handPlayer, String choiceText, boolean showMatchingOnly, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + return resolveCardsInHand(type, choiceFilter, choiceFilter, countSource, memory, choicePlayer, handPlayer, choiceText, showMatchingOnly, environment); + } + + public static EffectAppender resolveCardsInHand(String type, FilterableSource choiceFilter, FilterableSource playabilityFilter, ValueSource countSource, String memory, String choicePlayer, String handPlayer, String choiceText, boolean showMatchingOnly, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { final PlayerSource handSource = PlayerResolver.resolvePlayer(handPlayer); Function> cardSource = actionContext -> { String handPlayer1 = handSource.getPlayer(actionContext); @@ -148,11 +152,11 @@ public class CardResolver { } }; } else if (type.equals("self")) { - return resolveSelf(additionalFilter, additionalFilter, countSource, memory, cardSource); + return resolveSelf(choiceFilter, playabilityFilter, countSource, memory, cardSource); } else if (type.startsWith("memory(") && type.endsWith(")")) { - return resolveMemoryCards(type, additionalFilter, additionalFilter, countSource, memory, cardSource); + return resolveMemoryCards(type, choiceFilter, playabilityFilter, countSource, memory, cardSource); } else if (type.startsWith("all(") && type.endsWith(")")) { - return resolveAllCards(type, additionalFilter, memory, environment, cardSource); + return resolveAllCards(type, choiceFilter, memory, environment, cardSource); } else if (type.startsWith("choose(") && type.endsWith(")")) { final PlayerSource playerSource = PlayerResolver.resolvePlayer(choicePlayer); ChoiceEffectSource effectSource = (possibleCards, action, actionContext, min, max) -> { @@ -181,7 +185,7 @@ public class CardResolver { } }; - return resolveChoiceCards(type, additionalFilter, additionalFilter, countSource, environment, cardSource, effectSource); + return resolveChoiceCards(type, choiceFilter, playabilityFilter, countSource, environment, cardSource, effectSource); } throw new InvalidCardDefinitionException("Unable to resolve card resolver of type: " + type); } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set02/Card_02_070_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set02/Card_02_070_Tests.java index 48fdd44e1..c9acdb287 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set02/Card_02_070_Tests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set02/Card_02_070_Tests.java @@ -1,10 +1,7 @@ package com.gempukku.lotro.cards.official.set02; import com.gempukku.lotro.cards.GenericCardTestHelper; -import com.gempukku.lotro.common.CardType; -import com.gempukku.lotro.common.Culture; -import com.gempukku.lotro.common.Side; -import com.gempukku.lotro.common.Timeword; +import com.gempukku.lotro.common.*; import com.gempukku.lotro.game.CardNotFoundException; import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException; import org.junit.Test; @@ -20,8 +17,12 @@ public class Card_02_070_Tests return new GenericCardTestHelper( new HashMap<>() {{ - put("card", "2_70"); - // put other cards in here as needed for the test case + put("power", "2_70"); + put("balrog", "6_76"); + + put("orc1", "1_178"); + put("orc2", "1_179"); + put("orc3", "1_181"); }}, GenericCardTestHelper.FellowshipSites, GenericCardTestHelper.FOTRFrodo, @@ -46,7 +47,7 @@ public class Card_02_070_Tests var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); + var card = scn.GetFreepsCard("power"); assertEquals("Power and Terror", card.getBlueprint().getTitle()); assertNull(card.getBlueprint().getSubtitle()); @@ -58,18 +59,36 @@ public class Card_02_070_Tests assertEquals(0, card.getBlueprint().getTwilightCost()); } - // Uncomment any @Test markers below once this is ready to be used - //@Test - public void PowerandTerrorTest1() throws DecisionResultInvalidException, CardNotFoundException { + @Test + public void PowerandTerrorPlaysTheBalrogEvenWhenOnlyPlayableViaDisount() throws DecisionResultInvalidException, CardNotFoundException { //Pre-game setup var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); - scn.FreepsMoveCardToHand(card); + var power = scn.GetShadowCard("power"); + var balrog = scn.GetShadowCard("balrog"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + scn.ShadowMoveCardToHand(power, balrog, orc1, orc2, orc3); scn.StartGame(); - scn.FreepsPlayCard(card); + scn.SetTwilight(7); - assertEquals(0, scn.GetTwilight()); + scn.FreepsPassCurrentPhaseAction(); + + // Starting 7 + 1 companion + 2 from site = 10 + assertEquals(10, scn.GetTwilight()); + assertEquals(14, balrog.getBlueprint().getTwilightCost()); + + // With 3 moria orcs in hand, the maximum discount that Power and Terror can give is -6. + // 14 - 6 + 2 (roaming) = 10, which is exactly what's on the table. P&T should thus permit the play. + assertTrue(scn.ShadowPlayAvailable(power)); + + assertEquals(Zone.HAND, balrog.getZone()); + scn.ShadowPlayCard(power); + assertEquals(3, scn.GetShadowCardChoiceCount()); + scn.ShadowChooseCards(orc1, orc2, orc3); + scn.FreepsDismissRevealedCards(); + assertEquals(Zone.SHADOW_CHARACTERS, balrog.getZone()); } }