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());
}
}