From c5a84e03384b85b17ecb78e5eaa1a630dca6e83a Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Fri, 19 Aug 2022 19:28:26 -0500 Subject: [PATCH] Mountain-troll errata - Overhauled how Discounts work for json cards to support all the existing DiscountEffects. - Altered InZoneData to now be preserved when the data was set in the void/void-from-hand. - Converted the original Mountain-troll to json - Added unit tests for errata + original --- .../web/cards/official/set15/set15-orc.hjson | 93 ++++++++++ .../set15/set15-Orc-errata-playtest.hjson | 62 +++++-- .../second-edition/set40/set40-moria.hjson | 4 +- .../lotro/cards/set15/orc/Card15_112.java | 75 -------- .../build/field/effect/PotentialDiscount.java | 154 +++++++++++++-- .../field/effect/requirement/MemoryIs.java | 2 +- .../gempukku/lotro/game/state/GameState.java | 5 +- .../effects/ChooseActiveCardsEffect.java | 2 +- .../lotro/logic/effects/DiscountEffect.java | 4 +- .../lotro/logic/effects/OptionalEffect.java | 8 +- .../DiscardCardFromHandDiscountEffect.java | 3 + .../ExertCharactersDiscountEffect.java | 11 +- .../OptionalDiscardDiscountEffect.java | 7 +- .../RemoveCardsFromDiscardDiscountEffect.java | 3 + .../RemoveThreatsToDiscountEffect.java | 3 + .../lotro/cards/GenericCardTestHelper.java | 2 +- .../official/set15/Card_15_112_Tests.java | 175 ++++++++++++++++++ .../errata/set15/Card_15_112_ErrataTests.java | 148 ++++++++++++--- 18 files changed, 613 insertions(+), 148 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-async/src/main/web/cards/official/set15/set15-orc.hjson delete mode 100644 gemp-lotr/gemp-lotr-cards/src/main/java/com/gempukku/lotro/cards/set15/orc/Card15_112.java create mode 100644 gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set15/Card_15_112_Tests.java diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/official/set15/set15-orc.hjson b/gemp-lotr/gemp-lotr-async/src/main/web/cards/official/set15/set15-orc.hjson new file mode 100644 index 000000000..0b11b6010 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/official/set15/set15-orc.hjson @@ -0,0 +1,93 @@ +{ + 15_112: { + cardInfo: { + //Either a full URL, or a subpath for the PC image server + imagePath: decipher/LOTR15112.jpg + //If this is true, then all gameplay-related info outside this cardInfo definition + // will be ignored and the java class loaded instead. + javaClass: false + //Versioning differentiates releases within a particular alt path, such as PC errata + // version 1 vs version 2. PC version 2 will not conflict with, say, Decipher version 2. + //Top-level cards should always be version 0. + version: 0 + collInfo: 15R112 + rarity: R + setNum: "15" + cardNum: 112 + // Standard, Masterwork, Tengwar, FullArt, etc. Top-level cards are always Standard. + style: Standard + } + title: Mountain-troll + unique: false + side: Shadow + culture: Orc + twilight: 10 + type: Minion + race: Troll + strength: 22 + vitality: 6 + site: 5 + effects: [ + { + type: discount + memorize: DiscountPaid + max: { + type: requires + requires: { + type: CanSpot + filter: culture(orc),minion + count: 5 + } + true: 10 + false: 0 + } + discount: { + type: ifDiscardFromPlay + filter: culture(orc),minion + count: 5 + } + } + { + type: modifier + modifier: { + type: addkeyword + keyword: fierce + filter: self + requires: { + #This is only set if the discount was paid for. + type: HasInZoneData + filter: self + } + } + } + { + type: activated + phase: shadow + cost: { + type: removeTwilight + amount: 3 + } + effect: [ + { + type: playCardFromDiscard + removedTwilight: 2 + filter: choose(culture(orc), orc) + memorize: PlayedOrc + cost: -2 + } + ] + } + ] + gametext: When you play this minion, you may discard 5 [orc] minions from play to make it strength +10 and fierce until the regroup phase.
Shadow: Remove (2) to play an [orc] Orc from your discard pile. It comes into play exhausted. + lore: “Suddenly on the last stroke the Gate of Gondor broke.” + promotext: "" + alts: { + //These are just CardInfo objects + promos: { + } + //These are full card definitions, with redundant info that is the same as the original card ommitted + errata: { + } + } + } +} \ No newline at end of file diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/pc/errata/set15/set15-Orc-errata-playtest.hjson b/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/pc/errata/set15/set15-Orc-errata-playtest.hjson index 1eda4ba32..954e7c3a3 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/pc/errata/set15/set15-Orc-errata-playtest.hjson +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/pc/errata/set15/set15-Orc-errata-playtest.hjson @@ -26,33 +26,61 @@ } title: Mountain-troll unique: false + side: Shadow culture: Orc twilight: 8 type: Minion - requires: { - type: canSpot - filter: orc - } + race: Troll + strength: 13 + vitality: 4 + site: 5 effects: [ - { - type: modifier, - modifier: { - type: addKeyword - filter: dwarf - keyword: damage+1 - } - }, { type: trigger optional: true, trigger: { - type: winsSkirmish - filter: dwarf + type: played + filter: self }, - effect: { - type: wound - filter: choose(orc) + cost: { + type: discard + count: 5 + filter: choose(culture(orc), minion, another) } + effect: [ + { + type: addKeyword + keyword: fierce + filter: self + until: endofturn + } + { + type: modifystrength + amount: 10 + filter: self + until: endofturn + } + ] + } + { + type: activated + phase: shadow + cost: { + type: removeTwilight + amount: 2 + } + effect: [ + { + type: playCardFromDiscard + removedTwilight: 2 + filter: choose(culture(orc), orc) + memorize: PlayedOrc + } + { + type: exhaust + filter: memory(PlayedOrc) + } + ] } ] gametext: When you play this minion, you may discard 5 [orc] minions from play to make it strength +10 and fierce until the regroup phase.
Shadow: Remove (2) to play an [orc] Orc from your discard pile. It comes into play exhausted. diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/second-edition/set40/set40-moria.hjson b/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/second-edition/set40/set40-moria.hjson index f34a11405..1a674609c 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/second-edition/set40/set40-moria.hjson +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/unofficial/second-edition/set40/set40-moria.hjson @@ -135,12 +135,12 @@ ] effects: { type: discount - amount: { + max: { type: forEachInHand filter: culture(moria),goblin } discount: { - type: discardFromHand + type: perDiscardFromHand filter: culture(moria),goblin } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/java/com/gempukku/lotro/cards/set15/orc/Card15_112.java b/gemp-lotr/gemp-lotr-cards/src/main/java/com/gempukku/lotro/cards/set15/orc/Card15_112.java deleted file mode 100644 index 4366b27fc..000000000 --- a/gemp-lotr/gemp-lotr-cards/src/main/java/com/gempukku/lotro/cards/set15/orc/Card15_112.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.gempukku.lotro.cards.set15.orc; - -import com.gempukku.lotro.common.*; -import com.gempukku.lotro.game.PhysicalCard; -import com.gempukku.lotro.game.state.LotroGame; -import com.gempukku.lotro.logic.actions.ActivateCardAction; -import com.gempukku.lotro.logic.actions.CostToEffectAction; -import com.gempukku.lotro.logic.cardtype.AbstractMinion; -import com.gempukku.lotro.logic.effects.AddUntilEndOfTurnModifierEffect; -import com.gempukku.lotro.logic.effects.RemoveTwilightEffect; -import com.gempukku.lotro.logic.effects.choose.ChooseAndPlayCardFromDiscardEffect; -import com.gempukku.lotro.logic.effects.discount.OptionalDiscardDiscountEffect; -import com.gempukku.lotro.logic.modifiers.KeywordModifier; -import com.gempukku.lotro.logic.timing.PlayConditions; -import com.gempukku.lotro.logic.timing.UnrespondableEffect; - -import java.util.Collections; -import java.util.List; - -/** - * Set: The Hunters - * Side: Shadow - * Culture: Orc - * Twilight Cost: 10 - * Type: Minion • Troll - * Strength: 22 - * Vitality: 6 - * Site: 5 - * Game Text: When you play this minion, you may discard 5 [ORC] minions from play to make it twilight cost -10 - * and fierce. Shadow: Remove (3) to play an [ORC] Orc from your discard pile. Its twilight cost is -2. - */ -public class Card15_112 extends AbstractMinion { - public Card15_112() { - super(10, 22, 6, 5, Race.TROLL, Culture.ORC, "Mountain-troll"); - } - - @Override - public int getPotentialDiscount(LotroGame game, String playerId, PhysicalCard self) { - if (PlayConditions.canDiscardFromPlay(self, game, 5, Culture.ORC, CardType.MINION)) - return 10; - return 0; - } - - @Override - public void appendPotentialDiscountEffects(LotroGame game, final CostToEffectAction action, String playerId, final PhysicalCard self) { - action.appendPotentialDiscount(new OptionalDiscardDiscountEffect(action, 10, playerId, 5, Culture.ORC, CardType.MINION) { - @Override - protected void discountPaidCallback() { - action.appendEffect( - new UnrespondableEffect() { - @Override - protected void doPlayEffect(LotroGame game) { - action.appendEffect( - new AddUntilEndOfTurnModifierEffect( - new KeywordModifier(self, self, Keyword.FIERCE))); - } - }); - } - }); - } - - @Override - public List getPhaseActionsInPlay(String playerId, LotroGame game, PhysicalCard self) { - if (PlayConditions.canUseShadowCardDuringPhase(game, Phase.SHADOW, self, 3) - && PlayConditions.canPlayFromDiscard(playerId, game, 3, -2, Culture.ORC, Race.ORC)) { - ActivateCardAction action = new ActivateCardAction(self); - action.appendCost( - new RemoveTwilightEffect(3)); - action.appendEffect( - new ChooseAndPlayCardFromDiscardEffect(playerId, game, -2, Culture.ORC, Race.ORC)); - return Collections.singletonList(action); - } - return null; - } -} diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/PotentialDiscount.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/PotentialDiscount.java index c8b023afb..7c33d3638 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/PotentialDiscount.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/PotentialDiscount.java @@ -7,38 +7,160 @@ import com.gempukku.lotro.cards.build.field.effect.appender.resolver.ValueResolv import com.gempukku.lotro.common.Filterable; import com.gempukku.lotro.logic.actions.CostToEffectAction; import com.gempukku.lotro.logic.effects.DiscountEffect; -import com.gempukku.lotro.logic.effects.discount.DiscardCardFromHandDiscountEffect; +import com.gempukku.lotro.logic.effects.discount.*; import org.json.simple.JSONObject; public class PotentialDiscount implements EffectProcessor { @Override public void processEffect(JSONObject value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(value, "amount", "discount"); + FieldUtils.validateAllowedFields(value, "max", "discount", "memorize"); - final ValueSource amountSource = ValueResolver.resolveEvaluator(value.get("amount"), environment); + final ValueSource maxSource = ValueResolver.resolveEvaluator(value.get("max"), 1000, environment); final JSONObject discount = (JSONObject) value.get("discount"); + final String memory = FieldUtils.getString(value.get("memorize"), "memorize", "_temp"); final String discountType = FieldUtils.getString(discount.get("type"), "type"); - if (discountType.equals("discardFromHand")) { + if (discountType.equalsIgnoreCase("perDiscardFromHand")) { FieldUtils.validateAllowedFields(discount, "filter"); final String filter = FieldUtils.getString(discount.get("filter"), "filter", "any"); final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); blueprint.appendDiscountSource( - new DiscountSource() { - @Override - public int getPotentialDiscount(ActionContext actionContext) { - return amountSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); - } + new DiscountSource() { + @Override + public int getPotentialDiscount(ActionContext actionContext) { + return maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + } - @Override - public DiscountEffect getDiscountEffect(CostToEffectAction action, ActionContext actionContext) { - final Filterable filterable = filterableSource.getFilterable(actionContext); - return new DiscardCardFromHandDiscountEffect(action, actionContext.getPerformingPlayer(), filterable); - } - }); - } else { + @Override + public DiscountEffect getDiscountEffect(CostToEffectAction action, ActionContext actionContext) { + final Filterable filterable = filterableSource.getFilterable(actionContext); + return new DiscardCardFromHandDiscountEffect(action, actionContext.getPerformingPlayer(), filterable) { + @Override + protected void discountPaidCallback(int paid) { + actionContext.setValueToMemory(memory, String.valueOf(paid)); + actionContext.getSource().setWhileInZoneData(paid); + } + }; + } + }); + } + else if (discountType.equalsIgnoreCase("perExert")) { + FieldUtils.validateAllowedFields(discount, "multiplier", "filter"); + + final ValueSource multiplierSource = ValueResolver.resolveEvaluator(discount.get("multiplier"), environment); + final String filter = FieldUtils.getString(discount.get("filter"), "filter", "any"); + final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); + + blueprint.appendDiscountSource( + new DiscountSource() { + @Override + public int getPotentialDiscount(ActionContext actionContext) { + return maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + } + + @Override + public DiscountEffect getDiscountEffect(CostToEffectAction action, ActionContext actionContext) { + final Filterable filterable = filterableSource.getFilterable(actionContext); + final int multiplier = multiplierSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), null); + return new ExertCharactersDiscountEffect(action, actionContext.getSource(), + actionContext.getPerformingPlayer(), multiplier, filterable) { + + @Override + protected void discountPaidCallback(int paid) { + actionContext.setValueToMemory(memory, String.valueOf(paid)); + actionContext.getSource().setWhileInZoneData(paid); + } + }; + } + }); + } + else if (discountType.equalsIgnoreCase("perThreatRemoved")) { + //FieldUtils.validateAllowedFields(discount, "multiplier"); + + blueprint.appendDiscountSource( + new DiscountSource() { + @Override + public int getPotentialDiscount(ActionContext actionContext) { + return maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + } + + @Override + public DiscountEffect getDiscountEffect(CostToEffectAction action, ActionContext actionContext) { + //final int multiplier = multiplierSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), null); + return new RemoveThreatsToDiscountEffect(action) { + + @Override + protected void discountPaidCallback(int paid) { + actionContext.setValueToMemory(memory, String.valueOf(paid)); + actionContext.getSource().setWhileInZoneData(paid); + } + }; + } + }); + } + else if (discountType.equalsIgnoreCase("ifDiscardFromPlay")) { + FieldUtils.validateAllowedFields(discount, "count", "filter"); + + final ValueSource discardCountSource = ValueResolver.resolveEvaluator(discount.get("count"), environment); + final String filter = FieldUtils.getString(discount.get("filter"), "filter", "any"); + final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); + + blueprint.appendDiscountSource( + new DiscountSource() { + @Override + public int getPotentialDiscount(ActionContext actionContext) { + return maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + } + + @Override + public DiscountEffect getDiscountEffect(CostToEffectAction action, ActionContext actionContext) { + final Filterable filterable = filterableSource.getFilterable(actionContext); + final int max = maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + final int discardCount = discardCountSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + actionContext.setValueToMemory(memory, "No"); + return new OptionalDiscardDiscountEffect(action, max, actionContext.getPerformingPlayer(), discardCount, filterable) { + @Override + protected void discountPaidCallback(int paid) { + actionContext.setValueToMemory(memory, "Yes"); + actionContext.getSource().setWhileInZoneData(memory); + } + }; + } + }); + } + else if (discountType.equalsIgnoreCase("ifRemoveFromDiscard")) { + FieldUtils.validateAllowedFields(discount, "count", "filter"); + + final ValueSource removeCountSource = ValueResolver.resolveEvaluator(value.get("count"), environment); + final String filter = FieldUtils.getString(discount.get("filter"), "filter", "any"); + final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); + + blueprint.appendDiscountSource( + new DiscountSource() { + @Override + public int getPotentialDiscount(ActionContext actionContext) { + return maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + } + + @Override + public DiscountEffect getDiscountEffect(CostToEffectAction action, ActionContext actionContext) { + final Filterable filterable = filterableSource.getFilterable(actionContext); + final int max = maxSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + final int removeCount = removeCountSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), actionContext.getSource()); + actionContext.setValueToMemory(memory, "No"); + return new RemoveCardsFromDiscardDiscountEffect(actionContext.getSource(), actionContext.getPerformingPlayer(), removeCount, max, filterable) { + @Override + protected void discountPaidCallback(int paid) { + actionContext.setValueToMemory(memory, "Yes"); + actionContext.getSource().setWhileInZoneData(memory); + } + }; + } + }); + } + else { throw new InvalidCardDefinitionException("Unknown type of discount: " + discountType); } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/requirement/MemoryIs.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/requirement/MemoryIs.java index 588aceb05..917d3e3f2 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/requirement/MemoryIs.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/requirement/MemoryIs.java @@ -16,7 +16,7 @@ public class MemoryIs implements RequirementProducer { return (actionContext) -> { String valueFromMemory = actionContext.getValueFromMemory(memory); - return valueFromMemory != null && valueFromMemory.equals(value); + return valueFromMemory != null && valueFromMemory.equalsIgnoreCase(value); }; } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java index ac2a4737f..791defaca 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java @@ -455,7 +455,10 @@ public class GameState { removeFromSkirmish(card, false); removeAllTokens(card); - card.setWhileInZoneData(null); + //If this is reset, then there is no way for self-discounting effects (which are evaluated while in the void) + // to have any sort of permanent effect once the card is in play. + if(zone != Zone.VOID_FROM_HAND && zone != Zone.VOID) + card.setWhileInZoneData(null); } for (GameStateListener listener : getAllGameStateListeners()) diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ChooseActiveCardsEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ChooseActiveCardsEffect.java index ef22a449a..cf2ddc1c1 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ChooseActiveCardsEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ChooseActiveCardsEffect.java @@ -61,7 +61,7 @@ public abstract class ChooseActiveCardsEffect extends AbstractEffect { @Override public String getText(LotroGame game) { - return null; + return _choiceText; } @Override diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/DiscountEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/DiscountEffect.java index 767c5a232..ac909a04b 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/DiscountEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/DiscountEffect.java @@ -6,7 +6,7 @@ import com.gempukku.lotro.logic.timing.Effect; public interface DiscountEffect extends Effect { int getMaximumPossibleDiscount(LotroGame game); - public void setMinimalRequiredDiscount(int minimalDiscount); + void setMinimalRequiredDiscount(int minimalDiscount); - public int getDiscountPaidFor(); + int getDiscountPaidFor(); } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/OptionalEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/OptionalEffect.java index e862c6aa4..95e87db26 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/OptionalEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/OptionalEffect.java @@ -35,9 +35,12 @@ public class OptionalEffect extends AbstractSubActionEffect { @Override public void playEffect(final LotroGame game) { - if (_optionalEffect.isPlayableInFull(game)) + if (_optionalEffect.isPlayableInFull(game)) { + String text = _optionalEffect.getText(game); + if(text != null) + text = text.toLowerCase(); game.getUserFeedback().sendAwaitingDecision(_playerId, - new MultipleChoiceAwaitingDecision(1, "Do you wish to " + _optionalEffect.getText(game) + "?", new String[]{"Yes", "No"}) { + new MultipleChoiceAwaitingDecision(1, "Do you wish to " + text + "?", new String[]{"Yes", "No"}) { @Override protected void validDecisionMade(int index, String result) { if (index == 0) { @@ -47,5 +50,6 @@ public class OptionalEffect extends AbstractSubActionEffect { } } }); + } } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/DiscardCardFromHandDiscountEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/DiscardCardFromHandDiscountEffect.java index 07bcd760b..287f35ade 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/DiscardCardFromHandDiscountEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/DiscardCardFromHandDiscountEffect.java @@ -64,9 +64,12 @@ public class DiscardCardFromHandDiscountEffect extends AbstractSubActionEffect i @Override protected void cardsBeingDiscardedCallback(Collection cardsBeingDiscarded) { _discardedCount = cardsBeingDiscarded.size(); + discountPaidCallback(_discardedCount); } }); processSubAction(game, subAction); } } + + protected void discountPaidCallback(int paid) { } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/ExertCharactersDiscountEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/ExertCharactersDiscountEffect.java index 9bd167617..2be065956 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/ExertCharactersDiscountEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/ExertCharactersDiscountEffect.java @@ -10,6 +10,8 @@ import com.gempukku.lotro.logic.effects.choose.ChooseAndExertCharactersEffect; import com.gempukku.lotro.logic.timing.AbstractSubActionEffect; import com.gempukku.lotro.logic.timing.Action; +import java.util.Collection; + public class ExertCharactersDiscountEffect extends AbstractSubActionEffect implements DiscountEffect { private final Action _action; private final PhysicalCard _payingFor; @@ -30,7 +32,7 @@ public class ExertCharactersDiscountEffect extends AbstractSubActionEffect imple @Override public String getText(LotroGame game) { - return null; + return "Exert characters to reduce twilight cost"; } @Override @@ -70,6 +72,11 @@ public class ExertCharactersDiscountEffect extends AbstractSubActionEffect imple protected void forEachCardExertedCallback(PhysicalCard character) { _exertedCount++; } + + @Override + protected void cardsToBeExertedCallback(Collection characters) { + discountPaidCallback(_exertedCount); + } }); processSubAction(game, subAction); } @@ -79,4 +86,6 @@ public class ExertCharactersDiscountEffect extends AbstractSubActionEffect imple public int getDiscountPaidFor() { return _exertedCount * _multiplier; } + + protected void discountPaidCallback(int paid) { } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/OptionalDiscardDiscountEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/OptionalDiscardDiscountEffect.java index 607737bca..0ab42ad36 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/OptionalDiscardDiscountEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/OptionalDiscardDiscountEffect.java @@ -42,7 +42,7 @@ public class OptionalDiscardDiscountEffect extends AbstractSubActionEffect imple @Override public String getText(LotroGame game) { - return null; + return "Discard cards to get a twilight discount"; } @Override @@ -74,7 +74,7 @@ public class OptionalDiscardDiscountEffect extends AbstractSubActionEffect imple protected void cardsToBeDiscardedCallback(Collection cards) { if (cards.size() == _discardCount) { _paid = true; - discountPaidCallback(); + discountPaidCallback(_discardCount); } } })); @@ -82,6 +82,5 @@ public class OptionalDiscardDiscountEffect extends AbstractSubActionEffect imple } } - protected void discountPaidCallback() { - } + protected void discountPaidCallback(int paid) { } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveCardsFromDiscardDiscountEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveCardsFromDiscardDiscountEffect.java index 57f78d9c6..d38ae61e7 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveCardsFromDiscardDiscountEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveCardsFromDiscardDiscountEffect.java @@ -104,10 +104,13 @@ public class RemoveCardsFromDiscardDiscountEffect implements DiscountEffect { game.getGameState().addCardToZone(game, removedCard, Zone.REMOVED); game.getGameState().sendMessage(_playerId + " removed " + GameUtils.getAppendedNames(removedCards) + " from discard using " + GameUtils.getCardLink(_source)); + discountPaidCallback(removedCards.size()); } @Override public boolean wasCarriedOut() { return !_required || _paid; } + + protected void discountPaidCallback(int paid) { } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveThreatsToDiscountEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveThreatsToDiscountEffect.java index 4ab66d4de..64fdaf490 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveThreatsToDiscountEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/discount/RemoveThreatsToDiscountEffect.java @@ -60,6 +60,7 @@ public class RemoveThreatsToDiscountEffect extends AbstractSubActionEffect imple new RemoveThreatsEffect(_action.getActionSource(), threats)); processSubAction(game, subAction); _threatsRemoved = threats; + discountPaidCallback(threats); } } } @@ -78,4 +79,6 @@ public class RemoveThreatsToDiscountEffect extends AbstractSubActionEffect imple public int getDiscountPaidFor() { return _threatsRemoved; } + + protected void discountPaidCallback(int paid) { } } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java index 499deb9e5..d3b578adf 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java @@ -804,7 +804,7 @@ public class GenericCardTestHelper extends AbstractAtTest { public void ShadowAcceptOptionalTrigger() throws DecisionResultInvalidException { playerDecided(P2, "0"); } public void ShadowDeclineOptionalTrigger() throws DecisionResultInvalidException { playerDecided(P2, ""); } - public void ShadowDeclineReconciliation() throws DecisionResultInvalidException { playerDecided(P2, ""); } + public void ShadowDeclineReconciliation() throws DecisionResultInvalidException { ShadowPassCurrentPhaseAction(); } public void FreepsChooseYes() throws DecisionResultInvalidException { ChooseMultipleChoiceOption(P1, "Yes"); } public void ShadowChooseYes() throws DecisionResultInvalidException { ChooseMultipleChoiceOption(P2, "Yes"); } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set15/Card_15_112_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set15/Card_15_112_Tests.java new file mode 100644 index 000000000..0686a58b7 --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set15/Card_15_112_Tests.java @@ -0,0 +1,175 @@ +package com.gempukku.lotro.cards.official.set15; + +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 org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.*; + +public class Card_15_112_Tests +{ + + protected GenericCardTestHelper GetScenario() throws CardNotFoundException, DecisionResultInvalidException { + return new GenericCardTestHelper( + new HashMap() + {{ + put("troll", "15_112"); + put("orc1", "13_118"); + put("orc2", "13_118"); + put("orc3", "13_118"); + put("orc4", "13_118"); + put("orc5", "13_118"); + }}, + GenericCardTestHelper.FellowshipSites, + GenericCardTestHelper.FOTRFrodo, + GenericCardTestHelper.FOTRRing + ); + } + + @Test + public void MountaintrollStatsAndKeywordsAreCorrect() throws DecisionResultInvalidException, CardNotFoundException { + + /** + * Set: 15 + * Title: Mountain-troll + * Side: Free Peoples + * Culture: Orc + * Twilight Cost: 10 + * Type: minion + * Subtype: Troll + * Strength: 13 + * Vitality: 4 + * Site Number: 5 + * Game Text: When you play this minion, you may discard 5 [orc] minions from play to make it twilight cost -10 and + * fierce. + * Shadow: Remove (2) to play an [orc] Orc from your discard pile. It comes into play exhausted. + */ + + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + PhysicalCardImpl troll = scn.GetFreepsCard("troll"); + + assertFalse(troll.getBlueprint().isUnique()); + assertEquals(Side.SHADOW, troll.getBlueprint().getSide()); + assertEquals(Culture.ORC, troll.getBlueprint().getCulture()); + assertEquals(CardType.MINION, troll.getBlueprint().getCardType()); + assertEquals(Race.TROLL, troll.getBlueprint().getRace()); + assertEquals(10, troll.getBlueprint().getTwilightCost()); + assertEquals(22, troll.getBlueprint().getStrength()); + assertEquals(6, troll.getBlueprint().getVitality()); + //assertEquals(, troll.getBlueprint().getResistance()); + //assertEquals(Signet., troll.getBlueprint().getSignet()); + assertEquals(5, troll.getBlueprint().getSiteNumber()); // Change this to getAllyHomeSiteNumbers for allies + + } + + @Test + public void OnPlayTrollDoesNothingIfLessThan5OrcMinionsInPlay() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + var troll = scn.GetShadowCard("troll"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + var orc4 = scn.GetShadowCard("orc4"); + var orc5 = scn.GetShadowCard("orc5"); + scn.ShadowMoveCardToHand(troll); + scn.ShadowMoveCharToTable(orc1, orc2, orc3, orc4); + + scn.StartGame(); + + scn.SetTwilight(20); + + scn.FreepsPassCurrentPhaseAction(); + scn.ShadowPlayCard(troll); + + assertFalse(scn.ShadowHasOptionalTriggerAvailable()); + } + + @Test + public void OnPlayTrollOptionallDiscards5OrcMinionsForMinus10TwilightAndFierceUntilEndOfTurn() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + var troll = scn.GetShadowCard("troll"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + var orc4 = scn.GetShadowCard("orc4"); + var orc5 = scn.GetShadowCard("orc5"); + scn.ShadowMoveCardToHand(troll); + scn.ShadowMoveCharToTable(orc1, orc2, orc3, orc4, orc5); + + scn.StartGame(); + + scn.SetTwilight(17); + + scn.FreepsPassCurrentPhaseAction(); + assertEquals(20, scn.GetTwilight()); + scn.ShadowPlayCard(troll); + + assertTrue(scn.ShadowDecisionAvailable("choose cards to discard")); + assertFalse(scn.HasKeyword(troll, Keyword.FIERCE)); + + scn.ShadowChooseYes(); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + assertEquals(Zone.DISCARD, orc1.getZone()); + assertEquals(Zone.DISCARD, orc2.getZone()); + assertEquals(Zone.DISCARD, orc3.getZone()); + assertEquals(Zone.DISCARD, orc4.getZone()); + assertEquals(Zone.DISCARD, orc5.getZone()); + assertEquals(18, scn.GetTwilight()); //20 initial -2 for roaming, troll was otherwise free + + scn.SkipToPhase(Phase.ASSIGNMENT); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + + scn.SkipToPhase(Phase.REGROUP); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + + scn.PassCurrentPhaseActions(); + scn.ShadowDeclineReconciliation(); + scn.FreepsChooseToMove(); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + } + + @Test + public void ShadowAbilityRemoves3AndPlaysDiscountedOrcFromDiscard() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + var troll = scn.GetShadowCard("troll"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + var orc4 = scn.GetShadowCard("orc4"); + var orc5 = scn.GetShadowCard("orc5"); + scn.ShadowMoveCharToTable(troll); + scn.ShadowMoveCardToDiscard(orc1, orc2, orc3, orc4, orc5); + + scn.StartGame(); + + scn.SetTwilight(17); + + scn.FreepsPassCurrentPhaseAction(); + + assertEquals(20, scn.GetTwilight()); + assertTrue(scn.ShadowActionAvailable(troll)); + assertEquals(Zone.DISCARD, orc1.getZone()); + + scn.ShadowUseCardAction(troll); + assertEquals(5, scn.GetShadowCardChoiceCount()); + scn.ShadowChooseCardBPFromSelection(orc1); + + // -3 for ability, -3 for orc, +2 for discount, -2 for roaming + assertEquals(14, scn.GetTwilight()); + assertEquals(Zone.SHADOW_CHARACTERS, orc1.getZone()); + assertEquals(0, scn.GetWoundsOn(orc1)); + } +} diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set15/Card_15_112_ErrataTests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set15/Card_15_112_ErrataTests.java index dd8777ae4..2dce36793 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set15/Card_15_112_ErrataTests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set15/Card_15_112_ErrataTests.java @@ -6,6 +6,7 @@ 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.Assert; import org.junit.Test; import java.util.HashMap; @@ -21,8 +22,12 @@ public class Card_15_112_ErrataTests return new GenericCardTestHelper( new HashMap() {{ - put("card", "85_112"); - // put other cards in here as needed for the test case + put("troll", "85_112"); + put("orc1", "13_118"); + put("orc2", "13_118"); + put("orc3", "13_118"); + put("orc4", "13_118"); + put("orc5", "13_118"); }}, GenericCardTestHelper.FellowshipSites, GenericCardTestHelper.FOTRFrodo, @@ -30,9 +35,7 @@ public class Card_15_112_ErrataTests ); } - // Uncomment both @Test markers below once this is ready to be used - - //@Test + @Test public void MountaintrollStatsAndKeywordsAreCorrect() throws DecisionResultInvalidException, CardNotFoundException { /** @@ -46,40 +49,135 @@ public class Card_15_112_ErrataTests * Strength: 13 * Vitality: 4 * Site Number: 5 - * Game Text: When you play this minion, you may discard 5 [orc] minions from play to make it strength +10 and fierce until the regroup phase.
Shadow: Remove (2) to play an [orc] Orc from your discard pile. It comes into play exhausted. + * Game Text: When you play this minion, you may discard 5 [orc] minions from play to make it strength +10 and + * fierce until the end of the turn. + * Shadow: Remove (2) to play an [orc] Orc from your discard pile. It comes into play exhausted. */ //Pre-game setup GenericCardTestHelper scn = GetScenario(); - PhysicalCardImpl card = scn.GetFreepsCard("card"); + PhysicalCardImpl troll = scn.GetFreepsCard("troll"); - assertFalse(card.getBlueprint().isUnique()); - assertEquals(Side.SHADOW, card.getBlueprint().getSide()); - assertEquals(Culture.ORC, card.getBlueprint().getCulture()); - assertEquals(CardType.MINION, card.getBlueprint().getCardType()); - assertEquals(Race.CREATURE, card.getBlueprint().getRace()); - assertTrue(scn.HasKeyword(card, Keyword.SUPPORT_AREA)); - assertEquals(8, card.getBlueprint().getTwilightCost()); - assertEquals(13, card.getBlueprint().getStrength()); - assertEquals(4, card.getBlueprint().getVitality()); - //assertEquals(, card.getBlueprint().getResistance()); - //assertEquals(Signet., card.getBlueprint().getSignet()); - assertEquals(5, card.getBlueprint().getSiteNumber()); // Change this to getAllyHomeSiteNumbers for allies + assertFalse(troll.getBlueprint().isUnique()); + assertEquals(Side.SHADOW, troll.getBlueprint().getSide()); + assertEquals(Culture.ORC, troll.getBlueprint().getCulture()); + assertEquals(CardType.MINION, troll.getBlueprint().getCardType()); + assertEquals(Race.TROLL, troll.getBlueprint().getRace()); + assertEquals(8, troll.getBlueprint().getTwilightCost()); + assertEquals(13, troll.getBlueprint().getStrength()); + assertEquals(4, troll.getBlueprint().getVitality()); + //assertEquals(, troll.getBlueprint().getResistance()); + //assertEquals(Signet., troll.getBlueprint().getSignet()); + assertEquals(5, troll.getBlueprint().getSiteNumber()); // Change this to getAllyHomeSiteNumbers for allies } - //@Test - public void MountaintrollTest1() throws DecisionResultInvalidException, CardNotFoundException { + @Test + public void OnPlayTrollDoesNothingIfLessThan5OrcMinionsInPlay() throws DecisionResultInvalidException, CardNotFoundException { //Pre-game setup GenericCardTestHelper scn = GetScenario(); - PhysicalCardImpl card = scn.GetFreepsCard("card"); - scn.FreepsMoveCardToHand(card); + var troll = scn.GetShadowCard("troll"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + var orc4 = scn.GetShadowCard("orc4"); + var orc5 = scn.GetShadowCard("orc5"); + scn.ShadowMoveCardToHand(troll); + scn.ShadowMoveCharToTable(orc1, orc2, orc3, orc4); scn.StartGame(); - scn.FreepsPlayCard(card); - assertEquals(8, scn.GetTwilight()); + scn.SetTwilight(20); + + scn.FreepsPassCurrentPhaseAction(); + scn.ShadowPlayCard(troll); + + assertFalse(scn.ShadowHasOptionalTriggerAvailable()); + } + + @Test + public void OnPlayTrollOptionallDiscards5OrcMinionsForFierceAndStrengthPluse10UntilEndOfTurn() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + var troll = scn.GetShadowCard("troll"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + var orc4 = scn.GetShadowCard("orc4"); + var orc5 = scn.GetShadowCard("orc5"); + scn.ShadowMoveCardToHand(troll); + scn.ShadowMoveCharToTable(orc1, orc2, orc3, orc4, orc5); + + scn.StartGame(); + + scn.SetTwilight(17); + + scn.FreepsPassCurrentPhaseAction(); + assertEquals(20, scn.GetTwilight()); + scn.ShadowPlayCard(troll); + + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + assertEquals(13, scn.GetStrength(troll)); + assertFalse(scn.HasKeyword(troll, Keyword.FIERCE)); + + scn.ShadowAcceptOptionalTrigger(); + assertEquals(23, scn.GetStrength(troll)); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + assertEquals(Zone.DISCARD, orc1.getZone()); + assertEquals(Zone.DISCARD, orc2.getZone()); + assertEquals(Zone.DISCARD, orc3.getZone()); + assertEquals(Zone.DISCARD, orc4.getZone()); + assertEquals(Zone.DISCARD, orc5.getZone()); + assertEquals(10, scn.GetTwilight()); //20 initial -8 for troll -2 for roaming + + scn.SkipToPhase(Phase.ASSIGNMENT); + assertEquals(23, scn.GetStrength(troll)); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + + scn.SkipToPhase(Phase.REGROUP); + assertEquals(23, scn.GetStrength(troll)); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + + scn.PassCurrentPhaseActions(); + scn.ShadowDeclineReconciliation(); + scn.FreepsChooseToMove(); + assertEquals(23, scn.GetStrength(troll)); + assertTrue(scn.HasKeyword(troll, Keyword.FIERCE)); + } + + @Test + public void ShadowAbilityRemoves2AndPlaysExhaustedOrcFromDiscard() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + GenericCardTestHelper scn = GetScenario(); + + var troll = scn.GetShadowCard("troll"); + var orc1 = scn.GetShadowCard("orc1"); + var orc2 = scn.GetShadowCard("orc2"); + var orc3 = scn.GetShadowCard("orc3"); + var orc4 = scn.GetShadowCard("orc4"); + var orc5 = scn.GetShadowCard("orc5"); + scn.ShadowMoveCharToTable(troll); + scn.ShadowMoveCardToDiscard(orc1, orc2, orc3, orc4, orc5); + + scn.StartGame(); + + scn.SetTwilight(17); + + scn.FreepsPassCurrentPhaseAction(); + + assertEquals(20, scn.GetTwilight()); + assertTrue(scn.ShadowActionAvailable(troll)); + assertEquals(Zone.DISCARD, orc1.getZone()); + + scn.ShadowUseCardAction(troll); + assertEquals(5, scn.GetShadowCardChoiceCount()); + scn.ShadowChooseCardBPFromSelection(orc1); + + assertEquals(13, scn.GetTwilight()); // -2 for ability, -3 for orc, -2 for roaming + assertEquals(Zone.SHADOW_CHARACTERS, orc1.getZone()); + assertEquals(1, scn.GetWoundsOn(orc1)); } }