Merge branch 'grand-card-rework' into v2-cards

# Conflicts:
#	gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java
This commit is contained in:
Christian 'ketura' McCarty
2024-09-07 16:29:55 -05:00
3 changed files with 257 additions and 35 deletions

View File

@@ -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.<br><b>Shadow:</b> Remove X [raider] tokens here to play a corsair from your discard pile; its twilight cost is -X. Discard this possession.

View File

@@ -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(

View File

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