diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson
index cac126103..d09c7661b 100644
--- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson
+++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set08/set08-Raider.hjson
@@ -108,8 +108,9 @@
multiplier: -1
}
}
- cost: [
- {
+ effect: {
+ type: CostToEffect
+ cost: {
type: chooseANumber
text: Choose how many {raider} tokens to remove
from: 0
@@ -120,26 +121,34 @@
}
memorize: tokenCount
}
- {
- type: RemoveCultureTokens
- culture: raider
- select: self
- count: {
- type: fromMemory
- memory: tokenCount
- }
+ effect: {
+ type: CostToEffect
+ cost: {
+ type: RemoveCultureTokens
+ culture: raider
+ select: self
+ count: {
+ type: fromMemory
+ memory: tokenCount
+ }
+ }
+ effect: {
+ type: playCardFromDiscard
+ select: choose(corsair)
+ maxDiscount: {
+ type: ForEachCultureToken
+ culture: raider
+ filter: self
+ multiplier: -1
+ }
+ discount: {
+ type: fromMemory
+ memory: tokenCount
+ multiplier: -1
+ }
+ }
}
- ]
- effect: {
- type: playCardFromDiscard
- select: choose(corsair)
- discount: {
- type: ForEachCultureToken
- culture: raider
- filter: self
- multiplier: -1
- }
- }
+ }
}
]
gametext: When you play this possession, you may add a [raider] token here.
Shadow: Remove X [raider] tokens here to play a corsair from your discard pile; its twilight cost is -X. Discard this possession.
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 5dca2595f..bd0368c4f 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
@@ -27,13 +27,16 @@ import java.util.Collection;
public class PlayCardFromDiscard implements EffectAppenderProducer {
@Override
public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
- FieldUtils.validateAllowedFields(effectObject, "player", "select", "on", "discount", "removedTwilight", "optional", "extraEffects", "memorize");
+ FieldUtils.validateAllowedFields(effectObject, "select", "on", "discount", "maxDiscount", "removedTwilight", "optional", "extraEffects", "memorize");
final String player = FieldUtils.getString(effectObject.get("player"), "player", "you");
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 optional = FieldUtils.getBoolean(effectObject.get("optional"), "optional", false);
final String memorize = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp");
@@ -61,13 +64,13 @@ public class PlayCardFromDiscard implements EffectAppenderProducer {
},
(actionContext) -> {
final LotroGame game = actionContext.getGame();
- final int costModifier = costModifierSource.getEvaluator(actionContext).evaluateExpression(game, actionContext.getSource());
+ final int maxDiscountModifier = maxDiscountSource.getEvaluator(actionContext).evaluateExpression(game, actionContext.getSource());
if (onFilterableSource != null) {
final Filterable onFilterable = onFilterableSource.getFilterable(actionContext);
- return Filters.and(Filters.playable(actionContext.getGame(), removedTwilight, costModifier, false, false, true), ExtraFilters.attachableTo(actionContext.getGame(), costModifier, onFilterable));
+ return Filters.and(Filters.playable(actionContext.getGame(), removedTwilight, maxDiscountModifier, false, false, true), ExtraFilters.attachableTo(actionContext.getGame(), maxDiscountModifier, onFilterable));
}
- return Filters.playable(actionContext.getGame(), removedTwilight, costModifier, false, false, true);
+ return Filters.playable(actionContext.getGame(), removedTwilight, maxDiscountModifier, false, false, true);
},
actionContext -> countEvaluator, memorize, player, "Choose card to play from discard", environment));
result.addEffectAppender(
diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java
index b668c2314..d2f925e9e 100644
--- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java
+++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set08/Card_08_050_Tests.java
@@ -17,8 +17,8 @@ public class Card_08_050_Tests
return new GenericCardTestHelper(
new HashMap<>()
{{
- put("card", "8_50");
- // put other cards in here as needed for the test case
+ put("blacksails", "8_50");
+ put("corsair", "8_53");
}},
GenericCardTestHelper.FellowshipSites,
GenericCardTestHelper.FOTRFrodo,
@@ -43,7 +43,7 @@ public class Card_08_050_Tests
var scn = GetScenario();
- var card = scn.GetFreepsCard("card");
+ var card = scn.GetFreepsCard("blacksails");
assertEquals("Black Sails of Umbar", card.getBlueprint().getTitle());
assertNull(card.getBlueprint().getSubtitle());
@@ -55,18 +55,228 @@ public class Card_08_050_Tests
assertEquals(2, card.getBlueprint().getTwilightCost());
}
- // Uncomment any @Test markers below once this is ready to be used
- //@Test
- public void BlackSailsofUmbarTest1() throws DecisionResultInvalidException, CardNotFoundException {
+ @Test
+ public void BlackSailsOnPlayAdds1Token() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
var scn = GetScenario();
- var card = scn.GetFreepsCard("card");
- scn.FreepsMoveCardToHand(card);
+ var blacksails = scn.GetShadowCard("blacksails");
+ scn.ShadowMoveCardToHand(blacksails);
scn.StartGame();
- scn.FreepsPlayCard(card);
+ scn.SkipToPhase(Phase.SHADOW);
- assertEquals(2, scn.GetTwilight());
+ assertTrue(scn.ShadowPlayAvailable(blacksails));
+ scn.ShadowPlayCard(blacksails);
+
+ assertEquals(0, scn.GetCultureTokensOn(blacksails));
+ assertTrue(scn.ShadowHasOptionalTriggerAvailable());
+ scn.ShadowAcceptOptionalTrigger();
+ assertEquals(1, scn.GetCultureTokensOn(blacksails));
+ }
+
+ @Test
+ public void BlackSailsAbilityCannotActivateIfNoCorsairInDiscard() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardsToTopOfDeck(corsair);
+
+ scn.StartGame();
+ scn.SetTwilight(50);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(Zone.DECK, corsair.getZone());
+ assertFalse(scn.ShadowActionAvailable(blacksails));
+ }
+
+ @Test
+ public void BlackSailsAbilityCanRemove0TokensToDiscount0() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.SetTwilight(47);
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(50, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("0");
+
+ assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with no discount = 5
+ assertEquals(45, scn.GetTwilight());
+ }
+
+ @Test
+ public void BlackSailsAbilityCanRemove1TokensToDiscount1() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.SetTwilight(47);
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(50, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("1");
+
+ assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with a -1 discount = 4
+ assertEquals(46, scn.GetTwilight());
+ }
+
+ @Test
+ public void BlackSailsAbilityCanRemove2TokensToDiscount2() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.SetTwilight(47);
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(50, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("2");
+
+ assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with a -2 discount = 3
+ assertEquals(47, scn.GetTwilight());
+ }
+
+ @Test
+ public void BlackSailsAbilityCanRemove3TokensToDiscount3() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.SetTwilight(47);
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(50, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("3");
+
+ assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with a -3 discount = 2
+ assertEquals(48, scn.GetTwilight());
+ }
+
+ @Test
+ public void BlackSailsAbilityRemovingTooManyTokensCapsAtZeroCost() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.SetTwilight(47);
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(50, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("5");
+
+ assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with a -3 discount = 2, even tho 5 was removed
+ assertEquals(48, scn.GetTwilight());
+ }
+
+ @Test
+ public void BlackSailsAbilityUsableWhenDiscountRequired() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(3, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("2");
+
+ assertEquals(Zone.SHADOW_CHARACTERS, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with a -2 discount = 3
+ assertEquals(0, scn.GetTwilight());
+ }
+
+ @Test
+ public void BlackSailsAbilityUsableWhenPlayerFumblesAmounts() throws DecisionResultInvalidException, CardNotFoundException {
+ //Pre-game setup
+ var scn = GetScenario();
+
+ var blacksails = scn.GetShadowCard("blacksails");
+ var corsair = scn.GetShadowCard("corsair");
+ scn.ShadowMoveCardToSupportArea(blacksails);
+ scn.ShadowMoveCardToDiscard(corsair);
+
+ scn.StartGame();
+ scn.AddTokensToCard(blacksails, 5);
+ scn.SkipToPhase(Phase.SHADOW);
+
+ assertEquals(3, scn.GetTwilight());
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ assertTrue(scn.ShadowActionAvailable(blacksails));
+ scn.ShadowUseCardAction(blacksails);
+ assertTrue(scn.ShadowDecisionAvailable("remove"));
+ scn.ShadowChoose("1");
+
+ assertEquals(Zone.DISCARD, corsair.getZone());
+ //Buccaneer costs 3 + 2 for roaming, with a -1 discount = 4....which is not enough to have played the buccaneer.
+ assertEquals(3, scn.GetTwilight());
}
}