Corsair War Galley errata + tests
This commit is contained in:
@@ -283,5 +283,107 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78_59: {
|
||||
cardInfo: {
|
||||
//Either a full URL, or a subpath for the PC image server
|
||||
imagePath: errata/LOTR-EN08E059.2_card.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
|
||||
//This instructs the blueprint generator to insert this card as an alt of the listed
|
||||
// parent blueprint. Can of course be ommitted if not an errata or promo.
|
||||
parent: 8_59
|
||||
//This is the tree path to use within the alts structure on the parent.
|
||||
// Can of course be ommitted if parent is null.
|
||||
parentPath: errata/pc
|
||||
//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: 2
|
||||
collInfo: 8U59
|
||||
rarity: U
|
||||
setNum: "8"
|
||||
cardNum: 59
|
||||
// Standard, Masterwork, Tengwar, FullArt, etc. Top-level cards are always Standard.
|
||||
style: Standard
|
||||
}
|
||||
title: Corsair War Galley
|
||||
unique: true
|
||||
side: Shadow
|
||||
culture: Raider
|
||||
twilight: 1
|
||||
type: Possession
|
||||
keyword: Support Area
|
||||
effects: [
|
||||
{
|
||||
type: trigger
|
||||
optional: true
|
||||
trigger: {
|
||||
type: played
|
||||
filter: self
|
||||
}
|
||||
effect: {
|
||||
type: addTokens
|
||||
culture: raider
|
||||
filter: self
|
||||
}
|
||||
}
|
||||
{
|
||||
type: modifier
|
||||
modifier: {
|
||||
type: shadowhasinitiative
|
||||
requires: [
|
||||
{
|
||||
type: CanSpotCultureTokens
|
||||
culture: raider
|
||||
amount: 6
|
||||
}
|
||||
{
|
||||
type: canSpot
|
||||
filter: culture(raider),man
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
type: activated
|
||||
phase: regroup
|
||||
cost: {
|
||||
type: memorizeNumber
|
||||
memory: numberOfTokens
|
||||
amount: {
|
||||
type: forEachToken
|
||||
culture: raider
|
||||
filter: any
|
||||
}
|
||||
}
|
||||
effect: [
|
||||
{
|
||||
type: addTwilight
|
||||
amount: {
|
||||
type: fromMemory
|
||||
memory: numberOfTokens
|
||||
}
|
||||
}
|
||||
{
|
||||
type: discard
|
||||
filter: self
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
gametext: When you play this possession, you may add a [raider] token here.<br>While you can spot 6 [raider] tokens and a [raider] Man, the Shadow has initiative, regardless of the Free Peoples player's hand. <br><b>Regroup:</b> Add (1) for each [raider] token you can spot. Discard this possession.
|
||||
lore: ""
|
||||
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: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.gempukku.lotro.cards.build.field.effect.requirement;
|
||||
|
||||
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.Requirement;
|
||||
import com.gempukku.lotro.cards.build.field.FieldUtils;
|
||||
import com.gempukku.lotro.common.Culture;
|
||||
import com.gempukku.lotro.common.Token;
|
||||
import com.gempukku.lotro.logic.timing.PlayConditions;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class CanSpotCultureTokens implements RequirementProducer {
|
||||
@Override
|
||||
public Requirement getPlayRequirement(JSONObject object, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
|
||||
FieldUtils.validateAllowedFields(object, "culture", "filter", "amount");
|
||||
|
||||
final int count = FieldUtils.getInteger(object.get("amount"), "amount", 1);
|
||||
final String filter = FieldUtils.getString(object.get("filter"), "filter", "any");
|
||||
final Culture culture = FieldUtils.getEnum(Culture.class, object.get("culture"), "culture");
|
||||
final Token tokenForCulture = Token.findTokenForCulture(culture);
|
||||
|
||||
if(tokenForCulture == null)
|
||||
throw new InvalidCardDefinitionException("Culture is required for CanSpotCultureTokens.");
|
||||
|
||||
final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment);
|
||||
|
||||
return (actionContext) -> PlayConditions.canSpotCultureTokensOnCards(actionContext.getGame(), tokenForCulture, count,
|
||||
filterableSource.getFilterable(actionContext));
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class RequirementFactory {
|
||||
requirementProducers.put("canmove", new CanMove());
|
||||
requirementProducers.put("canspot", new CanSpot());
|
||||
requirementProducers.put("canspotburdens", new CanSpotBurdens());
|
||||
requirementProducers.put("canspotculturetokens", new CanSpotCultureTokens());
|
||||
requirementProducers.put("canspotindeadpile", new CanSpotInDeadPile());
|
||||
requirementProducers.put("canspotthreats", new CanSpotThreats());
|
||||
requirementProducers.put("canspottwilight", new CanSpotTwilight());
|
||||
|
||||
@@ -222,6 +222,10 @@ public class Filters {
|
||||
};
|
||||
}
|
||||
|
||||
public static Filter hasAnyCultureTokens() {
|
||||
return hasAnyCultureTokens(1);
|
||||
}
|
||||
|
||||
public static Filter hasAnyCultureTokens(final int count) {
|
||||
return new Filter() {
|
||||
@Override
|
||||
|
||||
@@ -211,15 +211,41 @@ public class GameUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
// "If you can spot X [elven] tokens..."
|
||||
public static int getSpottableTokensTotal(LotroGame game, Token token) {
|
||||
return getSpottableCultureTokensOfType(game, token, Filters.any);
|
||||
}
|
||||
|
||||
// "If you can spot X [elven] tokens on conditions..."
|
||||
public static int getSpottableCultureTokensOfType(LotroGame game, Token token, Filterable... filters) {
|
||||
int tokensTotal = 0;
|
||||
|
||||
for (PhysicalCard physicalCard : Filters.filterActive(game, Filters.hasToken(token)))
|
||||
final var cards = Filters.filterActive(game, Filters.and(filters, Filters.hasToken(token)));
|
||||
|
||||
for (PhysicalCard physicalCard : cards)
|
||||
tokensTotal += game.getGameState().getTokenCount(physicalCard, token);
|
||||
|
||||
return tokensTotal;
|
||||
}
|
||||
|
||||
// "If you can spot X culture tokens on conditions..."
|
||||
public static int getAllSpottableCultureTokens(LotroGame game, Filterable... filters) {
|
||||
int tokensTotal = 0;
|
||||
|
||||
final var cards = Filters.filterActive(game, Filters.and(filters, Filters.hasAnyCultureTokens()));
|
||||
|
||||
for (PhysicalCard physicalCard : cards) {
|
||||
var tokens = game.getGameState().getTokens(physicalCard);
|
||||
for(var token : tokens.entrySet()) {
|
||||
if(token.getKey().getCulture() != null) {
|
||||
tokensTotal += token.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokensTotal;
|
||||
}
|
||||
|
||||
public static int getSpottableCulturesCount(LotroGame game, Filterable... filters) {
|
||||
Set<Culture> cultures = new HashSet<>();
|
||||
for (PhysicalCard physicalCard : Filters.filterActive(game, filters)) {
|
||||
|
||||
@@ -254,6 +254,23 @@ public class PlayConditions {
|
||||
return game.getGameState().getBurdens() >= count;
|
||||
}
|
||||
|
||||
// "If you can spot X [elven] tokens..."
|
||||
public static boolean canSpotCultureTokensInPlay(LotroGame game, Token token, int count) {
|
||||
return GameUtils.getSpottableTokensTotal(game, token) >= count;
|
||||
}
|
||||
|
||||
// "If you can spot X [elven] tokens on conditions..."
|
||||
public static boolean canSpotCultureTokensOnCards(LotroGame game, Token token, int count, Filterable... filters) {
|
||||
return GameUtils.getSpottableCultureTokensOfType(game, token, filters) >= count;
|
||||
}
|
||||
|
||||
// "If you can spot X culture tokens on conditions..."
|
||||
// Strictly speaking this would only be needed if there was a card that could add culture tokens to cards
|
||||
// that did not match their own native culture.
|
||||
public static boolean canSpotAllCultureTokensOnCards(LotroGame game, int count, Filterable... filters) {
|
||||
return GameUtils.getAllSpottableCultureTokens(game, filters) >= count;
|
||||
}
|
||||
|
||||
public static boolean canSpotFPCultures(LotroGame game, int count, String playerId) {
|
||||
return GameUtils.getSpottableFPCulturesCount(game, playerId) >= count;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException;
|
||||
import com.gempukku.lotro.logic.effects.DiscardCardsFromPlayEffect;
|
||||
import com.gempukku.lotro.logic.modifiers.Modifier;
|
||||
import com.gempukku.lotro.logic.timing.EffectResult;
|
||||
import com.gempukku.lotro.logic.timing.PlayConditions;
|
||||
import com.gempukku.lotro.logic.timing.RuleUtils;
|
||||
import com.gempukku.lotro.logic.vo.LotroDeck;
|
||||
import org.junit.Assert;
|
||||
@@ -307,6 +308,10 @@ public class GenericCardTestHelper extends AbstractAtTest {
|
||||
|
||||
public int GetBurdens() { return _game.getGameState().getBurdens(); }
|
||||
|
||||
public boolean FreepsHasInitiative() { return PlayConditions.hasInitiative(_game, Side.FREE_PEOPLE); }
|
||||
|
||||
public boolean ShadowHasInitiative() { return PlayConditions.hasInitiative(_game, Side.SHADOW); }
|
||||
|
||||
public int GetFreepsArcheryTotal() { return RuleUtils.calculateFellowshipArcheryTotal(_game); }
|
||||
public int GetShadowArcheryTotal() { return RuleUtils.calculateShadowArcheryTotal(_game); }
|
||||
|
||||
@@ -455,6 +460,12 @@ public class GenericCardTestHelper extends AbstractAtTest {
|
||||
_game.getGameState().playerDrawsCard(P1);
|
||||
}
|
||||
|
||||
public void FreepsDrawCards(int count) {
|
||||
for(int i = 0; i < count; i++) {
|
||||
FreepsDrawCard();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShadowDrawCard() {
|
||||
_game.getGameState().playerDrawsCard(P2);
|
||||
}
|
||||
@@ -569,6 +580,10 @@ public class GenericCardTestHelper extends AbstractAtTest {
|
||||
_game.getGameState().addTokens(card, Token.findTokenForCulture(card.getBlueprint().getCulture()), count);
|
||||
}
|
||||
|
||||
public void RemoveTokensFromCard(PhysicalCardImpl card, int count) {
|
||||
_game.getGameState().removeTokens(card, Token.findTokenForCulture(card.getBlueprint().getCulture()), count);
|
||||
}
|
||||
|
||||
|
||||
public void AddThreats(int count) {
|
||||
_game.getGameState().addThreats(_game.getGameState().getCurrentPlayerId(), count);
|
||||
|
||||
@@ -3,15 +3,12 @@ package com.gempukku.lotro.cards.unofficial.pc.errata.set08;
|
||||
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 org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class Card_08_059_ErrataTests
|
||||
@@ -19,10 +16,14 @@ public class Card_08_059_ErrataTests
|
||||
|
||||
protected GenericCardTestHelper GetScenario() throws CardNotFoundException, DecisionResultInvalidException {
|
||||
return new GenericCardTestHelper(
|
||||
new HashMap<String, String>()
|
||||
new HashMap<>()
|
||||
{{
|
||||
put("card", "58_59");
|
||||
// put other cards in here as needed for the test case
|
||||
put("galley", "78_59");
|
||||
put("raider", "4_249");
|
||||
|
||||
put("chaff1", "4_250");
|
||||
put("chaff2", "4_251");
|
||||
put("chaff3", "4_252");
|
||||
}},
|
||||
GenericCardTestHelper.FellowshipSites,
|
||||
GenericCardTestHelper.FOTRFrodo,
|
||||
@@ -30,55 +31,130 @@ public class Card_08_059_ErrataTests
|
||||
);
|
||||
}
|
||||
|
||||
// Uncomment both @Test markers below once this is ready to be used
|
||||
|
||||
//@Test
|
||||
@Test
|
||||
public void CorsairWarGalleyStatsAndKeywordsAreCorrect() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
|
||||
/**
|
||||
* Set: 8
|
||||
* Title: Corsair War Galley
|
||||
* Side: Free Peoples
|
||||
* Unique: True
|
||||
* Side: SHADOW
|
||||
* Culture: Raider
|
||||
* Twilight Cost: 1
|
||||
* Type: possession
|
||||
* Subtype: Support Area
|
||||
* Game Text: When you play this possession, you may add a [Raider] token here.
|
||||
* While you can spot 4 [Raider] tokens here and a [Raider] Man, the Shadow has initiative, regardless of the Free Peoples player's hand.
|
||||
* Regroup: Add (1) for each [Raider] token here. Discard this possession.
|
||||
* Game Text: When you play this possession, you may add a [raider] token here.
|
||||
* While you can spot 6 [raider] tokens and a [raider] Man, the Shadow has initiative, regardless of the Free Peoples player’s hand. <br><b>Regroup:</b> Add (1) for each [raider] token you can spot. Discard this possession.
|
||||
*/
|
||||
|
||||
//Pre-game setup
|
||||
GenericCardTestHelper scn = GetScenario();
|
||||
var scn = GetScenario();
|
||||
|
||||
PhysicalCardImpl card = scn.GetFreepsCard("card");
|
||||
|
||||
assertFalse(card.getBlueprint().isUnique());
|
||||
assertEquals(Side.SHADOW, card.getBlueprint().getSide());
|
||||
assertEquals(Culture.RAIDER, card.getBlueprint().getCulture());
|
||||
assertEquals(CardType.POSSESSION, card.getBlueprint().getCardType());
|
||||
//assertEquals(Race.CREATURE, card.getBlueprint().getRace());
|
||||
assertTrue(scn.HasKeyword(card, Keyword.SUPPORT_AREA));
|
||||
assertEquals(1, card.getBlueprint().getTwilightCost());
|
||||
//assertEquals(, card.getBlueprint().getStrength());
|
||||
//assertEquals(, card.getBlueprint().getVitality());
|
||||
//assertEquals(, card.getBlueprint().getResistance());
|
||||
//assertEquals(Signet., card.getBlueprint().getSignet());
|
||||
//assertEquals(, card.getBlueprint().getSiteNumber()); // Change this to getAllyHomeSiteNumbers for allies
|
||||
var galley = scn.GetFreepsCard("galley");
|
||||
|
||||
assertTrue(galley.getBlueprint().isUnique());
|
||||
assertEquals(Side.SHADOW, galley.getBlueprint().getSide());
|
||||
assertEquals(Culture.RAIDER, galley.getBlueprint().getCulture());
|
||||
assertEquals(CardType.POSSESSION, galley.getBlueprint().getCardType());
|
||||
assertTrue(scn.HasKeyword(galley, Keyword.SUPPORT_AREA));
|
||||
assertEquals(1, galley.getBlueprint().getTwilightCost());
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void CorsairWarGalleyTest1() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
@Test
|
||||
public void GalleyAddsTokenOnPlay() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
GenericCardTestHelper scn = GetScenario();
|
||||
var scn = GetScenario();
|
||||
|
||||
PhysicalCardImpl card = scn.GetFreepsCard("card");
|
||||
scn.FreepsMoveCardToHand(card);
|
||||
var galley = scn.GetShadowCard("galley");
|
||||
scn.ShadowMoveCardToHand(galley);
|
||||
|
||||
scn.StartGame();
|
||||
scn.FreepsPlayCard(card);
|
||||
|
||||
assertEquals(1, scn.GetTwilight());
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
assertTrue(scn.ShadowPlayAvailable(galley));
|
||||
scn.ShadowPlayCard(galley);
|
||||
|
||||
assertEquals(0, scn.GetCultureTokensOn(galley));
|
||||
assertTrue(scn.ShadowHasOptionalTriggerAvailable());
|
||||
scn.ShadowAcceptOptionalTrigger();
|
||||
assertEquals(1, scn.GetCultureTokensOn(galley));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ShadowHasInitiativeIf6TokensAndARaider() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var galley = scn.GetShadowCard("galley");
|
||||
var raider = scn.GetShadowCard("raider");
|
||||
scn.ShadowMoveCardToHand(raider);
|
||||
scn.ShadowMoveCardToSupportArea(galley);
|
||||
|
||||
scn.StartGame();
|
||||
|
||||
scn.FreepsDrawCards(4);
|
||||
scn.SetTwilight(20);
|
||||
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
// Neither 6 tokens nor a raider man: Freeps has initiative
|
||||
assertEquals(0, scn.GetCultureTokensOn(galley));
|
||||
assertEquals(Zone.HAND, raider.getZone());
|
||||
assertTrue(scn.FreepsHasInitiative());
|
||||
|
||||
// 6 tokens, but no raider man: Freeps has initiative
|
||||
scn.AddTokensToCard(galley, 6);
|
||||
assertEquals(6, scn.GetCultureTokensOn(galley));
|
||||
assertEquals(Zone.HAND, raider.getZone());
|
||||
assertTrue(scn.FreepsHasInitiative());
|
||||
|
||||
// 6 tokens on galley and a raider man: Shadow has initiative
|
||||
scn.ShadowPlayCard(raider);
|
||||
assertEquals(6, scn.GetCultureTokensOn(galley));
|
||||
assertEquals(Zone.SHADOW_CHARACTERS, raider.getZone());
|
||||
assertTrue(scn.ShadowHasInitiative());
|
||||
|
||||
// 5 tokens on galley and a raider man: Freeps has initiative
|
||||
scn.RemoveTokensFromCard(galley, 1);
|
||||
assertEquals(5, scn.GetCultureTokensOn(galley));
|
||||
assertEquals(Zone.SHADOW_CHARACTERS, raider.getZone());
|
||||
assertTrue(scn.FreepsHasInitiative());
|
||||
|
||||
// 6 tokens between all raider cards and a raider man: Shadow has initiative
|
||||
scn.AddTokensToCard(raider, 1);
|
||||
assertEquals(5, scn.GetCultureTokensOn(galley));
|
||||
assertEquals(1, scn.GetCultureTokensOn(raider));
|
||||
assertEquals(Zone.SHADOW_CHARACTERS, raider.getZone());
|
||||
assertTrue(scn.ShadowHasInitiative());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void RegroupActionAdds1TwilightPerRaiderTokenAndSelfDiscards() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var galley = scn.GetShadowCard("galley");
|
||||
var raider = scn.GetShadowCard("raider");
|
||||
scn.ShadowMoveCardToHand(raider);
|
||||
scn.ShadowMoveCardToSupportArea(galley);
|
||||
|
||||
scn.StartGame();
|
||||
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
scn.SkipToPhase(Phase.REGROUP);
|
||||
|
||||
scn.ShadowMoveCharToTable(raider);
|
||||
scn.AddTokensToCard(raider, 3);
|
||||
scn.AddTokensToCard(galley, 3);
|
||||
assertEquals(3, scn.GetTwilight());
|
||||
assertEquals(Zone.SUPPORT, galley.getZone());
|
||||
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
assertTrue(scn.ShadowActionAvailable(galley));
|
||||
scn.ShadowUseCardAction(galley);
|
||||
assertEquals(9, scn.GetTwilight());
|
||||
assertEquals(Zone.DISCARD, galley.getZone());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user