From 1729f7de92cd5ac78482f3ee4fef4836a9952ad1 Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Wed, 27 Jul 2022 06:14:46 -0500 Subject: [PATCH] Added new flag to PlayCardFromX effects to bypass checking for the target's existence. This permits A Wizard is Never Late to play to no effect if necessary. --- .../src/main/web/cards/set1/set1-gandalf.json | 3 +- .../effect/appender/PlayCardFromDiscard.java | 14 +- .../effect/appender/PlayCardFromDrawDeck.java | 15 +- .../effect/appender/PlayCardFromHand.java | 14 +- .../effect/appender/PlayCardFromStacked.java | 20 ++- .../official/set01/Card_01_087_Tests.java | 140 ++++++++++++++++++ 6 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set01/Card_01_087_Tests.java diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-gandalf.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-gandalf.json index 001492eb8..26270b5b7 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-gandalf.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-gandalf.json @@ -555,7 +555,8 @@ "type": "event", "effect": { "type": "playCardFromDrawDeck", - "filter": "choose(culture(gandalf),character)" + "filter": "choose(culture(gandalf),character)", + "nocheck": true } } }, 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 6d8d00e32..dc130ff2b 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 @@ -24,13 +24,23 @@ import java.util.Collection; public class PlayCardFromDiscard implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "filter", "on", "cost", "removedTwilight", "memorize"); + FieldUtils.validateAllowedFields(effectObject, "filter", "on", "cost", "removedTwilight", "memorize", "nocheck"); final String filter = FieldUtils.getString(effectObject.get("filter"), "filter"); final String onFilter = FieldUtils.getString(effectObject.get("on"), "on"); final int removedTwilight = FieldUtils.getInteger(effectObject.get("removedTwilight"), "removedTwilight", 0); final ValueSource costModifierSource = ValueResolver.resolveEvaluator(effectObject.get("cost"), 0, environment); final String memorize = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp"); + final boolean noCheck = FieldUtils.getBoolean(effectObject.get("nocheck"), "nocheck", false); + + ValueSource countSource = new ConstantEvaluator(1); + if(noCheck) + { + //This range will cause choice checks to succeed even if no valid choices are found (which is how draw deck + // searching is supposed to work RAW). However we don't want this to be the default, else dual-choice cards + // that play "from draw deck or discard pile" would allow empty sources to be chosen, which is NPE. + countSource = ValueResolver.resolveEvaluator("0-1", 1, environment); + } final FilterableSource onFilterableSource = (onFilter != null) ? environment.getFilterFactory().generateFilter(onFilter, environment) : null; @@ -59,7 +69,7 @@ public class PlayCardFromDiscard implements EffectAppenderProducer { return Filters.playable(actionContext.getGame(), removedTwilight, costModifier, false, false); }, - new ConstantEvaluator(1), memorize, "you", "Choose card to play", environment)); + countSource, memorize, "you", "Choose card to play", environment)); result.addEffectAppender( new DelayedAppender() { @Override diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDrawDeck.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDrawDeck.java index 3c1ad82ea..9f4f43c3f 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDrawDeck.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDrawDeck.java @@ -15,6 +15,7 @@ import com.gempukku.lotro.logic.actions.CostToEffectAction; import com.gempukku.lotro.logic.effects.StackActionEffect; import com.gempukku.lotro.logic.modifiers.ModifierFlag; import com.gempukku.lotro.logic.modifiers.evaluator.ConstantEvaluator; +import com.gempukku.lotro.logic.modifiers.evaluator.Evaluator; import com.gempukku.lotro.logic.timing.Effect; import com.gempukku.lotro.logic.timing.ExtraFilters; import org.json.simple.JSONObject; @@ -24,12 +25,22 @@ import java.util.Collection; public class PlayCardFromDrawDeck implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "filter", "on", "cost", "memorize"); + FieldUtils.validateAllowedFields(effectObject, "filter", "on", "cost", "memorize", "nocheck"); final String filter = FieldUtils.getString(effectObject.get("filter"), "filter"); final String onFilter = FieldUtils.getString(effectObject.get("on"), "on"); final ValueSource costModifierSource = ValueResolver.resolveEvaluator(effectObject.get("cost"), 0, environment); final String memorize = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp"); + final boolean noCheck = FieldUtils.getBoolean(effectObject.get("nocheck"), "nocheck", false); + + ValueSource countSource = new ConstantEvaluator(1); + if(noCheck) + { + //This range will cause choice checks to succeed even if no valid choices are found (which is how draw deck + // searching is supposed to work RAW). However we don't want this to be the default, else dual-choice cards + // that play "from draw deck or discard pile" would allow empty sources to be chosen, which is NPE. + countSource = ValueResolver.resolveEvaluator("0-1", 1, environment); + } final FilterableSource onFilterableSource = (onFilter != null) ? environment.getFilterFactory().generateFilter(onFilter, environment) : null; @@ -47,7 +58,7 @@ public class PlayCardFromDrawDeck implements EffectAppenderProducer { } return Filters.playable(actionContext.getGame(), costModifier); }, - new ConstantEvaluator(1), memorize, "you", "you", "Choose card to play", environment)); + countSource, memorize, "you", "you", "Choose card to play", environment)); result.addEffectAppender( new DelayedAppender() { @Override 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 67e0f4363..7f82e926f 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 @@ -23,13 +23,23 @@ import java.util.Collection; public class PlayCardFromHand implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "filter", "on", "cost", "ignoreInDeadPile", "memorize"); + FieldUtils.validateAllowedFields(effectObject, "filter", "on", "cost", "ignoreInDeadPile", "memorize", "nocheck"); final String filter = FieldUtils.getString(effectObject.get("filter"), "filter"); final String onFilter = FieldUtils.getString(effectObject.get("on"), "on"); final ValueSource costModifierSource = ValueResolver.resolveEvaluator(effectObject.get("cost"), 0, environment); final boolean ignoreInDeadPile = FieldUtils.getBoolean(effectObject.get("ignoreInDeadPile"), "ignoreInDeadPile", false); final String memorize = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp"); + final boolean noCheck = FieldUtils.getBoolean(effectObject.get("nocheck"), "nocheck", false); + + ValueSource countSource = new ConstantEvaluator(1); + if(noCheck) + { + //This range will cause choice checks to succeed even if no valid choices are found (which is how draw deck + // searching is supposed to work RAW). However we don't want this to be the default, else dual-choice cards + // that play "from draw deck or discard pile" would allow empty sources to be chosen, which is NPE. + countSource = ValueResolver.resolveEvaluator("0-1", 1, environment); + } final FilterableSource onFilterableSource = (onFilter != null) ? environment.getFilterFactory().generateFilter(onFilter, environment) : null; @@ -47,7 +57,7 @@ public class PlayCardFromHand implements EffectAppenderProducer { } return Filters.playable(game, costModifier, false, ignoreInDeadPile); }, - new ConstantEvaluator(1), memorize, "you", "you", "Choose card to play from hand", environment)); + countSource, memorize, "you", "you", "Choose card to play from hand", environment)); result.addEffectAppender( new DelayedAppender() { @Override diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromStacked.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromStacked.java index b9f7b3e4b..8d48dbf52 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromStacked.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromStacked.java @@ -1,13 +1,11 @@ package com.gempukku.lotro.cards.build.field.effect.appender; -import com.gempukku.lotro.cards.build.ActionContext; -import com.gempukku.lotro.cards.build.CardGenerationEnvironment; -import com.gempukku.lotro.cards.build.FilterableSource; -import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; +import com.gempukku.lotro.cards.build.*; import com.gempukku.lotro.cards.build.field.FieldUtils; import com.gempukku.lotro.cards.build.field.effect.EffectAppender; import com.gempukku.lotro.cards.build.field.effect.EffectAppenderProducer; import com.gempukku.lotro.cards.build.field.effect.appender.resolver.CardResolver; +import com.gempukku.lotro.cards.build.field.effect.appender.resolver.ValueResolver; import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.logic.PlayUtils; @@ -22,11 +20,21 @@ import java.util.Collection; public class PlayCardFromStacked implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "filter", "on", "removedTwilight"); + FieldUtils.validateAllowedFields(effectObject, "filter", "on", "removedTwilight", "nocheck"); final String filter = FieldUtils.getString(effectObject.get("filter"), "filter", "choose(any)"); final String onFilter = FieldUtils.getString(effectObject.get("on"), "on"); final int removedTwilight = FieldUtils.getInteger(effectObject.get("removedTwilight"), "removedTwilight", 0); + final boolean noCheck = FieldUtils.getBoolean(effectObject.get("nocheck"), "nocheck", false); + + ValueSource countSource = new ConstantEvaluator(1); + if(noCheck) + { + //This range will cause choice checks to succeed even if no valid choices are found (which is how draw deck + // searching is supposed to work RAW). However we don't want this to be the default, else dual-choice cards + // that play "from draw deck or discard pile" would allow empty sources to be chosen, which is NPE. + countSource = ValueResolver.resolveEvaluator("0-1", 1, environment); + } final FilterableSource onFilterableSource = (onFilter != null) ? environment.getFilterFactory().generateFilter(onFilter, environment) : null; @@ -37,7 +45,7 @@ public class PlayCardFromStacked implements EffectAppenderProducer { CardResolver.resolveStackedCards(filter, actionContext -> Filters.playable(actionContext.getGame()), actionContext -> Filters.playable(actionContext.getGame(), removedTwilight, 0, false, false), - new ConstantEvaluator(1), onFilterableSource, "_temp", "you", "Choose card to play", environment)); + countSource, onFilterableSource, "_temp", "you", "Choose card to play", environment)); result.addEffectAppender( new DelayedAppender() { @Override diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set01/Card_01_087_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set01/Card_01_087_Tests.java new file mode 100644 index 000000000..52f338eff --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set01/Card_01_087_Tests.java @@ -0,0 +1,140 @@ + +package com.gempukku.lotro.cards.official.set01; + +import com.gempukku.lotro.cards.GenericCardTestHelper; +import com.gempukku.lotro.common.*; +import com.gempukku.lotro.game.CardNotFoundException; +import com.gempukku.lotro.game.PhysicalCardImpl; +import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException; +import com.gempukku.lotro.logic.modifiers.MoveLimitModifier; +import org.junit.Test; + +import java.util.HashMap; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class Card_01_087_Tests +{ + + protected GenericCardTestHelper GetScenario() throws CardNotFoundException, DecisionResultInvalidException { + return new GenericCardTestHelper( + new HashMap() + {{ + put("late", "1_87"); + put("gandalf", "1_72"); + put("bb", "1_70"); + }}, + GenericCardTestHelper.FellowshipSites, + GenericCardTestHelper.FOTRFrodo, + GenericCardTestHelper.FOTRRing + ); + } + + @Test + public void AWizardIsNeverLateStatsAndKeywordsAreCorrect() throws DecisionResultInvalidException, CardNotFoundException { + + /** + * Set: 1 + * Title: A Wizard Is Never Late + * Side: Free Peoples + * Culture: Gandalf + * Twilight Cost: 1 + * Type: Event + * Subtype: Fellowship + * Game Text: Fellowship: Play a [GANDALF] character from your draw deck. + */ + + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + PhysicalCardImpl late = scn.GetFreepsCard("late"); + + assertFalse(late.getBlueprint().isUnique()); + assertEquals(Side.FREE_PEOPLE, late.getBlueprint().getSide()); + assertEquals(Culture.GANDALF, late.getBlueprint().getCulture()); + assertEquals(CardType.EVENT, late.getBlueprint().getCardType()); + assertTrue(scn.HasKeyword(late, Keyword.FELLOWSHIP)); // test for keywords as needed + assertEquals(1, late.getBlueprint().getTwilightCost()); +// assertEquals(2, late.getBlueprint().getStrength()); +// assertEquals(3, late.getBlueprint().getVitality()); + //assertEquals(, late.getBlueprint().getResistance()); + //assertEquals(Signet., late.getBlueprint().getSignet()); + //assertEquals(3, late.getBlueprint().getAllyHomeSiteNumbers()[0]); // Change this to getAllyHomeSiteNumbers for allies + + } + + @Test + public void PlaysAGandalfCompanionFromDeck() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + PhysicalCardImpl late = scn.GetFreepsCard("late"); + PhysicalCardImpl gandalf = scn.GetFreepsCard("gandalf"); + PhysicalCardImpl bb = scn.GetFreepsCard("bb"); + scn.FreepsMoveCardToHand(late); + scn.FreepsMoveCardsToTopOfDeck(gandalf, bb); + + scn.StartGame(); + + assertTrue(scn.FreepsCardPlayAvailable(late)); + assertEquals(Zone.DECK, gandalf.getZone()); + assertEquals(Zone.DECK, bb.getZone()); + + scn.FreepsPlayCard(late); + assertTrue(scn.FreepsDecisionAvailable("Choose card from deck")); + assertEquals(2, scn.GetFreepsCardChoiceCount()); + scn.FreepsChooseCardBPFromSelection(gandalf); + + assertEquals(Zone.FREE_CHARACTERS, gandalf.getZone()); + assertEquals(Zone.DECK, bb.getZone()); + } + + @Test + public void PlaysAGandalfAllyFromDeck() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + PhysicalCardImpl late = scn.GetFreepsCard("late"); + PhysicalCardImpl gandalf = scn.GetFreepsCard("gandalf"); + PhysicalCardImpl bb = scn.GetFreepsCard("bb"); + scn.FreepsMoveCardToHand(late); + scn.FreepsMoveCardsToTopOfDeck(gandalf, bb); + + scn.StartGame(); + + assertTrue(scn.FreepsCardPlayAvailable(late)); + assertEquals(Zone.DECK, gandalf.getZone()); + assertEquals(Zone.DECK, bb.getZone()); + + scn.FreepsPlayCard(late); + assertTrue(scn.FreepsDecisionAvailable("Choose card from deck")); + assertEquals(2, scn.GetFreepsCardChoiceCount()); + scn.FreepsChooseCardBPFromSelection(bb); + + assertEquals(Zone.DECK, gandalf.getZone()); + assertEquals(Zone.SUPPORT, bb.getZone()); + } + + @Test + public void CanPlayWithNoGandalfCharactersInTheDeck() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + PhysicalCardImpl late = scn.GetFreepsCard("late"); + PhysicalCardImpl gandalf = scn.GetFreepsCard("gandalf"); + PhysicalCardImpl bb = scn.GetFreepsCard("bb"); + scn.FreepsMoveCardToHand(late, gandalf, bb); + + scn.StartGame(); + + assertTrue(scn.FreepsCardPlayAvailable(late)); + assertEquals(Zone.HAND, gandalf.getZone()); + assertEquals(Zone.HAND, bb.getZone()); + + scn.FreepsPlayCard(late); + + assertEquals(Zone.DISCARD, late.getZone()); + } +}