Fixing Alatar/Pallando decieved not being pumped by Throne of Isengard

This commit is contained in:
Christian 'ketura' McCarty
2024-12-07 23:45:44 -06:00
parent 117115b471
commit c7e499b884
17 changed files with 141 additions and 46 deletions

View File

@@ -32,6 +32,7 @@
strength: 11
vitality: 1
keywords: fierce
race: Wizard
until: regroup
}
{
@@ -97,6 +98,7 @@
strength: 10
vitality: 1
keywords: fierce
race: Wizard
until: regroup
}
{

View File

@@ -6,7 +6,6 @@ import com.gempukku.lotro.cards.build.InvalidCardDefinitionException;
import com.gempukku.lotro.cards.build.field.FieldUtils;
import com.gempukku.lotro.cards.build.field.effect.EffectAppender;
import com.gempukku.lotro.cards.build.field.effect.EffectAppenderProducer;
import com.gempukku.lotro.common.Race;
import com.gempukku.lotro.game.PhysicalCard;
import com.gempukku.lotro.game.ReadableStringWhileInZoneData;
import com.gempukku.lotro.game.state.LotroGame;
@@ -30,7 +29,10 @@ public class StoreRaceFromCard implements EffectAppenderProducer {
protected void doPlayEffect(LotroGame game) {
PhysicalCard card = actionContext.getCardFromMemory(memory);
if (card != null) {
Race race = card.getBlueprint().getRace();
var races = game.getModifiersQuerying().getRaces(game, card);
if(races.isEmpty())
return;
var race = races.getFirst();
actionContext.getSource().setWhileInZoneData(new ReadableStringWhileInZoneData(race.name(), race.getHumanReadable()));
}
}

View File

@@ -12,6 +12,7 @@ import com.gempukku.lotro.cards.build.field.effect.appender.resolver.TimeResolve
import com.gempukku.lotro.cards.build.field.effect.appender.resolver.ValueResolver;
import com.gempukku.lotro.common.CardType;
import com.gempukku.lotro.common.Keyword;
import com.gempukku.lotro.common.Race;
import com.gempukku.lotro.common.Zone;
import com.gempukku.lotro.filters.Filters;
import com.gempukku.lotro.game.AbstractActionProxy;
@@ -34,7 +35,7 @@ import java.util.List;
public class TurnIntoMinion implements EffectAppenderProducer {
@Override
public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
FieldUtils.validateAllowedFields(effectObject, "select", "count", "strength", "vitality", "keywords", "until", "memorize");
FieldUtils.validateAllowedFields(effectObject, "select", "count", "strength", "vitality", "keywords", "race", "until", "memorize");
final String select = FieldUtils.getString(effectObject.get("select"), "select", "choose(any)");
final ValueSource valueSource = ValueResolver.resolveEvaluator(effectObject.get("count"), 1, environment);
@@ -42,6 +43,7 @@ public class TurnIntoMinion implements EffectAppenderProducer {
final ValueSource vitality = ValueResolver.resolveEvaluator(effectObject.get("vitality"), environment);
final String[] keywordStrings = FieldUtils.getStringArray(effectObject.get("keywords"), "keywords");
final TimeResolver.Time until = TimeResolver.resolveTime(effectObject.get("until"), "end(current)");
final Race race = FieldUtils.getEnum(Race.class, effectObject.get("race"), "race");
String memory = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp");
@@ -75,6 +77,9 @@ public class TurnIntoMinion implements EffectAppenderProducer {
addModifier(action, new StrengthModifier(card, card, null, strengthValue), until);
addModifier(action, new VitalityModifier(card, card, null, vitalityValue), until);
addModifier(action, new IsAdditionalCardTypeModifier(card, card, null, CardType.MINION), until);
if(race != null) {
addModifier(action, new IsAdditionalRaceModifier(card, card, null, race), until);
}
for (Keyword keyword : keywordsList) {
addModifier(action, new AddKeywordModifier(card, card, null, keyword), until);

View File

@@ -14,11 +14,8 @@ import com.gempukku.lotro.logic.modifiers.evaluator.Evaluator;
import com.gempukku.lotro.logic.modifiers.evaluator.SingleMemoryEvaluator;
import com.gempukku.lotro.logic.timing.Effect;
import com.gempukku.lotro.logic.timing.results.CharacterLostSkirmishResult;
import com.google.common.base.Predicates;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FilterFactory {
private final Map<String, FilterableSource> simpleFilters = new HashMap<>();
@@ -111,7 +108,7 @@ public class FilterFactory {
return Filters.none;
}));
simpleFilters.put("hasrace",
(actionContext -> (Filter) (game, physicalCard) -> physicalCard.getBlueprint().getRace() != null));
(actionContext -> (Filter) (game, physicalCard) -> !game.getModifiersQuerying().getRaces(game, physicalCard).isEmpty()));
simpleFilters.put("idinstored",
(actionContext ->
(Filter) (game, physicalCard) -> {
@@ -337,12 +334,12 @@ public class FilterFactory {
(parameter, environment) -> {
final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(parameter, environment);
return actionContext -> {
var game = actionContext.getGame();
final Filterable sourceFilterable = filterableSource.getFilterable(actionContext);
Map<Race, Integer> counts = new HashMap<>();
for (PhysicalCard card : Filters.filterActive(actionContext.getGame(), sourceFilterable)) {
final Race race = card.getBlueprint().getRace();
if (race != null) {
for(var race : game.getModifiersQuerying().getRaces(game, card)) {
Integer count = counts.get(race);
if (count == null)
count = 0;
@@ -572,23 +569,31 @@ public class FilterFactory {
if (parameter.startsWith("memory(") && parameter.endsWith(")")) {
return actionContext -> (Filter) (game, physicalCard) -> {
String memory = parameter.substring(parameter.indexOf("(") + 1, parameter.lastIndexOf(")"));
Set<Race> races = actionContext.getCardsFromMemory(memory).stream().map(
(Function<PhysicalCard, Race>) cardFromMemory -> cardFromMemory.getBlueprint().getRace()).filter(Predicates.notNull()).collect(Collectors.toSet());
return races.contains(physicalCard.getBlueprint().getRace());
var races = new HashSet<Race>();
for(var memoryCard : actionContext.getCardsFromMemory(memory)) {
races.addAll(game.getModifiersQuerying().getRaces(game, memoryCard));
}
//Disjoint returns true if there are no elements in common between two collections
return !Collections.disjoint(races, game.getModifiersQuerying().getRaces(game, physicalCard));
};
} else if (parameter.equals("stored")) {
return actionContext -> (Filter) (game, physicalCard) -> {
final PhysicalCard.WhileInZoneData value = actionContext.getSource().getWhileInZoneData();
if (value != null)
return Race.valueOf(value.getValue()) == physicalCard.getBlueprint().getRace();
return game.getModifiersQuerying().getRaces(game, physicalCard).contains(Race.valueOf(value.getValue()));
return false;
};
} else if (parameter.equals("cannotspot")) {
return actionContext -> (Filter) (game, physicalCard) -> {
final Race race = physicalCard.getBlueprint().getRace();
if (race != null)
return !Filters.canSpot(game, race);
return false;
var races = game.getModifiersQuerying().getRaces(game, physicalCard);
if(races.isEmpty())
return false;
for(var race : races) {
if(Filters.canSpot(game, race))
return false;
}
return true;
};
}
throw new InvalidCardDefinitionException("Unknown race definition in filter: " + parameter

View File

@@ -417,10 +417,7 @@ public class Filters {
private static Filter race(final Race race) {
return Filters.and(
Filters.or(CardType.COMPANION, CardType.ALLY, CardType.MINION, CardType.FOLLOWER),
(Filter) (game, physicalCard) -> {
LotroCardBlueprint blueprint = physicalCard.getBlueprint();
return blueprint.getRace() == race;
});
(Filter) (game, physicalCard) -> game.getModifiersQuerying().isRace(game, physicalCard, race));
}
@@ -572,8 +569,7 @@ public class Filters {
}
private static Filter type(final CardType cardType) {
return (game, physicalCard) -> (physicalCard.getBlueprint().getCardType() == cardType)
|| game.getModifiersQuerying().isAdditionalCardType(game, physicalCard, cardType);
return (game, physicalCard) -> game.getModifiersQuerying().isCardType(game, physicalCard, cardType);
}
public static Filter attachedTo(final Filterable... filters) {

View File

@@ -308,9 +308,7 @@ public class GameUtils {
public static int getSpottableRacesCount(LotroGame game, Filterable... filters) {
Set<Race> races = new HashSet<>();
for (PhysicalCard physicalCard : Filters.filterActive(game, filters)) {
final Race race = physicalCard.getBlueprint().getRace();
if (race != null)
races.add(race);
races.addAll(game.getModifiersQuerying().getRaces(game, physicalCard));
}
return races.size();
}

View File

@@ -124,6 +124,11 @@ public abstract class AbstractModifier implements Modifier {
return false;
}
@Override
public boolean isAdditionalRaceModifier(LotroGame game, PhysicalCard physicalCard, Race race) {
return false;
}
@Override
public int getTwilightCostModifier(LotroGame game, PhysicalCard physicalCard, PhysicalCard target, boolean ignoreRoamingPenalty) {
return 0;

View File

@@ -319,6 +319,11 @@ public class DelegateModifier implements Modifier {
return delegate.isAdditionalCardTypeModifier(game, physicalCard, cardType);
}
@Override
public boolean isAdditionalRaceModifier(LotroGame game, PhysicalCard physicalCard, Race race) {
return delegate.isAdditionalRaceModifier(game, physicalCard, race);
}
@Override
public boolean isAllyParticipateInArcheryFire(LotroGame game, PhysicalCard card) {
return delegate.isAllyParticipateInArcheryFire(game, card);

View File

@@ -17,4 +17,8 @@ public class IsAdditionalCardTypeModifier extends AbstractModifier {
public boolean isAdditionalCardTypeModifier(LotroGame game, PhysicalCard physicalCard, CardType cardType) {
return cardType == _cardType;
}
public CardType getType() {
return _cardType;
}
}

View File

@@ -0,0 +1,24 @@
package com.gempukku.lotro.logic.modifiers;
import com.gempukku.lotro.common.Filterable;
import com.gempukku.lotro.common.Race;
import com.gempukku.lotro.game.PhysicalCard;
import com.gempukku.lotro.game.state.LotroGame;
public class IsAdditionalRaceModifier extends AbstractModifier {
private final Race _race;
public IsAdditionalRaceModifier(PhysicalCard source, Filterable affectFilter, Condition condition, Race race) {
super(source, "Has additional race - " + race.toString(), affectFilter, condition, ModifierEffect.ADDITIONAL_RACE);
_race = race;
}
@Override
public boolean isAdditionalRaceModifier(LotroGame game, PhysicalCard physicalCard, Race race) {
return race == _race;
}
public Race getRace() {
return _race;
}
}

View File

@@ -50,6 +50,7 @@ public interface Modifier {
int getMinionSiteNumberModifier(LotroGame game, PhysicalCard physicalCard);
boolean isAdditionalCardTypeModifier(LotroGame game, PhysicalCard physicalCard, CardType cardType);
boolean isAdditionalRaceModifier(LotroGame game, PhysicalCard physicalCard, Race race);
int getTwilightCostModifier(LotroGame game, PhysicalCard physicalCard, PhysicalCard target, boolean ignoreRoamingPenalty);

View File

@@ -17,7 +17,7 @@ public enum ModifierEffect {
SPECIAL_FLAG_MODIFIER, REPLACE_SITE_MODIFIER, PLAY_SITE_MODIFIER,
ADDITIONAL_CARD_TYPE,
ADDITIONAL_CARD_TYPE, ADDITIONAL_RACE,
REMOVE_KEYWORD_MODIFIER, GIVE_KEYWORD_MODIFIER, CANCEL_KEYWORD_BONUS_TARGET_MODIFIER, LOSE_ALL_KEYWORDS_MODIFIER,

View File

@@ -531,11 +531,44 @@ public class ModifiersLogic implements ModifiersEnvironment, ModifiersQuerying {
}
@Override
public boolean isAdditionalCardType(LotroGame game, PhysicalCard card, CardType cardType) {
for (Modifier modifier : getModifiersAffectingCard(game, ModifierEffect.ADDITIONAL_CARD_TYPE, card))
if (modifier.isAdditionalCardTypeModifier(game, card, cardType))
return true;
return false;
public List<CardType> getCardTypes(LotroGame game, PhysicalCard card) {
var types = new ArrayList<CardType>();
types.add(card.getBlueprint().getCardType());
for (Modifier modifier : getModifiersAffectingCard(game, ModifierEffect.ADDITIONAL_CARD_TYPE, card)) {
var cardTypeMod = (IsAdditionalCardTypeModifier)modifier;
types.add(cardTypeMod.getType());
}
return types;
}
@Override
public boolean isCardType(LotroGame game, PhysicalCard card, CardType cardType) {
var types = getCardTypes(game, card);
return types.contains(cardType);
}
@Override
public List<Race> getRaces(LotroGame game, PhysicalCard card) {
var races = new ArrayList<Race>();
if(card.getBlueprint().getRace() != null) {
races.add(card.getBlueprint().getRace());
}
for (Modifier modifier : getModifiersAffectingCard(game, ModifierEffect.ADDITIONAL_RACE, card)) {
var cardTypeMod = (IsAdditionalRaceModifier)modifier;
races.add(cardTypeMod.getRace());
}
return races;
}
@Override
public boolean isRace(LotroGame game, PhysicalCard card, Race race) {
var races = getRaces(game, card);
return races.contains(race);
}
@Override

View File

@@ -68,7 +68,10 @@ public interface ModifiersQuerying {
int getOverwhelmMultiplier(LotroGame game, PhysicalCard card);
boolean isAdditionalCardType(LotroGame game, PhysicalCard card, CardType cardType);
List<CardType> getCardTypes(LotroGame game, PhysicalCard card);
boolean isCardType(LotroGame game, PhysicalCard card, CardType cardType);
List<Race> getRaces(LotroGame game, PhysicalCard card);
boolean isRace(LotroGame game, PhysicalCard card, Race race);
// Wounds/exertions
boolean canTakeWounds(LotroGame game, Collection<PhysicalCard> woundSources, PhysicalCard card, int woundsToTake);

View File

@@ -170,7 +170,7 @@ public class GameStats {
newCharStrengths.put(character.getCardId(), game.getModifiersQuerying().getStrength(game, character));
newCharVitalities.put(character.getCardId(), game.getModifiersQuerying().getVitality(game, character));
final LotroCardBlueprint blueprint = character.getBlueprint();
if (blueprint.getCardType() == CardType.MINION || game.getModifiersQuerying().isAdditionalCardType(game, character, CardType.MINION))
if (blueprint.getCardType() == CardType.MINION || game.getModifiersQuerying().isCardType(game, character, CardType.MINION))
newSiteNumbers.put(character.getCardId(), game.getModifiersQuerying().getMinionSiteNumber(game, character));
else {
final int resistance = game.getModifiersQuerying().getResistance(game, character);

View File

@@ -1021,8 +1021,12 @@ public class GenericCardTestHelper extends AbstractAtTest {
public boolean IsType(PhysicalCardImpl card, CardType type)
{
return card.getBlueprint().getCardType() == type
|| _game.getModifiersQuerying().isAdditionalCardType(_game, card, type);
return _game.getModifiersQuerying().isCardType(_game, card, type);
}
public boolean IsRace(PhysicalCardImpl card, Race race)
{
return _game.getModifiersQuerying().isRace(_game, card, race);
}
public Boolean CanBeAssigned(PhysicalCardImpl card)

View File

@@ -17,8 +17,9 @@ public class Card_13_079_Tests
return new GenericCardTestHelper(
new HashMap<>()
{{
put("card", "13_79");
// put other cards in here as needed for the test case
put("pallando", "13_79");
put("throne", "17_39");
put("saruman", "4_173");
}},
GenericCardTestHelper.FellowshipSites,
GenericCardTestHelper.FOTRFrodo,
@@ -43,7 +44,7 @@ public class Card_13_079_Tests
var scn = GetScenario();
var card = scn.GetFreepsCard("card");
var card = scn.GetFreepsCard("pallando");
assertEquals("Pallando Deceived", card.getBlueprint().getTitle());
assertNull(card.getBlueprint().getSubtitle());
@@ -55,18 +56,25 @@ public class Card_13_079_Tests
assertEquals(3, card.getBlueprint().getTwilightCost());
}
// Uncomment any @Test markers below once this is ready to be used
//@Test
public void PallandoDeceivedTest1() throws DecisionResultInvalidException, CardNotFoundException {
@Test
public void PallandoDeceivedIsPumpedByThroneOfIsengardAfterConversion() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
var scn = GetScenario();
var card = scn.GetFreepsCard("card");
scn.FreepsMoveCardToHand(card);
var pallando = scn.GetShadowCard("pallando");
var throne = scn.GetShadowCard("throne");
var saruman = scn.GetShadowCard("saruman");
scn.ShadowMoveCardToSupportArea(pallando, throne);
scn.ShadowMoveCharToTable(saruman);
scn.StartGame();
scn.FreepsPlayCard(card);
assertEquals(3, scn.GetTwilight());
scn.SkipToPhase(Phase.MANEUVER);
assertTrue(scn.ShadowHasOptionalTriggerAvailable());
scn.ShadowAcceptOptionalTrigger();
//10 base +3 from throne making Wizards stronger
assertEquals(13, scn.getStrength(pallando));
}
}