Fixed V2 Breaching Shot being playable if there aren't enough tokens on machines to assign an uruk, and also fixed it crashing the game if not enough tokens were removed to assign an uruk.
This commit is contained in:
@@ -183,11 +183,18 @@
|
||||
timewords: Assignment
|
||||
effects: {
|
||||
type: event
|
||||
requires: {
|
||||
type: CanSpotCultureTokens
|
||||
culture: isengard
|
||||
filter: machine
|
||||
}
|
||||
requires: [
|
||||
{
|
||||
type: CanSpotCultureTokens
|
||||
culture: isengard
|
||||
filter: machine
|
||||
memorize: tokenCount
|
||||
}
|
||||
{
|
||||
type: CanSpot
|
||||
filter: uruk-hai,MaxTwilight(memory(tokenCount))
|
||||
}
|
||||
]
|
||||
cost: [
|
||||
{
|
||||
type: ChooseANumber
|
||||
@@ -199,7 +206,6 @@
|
||||
}
|
||||
memorize: removed
|
||||
}
|
||||
|
||||
],
|
||||
effect: [
|
||||
{
|
||||
|
||||
@@ -241,7 +241,7 @@ public class ValueResolver {
|
||||
return (game, cardAffected) -> {
|
||||
int result = 0;
|
||||
for (PhysicalCard physicalCard : Filters.filterActive(game, filterable)) {
|
||||
result += game.getModifiersQuerying().getTwilightCostToPlay(game, physicalCard, null, 0, false);
|
||||
result += game.getModifiersQuerying().getCurrentTwilightCost(game, physicalCard);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -406,6 +406,50 @@ public class FilterFactory {
|
||||
);
|
||||
};
|
||||
});
|
||||
parameterFilters.put("highesttwilight",
|
||||
(parameter, environment) -> {
|
||||
final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(parameter, environment);
|
||||
return actionContext -> {
|
||||
final Filterable sourceFilterable = filterableSource.getFilterable(actionContext);
|
||||
return Filters.and(
|
||||
sourceFilterable, Filters.strengthEqual(
|
||||
new SingleMemoryEvaluator(
|
||||
new Evaluator() {
|
||||
@Override
|
||||
public int evaluateExpression(LotroGame game, PhysicalCard cardAffected) {
|
||||
int maxTwilight = Integer.MIN_VALUE;
|
||||
for (PhysicalCard card : Filters.filterActive(game, sourceFilterable))
|
||||
maxTwilight = Math.max(maxTwilight, game.getModifiersQuerying().getCurrentTwilightCost(game, card));
|
||||
return maxTwilight;
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
});
|
||||
parameterFilters.put("lowesttwilight",
|
||||
(parameter, environment) -> {
|
||||
final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(parameter, environment);
|
||||
return actionContext -> {
|
||||
final Filterable sourceFilterable = filterableSource.getFilterable(actionContext);
|
||||
return Filters.and(
|
||||
sourceFilterable, Filters.strengthEqual(
|
||||
new SingleMemoryEvaluator(
|
||||
new Evaluator() {
|
||||
@Override
|
||||
public int evaluateExpression(LotroGame game, PhysicalCard cardAffected) {
|
||||
int minTwilight = Integer.MAX_VALUE;
|
||||
for (PhysicalCard card : Filters.filterActive(game, sourceFilterable))
|
||||
minTwilight = Math.min(minTwilight, game.getModifiersQuerying().getCurrentTwilightCost(game, card));
|
||||
return minTwilight;
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
});
|
||||
parameterFilters.put("inskirmishagainst",
|
||||
(parameter, environment) -> {
|
||||
final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(parameter, environment);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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.*;
|
||||
import com.gempukku.lotro.cards.build.field.FieldUtils;
|
||||
import com.gempukku.lotro.cards.build.field.effect.appender.resolver.ValueResolver;
|
||||
import com.gempukku.lotro.common.Culture;
|
||||
import com.gempukku.lotro.common.Filterable;
|
||||
import com.gempukku.lotro.common.Token;
|
||||
@@ -15,12 +13,13 @@ 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");
|
||||
FieldUtils.validateAllowedFields(object, "culture", "filter", "amount", "memorize");
|
||||
|
||||
final int count = FieldUtils.getInteger(object.get("amount"), "amount", 1);
|
||||
final ValueSource valueSource = ValueResolver.resolveEvaluator(object.get("amount"), 1, environment);
|
||||
final String filter = FieldUtils.getString(object.get("filter"), "filter", "any");
|
||||
final Culture culture = FieldUtils.getEnum(Culture.class, object.get("culture"), "culture");
|
||||
final Token tokenForCulture = culture != null ? Token.findTokenForCulture(culture) : null;
|
||||
final String memory = FieldUtils.getString(object.get("memorize"), "memorize");
|
||||
|
||||
final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment);
|
||||
|
||||
@@ -28,13 +27,22 @@ public class CanSpotCultureTokens implements RequirementProducer {
|
||||
return (actionContext) -> {
|
||||
LotroGame game = actionContext.getGame();
|
||||
Filterable[] filters = new Filterable[]{filterableSource.getFilterable(actionContext)};
|
||||
int count = valueSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), null);
|
||||
|
||||
return GameUtils.getAllSpottableCultureTokens(game, filters) >= count;
|
||||
};
|
||||
} else {
|
||||
return (actionContext) -> {
|
||||
LotroGame game = actionContext.getGame();
|
||||
Filterable[] filters = new Filterable[]{filterableSource.getFilterable(actionContext)};
|
||||
return GameUtils.getSpottableCultureTokensOfType(game, tokenForCulture, filters) >= count;
|
||||
int count = valueSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), null);
|
||||
int tokens = GameUtils.getSpottableCultureTokensOfType(game, tokenForCulture, filters);
|
||||
|
||||
if (memory != null) {
|
||||
actionContext.setValueToMemory(memory, String.valueOf(tokens));
|
||||
}
|
||||
|
||||
return tokens >= count;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public class GameUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<PhysicalCard> getRandomCards(List<? extends PhysicalCard> cards, int count) {
|
||||
public static List<PhysicalCard> getRandomCards(Collection<? extends PhysicalCard> cards, int count) {
|
||||
List<PhysicalCard> randomizedCards = new ArrayList<>(cards);
|
||||
Collections.shuffle(randomizedCards, ThreadLocalRandom.current());
|
||||
|
||||
|
||||
@@ -492,6 +492,11 @@ public class ModifiersLogic implements ModifiersEnvironment, ModifiersQuerying {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentTwilightCost(LotroGame game, PhysicalCard physicalCard) {
|
||||
return getTwilightCostToPlay(game, physicalCard, null, 0, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTwilightCostToPlay(LotroGame game, PhysicalCard physicalCard, PhysicalCard target, int twilightCostModifier, boolean ignoreRoamingPenalty) {
|
||||
LoggingThreadLocal.logMethodStart(physicalCard, "getTwilightCost");
|
||||
|
||||
@@ -52,6 +52,7 @@ public interface ModifiersQuerying {
|
||||
|
||||
// Twilight cost
|
||||
int getTwilightCostToPlay(LotroGame game, PhysicalCard physicalCard, PhysicalCard target, int twilightCostModifier, boolean ignoreRoamingPenalty);
|
||||
int getCurrentTwilightCost(LotroGame game, PhysicalCard physicalCard);
|
||||
|
||||
int getRoamingPenalty(LotroGame game, PhysicalCard physicalCard);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.gempukku.lotro.cards.unofficial.pc.vsets.set_v02;
|
||||
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;
|
||||
|
||||
@@ -18,8 +17,14 @@ public class Card_V2_021_Tests
|
||||
return new GenericCardTestHelper(
|
||||
new HashMap<>()
|
||||
{{
|
||||
put("card", "102_21");
|
||||
// put other cards in here as needed for the test case
|
||||
put("shot", "102_21");
|
||||
put("ram", "5_44");
|
||||
put("ladder", "5_57");
|
||||
put("fighter", "1_146"); // 3
|
||||
put("guard", "1_147"); // 4
|
||||
|
||||
put("merry", "1_302");
|
||||
|
||||
}},
|
||||
GenericCardTestHelper.FellowshipSites,
|
||||
GenericCardTestHelper.FOTRFrodo,
|
||||
@@ -44,7 +49,7 @@ public class Card_V2_021_Tests
|
||||
|
||||
var scn = GetScenario();
|
||||
|
||||
var card = scn.GetFreepsCard("card");
|
||||
var card = scn.GetFreepsCard("shot");
|
||||
|
||||
assertEquals("Breaching Shot", card.getBlueprint().getTitle());
|
||||
assertNull(card.getBlueprint().getSubtitle());
|
||||
@@ -56,18 +61,191 @@ public class Card_V2_021_Tests
|
||||
assertEquals(2, card.getBlueprint().getTwilightCost());
|
||||
}
|
||||
|
||||
// Uncomment any @Test markers below once this is ready to be used
|
||||
//@Test
|
||||
public void BreachingShotTest1() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
@Test
|
||||
public void BreachingShotCanRemove3TokensToAssignAnUrukCosting3() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var card = scn.GetFreepsCard("card");
|
||||
scn.FreepsMoveCardToHand(card);
|
||||
var shot = scn.GetShadowCard("shot");
|
||||
var ladder = scn.GetShadowCard("ladder");
|
||||
var fighter = scn.GetShadowCard("fighter");
|
||||
var guard = scn.GetShadowCard("guard");
|
||||
scn.ShadowMoveCardToHand(shot);
|
||||
scn.ShadowMoveCardToSupportArea(ladder);
|
||||
scn.ShadowMoveCharToTable(fighter, guard);
|
||||
|
||||
var frodo = scn.GetRingBearer();
|
||||
var merry = scn.GetFreepsCard("merry");
|
||||
scn.FreepsMoveCharToTable(merry);
|
||||
|
||||
scn.StartGame();
|
||||
scn.FreepsPlayCard(card);
|
||||
scn.AddTokensToCard(ladder, 3);
|
||||
scn.SkipToPhase(Phase.ASSIGNMENT);
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
assertEquals(2, scn.GetTwilight());
|
||||
assertTrue(scn.ShadowPlayAvailable(shot));
|
||||
assertEquals(3, scn.GetCultureTokensOn(ladder));
|
||||
assertEquals(3, fighter.getBlueprint().getTwilightCost());
|
||||
assertEquals(4, guard.getBlueprint().getTwilightCost());
|
||||
assertFalse(scn.IsCharAssigned(merry));
|
||||
assertFalse(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
scn.ShadowPlayCard(shot);
|
||||
scn.ShadowChoose("3");
|
||||
|
||||
assertEquals(0, scn.GetCultureTokensOn(ladder));
|
||||
assertTrue(scn.IsCharAssigned(merry));
|
||||
assertTrue(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void BreachingShotCanRemove3TokensFromVariousMachinesToAssignAnUrukCosting3() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var shot = scn.GetShadowCard("shot");
|
||||
var ladder = scn.GetShadowCard("ladder");
|
||||
var ram = scn.GetShadowCard("ram");
|
||||
var fighter = scn.GetShadowCard("fighter");
|
||||
var guard = scn.GetShadowCard("guard");
|
||||
scn.ShadowMoveCardToHand(shot);
|
||||
scn.ShadowMoveCardToSupportArea(ladder, ram);
|
||||
scn.ShadowMoveCharToTable(fighter, guard);
|
||||
|
||||
var merry = scn.GetFreepsCard("merry");
|
||||
scn.FreepsMoveCharToTable(merry);
|
||||
|
||||
scn.StartGame();
|
||||
scn.AddTokensToCard(ladder, 2);
|
||||
scn.AddTokensToCard(ram, 2);
|
||||
scn.SkipToPhase(Phase.ASSIGNMENT);
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
assertTrue(scn.ShadowPlayAvailable(shot));
|
||||
assertEquals(2, scn.GetCultureTokensOn(ladder));
|
||||
assertEquals(2, scn.GetCultureTokensOn(ram));
|
||||
assertEquals(3, fighter.getBlueprint().getTwilightCost());
|
||||
assertEquals(4, guard.getBlueprint().getTwilightCost());
|
||||
assertFalse(scn.IsCharAssigned(merry));
|
||||
assertFalse(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
scn.ShadowPlayCard(shot);
|
||||
scn.ShadowChoose("3");
|
||||
|
||||
assertEquals(2, scn.ShadowGetCardChoiceCount()); //2 machines with tokens
|
||||
scn.ShadowChooseCard(ram);
|
||||
scn.ShadowChooseCard(ladder);
|
||||
scn.ShadowChooseCard(ram);
|
||||
|
||||
assertEquals(0, scn.GetCultureTokensOn(ram));
|
||||
assertEquals(1, scn.GetCultureTokensOn(ladder));
|
||||
assertTrue(scn.IsCharAssigned(merry));
|
||||
assertTrue(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void BreachingShotCanRemove4TokensToAssignAnUrukCosting3Or4() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var shot = scn.GetShadowCard("shot");
|
||||
var ladder = scn.GetShadowCard("ladder");
|
||||
var fighter = scn.GetShadowCard("fighter");
|
||||
var guard = scn.GetShadowCard("guard");
|
||||
scn.ShadowMoveCardToHand(shot);
|
||||
scn.ShadowMoveCardToSupportArea(ladder);
|
||||
scn.ShadowMoveCharToTable(fighter, guard);
|
||||
|
||||
var frodo = scn.GetRingBearer();
|
||||
var merry = scn.GetFreepsCard("merry");
|
||||
scn.FreepsMoveCharToTable(merry);
|
||||
|
||||
scn.StartGame();
|
||||
scn.AddTokensToCard(ladder, 4);
|
||||
scn.SkipToPhase(Phase.ASSIGNMENT);
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
assertTrue(scn.ShadowPlayAvailable(shot));
|
||||
assertEquals(4, scn.GetCultureTokensOn(ladder));
|
||||
assertEquals(3, fighter.getBlueprint().getTwilightCost());
|
||||
assertEquals(4, guard.getBlueprint().getTwilightCost());
|
||||
assertFalse(scn.IsCharAssigned(merry));
|
||||
assertFalse(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
scn.ShadowPlayCard(shot);
|
||||
scn.ShadowChoose("4");
|
||||
|
||||
assertEquals(2, scn.ShadowGetCardChoiceCount()); //Both uruks, one costing 3, the other 4
|
||||
scn.ShadowChooseCard(guard);
|
||||
|
||||
assertEquals(0, scn.GetCultureTokensOn(ladder));
|
||||
assertTrue(scn.IsCharAssigned(merry));
|
||||
assertFalse(scn.IsCharAssigned(fighter));
|
||||
assertTrue(scn.IsCharAssigned(guard));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void BreachingShotNotPlayableIfFewerTokensThanTwilightCostOfUruk() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var shot = scn.GetShadowCard("shot");
|
||||
var ladder = scn.GetShadowCard("ladder");
|
||||
var fighter = scn.GetShadowCard("fighter");
|
||||
var guard = scn.GetShadowCard("guard");
|
||||
scn.ShadowMoveCardToHand(shot);
|
||||
scn.ShadowMoveCardToSupportArea(ladder);
|
||||
scn.ShadowMoveCharToTable(fighter, guard);
|
||||
|
||||
var frodo = scn.GetRingBearer();
|
||||
var merry = scn.GetFreepsCard("merry");
|
||||
scn.FreepsMoveCharToTable(merry);
|
||||
|
||||
scn.StartGame();
|
||||
scn.AddTokensToCard(ladder, 2);
|
||||
scn.SkipToPhase(Phase.ASSIGNMENT);
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
assertFalse(scn.ShadowPlayAvailable(shot));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void BreachingShotDoesNotCrashIfTooFewTokensAreRemovedToAssign() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
var scn = GetScenario();
|
||||
|
||||
var shot = scn.GetShadowCard("shot");
|
||||
var ladder = scn.GetShadowCard("ladder");
|
||||
var fighter = scn.GetShadowCard("fighter");
|
||||
var guard = scn.GetShadowCard("guard");
|
||||
scn.ShadowMoveCardToHand(shot);
|
||||
scn.ShadowMoveCardToSupportArea(ladder);
|
||||
scn.ShadowMoveCharToTable(fighter, guard);
|
||||
|
||||
var frodo = scn.GetRingBearer();
|
||||
var merry = scn.GetFreepsCard("merry");
|
||||
scn.FreepsMoveCharToTable(merry);
|
||||
|
||||
scn.StartGame();
|
||||
scn.AddTokensToCard(ladder, 3);
|
||||
scn.SkipToPhase(Phase.ASSIGNMENT);
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
|
||||
assertTrue(scn.ShadowPlayAvailable(shot));
|
||||
assertEquals(3, scn.GetCultureTokensOn(ladder));
|
||||
|
||||
assertFalse(scn.IsCharAssigned(merry));
|
||||
assertFalse(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
scn.ShadowPlayCard(shot);
|
||||
scn.ShadowChoose("2");
|
||||
|
||||
assertEquals(1, scn.GetCultureTokensOn(ladder));
|
||||
assertFalse(scn.IsCharAssigned(merry));
|
||||
assertFalse(scn.IsCharAssigned(fighter));
|
||||
assertFalse(scn.IsCharAssigned(guard));
|
||||
assertTrue(scn.FreepsAnyDecisionsAvailable());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user