diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1.json index ac442ffd2..c29904928 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1.json @@ -32,7 +32,8 @@ }, "effect": { "type": "discardTopCardFromDeck", - "deck": "owner(skirmishLoser)" + "deck": "owner(skirmishLoser)", + "forced": true } } }, diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json index 36936777a..1f3a8b562 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json @@ -50,7 +50,8 @@ "filter": "choose(hasAttached(self))" }, { - "type": "discardCardAtRandomFromHand" + "type": "discardCardAtRandomFromHand", + "forced": false } ] }, diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/FieldUtils.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/FieldUtils.java index b38daf2ee..b537ef149 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/FieldUtils.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/FieldUtils.java @@ -44,6 +44,14 @@ public class FieldUtils { return (String) value; } + public static boolean getBoolean(Object value, String key) throws InvalidCardDefinitionException { + if (value == null) + throw new InvalidCardDefinitionException("Value of " + key + " is required"); + if (!(value instanceof Boolean)) + throw new InvalidCardDefinitionException("Unknown type in " + key + " field"); + return (Boolean) value; + } + public static boolean getBoolean(Object value, String key, boolean defaultValue) throws InvalidCardDefinitionException { if (value == null) return defaultValue; diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardCardAtRandomFromHand.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardCardAtRandomFromHand.java index 7bc5463ef..fec012b2f 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardCardAtRandomFromHand.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardCardAtRandomFromHand.java @@ -17,24 +17,31 @@ import org.json.simple.JSONObject; public class DiscardCardAtRandomFromHand implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject); + FieldUtils.validateAllowedFields(effectObject, "forced"); + + final boolean forced = FieldUtils.getBoolean(effectObject.get("forced"), "forced"); return new DelayedAppender() { @Override protected Effect createEffect(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { - return new DiscardCardAtRandomFromHandEffect(self, self.getOwner(), false); + return new DiscardCardAtRandomFromHandEffect(self, self.getOwner(), forced); } @Override public boolean isPlayableInFull(String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { - return game.getGameState().getHand(self.getOwner()).size() >= 1; + return game.getGameState().getHand(self.getOwner()).size() >= 1 + && (!forced || game.getModifiersQuerying().canDiscardCardsFromHand(game, playerId, self)); } }; } @Override public Requirement createCostRequirement(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject); - return (playerId, game, self, effectResult, effect) -> game.getGameState().getHand(self.getOwner()).size() >= 1; + FieldUtils.validateAllowedFields(effectObject, "forced"); + + final boolean forced = FieldUtils.getBoolean(effectObject.get("forced"), "forced"); + + return (playerId, game, self, effectResult, effect) -> game.getGameState().getHand(self.getOwner()).size() >= 1 + && (!forced || game.getModifiersQuerying().canDiscardCardsFromHand(game, playerId, self)); } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardTopCardFromDeck.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardTopCardFromDeck.java index 4c0ecad72..7113ea90f 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardTopCardFromDeck.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/DiscardTopCardFromDeck.java @@ -19,10 +19,11 @@ import org.json.simple.JSONObject; public class DiscardTopCardFromDeck implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "deck", "count"); + FieldUtils.validateAllowedFields(effectObject, "deck", "count", "forced"); final String deck = FieldUtils.getString(effectObject.get("deck"), "deck", "owner"); final int count = FieldUtils.getInteger(effectObject.get("count"), "count", 1); + final boolean forced = FieldUtils.getBoolean(effectObject.get("forced"), "forced"); final PlayerSource playerSource = PlayerResolver.resolvePlayer(deck, environment); @@ -34,15 +35,16 @@ public class DiscardTopCardFromDeck implements EffectAppenderProducer { public boolean isPlayableInFull(String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { final String deckId = playerSource.getPlayer(playerId, game, self, effectResult, effect); + // Don't check if can discard top cards, since it's a cost return game.getGameState().getDeck(deckId).size() >= count - && game.getModifiersQuerying().canDiscardCardsFromTopOfDeck(game, deckId, self); + && (!forced || game.getModifiersQuerying().canDiscardCardsFromTopOfDeck(game, playerId, self)); } @Override protected Effect createEffect(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { final String deckId = playerSource.getPlayer(playerId, game, self, effectResult, effect); - return new DiscardTopCardFromDeckEffect(self, deckId, count, true); + return new DiscardTopCardFromDeckEffect(self, deckId, count, forced); } }); @@ -51,17 +53,19 @@ public class DiscardTopCardFromDeck implements EffectAppenderProducer { @Override public Requirement createCostRequirement(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "deck", "count"); + FieldUtils.validateAllowedFields(effectObject, "deck", "count", "forced"); final String deck = FieldUtils.getString(effectObject.get("deck"), "deck", "owner"); final int count = FieldUtils.getInteger(effectObject.get("count"), "count", 1); + final boolean forced = FieldUtils.getBoolean(effectObject.get("forced"), "forced"); final PlayerSource playerSource = PlayerResolver.resolvePlayer(deck, environment); + // Don't check if can discard top cards, since it's a cost return (playerId, game, self, effectResult, effect) -> { final String deckId = playerSource.getPlayer(playerId, game, self, effectResult, effect); return game.getGameState().getDeck(deckId).size() >= count - && game.getModifiersQuerying().canDiscardCardsFromTopOfDeck(game, deckId, self); + && (!forced || game.getModifiersQuerying().canDiscardCardsFromTopOfDeck(game, playerId, self)); }; } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/modifiers/CantDiscardCardsFromHandOrTopOfDeckModifier.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/modifiers/CantDiscardCardsFromHandOrTopOfDeckModifier.java index 4e197782e..3320cca4d 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/modifiers/CantDiscardCardsFromHandOrTopOfDeckModifier.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/modifiers/CantDiscardCardsFromHandOrTopOfDeckModifier.java @@ -1,34 +1,29 @@ package com.gempukku.lotro.logic.modifiers; import com.gempukku.lotro.common.Filterable; -import com.gempukku.lotro.filters.Filter; import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.game.state.LotroGame; public class CantDiscardCardsFromHandOrTopOfDeckModifier extends AbstractModifier { - private Filter _discardSourceAffected; private String _playerId; public CantDiscardCardsFromHandOrTopOfDeckModifier(PhysicalCard source, Condition condition, String playerId, Filterable... discardSourceAffected) { - super(source, null, null, condition, ModifierEffect.DISCARD_NOT_FROM_PLAY); + super(source, null, Filters.and(discardSourceAffected), condition, ModifierEffect.DISCARD_NOT_FROM_PLAY); _playerId = playerId; - _discardSourceAffected = Filters.and(discardSourceAffected); } @Override public boolean canDiscardCardsFromHand(LotroGame game, String playerId, PhysicalCard source) { if (playerId.equals(_playerId)) - if (source == null || _discardSourceAffected.accepts(game, source)) - return false; + return false; return true; } @Override public boolean canDiscardCardsFromTopOfDeck(LotroGame game, String playerId, PhysicalCard source) { if (playerId.equals(_playerId)) - if (source == null || _discardSourceAffected.accepts(game, source)) - return false; + return false; return true; } } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/IndividualCardAtTest.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/IndividualCardAtTest.java index 6dde3730c..67e2a75b5 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/IndividualCardAtTest.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/IndividualCardAtTest.java @@ -1246,4 +1246,43 @@ public class IndividualCardAtTest extends AbstractAtTest { assertTrue(_userFeedback.getAwaitingDecision(P1).getText().startsWith("Do you wish to mulligan")); } + + @Test + public void courteousFrodoPreventsDiscard() throws DecisionResultInvalidException, CardNotFoundException { + Map decks = new HashMap(); + final LotroDeck p1Deck = createSimplestDeck(); + p1Deck.setRingBearer("4_301"); + decks.put(P1, p1Deck); + + decks.put(P2, createSimplestDeck()); + + initializeGameWithDecks(decks); + + PhysicalCardImpl arwen = new PhysicalCardImpl(100, "7_16", P1, _library.getLotroCardBlueprint("7_16")); + PhysicalCardImpl legolas = new PhysicalCardImpl(100, "1_50", P1, _library.getLotroCardBlueprint("1_50")); + PhysicalCardImpl aragorn = new PhysicalCardImpl(100, "1_89", P1, _library.getLotroCardBlueprint("1_89")); + + PhysicalCardImpl cardInHand = new PhysicalCardImpl(100, "1_268", P1, _library.getLotroCardBlueprint("1_268")); + PhysicalCardImpl inquisitor = new PhysicalCardImpl(100, "1_268", P2, _library.getLotroCardBlueprint("1_268")); + + _game.getGameState().addCardToZone(_game, arwen, Zone.FREE_CHARACTERS); + _game.getGameState().addCardToZone(_game, legolas, Zone.FREE_CHARACTERS); + _game.getGameState().addCardToZone(_game, aragorn, Zone.FREE_CHARACTERS); + _game.getGameState().addCardToZone(_game, inquisitor, Zone.HAND); + _game.getGameState().addCardToZone(_game, cardInHand, Zone.HAND); + + skipMulligans(); + + // Pass in fellowship + playerDecided(P1, ""); + + // Play inquisitor + playerDecided(P2, "0"); + // Use inquisitor + playerDecided(P2, "0"); + + assertEquals(Zone.HAND, cardInHand.getZone()); + } + + }