diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index a8a628691c5..f5a921681ab 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -1136,27 +1136,13 @@ public static Card considerCardToGet(final Player ai, final SpellAbility sa) { public static class MairsilThePretender { // Scan the fetch list for a card with at least one activated ability. // TODO: can be improved to a full consider(sa, ai) logic which would scan the graveyard first and hand last - public static Card considerCardFromList(final CardCollection fetchList) { - for (Card c : CardLists.filter(fetchList, CardPredicates.ARTIFACTS.or(CardPredicates.CREATURES))) { - for (SpellAbility ab : c.getSpellAbilities()) { - if (ab.isActivatedAbility()) { - Player controller = c.getController(); - boolean wasCaged = false; - for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile), - CardPredicates.hasCounter(CounterEnumType.CAGE))) { - if (c.getName().equals(caged.getName())) { - wasCaged = true; - break; - } - } - - if (!wasCaged) { - return c; - } - } - } - } - return null; + public static Card considerCardFromList(final CardCollection fetchList, SpellAbility sa) { + CardCollectionView caged = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Exile), + CardPredicates.hasCounter(CounterType.getType("CAGE"))); + return fetchList.stream().filter(CardPredicates.ARTIFACTS.or(CardPredicates.CREATURES)) + .filter(c -> c.getSpellAbilities().stream().anyMatch(SpellAbility::isActivatedAbility)) + .filter(c -> caged.stream().noneMatch(CardPredicates.sharesNameWith(c))) + .findFirst().orElse(null); } } @@ -1765,7 +1751,7 @@ public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); int lifeInDanger = aic.getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD); - int numCtrs = sa.getHostCard().getCounters(CounterEnumType.BURDEN); + int numCtrs = sa.getHostCard().getCounters(CounterType.getType("BURDEN")); if (ai.getLife() > numCtrs + 1 && ai.getLife() > lifeInDanger && ai.getMaxHandSize() >= ai.getCardsIn(ZoneType.Hand).size() + numCtrs + 1) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 3a5f11b0423..e4938a877fe 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -1500,7 +1500,7 @@ public static Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List } else if ("BestCard".equals(logic)) { return ComputerUtilCard.getBestAI(fetchList); // generally also means the most expensive one or close to it } else if ("Mairsil".equals(logic)) { - return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList); + return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList, sa); } else if ("SurvivalOfTheFittest".equals(logic)) { return SpecialCardAi.SurvivalOfTheFittest.considerCardToGet(decider, sa); } else if ("MazesEnd".equals(logic)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java b/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java index 393eaec9943..991a4f18310 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java @@ -7,7 +7,7 @@ import forge.ai.SpellAbilityAi; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CounterEnumType; +import forge.game.card.CounterType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -41,21 +41,23 @@ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, } else { // AI decision making - should AI compare damage and debuffs? } + CounterType ice = CounterType.getType("ICE"); + CounterType ki = CounterType.getType("KI"); // TODO: Can this be made more generic somehow? if (firstOption.getName().equals("Dark Depths")) { Card best = firstOption; for (Card c : options) { - if (c.getCounters(CounterEnumType.ICE) < best.getCounters(CounterEnumType.ICE)) { + if (c.getCounters(ice) < best.getCounters(ice)) { best = c; } } return best; - } else if (firstOption.getCounters(CounterEnumType.KI) > 0) { + } else if (firstOption.getCounters(ki) > 0) { // Extra Rule for KI counter Card best = firstOption; for (Card c : options) { - if (c.getCounters(CounterEnumType.KI) > best.getCounters(CounterEnumType.KI)) { + if (c.getCounters(ki) > best.getCounters(ki)) { best = c; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java index bedfee9e657..8c118d050ae 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java @@ -236,8 +236,9 @@ private boolean isSafeToTransformIntoLegendary(Player aiPlayer, Card source) { final Card othercard = aiPlayer.getCardsIn(ZoneType.Battlefield, other.getName()).getFirst(); + CounterType ki = CounterType.getType("KI"); // for legendary KI counter creatures - if (othercard.getCounters(CounterEnumType.KI) >= source.getCounters(CounterEnumType.KI)) { + if (othercard.getCounters(ki) >= source.getCounters(ki)) { // if the other legendary is useless try to replace it return ComputerUtilCard.isUselessCreature(aiPlayer, othercard); } diff --git a/forge-game/src/main/java/forge/game/card/CounterEnumType.java b/forge-game/src/main/java/forge/game/card/CounterEnumType.java index ec28142e260..face4c96680 100644 --- a/forge-game/src/main/java/forge/game/card/CounterEnumType.java +++ b/forge-game/src/main/java/forge/game/card/CounterEnumType.java @@ -71,10 +71,6 @@ public enum CounterEnumType implements CounterType { BRICK("BRICK", 226, 192, 164), - BURDEN("BURDEN", 135, 62, 35), - - CAGE("CAGE", 155, 155, 155), - CARRION("CRRON", 255, 163, 222), CELL ("CELL", 90, 10, 95), @@ -251,8 +247,6 @@ public enum CounterEnumType implements CounterType { JUDGMENT("JUDGM", 249, 220, 52), - KI("KI", 190, 189, 255), - KICK("KICK", 255, 255, 240), KNOWLEDGE("KNOWL", 0, 115, 255), diff --git a/forge-game/src/main/java/forge/game/card/CounterListType.java b/forge-game/src/main/java/forge/game/card/CounterListType.java new file mode 100644 index 00000000000..cf28eee7b3c --- /dev/null +++ b/forge-game/src/main/java/forge/game/card/CounterListType.java @@ -0,0 +1,74 @@ +package forge.game.card; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Maps; + + +public record CounterListType(String name, String desc, int red, int green, int blue) implements CounterType { + private static Map sMap = Maps.newLinkedHashMap(); + + public static CounterListType get(String s) { + return sMap.get(s); + } + + public static Collection getValues() { + return sMap.values(); + } + + public static void add(String name, String desc, int red, int green, int blue) { + sMap.put(desc, new CounterListType(StringUtils.capitalize(name), desc, red, green, blue)); + } + + public static final void parseTypes(List content) { + for (String line : content) { + if (line.startsWith("#") || line.isEmpty()) { + continue; + } + // Name=Description,red,green,blue + String k[] = line.split("="); + String l[] = k[1].split(","); + add(k[0], l[0], Integer.valueOf(l[1]), Integer.valueOf(l[2]), Integer.valueOf(l[3])); + } + } + + @Override + public String getName() { + return name; + } + + @Override + public String getCounterOnCardDisplayName() { + return desc; + } + + @Override + public boolean is(CounterEnumType eType) { + return false; + } + + @Override + public boolean isKeywordCounter() { + return false; + } + + @Override + public int getRed() { + return this.red; + } + + @Override + public int getGreen() { + return this.green; + } + + @Override + public int getBlue() { + return this.blue; + } + +} diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-game/src/main/java/forge/game/card/CounterType.java index 0cbff378932..66849c4d484 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-game/src/main/java/forge/game/card/CounterType.java @@ -16,12 +16,17 @@ static CounterType getType(String name) { try { return CounterEnumType.getType(name); } catch (final IllegalArgumentException ex) { + CounterType result = CounterListType.get(name); + if (result != null) { + return result; + } return CounterKeywordType.get(name); } } static List getValues() { List result = Lists.newArrayList(); result.addAll(List.of(CounterEnumType.values())); + result.addAll(CounterListType.getValues()); result.addAll(CounterKeywordType.getValues()); return result; } diff --git a/forge-gui/res/lists/CounterLists.txt b/forge-gui/res/lists/CounterLists.txt new file mode 100644 index 00000000000..4875dd96809 --- /dev/null +++ b/forge-gui/res/lists/CounterLists.txt @@ -0,0 +1,3 @@ +BURDEN=BURDEN,135,62,35 +CAGE=CAGE,155,155,155 +KI=KI,190,189,255 \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index b550b70bb02..e203280a6a0 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -45,6 +45,7 @@ public final class ForgeConstants { public static final String SETLOOKUP_DIR = RES_DIR + "setlookup" + PATH_SEPARATOR; public static final String KEYWORD_LIST_FILE = LISTS_DIR + "NonStackingKWList.txt"; public static final String TYPE_LIST_FILE = LISTS_DIR + "TypeLists.txt"; + public static final String COUNTER_LIST_FILE = LISTS_DIR + "CounterLists.txt"; public static final String SPECIAL_CARD_ACHIEVEMENT_LIST_FILE = LISTS_DIR + "special-card-achievements.txt"; public static final String PLANESWALKER_ACHIEVEMENT_LIST_FILE = LISTS_DIR + "planeswalker-achievements.txt"; public static final String ALTWIN_ACHIEVEMENT_LIST_FILE = LISTS_DIR + "altwin-achievements.txt"; diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index 36f477b6ed3..cc8042d71b4 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -31,6 +31,7 @@ import forge.game.GameFormat; import forge.game.GameType; import forge.game.card.CardUtil; +import forge.game.card.CounterListType; import forge.game.spellability.Spell; import forge.gamemodes.gauntlet.GauntletData; import forge.gamemodes.limited.GauntletMini; @@ -342,6 +343,7 @@ public static ItemPool getContraptionPool() { } private static boolean keywordsLoaded = false; + private static boolean countersLoaded = false; /** * Load dynamic gamedata. @@ -357,6 +359,10 @@ public static void loadDynamicGamedata() { CardType.Constant.LOADED.set(); } + if (!countersLoaded) { + CounterListType.parseTypes(FileUtil.readFile(ForgeConstants.COUNTER_LIST_FILE)); + countersLoaded = true; + } if (!keywordsLoaded) { final List nskwListFile = FileUtil.readFile(ForgeConstants.KEYWORD_LIST_FILE);