From e2c3908df637f5da45c23cfcd58d95b855e84cca Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 21 Feb 2023 21:21:39 +0800 Subject: [PATCH 1/5] LandEvaluator for AI --- .../src/main/java/forge/ai/AiController.java | 100 ++--- .../main/java/forge/ai/ComputerUtilCard.java | 345 +++++++++--------- 2 files changed, 236 insertions(+), 209 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 22f3fbeec25..a2f32fe7af9 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -75,7 +75,7 @@ import java.util.*; *

* AiController class. *

- * + * * @author Forge * @version $Id$ */ @@ -108,6 +108,7 @@ public class AiController { public boolean usesSimulation() { return this.useSimulation; } + public void setUseSimulation(boolean value) { this.useSimulation = value; } @@ -153,7 +154,7 @@ public class AiController { private List getPossibleETBCounters() { CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand)); CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library); - + all.addAll(player.getCardsIn(ZoneType.Exile)); all.addAll(player.getCardsIn(ZoneType.Graveyard)); if (!ccvPlayerLibrary.isEmpty()) { @@ -175,7 +176,7 @@ public class AiController { } return spellAbilities; } - + // look for cards on the battlefield that should prevent the AI from using that spellability private boolean checkCurseEffects(final SpellAbility sa) { CardCollectionView ccvGameBattlefield = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.hasSVar("AICurseEffect")); @@ -194,7 +195,7 @@ public class AiController { } else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host) && host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) { return true; - } else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) { + } else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) { String hostName = host.getName(); for (Card card : ccvGameBattlefield) { if (!card.isToken() && card.sharesNameWith(host)) { @@ -217,6 +218,7 @@ public class AiController { card.setCastSA(null); return result; } + private boolean checkETBEffectsPreparedCard(final Card card, final SpellAbility sa, final ApiType api) { final Player activator = sa.getActivatingPlayer(); @@ -301,7 +303,7 @@ public class AiController { String s = validCard.split("kicked ")[1]; if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue; if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue; - } else if (!sa.isKicked()) { + } else if (!sa.isKicked()) { continue; } } @@ -476,6 +478,8 @@ public class AiController { // If nothing is done here, proceeds to the default land picking strategy } + return ComputerUtilCard.getBestLandToPlayAI(landList); +/* //Skip reflected lands. CardCollection unreflectedLands = new CardCollection(landList); for (Card l : landList) { @@ -574,7 +578,7 @@ public class AiController { landList = CardLists.filter(landList, Predicates.not(CardPredicates.Presets.BASIC_LANDS)); } } - return landList.get(0); + return landList.get(0);*/ } // if return true, go to next phase @@ -600,7 +604,7 @@ public class AiController { } else { // Compare bestSA with this SA final int restrictionLevel = ComputerUtil.counterSpellRestriction(player, currentSA); - + if (restrictionLevel > bestRestriction) { bestRestriction = restrictionLevel; bestSA = currentSA; @@ -617,20 +621,20 @@ public class AiController { public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) { return predictSpellToCastInMain2(exceptSA, true); } + private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) { if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) { return null; } final CardCollectionView cards = handOnly ? player.getCardsIn(ZoneType.Hand) : - ComputerUtilAbility.getAvailableCards(game, player); + ComputerUtilAbility.getAvailableCards(game, player); List all = ComputerUtilAbility.getSpellAbilities(cards, player); try { Collections.sort(all, saComparator); // put best spells first - } - catch (IllegalArgumentException ex) { + } catch (IllegalArgumentException ex) { System.err.println(ex.getMessage()); String assertex = ComparatorUtil.verifyTransitivity(saComparator, all); Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex); @@ -638,7 +642,7 @@ public class AiController { for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) { ApiType saApi = sa.getApi(); - + if (saApi == ApiType.Counter || saApi == exceptSA) { continue; } @@ -657,12 +661,15 @@ public class AiController { public boolean reserveManaSourcesForNextSpell(SpellAbility sa, SpellAbility exceptForSa) { return reserveManaSources(sa, null, false, true, exceptForSa); } + public boolean reserveManaSources(SpellAbility sa) { return reserveManaSources(sa, PhaseType.MAIN2, false, false, null); } + public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) { return reserveManaSources(sa, phaseType, enemy, true, null); } + public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy, boolean forNextSpell, SpellAbility exceptForThisSa) { ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player); @@ -784,8 +791,9 @@ public class AiController { return AiPlayDecision.CantAfford; } } - SpellAbilityAi topAI = new SpellAbilityAi() {}; - if (!topAI.willPayCosts(player, sa , wardCost, host)) { + SpellAbilityAi topAI = new SpellAbilityAi() { + }; + if (!topAI.willPayCosts(player, sa, wardCost, host)) { return AiPlayDecision.CostNotAcceptable; } } @@ -899,7 +907,7 @@ public class AiController { return AiPlayDecision.AnotherTime; } if (sa instanceof SpellPermanent) { - return canPlayFromEffectAI((SpellPermanent)sa, false, true); + return canPlayFromEffectAI((SpellPermanent) sa, false, true); } if (sa.usesTargeting()) { if (!sa.isTargetNumberValid() && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) { @@ -1037,7 +1045,7 @@ public class AiController { } else if (a1 > 0 && b1 == 0 && ApiType.Mana != b.getApi()) { return 1; } - + if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) { return -1; } else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) { @@ -1060,7 +1068,7 @@ public class AiController { return b1 - a1; } - + private int getSpellAbilityPriority(SpellAbility sa) { int p = 0; Card source = sa.getHostCard(); @@ -1142,6 +1150,7 @@ public class AiController { public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa) { return getCardsToDiscard(numDiscard, uTypes, sa, CardCollection.EMPTY); } + public CardCollection getCardsToDiscard(final int numDiscard, final String[] uTypes, final SpellAbility sa, final CardCollectionView exclude) { boolean noFiltering = sa != null && "DiscardCMCX".equals(sa.getParam("AILogic")); // list AI logic for which filtering is taken care of elsewhere CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand)); @@ -1193,7 +1202,9 @@ public class AiController { } } for (Card c : inHand) { - if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; } + if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { + continue; + } if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), player)) { discards.add(c); } @@ -1232,8 +1243,7 @@ public class AiController { discardList.add(prefCard); validCards.remove(prefCard); count++; - } - else { + } else { break; } } @@ -1248,16 +1258,15 @@ public class AiController { final int numLandsInPlay = CardLists.count(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA); final CardCollection landsInHand = CardLists.filter(validCards, CardPredicates.Presets.LANDS); final int numLandsInHand = landsInHand.size(); - + // Discard a land - boolean canDiscardLands = numLandsInHand > 3 || (numLandsInHand > 2 && numLandsInPlay > 0) - || (numLandsInHand > 1 && numLandsInPlay > 2) || (numLandsInHand > 0 && numLandsInPlay > 5); - + boolean canDiscardLands = numLandsInHand > 3 || (numLandsInHand > 2 && numLandsInPlay > 0) + || (numLandsInHand > 1 && numLandsInPlay > 2) || (numLandsInHand > 0 && numLandsInPlay > 5); + if (canDiscardLands) { discardList.add(landsInHand.get(0)); validCards.remove(landsInHand.get(0)); - } - else { // Discard other stuff + } else { // Discard other stuff CardLists.sortByCmcDesc(validCards); int numLandsAvailable = numLandsInPlay; if (numLandsInHand > 0) { @@ -1346,7 +1355,7 @@ public class AiController { return false; default: return false; - } + } return false; } @@ -1448,7 +1457,9 @@ public class AiController { } private List singleSpellAbilityList(SpellAbility sa) { - if (sa == null) { return null; } + if (sa == null) { + return null; + } final List abilities = Lists.newArrayList(); abilities.add(sa); @@ -1470,17 +1481,17 @@ public class AiController { } CardCollection playBeforeLand = CardLists.filter( - player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop") + player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop") ); if (!playBeforeLand.isEmpty()) { SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList( - ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false + ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false ); if (wantToPlayBeforeLand != null) { return singleSpellAbilityList(wantToPlayBeforeLand); } } - + CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); if (landsWannaPlay != null) { landsWannaPlay = filterLandsToPlay(landsWannaPlay); @@ -1672,7 +1683,7 @@ public class AiController { if (!game.getStack().isEmpty()) { SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards)); if (counter != null) return counter; - + SpellAbility counterETB = chooseSpellAbilityToPlayFromList(getPossibleETBCounters(), false); if (counterETB != null) return counterETB; @@ -1705,8 +1716,7 @@ public class AiController { try { Collections.sort(all, saComparator); // put best spells first - } - catch (IllegalArgumentException ex) { + } catch (IllegalArgumentException ex) { System.err.println(ex.getMessage()); String assertex = ComparatorUtil.verifyTransitivity(saComparator, all); Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex); @@ -1755,7 +1765,7 @@ public class AiController { public CardCollection chooseCardsToDelve(int genericCost, CardCollection grave) { CardCollection toExile = new CardCollection(); int numToExile = Math.min(grave.size(), genericCost); - + for (int i = 0; i < numToExile; i++) { Card chosen = null; for (final Card c : grave) { // Exile noncreatures first in @@ -1783,7 +1793,7 @@ public class AiController { public boolean doTrigger(SpellAbility spell, boolean mandatory) { if (spell instanceof WrappedAbility) - return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory); + return doTrigger(((WrappedAbility) spell).getWrappedAbility(), mandatory); if (spell.getApi() != null) return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory); if (spell.getPayCosts() == Cost.Zero && !spell.usesTargeting()) { @@ -1929,7 +1939,7 @@ public class AiController { } else if ("LowestLoseLife".equals(logic)) { return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1; } else if ("HighestLoseLife".equals(logic)) { - return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1); + return Math.min(player.getLife() - 1, MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1); } else if ("HighestGetCounter".equals(logic)) { return MyRandom.getRandom().nextInt(3); } else if (sa.hasSVar("EnergyToPay")) { @@ -1949,8 +1959,7 @@ public class AiController { } public int chooseNumber(SpellAbility sa, String title, List options, Player relatedPlayer) { - switch(sa.getApi()) - { + switch (sa.getApi()) { case SetLife: // Reverse the Sands if (relatedPlayer.equals(sa.getHostCard().getController())) { return Collections.max(options); @@ -2076,14 +2085,13 @@ public class AiController { // this is where the computer cheats // changes AllZone.getComputerPlayer().getZone(Zone.Library) - + /** *

* smoothComputerManaCurve. *

- * - * @param in - * an array of {@link forge.game.card.Card} objects. + * + * @param in an array of {@link forge.game.card.Card} objects. * @return an array of {@link forge.game.card.Card} objects. */ public CardCollectionView cheatShuffle(CardCollectionView in) { @@ -2111,7 +2119,7 @@ public class AiController { library.add(8, land.get(2)); library.add(9, land.get(3)); library.add(10, land.get(4)); - + library.add(12, land.get(5)); library.add(15, land.get(6)); } catch (final IndexOutOfBoundsException e) { @@ -2183,7 +2191,7 @@ public class AiController { } public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List origin, SpellAbility sa, - CardCollection fetchList, Player player2, Player decider) { + CardCollection fetchList, Player player2, Player decider) { if (useSimulation) { return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 127b0651f02..41b01700ac6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -9,6 +9,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import com.google.common.base.Function; +import forge.ai.simulation.GameStateEvaluator; import forge.card.mana.ManaCost; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.MutablePair; @@ -84,7 +86,7 @@ public class ComputerUtilCard { * Sorts a List by "best" using the EvaluateCreature function. * the best creatures will be first in the list. *

- * + * * @param list */ public static void sortByEvaluateCreature(final CardCollection list) { @@ -92,11 +94,12 @@ public class ComputerUtilCard { } // The AI doesn't really pick the best artifact, just the most expensive. + /** *

* getBestArtifactAI. *

- * + * * @param list * @return a {@link forge.game.card.Card} object. */ @@ -111,6 +114,7 @@ public class ComputerUtilCard { /** * Returns the best Planeswalker from a given list + * * @param list list of cards to evaluate * @return best Planeswalker */ @@ -125,6 +129,7 @@ public class ComputerUtilCard { /** * Returns the worst Planeswalker from a given list + * * @param list list of cards to evaluate * @return best Planeswalker */ @@ -188,16 +193,15 @@ public class ComputerUtilCard { } // The AI doesn't really pick the best enchantment, just the most expensive. + /** *

* getBestEnchantmentAI. *

- * + * * @param list - * @param spell - * a {@link forge.game.card.Card} object. - * @param targeted - * a boolean. + * @param spell a {@link forge.game.card.Card} object. + * @param targeted a boolean. * @return a {@link forge.game.card.Card} object. */ public static Card getBestEnchantmentAI(final List list, final SpellAbility spell, final boolean targeted) { @@ -219,7 +223,7 @@ public class ComputerUtilCard { *

* getBestLandAI. *

- * + * * @param list * @return a {@link forge.game.card.Card} object. */ @@ -231,7 +235,7 @@ public class ComputerUtilCard { // prefer to target non basic lands final List nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS)); - + if (!nbLand.isEmpty()) { // TODO - Rank non basics? return Aggregates.random(nbLand); @@ -267,7 +271,7 @@ public class ComputerUtilCard { *

* getWorstLand. *

- * + * * @param lands * @return a {@link forge.game.card.Card} object. */ @@ -324,12 +328,10 @@ public class ComputerUtilCard { *

* getCheapestPermanentAI. *

- * + * * @param all - * @param spell - * a {@link forge.game.card.Card} object. - * @param targeted - * a boolean. + * @param spell a {@link forge.game.card.Card} object. + * @param targeted a boolean. * @return a {@link forge.game.card.Card} object. */ public static Card getCheapestPermanentAI(Iterable all, final SpellAbility spell, final boolean targeted) { @@ -347,7 +349,7 @@ public class ComputerUtilCard { // get cheapest card: Card cheapest = null; - + for (Card c : all) { if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) { cheapest = c; @@ -358,11 +360,12 @@ public class ComputerUtilCard { } // returns null if list.size() == 0 + /** *

* getBestAI. *

- * + * * @param list * @return a {@link forge.game.card.Card} object. */ @@ -380,9 +383,8 @@ public class ComputerUtilCard { /** * getBestCreatureAI. - * - * @param list - * the list + * + * @param list the list * @return the card */ public static Card getBestCreatureAI(final Iterable list) { @@ -392,11 +394,24 @@ public class ComputerUtilCard { return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.creatureEvaluator); } + /** + * getBestLandToPlayAI. + * + * @param list the list + * @return the card + */ + public static Card getBestLandToPlayAI(final Iterable list) { + if (Iterables.size(list) == 1) { + return Iterables.get(list, 0); + } + return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.LANDS), ComputerUtilCard.landEvaluator); + } + /** *

* getWorstCreatureAI. *

- * + * * @param list * @return a {@link forge.game.card.Card} object. */ @@ -408,11 +423,12 @@ public class ComputerUtilCard { } // This selection rates tokens higher + /** *

* getBestCreatureToBounceAI. *

- * + * * @param list * @return a {@link forge.game.card.Card} object. */ @@ -438,7 +454,7 @@ public class ComputerUtilCard { // For ability of Oracle en-Vec, return the first card that are going to attack next turn public static Card getBestCreatureToAttackNextTurnAI(final Player aiPlayer, final Iterable list) { - AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi(); + AiController aic = ((PlayerControllerAi) aiPlayer.getController()).getAi(); for (final Card card : list) { if (aic.getPredictedCombatNextTurn().isAttacking(card)) { return card; @@ -451,7 +467,7 @@ public class ComputerUtilCard { *

* getWorstAI. *

- * + * * @param list * @return a {@link forge.game.card.Card} object. */ @@ -463,20 +479,16 @@ public class ComputerUtilCard { *

* getWorstPermanentAI. *

- * + * * @param list - * @param biasEnch - * a boolean. - * @param biasLand - * a boolean. - * @param biasArt - * a boolean. - * @param biasCreature - * a boolean. + * @param biasEnch a boolean. + * @param biasLand a boolean. + * @param biasArt a boolean. + * @param biasCreature a boolean. * @return a {@link forge.game.card.Card} object. */ public static Card getWorstPermanentAI(final Iterable list, final boolean biasEnch, final boolean biasLand, - final boolean biasArt, final boolean biasCreature) { + final boolean biasArt, final boolean biasCreature) { if (Iterables.isEmpty(list)) { return null; } @@ -486,7 +498,7 @@ public class ComputerUtilCard { return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false); } - final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS); + final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS); if (biasArt && hasArtifacts) { return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false); } @@ -557,14 +569,14 @@ public class ComputerUtilCard { }; private static final CreatureEvaluator creatureEvaluator = new CreatureEvaluator(); + private static final LandEvaluator landEvaluator = new LandEvaluator(); /** *

* evaluateCreature. *

- * - * @param c - * a {@link forge.game.card.Card} object. + * + * @param c a {@link forge.game.card.Card} object. * @return a int. */ public static int evaluateCreature(final Card c) { @@ -603,14 +615,15 @@ public class ComputerUtilCard { } public static boolean doesCreatureAttackAI(final Player aiPlayer, final Card card) { - AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi(); + AiController aic = ((PlayerControllerAi) aiPlayer.getController()).getAi(); return aic.getPredictedCombat().isAttacking(card); } /** * Extension of doesCreatureAttackAI() for "virtual" creatures that do not actually exist on the battlefield yet * such as unanimated manlands. - * @param ai controller of creature + * + * @param ai controller of creature * @param card creature to be evaluated * @return creature will be attack */ @@ -622,8 +635,9 @@ public class ComputerUtilCard { } /** - * Create a mock combat where ai is being attacked and returns the list of likely blockers. - * @param ai blocking player + * Create a mock combat where ai is being attacked and returns the list of likely blockers. + * + * @param ai blocking player * @param blockers list of additional blockers to be considered * @return list of creatures assigned to block in the simulation */ @@ -654,7 +668,8 @@ public class ComputerUtilCard { /** * Decide if a creature is going to be used as a blocker. - * @param ai controller of creature + * + * @param ai controller of creature * @param blocker creature to be evaluated * @return creature will be a blocker */ @@ -664,7 +679,8 @@ public class ComputerUtilCard { /** * Check if an attacker can be blocked profitably (ie. kill attacker) - * @param ai controller of attacking creature + * + * @param ai controller of attacking creature * @param attacker attacking creature to evaluate * @return attacker will die */ @@ -708,9 +724,8 @@ public class ComputerUtilCard { /** * getMostExpensivePermanentAI. - * - * @param all - * the all + * + * @param all the all * @return the card */ public static Card getMostExpensivePermanentAI(final Iterable all) { @@ -742,7 +757,7 @@ public class ComputerUtilCard { } final Map map = Maps.newHashMap(); - + for (final Card c : list) { final String name = c.getName(); Integer currentCnt = map.get(name); @@ -901,7 +916,7 @@ public class ComputerUtilCard { *

* getMostProminentColor. *

- * + * * @param list * @return a {@link java.lang.String} object. */ @@ -926,22 +941,23 @@ public class ComputerUtilCard { public static List getColorByProminence(final List list) { int cntColors = MagicColor.WUBRG.length; - final List> map = new ArrayList<>(); + final List> map = new ArrayList<>(); for (int i = 0; i < cntColors; i++) { map.add(MutablePair.of(MagicColor.WUBRG[i], 0)); } for (final Card crd : list) { ColorSet color = crd.getColor(); - if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1)); - if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1)); - if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1)); - if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue()+1)); - if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue()+1)); + if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue() + 1)); + if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue() + 1)); + if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue() + 1)); + if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue() + 1)); + if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue() + 1)); } - Collections.sort(map, new Comparator>() { - @Override public int compare(Pair o1, Pair o2) { + Collections.sort(map, new Comparator>() { + @Override + public int compare(Pair o1, Pair o2) { return o2.getValue() - o1.getValue(); } }); @@ -978,64 +994,52 @@ public class ComputerUtilCard { if (logic.equals("MostProminentInHumanDeck")) { chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices)); - } - else if (logic.equals("MostProminentInComputerDeck")) { + } else if (logic.equals("MostProminentInComputerDeck")) { chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices)); - } - else if (logic.equals("MostProminentDualInComputerDeck")) { + } else if (logic.equals("MostProminentDualInComputerDeck")) { List prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai)); chosen.add(prominence.get(0)); chosen.add(prominence.get(1)); - } - else if (logic.equals("MostProminentInGame")) { + } else if (logic.equals("MostProminentInGame")) { chosen.add(getMostProminentColor(game.getCardsInGame(), colorChoices)); - } - else if (logic.equals("MostProminentHumanCreatures")) { + } else if (logic.equals("MostProminentHumanCreatures")) { CardCollectionView list = opp.getCreaturesInPlay(); if (list.isEmpty()) { list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES); } chosen.add(getMostProminentColor(list, colorChoices)); - } - else if (logic.equals("MostProminentComputerControls")) { + } else if (logic.equals("MostProminentComputerControls")) { chosen.add(getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices)); - } - else if (logic.equals("MostProminentHumanControls")) { + } else if (logic.equals("MostProminentHumanControls")) { chosen.add(getMostProminentColor(opp.getCardsIn(ZoneType.Battlefield), colorChoices)); - } - else if (logic.equals("MostProminentPermanent")) { + } else if (logic.equals("MostProminentPermanent")) { chosen.add(getMostProminentColor(game.getCardsIn(ZoneType.Battlefield), colorChoices)); - } - else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) { + } else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) { chosen.add(getMostProminentColor(game.getCombat().getAttackers(), colorChoices)); - } - else if (logic.equals("MostProminentInActivePlayerHand")) { + } else if (logic.equals("MostProminentInActivePlayerHand")) { chosen.add(getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices)); - } - else if (logic.equals("MostProminentInComputerDeckButGreen")) { - List prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai)); - if (prominence.get(0).equals(MagicColor.Constant.GREEN)) { + } else if (logic.equals("MostProminentInComputerDeckButGreen")) { + List prominence = getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai)); + if (prominence.get(0).equals(MagicColor.Constant.GREEN)) { chosen.add(prominence.get(1)); - } else { + } else { chosen.add(prominence.get(0)); - } - } - else if (logic.equals("MostExcessOpponentControls")) { - int maxExcess = 0; - String bestColor = Constant.GREEN; - for (byte color : MagicColor.WUBRG) { - CardCollectionView ailist = ai.getColoredCardsInPlay(color); - CardCollectionView opplist = opp.getColoredCardsInPlay(color); + } + } else if (logic.equals("MostExcessOpponentControls")) { + int maxExcess = 0; + String bestColor = Constant.GREEN; + for (byte color : MagicColor.WUBRG) { + CardCollectionView ailist = ai.getColoredCardsInPlay(color); + CardCollectionView opplist = opp.getColoredCardsInPlay(color); int excess = evaluatePermanentList(opplist) - evaluatePermanentList(ailist); if (excess > maxExcess) { - maxExcess = excess; - bestColor = MagicColor.toLongString(color); + maxExcess = excess; + bestColor = MagicColor.toLongString(color); } } chosen.add(bestColor); - } - else if (logic.equals("MostProminentKeywordInComputerDeck")) { + } else if (logic.equals("MostProminentKeywordInComputerDeck")) { CardCollectionView list = ai.getAllCards(); int m1 = 0; String chosenColor = MagicColor.Constant.WHITE; @@ -1048,8 +1052,7 @@ public class ComputerUtilCard { } } chosen.add(chosenColor); - } - else if (logic.equals("HighestDevotionToColor")) { + } else if (logic.equals("HighestDevotionToColor")) { int curDevotion = 0; String chosenColor = MagicColor.Constant.WHITE; CardCollectionView hand = ai.getCardsIn(ZoneType.Hand); @@ -1075,7 +1078,7 @@ public class ComputerUtilCard { public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) { final Player ai = sa.getActivatingPlayer(); - final AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); + final AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); final Game game = ai.getGame(); final PhaseHandler ph = game.getPhaseHandler(); final PhaseType phaseType = ph.getPhase(); @@ -1085,7 +1088,7 @@ public class ComputerUtilCard { final int costTarget = c.getCMC(); if (!sa.isSpell()) { - return true; + return true; } //Check for cards that profit from spells - for example Prowess or Threshold @@ -1099,14 +1102,14 @@ public class ComputerUtilCard { final Combat combat = new Combat(ai); aiAtk.removeBlocker(c); aiAtk.declareAttackers(combat); - if (!combat.getAttackers().isEmpty()) { + if (!combat.getAttackers().isEmpty()) { AiAttackController aiAtk2 = new AiAttackController(ai); final Combat combat2 = new Combat(ai); aiAtk2.declareAttackers(combat2); if (combat.getAttackers().size() > combat2.getAttackers().size()) { - return true; + return true; } - } + } } // interrupt 2: remove blocker to save my attacker @@ -1152,7 +1155,7 @@ public class ComputerUtilCard { if (!stack.isEmpty()) { final SpellAbility topStack = stack.peekAbility(); if (topStack.getActivatingPlayer().equals(opp) && c.equals(topStack.getTargetCard()) && topStack.isSpell()) { - return true; + return true; } } @@ -1168,7 +1171,7 @@ public class ComputerUtilCard { valueBurn /= 2; //preserve option to burn to the face } if (valueBurn >= 0.8 && phaseType.isBefore(PhaseType.COMBAT_END)) { - return true; + return true; } } @@ -1193,33 +1196,33 @@ public class ComputerUtilCard { if (c.isLand()) { valueTempo += 0.5f / opp.getLandsInPlay().size(); //set back opponent's mana if ("Land".equals(sa.getParam("ValidTgts")) && ph.getPhase().isAfter(PhaseType.COMBAT_END)) { - valueTempo += 0.5; // especially when nothing else can be targeted + valueTempo += 0.5; // especially when nothing else can be targeted } } if (!ph.isPlayerTurn(ai) && ph.getPhase().equals(PhaseType.END_OF_TURN)) { valueTempo *= 2; //prefer to cast at opponent EOT } if (valueTempo >= 0.8 && ph.getPhase().isBefore(PhaseType.COMBAT_END)) { - return true; + return true; } //evaluate threat of targeted card float threat = 0; if (c.isCreature()) { - // the base value for evaluate creature is 100 - threat += (-1 + 1.0f * evaluateCreature(c) / 100) / costRemoval; - if (ai.getLife() > 0 && ComputerUtilCombat.canAttackNextTurn(c)) { - Combat combat = game.getCombat(); - threat += 1.0f * ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) / ai.getLife(); - //TODO:add threat from triggers and other abilities (ie. Master of Cruelties) - } - if (ph.isPlayerTurn(ai) && phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - threat *= 0.1f; - } - if (!ph.isPlayerTurn(ai) && - (phaseType.isBefore(PhaseType.COMBAT_BEGIN) || phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) { - threat *= 0.1f; - } + // the base value for evaluate creature is 100 + threat += (-1 + 1.0f * evaluateCreature(c) / 100) / costRemoval; + if (ai.getLife() > 0 && ComputerUtilCombat.canAttackNextTurn(c)) { + Combat combat = game.getCombat(); + threat += 1.0f * ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) / ai.getLife(); + //TODO:add threat from triggers and other abilities (ie. Master of Cruelties) + } + if (ph.isPlayerTurn(ai) && phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + threat *= 0.1f; + } + if (!ph.isPlayerTurn(ai) && + (phaseType.isBefore(PhaseType.COMBAT_BEGIN) || phaseType.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) { + threat *= 0.1f; + } } else if (c.isPlaneswalker()) { threat = 1; } else if (aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) { @@ -1296,20 +1299,22 @@ public class ComputerUtilCard { /** * Decides if the "pump" is worthwhile - * @param ai casting player - * @param sa Pump* or CounterPut* - * @param c target of sa + * + * @param ai casting player + * @param sa Pump* or CounterPut* + * @param c target of sa * @param toughness +T - * @param power +P - * @param keywords additional keywords from sa (only for Pump) + * @param power +P + * @param keywords additional keywords from sa (only for Pump) * @return */ public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness, final int power, final List keywords) { return shouldPumpCard(ai, sa, c, toughness, power, keywords, false); } + public static boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int toughness, - final int power, final List keywords, boolean immediately) { + final int power, final List keywords, boolean immediately) { final Game game = ai.getGame(); final PhaseHandler phase = game.getPhaseHandler(); final Combat combat = phase.getCombat(); @@ -1452,7 +1457,9 @@ public class ComputerUtilCard { boolean pumpedWillDie = false; final boolean isAttacking = combat.isAttacking(c); - if ((isBerserk && isAttacking) || loseCardAtEOT) { pumpedWillDie = true; } + if ((isBerserk && isAttacking) || loseCardAtEOT) { + pumpedWillDie = true; + } if (isAttacking) { pumpedCombat.addAttacker(pumped, opp); @@ -1475,7 +1482,7 @@ public class ComputerUtilCard { } //1. save combatant - if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie + if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie && !c.hasKeyword(Keyword.INDESTRUCTIBLE)) { // hack because attackerWouldBeDestroyed() // does not check for Indestructible when computing lethal damage @@ -1492,8 +1499,8 @@ public class ComputerUtilCard { } if (survivor) { for (Card o : opposing) { - if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat) - && !(o.hasSVar("SacMe") && Integer.parseInt(o.getSVar("SacMe")) > 2)) { + if (!ComputerUtilCombat.combatantWouldBeDestroyed(opp, o, combat) + && !(o.hasSVar("SacMe") && Integer.parseInt(o.getSVar("SacMe")) > 2)) { if (isAttacking) { if (ComputerUtilCombat.blockerWouldBeDestroyed(opp, o, pumpedCombat)) { return true; @@ -1526,9 +1533,9 @@ public class ComputerUtilCard { dmg = 0; } if (c.hasKeyword(Keyword.TRAMPLE) || keywords.contains("Trample")) { - for (Card b : combat.getBlockers(c)) { - pumpedDmg -= ComputerUtilCombat.getDamageToKill(b, false); - } + for (Card b : combat.getBlockers(c)) { + pumpedDmg -= ComputerUtilCombat.getDamageToKill(b, false); + } } else { pumpedDmg = 0; } @@ -1615,7 +1622,7 @@ public class ComputerUtilCard { // if we got here, Berserk will result in the pumped creature dying at EOT and the opponent will not lose // (other similar cards with AILogic$ Berserk that do not die only when attacking are excluded from consideration) if (ai.getController() instanceof PlayerControllerAi) { - boolean aggr = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.USE_BERSERK_AGGRESSIVELY) + boolean aggr = ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.USE_BERSERK_AGGRESSIVELY) || sa.hasParam("AtEOT"); if (!aggr) { return false; @@ -1635,44 +1642,45 @@ public class ComputerUtilCard { boolean isHeldCombatTrick = combatTrick && wantToHoldTrick; if (isHeldCombatTrick) { - if (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.TRICK_ATTACKERS)) { - // Attempt to hold combat tricks until blockers are declared, and try to lure the opponent into blocking - // (The AI will only do it for one attacker at the moment, otherwise it risks running his attackers into - // an army of opposing blockers with only one combat trick in hand) - // Reserve the mana until Declare Blockers such that the AI doesn't tap out before having a chance to use - // the combat trick - boolean reserved = false; - if (ai.getController().isAI()) { - reserved = ((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, false); - // Only proceed with this if we could actually reserve mana - if (reserved) { - AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.MANDATORY_ATTACKERS); - AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.TRICK_ATTACKERS); - return false; - } - } - } else { - // Don't try to mix "lure" and "precast" paradigms for combat tricks, since that creates issues with - // the AI overextending the attack - return false; - } + if (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.TRICK_ATTACKERS)) { + // Attempt to hold combat tricks until blockers are declared, and try to lure the opponent into blocking + // (The AI will only do it for one attacker at the moment, otherwise it risks running his attackers into + // an army of opposing blockers with only one combat trick in hand) + // Reserve the mana until Declare Blockers such that the AI doesn't tap out before having a chance to use + // the combat trick + boolean reserved = false; + if (ai.getController().isAI()) { + reserved = ((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, false); + // Only proceed with this if we could actually reserve mana + if (reserved) { + AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.MANDATORY_ATTACKERS); + AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.TRICK_ATTACKERS); + return false; + } + } + } else { + // Don't try to mix "lure" and "precast" paradigms for combat tricks, since that creates issues with + // the AI overextending the attack + return false; + } } return simAI || MyRandom.getRandom().nextFloat() < chance; } - + /** * Apply "pump" ability and return modified creature - * @param ai casting player - * @param sa Pump* or CounterPut* - * @param c target of sa + * + * @param ai casting player + * @param sa Pump* or CounterPut* + * @param c target of sa * @param toughness +T - * @param power +P - * @param keywords additional keywords from sa (only for Pump) + * @param power +P + * @param keywords additional keywords from sa (only for Pump) * @return */ public static Card getPumpedCreature(final Player ai, final SpellAbility sa, - final Card c, int toughness, int power, final List keywords) { + final Card c, int toughness, int power, final List keywords) { Card pumped = CardFactory.copyCard(c, false); pumped.setSickness(c.hasSickness()); final long timestamp = c.getGame().getNextTimestamp(); @@ -1740,8 +1748,9 @@ public class ComputerUtilCard { /** * Applies static continuous Power/Toughness effects to a (virtual) creature. - * @param game game instance to work with - * @param vCard creature to work with + * + * @param game game instance to work with + * @param vCard creature to work with * @param exclude list of cards to exclude when considering ability sources, accepts null */ public static void applyStaticContPT(final Game game, Card vCard, final CardCollectionView exclude) { @@ -1787,6 +1796,7 @@ public class ComputerUtilCard { /** * Evaluate if the ability can save a target against removal + * * @param ai casting player * @param sa Pump* or CounterPut* * @return @@ -1889,7 +1899,7 @@ public class ComputerUtilCard { int priorityRemovalThreshold = 0; int lifeInDanger = 5; if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); + AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); enablePriorityRemoval = aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE); priorityRemovalThreshold = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD); priorityRemovalOnlyInDanger = aic.getBooleanProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR); @@ -1973,7 +1983,7 @@ public class ComputerUtilCard { if (needsToPlay.equalsIgnoreCase("WillAttack")) { if (sa != null && game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) { return doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ? - AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects; + AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects; } else { return AiPlayDecision.WillPlay; // not our turn, skip this check for the possible Flash use etc. } @@ -2083,10 +2093,19 @@ public class ComputerUtilCard { public static boolean isCardRemAIDeck(final Card card) { return card.getRules() != null && card.getRules().getAiHints().getRemAIDecks(); } + public static boolean isCardRemRandomDeck(final Card card) { return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks(); } + public static boolean isCardRemNonCommanderDeck(final Card card) { return card.getRules() != null && card.getRules().getAiHints().getRemNonCommanderDecks(); } + + static class LandEvaluator implements Function { + @Override + public Integer apply(Card card) { + return GameStateEvaluator.evaluateLand(card); + } + } } From eaf83488631a8d8b589b458a82316212d1e3130f Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 21 Feb 2023 21:25:19 +0800 Subject: [PATCH 2/5] unused import --- forge-ai/src/main/java/forge/ai/AiController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index a2f32fe7af9..1939621d7cc 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -50,7 +50,6 @@ import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.replacement.ReplaceMoved; import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.*; import forge.game.staticability.StaticAbility; From 9d91f404d2f99df6c33369089382f9737aa8cc50 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 22 Feb 2023 03:35:33 +0800 Subject: [PATCH 3/5] update --- forge-ai/src/main/java/forge/ai/AiController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 1939621d7cc..d5e3b02efa1 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -50,6 +50,7 @@ import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.replacement.ReplaceMoved; import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.*; import forge.game.staticability.StaticAbility; @@ -477,8 +478,6 @@ public class AiController { // If nothing is done here, proceeds to the default land picking strategy } - return ComputerUtilCard.getBestLandToPlayAI(landList); -/* //Skip reflected lands. CardCollection unreflectedLands = new CardCollection(landList); for (Card l : landList) { @@ -577,7 +576,7 @@ public class AiController { landList = CardLists.filter(landList, Predicates.not(CardPredicates.Presets.BASIC_LANDS)); } } - return landList.get(0);*/ + return ComputerUtilCard.getBestLandToPlayAI(landList); } // if return true, go to next phase From ba27d79ad90e5fdfcfec7d8fb81629a7db3b0efc Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 22 Feb 2023 03:43:22 +0800 Subject: [PATCH 4/5] update new cube and draft --- ...ck => MTGArena_Chromatic_Cube_June_24.dck} | 1126 ++++++++--------- .../The_Arena_Cube_December13_-_January1.dck | 4 +- .../res/draft/MTGArena_Chromatic_Cube.draft | 6 - .../MTGArena_Chromatic_Cube_June_24.draft | 6 + ...The_Arena_Cube_December13_-_January1.draft | 4 +- 5 files changed, 573 insertions(+), 573 deletions(-) rename forge-gui/res/cube/{MTGArena_Chromatic_Cube.dck => MTGArena_Chromatic_Cube_June_24.dck} (96%) delete mode 100644 forge-gui/res/draft/MTGArena_Chromatic_Cube.draft create mode 100644 forge-gui/res/draft/MTGArena_Chromatic_Cube_June_24.draft diff --git a/forge-gui/res/cube/MTGArena_Chromatic_Cube.dck b/forge-gui/res/cube/MTGArena_Chromatic_Cube_June_24.dck similarity index 96% rename from forge-gui/res/cube/MTGArena_Chromatic_Cube.dck rename to forge-gui/res/cube/MTGArena_Chromatic_Cube_June_24.dck index c4be015b08a..75c1a75cb95 100644 --- a/forge-gui/res/cube/MTGArena_Chromatic_Cube.dck +++ b/forge-gui/res/cube/MTGArena_Chromatic_Cube_June_24.dck @@ -1,563 +1,563 @@ -[metadata] -Name=MTG Arena Chromatic Cube June 24 -[Main] -1 Esper Sentinel|MH2 -1 Giant Killer|ELD -1 Mikaeus, the Lunarch|JMP -1 Thraben Inspector|2XM -1 Captain Eberhart|YMID -1 Cathar Commando|MID -1 Charming Prince|ELD -1 Intrepid Adversary|MID -1 Lion Sash|NEO -1 Sigardian Evangel|YMID -1 Spirited Companion|NEO -1 Adeline, Resplendent Cathar|MID -1 Blade Splicer|2XM -1 Elite Spellbinder|STX -1 Heliod, Sun-Crowned|THB -1 Inspiring Overseer|SNC -1 Mentor of the Meek|JMP -1 Skyclave Apparition|ZNR -1 Welcoming Vampire|VOW -1 Icingdeath, Frost Tyrant|AFR -1 Luminous Broodmoth|IKO -1 Restoration Angel|IMA -1 Angel of Invention|KLD -1 Angel of Sanctions|AKH -1 Ao, the Dawn Sky|NEO -1 Cavalier of Dawn|M20 -1 God-Eternal Oketra|WAR -1 Lyra Dawnbringer|DOM -1 Regal Caracal|PLIST -1 Harmonious Archon|ELD -1 Sanctuary Warden|SNC -1 Elesh Norn, Grand Cenobite|MM2 -1 Elspeth, Sun's Nemesis|THB -1 The Wandering Emperor|NEO -1 Ephemerate|MH1 -1 Raise the Alarm|MD1 -1 Shelter|MH1 -1 Valorous Stance|VOW -1 Banishing Slash|NEO -1 Finale of Glory|WAR -1 Battle Screech|JUD -1 Late to Dinner|MH2 -1 Release the Dogs|PLIST -1 Sram's Expertise|AER -1 Starnheim Unleashed|KHM -1 Farewell|NEO -1 Banishing Light|KHC -1 History of Benalia|DOM -1 The Restoration of Eiganjo|NEO -1 Touch the Spirit Realm|NEO -1 Anointed Procession|AKH -1 Cast Out|C20 -1 Faith's Fetters|CMR -1 Teleportation Circle|AFR -1 Blessed Sanctuary|JMP -1 Divine Visitation|GRN -1 Elspeth Conquers Death|PLIST -1 Agent of Raffine|YSNC -1 A-Stitched Assistant|VOW -1 Clone Crafter|YMID -1 Fae of Wishes|ELD -1 Ghostly Pilferer|M21 -1 Jacob Hauken, Inspector|VOW -1 The Reality Chip|NEO -1 Triskaidekaphile|MID -1 Alirios, Enraptured|THB -1 Barrin, Tolarian Archmage|M21 -1 Cemetery Illuminator|VOW -1 Champion of Wits|C21 -1 Futurist Spellthief|YNEO -1 Glasspool Mimic|ZNR -1 Vizier of Tumbling Sands|C20 -1 Mirrorhall Mimic|VOW -1 Murmuring Mystic|GRN -1 Saiba Syphoner|YNEO -1 Spark Double|WAR -1 Thassa, Deep-Dwelling|THB -1 Vizier of Many Faces|AKH -1 Whirler Rogue|GNT -1 A-Alrund, God of the Cosmos|KHM -1 A-Lier, Disciple of the Drowned|MID -1 Cavalier of Gales|M20 -1 Djinn of Wishes|M19 -1 Mulldrifter|AFC -1 Nightclub Bouncer|YSNC -1 Voracious Greatshark|IKO -1 Dream Eater|GRN -1 Torrential Gearhulk|KLD -1 A-Hullbreaker Horror|VOW -1 Jin-Gitaxias, Progress Tyrant|NEO -1 Scholar of the Lost Trove|JMP -1 Teferi, Master of Time|M21 -1 Into the Roil|C17 -1 Thassa's Intervention|THB -1 Hornswoggle|RIX -1 Supreme Will|HOU -1 Whirlwind Denial|THB -1 Kindred Denial|YMID -1 Memory Deluge|MID -1 Spell Swindle|PLIST -1 Commence the Endgame|WAR -1 Sublime Epiphany|M21 -1 Better Offer|YNEO -1 Hard Evidence|MH2 -1 Multiple Choice|STX -1 Chart a Course|JMP -1 Quasiduplicate|GRN -1 Inscription of Insight|ZNR -1 Tezzeret's Gambit|STA -1 Baral's Expertise|AER -1 Karn's Temporal Sundering|DOM -1 A-Alrund's Epiphany|KHM -1 Midnight Clock|ELD -1 Search for Azcanta|XLN -1 The Mirari Conjecture|DOM -1 Shark Typhoon|IKO -1 Cryptbreaker|EMN -1 Forsworn Paladin|AFR -1 A-Blood Artist|JMP -1 Brain Maggot|JOU -1 Jadar, Ghoulcaller of Nephalia|MID -1 Kitesail Freebooter|M21 -1 Nullpriest of Oblivion|ZNR -1 Swarm Saboteur|YNEO -1 Tenacious Underdog|SNC -1 Callous Bloodmage|STX -1 Midnight Reaper|GRN -1 Murderous Rider|ELD -1 Nashi, Moon Sage's Scion|NEO -1 Plaguecrafter|AFC -1 Sedgemoor Witch|STX -1 Woe Strider|THB -1 Erebos, Bleak-Hearted|THB -1 Gonti, Lord of Luxury|JMP -1 Henrika Domnathi|VOW -1 Nightmare Shepherd|THB -1 Rankle, Master of Pranks|ELD -1 Ravenous Chupacabra|RIX -1 Sling-Gang Lieutenant|MH1 -1 Yawgmoth, Thran Physician|J21 -1 Cavalier of Night|PLIST -1 Doom Whisperer|GRN -1 God-Eternal Bontu|WAR -1 Gray Merchant of Asphodel|C14 -1 Junji, the Midnight Sky|NEO -1 Tergrid, God of Fright|KHM -1 Massacre Wurm|M21 -1 Noxious Gearhulk|C21 -1 Sheoldred, Whispering One|NPH -1 Davriel, Soul Broker|J21 -1 Sorin the Mirthless|VOW -1 Liliana, Death's Majesty|AKH -1 Lolth, Spider Queen|AFR -1 Liliana, Dreadhorde General|WAR -1 Assemble from Parts|YMID -1 Sap Vitality|YMID -1 Soul Servitude|YNEO -1 Price of Fame|ZNC -1 Vraska's Contempt|XLN -1 Bone Shards|J21 -1 Innocent Blood|JMP -1 Agonizing Remorse|THB -1 Chainer's Edict|TOR -1 Feed the Swarm|NCC -1 Finale of Eternity|WAR -1 Blood for Bones|M20 -1 Crux of Fate|C17 -1 Yawgmoth's Vile Offering|DOM -1 Dreadhorde Invasion|WAR -1 The Meathook Massacre|MID -1 Phyrexian Arena|HOP -1 The Eldest Reborn|C19 -1 Magda, Brazen Outlaw|KHM -1 Magmatic Channeler|ZNR -1 Rahilda, Wanted Cutthroat|YMID -1 Robber of the Rich|ELD -1 Smoldering Egg|MID -1 Young Pyromancer|JMP -1 Captain Lannery Storm|XLN -1 Dualcaster Mage|C14 -1 Krenko, Tin Street Kingpin|WAR -1 Professional Face-Breaker|SNC -1 Seasoned Pyromancer|MH1 -1 Toralf's Disciple|YMID -1 Zurzoth, Chaos Rider|JMP -1 Atsushi, the Blazing Sky|NEO -1 Captivating Crew|C20 -1 Delina, Wild Mage|AFR -1 Efreet Flamepainter|STX -1 Jaxis, the Troublemaker|SNC -1 Manaform Hellkite|VOW -1 Neheb, Dreadhorde Champion|WAR -1 Torbran, Thane of Red Fell|ELD -1 Cavalier of Flame|M20 -1 Ilharg, the Raze-Boar|WAR -1 Invalid Card| -1 Siege-Gang Commander|GNT -1 Terror of the Peaks|M21 -1 Urabrask, Heretic Praetor|SNC -1 Charmbreaker Devils|C20 -1 Combustible Gearhulk|C21 -1 Etali, Primal Storm|RIX -1 Drakuseth, Maw of Flames|CLB -1 Sarkhan, Wanderer to Shiv|J21 -1 Zariel, Archduke of Avernus|AFR -1 Lightning Bolt|J21 -1 Play with Fire|MID -1 Abrade|2XM -1 Electrodominance|RNA -1 Electrostatic Blast|YMID -1 Fire Prophecy|IKO -1 Pass the Torch|YSNC -1 You Find Some Prisoners|AFR -1 Big Score|SNC -1 Fast // Furious|MH2 -1 Faithless Looting|C21 -1 Purphoros's Intervention|THB -1 Crackle with Power|STX -1 Roil Eruption|ZNR -1 Shatterskull Smashing|ZNR -1 Fight with Fire|DOM -1 Hordeling Outburst|DDT -1 Seize the Spoils|KHM -1 Mizzix's Mastery|C15 -1 Burn Down the House|MID -1 Geistflame Reservoir|MID -1 Fable of the Mirror-Breaker|NEO -1 Double Vision|NCC -1 Arcane Bombardment|SNC -1 Fiery Emancipation|M21 -1 Gilded Goose|ELD -1 Llanowar Elves|M12 -1 Fauna Shaman|UMA -1 Gala Greeters|SNC -1 Ilysian Caryatid|THB -1 Incubation Druid|RNA -1 Lotus Cobra|ZEN -1 Menagerie Curator|YSNC -1 Paradise Druid|AFC -1 Shigeki, Jukai Visionary|NEO -1 Voracious Hydra|M20 -1 Augur of Autumn|MID -1 Dryad of the Ilysian Grove|THB -1 Llanowar Visionary|M21 -1 Reclamation Sage|C18 -1 Selvala, Heart of the Wilds|CN2 -1 Springbloom Druid|MH1 -1 Tireless Provisioner|MH2 -1 Beast Whisperer|C20 -1 Champion of Rhonas|AKH -1 Forceful Cultivator|YNEO -1 Howlpack Piper|VOW -1 Oracle of Mul Daya|JMP -1 Timeless Witness|MH2 -1 Toski, Bearer of Secrets|KHM -1 Vizier of the Menagerie|AKH -1 Ashaya, Soul of the Wild|ZNR -1 Cavalier of Thorns|M20 -1 God-Eternal Rhonas|WAR -1 Workshop Warchief|SNC -1 Kogla, the Titan Ape|IKO -1 Tovolar's Huntmaster|MID -1 Vorinclex, Monstrous Raider|KHM -1 Beanstalk Giant|ZNC -1 Hornet Queen|M15 -1 Nyxbloom Ancient|THB -1 End-Raze Forerunners|CLB -1 Garruk, Unleashed|M21 -1 Nissa, Vital Force|KLD -1 Vivien, Monsters' Advocate|IKO -1 Wrenn and Seven|MID -1 Vivien on the Hunt|SNC -1 Inscription of Abundance|ZNR -1 Giant Regrowth|YSNC -1 Harrow|DDP -1 Primal Might|M21 -1 Emergent Sequence|STX -1 Explore|SLD -1 Finale of Devastation|WAR -1 Cultivate|AFC -1 Settle the Wilds|YMID -1 Storm the Festival|MID -1 Pool of Vigorous Growth|J21 -1 Invalid Card| -1 The Great Henge|ELD -1 Parallel Lives|ISD -1 Unnatural Growth|MID -1 Dennick, Pious Apprentice|MID -1 Hallowed Respite|MID -1 Momentary Blink|TSP -1 Scheming Fence|SNC -1 Deputy of Detention|RNA -1 Obscura Polymorphist|YSNC -1 Soulherder|KHC -1 Elite Guardmage|WAR -1 Cloudblazer|AFC -1 Yorion, Sky Nomad|IKO -1 Siphon Insight|MID -1 Kaito Shizuki|NEO -1 Satoru Umezawa|NEO -1 Thief of Sanity|GRN -1 Atris, Oracle of Half-Truths|THB -1 Hostage Taker|AFC -1 Kels, Fight Fixer|JMP -1 Silumgar's Command|C17 -1 Gyruda, Doom of Depths|IKO -1 Xanathar, Guild Kingpin|AFR -1 Discovery // Dispersal|GRN -1 Toxrill, the Corrosive|VOW -1 Valki, God of Lies|KHM -1 Bank Job|YSNC -1 Kolaghan's Command|DTK -1 Ob Nixilis, the Adversary|SNC -1 Anje, Maid of Dishonor|VOW -1 Chainer, Nightmare Adept|MH2 -1 Cut // Ribbons|PLIST -1 Hidetsugu, Devouring Chaos|NEO -1 Immersturm Predator|KHM -1 Angrath, the Flame-Chained|RIX -1 Obosh, the Preypiercer|IKO -1 Olivia, Crimson Bride|VOW -1 Black Market Tycoon|SNC -1 Territorial Kavu|MH2 -1 Cabaretti Revels|YSNC -1 Dire-Strain Rampage|MID -1 Domri, Anarch of Bolas|WAR -1 Radha, Heart of Keld|M21 -1 Halana and Alena, Partners|VOW -1 Neyith of the Dire Hunt|JMP -1 Escape to the Wilds|ELD -1 Ravager Wurm|RNA -1 Rhys the Redeemed|SHM -1 Join the Dance|MID -1 Rope Line Attendant|YSNC -1 Faeburrow Elder|ELD -1 Torens, Fist of the Angels|VOW -1 Captain Sisay|INV -1 Emiel the Blessed|JMP -1 Arming Gala|YSNC -1 Mirari's Wake|C17 -1 Tolsimir, Friend to Wolves|WAR -1 Trostani Discordant|GRN -1 Blot Out the Sky|STX -1 Despark|WAR -1 Priest of Fell Rites|MH2 -1 Vanishing Verse|STX -1 Lurrus of the Dream-Den|IKO -1 Silverquill Command|STX -1 Kaya the Inexorable|KHM -1 Liesa, Forgotten Archangel|MID -1 Shadrix Silverquill|STX -1 Spirit-Sister's Call|NEO -1 Unburial Rites|ISD -1 Alchemist's Gambit|VOW -1 Electrolyze|STA -1 Lutri, the Spellchaser|IKO -1 Prismari Command|STX -1 Rootha, Mercurial Artist|STX -1 Saheeli, Sublime Artificer|WAR -1 Ral, Storm Conduit|WAR -1 Ral's Outburst|WAR -1 Expansion // Explosion|GRN -1 Niv-Mizzet, Parun|CLB -1 Magma Opus|STX -1 Ravenous Squirrel|MH2 -1 Fiend Artisan|IKO -1 A-Shessra, Death's Whisper|AFR -1 Chatterfang, Squirrel General|MH2 -1 Leyline Prowler|WAR -1 A-Tyvar Kell|KHM -1 Binding the Old Gods|KHM -1 Gitrog, Horror of Zhava|YMID -1 Polukranos, Unchained|THB -1 Vraska, Golgari Queen|GRN -1 Diregraf Rebirth|MID -1 The Gitrog Monster|SOI -1 Lightning Helix|RAV -1 Plargg, Dean of Chaos|STX -1 Rip Apart|STX -1 Sacred Fire|MID -1 Brutal Cathar|MID -1 Deafening Clarion|GRN -1 Rem Karolus, Stalwart Slayer|MID -1 Radiant Scrollwielder|STX -1 Integrity // Intervention|GRN -1 Lorehold Command|STX -1 Firesong and Sunspeaker|DOM -1 Response // Resurgence|GRN -1 Velomachus Lorehold|STX -1 Growth Spiral|CMR -1 Hydroid Krasis|RNA -1 Kinnan, Bonder Prodigy|IKO -1 Suspicious Stowaway|MID -1 Croaking Counterpart|MID -1 Kiora, Behemoth Beckoner|WAR -1 Risen Reef|M20 -1 Uro, Titan of Nature's Wrath|PLIST -1 Rashmi, Eternities Crafter|KLD -1 Volo, Guide to Monsters|AFR -1 Keruga, the Macrosage|IKO -1 Tatyova, Benthic Druid|DOM -1 Falco Spara, Pactweaver|SNC -1 Chulane, Teller of Tales|ELD -1 Raffine, Scheming Seer|SNC -1 Obscura Interceptor|SNC -1 Nicol Bolas, the Ravager|M19 -1 Lord Xander, the Collector|SNC -1 Nicol Bolas, God-Pharaoh|HOU -1 Ognis, the Dragon's Lash|SNC -1 Ziatora's Envoy|SNC -1 Korvold, Fae-Cursed King|ELD -1 Ziatora, the Incinerator|SNC -1 Back-Alley Gardener|YSNC -1 Jinnie Fay, Jetmir's Second|SNC -1 Jetmir, Nexus of Revels|SNC -1 Zacama, Primal Calamity|RIX -1 Kaalia, Zenith Seeker|M20 -1 Extus, Oriq Overlord|STX -1 Ruinous Ultimatum|IKO -1 Mythos of Illuna|IKO -1 Illuna, Apex of Wishes|IKO -1 Genesis Ultimatum|IKO -1 Mythos of Nethroi|IKO -1 Nethroi, Apex of Death|IKO -1 Eerie Ultimatum|IKO -1 Narset of the Ancient Way|IKO -1 Inspired Ultimatum|IKO -1 Yarok, the Desecrated|M20 -1 Muldrotha, the Gravetide|DOM -1 A-Omnath, Locus of Creation|ZNR -1 Courier's Briefcase|SNC -1 Esika, God of the Tree|KHM -1 Kyodai, Soul of Kamigawa|NEO -1 Golos, Tireless Pilgrim|M20 -1 Jegantha, the Wellspring|IKO -1 Kenrith, the Returned King|ELD -1 Maelstrom Archangel|CON -1 Niv-Mizzet Reborn|WAR -1 The Kami War|NEO -1 Tiamat|AFR -1 Ornithopter of Paradise|MH2 -1 Alloy Myr|NPH -1 Circuit Mender|NEO -1 Palladium Myr|SOM -1 Scuttlemutt|SHM -1 Skittering Surveyor|DOM -1 Solemn Simulacrum|MRD -1 Meteor Golem|KHC -1 Platinum Angel|V15 -1 Karn, Scion of Urza|DOM -1 Ugin, the Ineffable|WAR -1 Eater of Virtue|NEO -1 Shadowspear|THB -1 Coldsteel Heart|CM2 -1 Guardian Idol|JMP -1 Mazemind Tome|M21 -1 Mind Stone|AFC -1 Reckoner Bankbuster|NEO -1 Treasure Map|XLN -1 Chromatic Lantern|RTR -1 Cultivator's Caravan|KLD -1 Dragon's Hoard|CLB -1 Heraldic Banner|ELD -1 Letter of Acceptance|STX -1 Skyclave Relic|ZNR -1 The Celestus|MID -1 Firemind Vessel|WAR -1 Hedron Archive|BFZ -1 Key to the Archive|YMID -1 Lithoform Engine|ZNR -1 Primal Amulet|XLN -1 Gilded Lotus|M13 -1 Tome of the Guildpact|RNA -1 The Immortal Sun|RIX -1 Chromatic Orrery|M21 -1 God-Pharaoh's Gift|HOU -1 Deserted Beach|MID -1 Glacial Fortress|XLN -1 Hallowed Fountain|RNA -1 Hengegate Pathway|KHM -1 Irrigated Farmland|AKH -1 Temple of Enlightenment|NEC -1 Clearwater Pathway|ZNR -1 Drowned Catacomb|XLN -1 Fetid Pools|AKH -1 Shipwreck Marsh|MID -1 Temple of Deceit|CLB -1 Watery Grave|GRN -1 Blightstep Pathway|KHM -1 Blood Crypt|RNA -1 Canyon Slough|AKH -1 Dragonskull Summit|XLN -1 Haunted Ridge|MID -1 Temple of Malice|VOC -1 Cragcrown Pathway|ZNR -1 Rockfall Vale|MID -1 Rootbound Crag|XLN -1 Sheltered Thicket|AKH -1 Stomping Ground|RNA -1 Temple of Abandon|NEC -1 Branchloft Pathway|ZNR -1 Overgrown Farmland|MID -1 Scattered Groves|AKH -1 Sunpetal Grove|XLN -1 Temple Garden|GRN -1 Temple of Plenty|THB -1 Brightclimb Pathway|ZNR -1 Concealed Courtyard|KLD -1 Godless Shrine|RNA -1 Isolated Chapel|DOM -1 Shattered Sanctum|VOW -1 Temple of Silence|C21 -1 Riverglide Pathway|ZNR -1 Spirebluff Canal|KLD -1 Steam Vents|GRN -1 Stormcarved Coast|VOW -1 Sulfur Falls|DOM -1 Temple of Epiphany|M20 -1 Blooming Marsh|KLD -1 Darkbore Pathway|KHM -1 Deathcap Glade|VOW -1 Overgrown Tomb|GRN -1 Temple of Malady|C21 -1 Woodland Cemetery|DOM -1 Clifftop Retreat|DOM -1 Inspiring Vantage|KLD -1 Needleverge Pathway|ZNR -1 Sacred Foundry|GRN -1 Sundown Pass|VOW -1 Temple of Triumph|M21 -1 Barkchannel Pathway|KHM -1 Botanical Sanctum|KLD -1 Breeding Pool|SLD -1 Dreamroot Cascade|VOW -1 Hinterland Harbor|ISD -1 Temple of Mystery|M20 -1 Spara's Headquarters|SNC -1 Raffine's Tower|SNC -1 Xander's Lounge|SNC -1 Ziatora's Proving Ground|SNC -1 Jetmir's Garden|SNC -1 Savai Triome|IKO -1 Ketria Triome|IKO -1 Indatha Triome|IKO -1 Raugrin Triome|IKO -1 Zagoth Triome|IKO -1 Boseiju, Who Endures|NEO -1 Eiganjo, Seat of the Empire|NEO -1 Fabled Passage|ELD -1 Field of the Dead|M20 -1 Forsaken Crossroads|YMID -1 Otawara, Soaring City|NEO -1 Sokenzan, Crucible of Defiance|NEO -1 Takenuma, Abandoned Mire|NEO -1 Temple of the Dragon Queen|AFR -1 Thriving Bluff|NCC -1 Thriving Grove|AFC -1 Thriving Heath|NCC -1 Thriving Isle|NCC -1 Thriving Moor|JMP +[metadata] +Name=MTG Arena Chromatic Cube June 24 +[Main] +1 Esper Sentinel|MH2 +1 Giant Killer|ELD +1 Mikaeus, the Lunarch|JMP +1 Thraben Inspector|2XM +1 Captain Eberhart|YMID +1 Cathar Commando|MID +1 Charming Prince|ELD +1 Intrepid Adversary|MID +1 Lion Sash|NEO +1 Sigardian Evangel|YMID +1 Spirited Companion|NEO +1 Adeline, Resplendent Cathar|MID +1 Blade Splicer|2XM +1 Elite Spellbinder|STX +1 Heliod, Sun-Crowned|THB +1 Inspiring Overseer|SNC +1 Mentor of the Meek|JMP +1 Skyclave Apparition|ZNR +1 Welcoming Vampire|VOW +1 Icingdeath, Frost Tyrant|AFR +1 Luminous Broodmoth|IKO +1 Restoration Angel|IMA +1 Angel of Invention|KLD +1 Angel of Sanctions|AKH +1 Ao, the Dawn Sky|NEO +1 Cavalier of Dawn|M20 +1 God-Eternal Oketra|WAR +1 Lyra Dawnbringer|DOM +1 Regal Caracal|PLIST +1 Harmonious Archon|ELD +1 Sanctuary Warden|SNC +1 Elesh Norn, Grand Cenobite|MM2 +1 Elspeth, Sun's Nemesis|THB +1 The Wandering Emperor|NEO +1 Ephemerate|MH1 +1 Raise the Alarm|MD1 +1 Shelter|MH1 +1 Valorous Stance|VOW +1 Banishing Slash|NEO +1 Finale of Glory|WAR +1 Battle Screech|JUD +1 Late to Dinner|MH2 +1 Release the Dogs|PLIST +1 Sram's Expertise|AER +1 Starnheim Unleashed|KHM +1 Farewell|NEO +1 Banishing Light|KHC +1 History of Benalia|DOM +1 The Restoration of Eiganjo|NEO +1 Touch the Spirit Realm|NEO +1 Anointed Procession|AKH +1 Cast Out|C20 +1 Faith's Fetters|CMR +1 Teleportation Circle|AFR +1 Blessed Sanctuary|JMP +1 Divine Visitation|GRN +1 Elspeth Conquers Death|PLIST +1 Agent of Raffine|YSNC +1 A-Stitched Assistant|VOW +1 Clone Crafter|YMID +1 Fae of Wishes|ELD +1 Ghostly Pilferer|M21 +1 Jacob Hauken, Inspector|VOW +1 The Reality Chip|NEO +1 Triskaidekaphile|MID +1 Alirios, Enraptured|THB +1 Barrin, Tolarian Archmage|M21 +1 Cemetery Illuminator|VOW +1 Champion of Wits|C21 +1 Futurist Spellthief|YNEO +1 Glasspool Mimic|ZNR +1 Vizier of Tumbling Sands|C20 +1 Mirrorhall Mimic|VOW +1 Murmuring Mystic|GRN +1 Saiba Syphoner|YNEO +1 Spark Double|WAR +1 Thassa, Deep-Dwelling|THB +1 Vizier of Many Faces|AKH +1 Whirler Rogue|GNT +1 A-Alrund, God of the Cosmos|KHM +1 A-Lier, Disciple of the Drowned|MID +1 Cavalier of Gales|M20 +1 Djinn of Wishes|M19 +1 Mulldrifter|AFC +1 Nightclub Bouncer|YSNC +1 Voracious Greatshark|IKO +1 Dream Eater|GRN +1 Torrential Gearhulk|KLD +1 A-Hullbreaker Horror|VOW +1 Jin-Gitaxias, Progress Tyrant|NEO +1 Scholar of the Lost Trove|JMP +1 Teferi, Master of Time|M21 +1 Into the Roil|C17 +1 Thassa's Intervention|THB +1 Hornswoggle|RIX +1 Supreme Will|HOU +1 Whirlwind Denial|THB +1 Kindred Denial|YMID +1 Memory Deluge|MID +1 Spell Swindle|PLIST +1 Commence the Endgame|WAR +1 Sublime Epiphany|M21 +1 Better Offer|YNEO +1 Hard Evidence|MH2 +1 Multiple Choice|STX +1 Chart a Course|JMP +1 Quasiduplicate|GRN +1 Inscription of Insight|ZNR +1 Tezzeret's Gambit|STA +1 Baral's Expertise|AER +1 Karn's Temporal Sundering|DOM +1 A-Alrund's Epiphany|KHM +1 Midnight Clock|ELD +1 Search for Azcanta|XLN +1 The Mirari Conjecture|DOM +1 Shark Typhoon|IKO +1 Cryptbreaker|EMN +1 Forsworn Paladin|AFR +1 A-Blood Artist|JMP +1 Brain Maggot|JOU +1 Jadar, Ghoulcaller of Nephalia|MID +1 Kitesail Freebooter|M21 +1 Nullpriest of Oblivion|ZNR +1 Swarm Saboteur|YNEO +1 Tenacious Underdog|SNC +1 Callous Bloodmage|STX +1 Midnight Reaper|GRN +1 Murderous Rider|ELD +1 Nashi, Moon Sage's Scion|NEO +1 Plaguecrafter|AFC +1 Sedgemoor Witch|STX +1 Woe Strider|THB +1 Erebos, Bleak-Hearted|THB +1 Gonti, Lord of Luxury|JMP +1 Henrika Domnathi|VOW +1 Nightmare Shepherd|THB +1 Rankle, Master of Pranks|ELD +1 Ravenous Chupacabra|RIX +1 Sling-Gang Lieutenant|MH1 +1 Yawgmoth, Thran Physician|J21 +1 Cavalier of Night|PLIST +1 Doom Whisperer|GRN +1 God-Eternal Bontu|WAR +1 Gray Merchant of Asphodel|C14 +1 Junji, the Midnight Sky|NEO +1 Tergrid, God of Fright|KHM +1 Massacre Wurm|M21 +1 Noxious Gearhulk|C21 +1 Sheoldred, Whispering One|NPH +1 Davriel, Soul Broker|J21 +1 Sorin the Mirthless|VOW +1 Liliana, Death's Majesty|AKH +1 Lolth, Spider Queen|AFR +1 Liliana, Dreadhorde General|WAR +1 Assemble from Parts|YMID +1 Sap Vitality|YMID +1 Soul Servitude|YNEO +1 Price of Fame|ZNC +1 Vraska's Contempt|XLN +1 Bone Shards|J21 +1 Innocent Blood|JMP +1 Agonizing Remorse|THB +1 Chainer's Edict|TOR +1 Feed the Swarm|NCC +1 Finale of Eternity|WAR +1 Blood for Bones|M20 +1 Crux of Fate|C17 +1 Yawgmoth's Vile Offering|DOM +1 Dreadhorde Invasion|WAR +1 The Meathook Massacre|MID +1 Phyrexian Arena|HOP +1 The Eldest Reborn|C19 +1 Magda, Brazen Outlaw|KHM +1 Magmatic Channeler|ZNR +1 Rahilda, Wanted Cutthroat|YMID +1 Robber of the Rich|ELD +1 Smoldering Egg|MID +1 Young Pyromancer|JMP +1 Captain Lannery Storm|XLN +1 Dualcaster Mage|C14 +1 Krenko, Tin Street Kingpin|WAR +1 Professional Face-Breaker|SNC +1 Seasoned Pyromancer|MH1 +1 Toralf's Disciple|YMID +1 Zurzoth, Chaos Rider|JMP +1 Atsushi, the Blazing Sky|NEO +1 Captivating Crew|C20 +1 Delina, Wild Mage|AFR +1 Efreet Flamepainter|STX +1 Jaxis, the Troublemaker|SNC +1 Manaform Hellkite|VOW +1 Neheb, Dreadhorde Champion|WAR +1 Torbran, Thane of Red Fell|ELD +1 Cavalier of Flame|M20 +1 Ilharg, the Raze-Boar|WAR +1 Invalid Card| +1 Siege-Gang Commander|GNT +1 Terror of the Peaks|M21 +1 Urabrask, Heretic Praetor|SNC +1 Charmbreaker Devils|C20 +1 Combustible Gearhulk|C21 +1 Etali, Primal Storm|RIX +1 Drakuseth, Maw of Flames|CLB +1 Sarkhan, Wanderer to Shiv|J21 +1 Zariel, Archduke of Avernus|AFR +1 Lightning Bolt|J21 +1 Play with Fire|MID +1 Abrade|2XM +1 Electrodominance|RNA +1 Electrostatic Blast|YMID +1 Fire Prophecy|IKO +1 Pass the Torch|YSNC +1 You Find Some Prisoners|AFR +1 Big Score|SNC +1 Fast // Furious|MH2 +1 Faithless Looting|C21 +1 Purphoros's Intervention|THB +1 Crackle with Power|STX +1 Roil Eruption|ZNR +1 Shatterskull Smashing|ZNR +1 Fight with Fire|DOM +1 Hordeling Outburst|DDT +1 Seize the Spoils|KHM +1 Mizzix's Mastery|C15 +1 Burn Down the House|MID +1 Geistflame Reservoir|MID +1 Fable of the Mirror-Breaker|NEO +1 Double Vision|NCC +1 Arcane Bombardment|SNC +1 Fiery Emancipation|M21 +1 Gilded Goose|ELD +1 Llanowar Elves|M12 +1 Fauna Shaman|UMA +1 Gala Greeters|SNC +1 Ilysian Caryatid|THB +1 Incubation Druid|RNA +1 Lotus Cobra|ZEN +1 Menagerie Curator|YSNC +1 Paradise Druid|AFC +1 Shigeki, Jukai Visionary|NEO +1 Voracious Hydra|M20 +1 Augur of Autumn|MID +1 Dryad of the Ilysian Grove|THB +1 Llanowar Visionary|M21 +1 Reclamation Sage|C18 +1 Selvala, Heart of the Wilds|CN2 +1 Springbloom Druid|MH1 +1 Tireless Provisioner|MH2 +1 Beast Whisperer|C20 +1 Champion of Rhonas|AKH +1 Forceful Cultivator|YNEO +1 Howlpack Piper|VOW +1 Oracle of Mul Daya|JMP +1 Timeless Witness|MH2 +1 Toski, Bearer of Secrets|KHM +1 Vizier of the Menagerie|AKH +1 Ashaya, Soul of the Wild|ZNR +1 Cavalier of Thorns|M20 +1 God-Eternal Rhonas|WAR +1 Workshop Warchief|SNC +1 Kogla, the Titan Ape|IKO +1 Tovolar's Huntmaster|MID +1 Vorinclex, Monstrous Raider|KHM +1 Beanstalk Giant|ZNC +1 Hornet Queen|M15 +1 Nyxbloom Ancient|THB +1 End-Raze Forerunners|CLB +1 Garruk, Unleashed|M21 +1 Nissa, Vital Force|KLD +1 Vivien, Monsters' Advocate|IKO +1 Wrenn and Seven|MID +1 Vivien on the Hunt|SNC +1 Inscription of Abundance|ZNR +1 Giant Regrowth|YSNC +1 Harrow|DDP +1 Primal Might|M21 +1 Emergent Sequence|STX +1 Explore|SLD +1 Finale of Devastation|WAR +1 Cultivate|AFC +1 Settle the Wilds|YMID +1 Storm the Festival|MID +1 Pool of Vigorous Growth|J21 +1 Invalid Card| +1 The Great Henge|ELD +1 Parallel Lives|ISD +1 Unnatural Growth|MID +1 Dennick, Pious Apprentice|MID +1 Hallowed Respite|MID +1 Momentary Blink|TSP +1 Scheming Fence|SNC +1 Deputy of Detention|RNA +1 Obscura Polymorphist|YSNC +1 Soulherder|KHC +1 Elite Guardmage|WAR +1 Cloudblazer|AFC +1 Yorion, Sky Nomad|IKO +1 Siphon Insight|MID +1 Kaito Shizuki|NEO +1 Satoru Umezawa|NEO +1 Thief of Sanity|GRN +1 Atris, Oracle of Half-Truths|THB +1 Hostage Taker|AFC +1 Kels, Fight Fixer|JMP +1 Silumgar's Command|C17 +1 Gyruda, Doom of Depths|IKO +1 Xanathar, Guild Kingpin|AFR +1 Discovery // Dispersal|GRN +1 Toxrill, the Corrosive|VOW +1 Valki, God of Lies|KHM +1 Bank Job|YSNC +1 Kolaghan's Command|DTK +1 Ob Nixilis, the Adversary|SNC +1 Anje, Maid of Dishonor|VOW +1 Chainer, Nightmare Adept|MH2 +1 Cut // Ribbons|PLIST +1 Hidetsugu, Devouring Chaos|NEO +1 Immersturm Predator|KHM +1 Angrath, the Flame-Chained|RIX +1 Obosh, the Preypiercer|IKO +1 Olivia, Crimson Bride|VOW +1 Black Market Tycoon|SNC +1 Territorial Kavu|MH2 +1 Cabaretti Revels|YSNC +1 Dire-Strain Rampage|MID +1 Domri, Anarch of Bolas|WAR +1 Radha, Heart of Keld|M21 +1 Halana and Alena, Partners|VOW +1 Neyith of the Dire Hunt|JMP +1 Escape to the Wilds|ELD +1 Ravager Wurm|RNA +1 Rhys the Redeemed|SHM +1 Join the Dance|MID +1 Rope Line Attendant|YSNC +1 Faeburrow Elder|ELD +1 Torens, Fist of the Angels|VOW +1 Captain Sisay|INV +1 Emiel the Blessed|JMP +1 Arming Gala|YSNC +1 Mirari's Wake|C17 +1 Tolsimir, Friend to Wolves|WAR +1 Trostani Discordant|GRN +1 Blot Out the Sky|STX +1 Despark|WAR +1 Priest of Fell Rites|MH2 +1 Vanishing Verse|STX +1 Lurrus of the Dream-Den|IKO +1 Silverquill Command|STX +1 Kaya the Inexorable|KHM +1 Liesa, Forgotten Archangel|MID +1 Shadrix Silverquill|STX +1 Spirit-Sister's Call|NEO +1 Unburial Rites|ISD +1 Alchemist's Gambit|VOW +1 Electrolyze|STA +1 Lutri, the Spellchaser|IKO +1 Prismari Command|STX +1 Rootha, Mercurial Artist|STX +1 Saheeli, Sublime Artificer|WAR +1 Ral, Storm Conduit|WAR +1 Ral's Outburst|WAR +1 Expansion // Explosion|GRN +1 Niv-Mizzet, Parun|CLB +1 Magma Opus|STX +1 Ravenous Squirrel|MH2 +1 Fiend Artisan|IKO +1 A-Shessra, Death's Whisper|AFR +1 Chatterfang, Squirrel General|MH2 +1 Leyline Prowler|WAR +1 A-Tyvar Kell|KHM +1 Binding the Old Gods|KHM +1 Gitrog, Horror of Zhava|YMID +1 Polukranos, Unchained|THB +1 Vraska, Golgari Queen|GRN +1 Diregraf Rebirth|MID +1 The Gitrog Monster|SOI +1 Lightning Helix|RAV +1 Plargg, Dean of Chaos|STX +1 Rip Apart|STX +1 Sacred Fire|MID +1 Brutal Cathar|MID +1 Deafening Clarion|GRN +1 Rem Karolus, Stalwart Slayer|MID +1 Radiant Scrollwielder|STX +1 Integrity // Intervention|GRN +1 Lorehold Command|STX +1 Firesong and Sunspeaker|DOM +1 Response // Resurgence|GRN +1 Velomachus Lorehold|STX +1 Growth Spiral|CMR +1 Hydroid Krasis|RNA +1 Kinnan, Bonder Prodigy|IKO +1 Suspicious Stowaway|MID +1 Croaking Counterpart|MID +1 Kiora, Behemoth Beckoner|WAR +1 Risen Reef|M20 +1 Uro, Titan of Nature's Wrath|PLIST +1 Rashmi, Eternities Crafter|KLD +1 Volo, Guide to Monsters|AFR +1 Keruga, the Macrosage|IKO +1 Tatyova, Benthic Druid|DOM +1 Falco Spara, Pactweaver|SNC +1 Chulane, Teller of Tales|ELD +1 Raffine, Scheming Seer|SNC +1 Obscura Interceptor|SNC +1 Nicol Bolas, the Ravager|M19 +1 Lord Xander, the Collector|SNC +1 Nicol Bolas, God-Pharaoh|HOU +1 Ognis, the Dragon's Lash|SNC +1 Ziatora's Envoy|SNC +1 Korvold, Fae-Cursed King|ELD +1 Ziatora, the Incinerator|SNC +1 Back-Alley Gardener|YSNC +1 Jinnie Fay, Jetmir's Second|SNC +1 Jetmir, Nexus of Revels|SNC +1 Zacama, Primal Calamity|RIX +1 Kaalia, Zenith Seeker|M20 +1 Extus, Oriq Overlord|STX +1 Ruinous Ultimatum|IKO +1 Mythos of Illuna|IKO +1 Illuna, Apex of Wishes|IKO +1 Genesis Ultimatum|IKO +1 Mythos of Nethroi|IKO +1 Nethroi, Apex of Death|IKO +1 Eerie Ultimatum|IKO +1 Narset of the Ancient Way|IKO +1 Inspired Ultimatum|IKO +1 Yarok, the Desecrated|M20 +1 Muldrotha, the Gravetide|DOM +1 A-Omnath, Locus of Creation|ZNR +1 Courier's Briefcase|SNC +1 Esika, God of the Tree|KHM +1 Kyodai, Soul of Kamigawa|NEO +1 Golos, Tireless Pilgrim|M20 +1 Jegantha, the Wellspring|IKO +1 Kenrith, the Returned King|ELD +1 Maelstrom Archangel|CON +1 Niv-Mizzet Reborn|WAR +1 The Kami War|NEO +1 Tiamat|AFR +1 Ornithopter of Paradise|MH2 +1 Alloy Myr|NPH +1 Circuit Mender|NEO +1 Palladium Myr|SOM +1 Scuttlemutt|SHM +1 Skittering Surveyor|DOM +1 Solemn Simulacrum|MRD +1 Meteor Golem|KHC +1 Platinum Angel|V15 +1 Karn, Scion of Urza|DOM +1 Ugin, the Ineffable|WAR +1 Eater of Virtue|NEO +1 Shadowspear|THB +1 Coldsteel Heart|CM2 +1 Guardian Idol|JMP +1 Mazemind Tome|M21 +1 Mind Stone|AFC +1 Reckoner Bankbuster|NEO +1 Treasure Map|XLN +1 Chromatic Lantern|RTR +1 Cultivator's Caravan|KLD +1 Dragon's Hoard|CLB +1 Heraldic Banner|ELD +1 Letter of Acceptance|STX +1 Skyclave Relic|ZNR +1 The Celestus|MID +1 Firemind Vessel|WAR +1 Hedron Archive|BFZ +1 Key to the Archive|YMID +1 Lithoform Engine|ZNR +1 Primal Amulet|XLN +1 Gilded Lotus|M13 +1 Tome of the Guildpact|RNA +1 The Immortal Sun|RIX +1 Chromatic Orrery|M21 +1 God-Pharaoh's Gift|HOU +1 Deserted Beach|MID +1 Glacial Fortress|XLN +1 Hallowed Fountain|RNA +1 Hengegate Pathway|KHM +1 Irrigated Farmland|AKH +1 Temple of Enlightenment|NEC +1 Clearwater Pathway|ZNR +1 Drowned Catacomb|XLN +1 Fetid Pools|AKH +1 Shipwreck Marsh|MID +1 Temple of Deceit|CLB +1 Watery Grave|GRN +1 Blightstep Pathway|KHM +1 Blood Crypt|RNA +1 Canyon Slough|AKH +1 Dragonskull Summit|XLN +1 Haunted Ridge|MID +1 Temple of Malice|VOC +1 Cragcrown Pathway|ZNR +1 Rockfall Vale|MID +1 Rootbound Crag|XLN +1 Sheltered Thicket|AKH +1 Stomping Ground|RNA +1 Temple of Abandon|NEC +1 Branchloft Pathway|ZNR +1 Overgrown Farmland|MID +1 Scattered Groves|AKH +1 Sunpetal Grove|XLN +1 Temple Garden|GRN +1 Temple of Plenty|THB +1 Brightclimb Pathway|ZNR +1 Concealed Courtyard|KLD +1 Godless Shrine|RNA +1 Isolated Chapel|DOM +1 Shattered Sanctum|VOW +1 Temple of Silence|C21 +1 Riverglide Pathway|ZNR +1 Spirebluff Canal|KLD +1 Steam Vents|GRN +1 Stormcarved Coast|VOW +1 Sulfur Falls|DOM +1 Temple of Epiphany|M20 +1 Blooming Marsh|KLD +1 Darkbore Pathway|KHM +1 Deathcap Glade|VOW +1 Overgrown Tomb|GRN +1 Temple of Malady|C21 +1 Woodland Cemetery|DOM +1 Clifftop Retreat|DOM +1 Inspiring Vantage|KLD +1 Needleverge Pathway|ZNR +1 Sacred Foundry|GRN +1 Sundown Pass|VOW +1 Temple of Triumph|M21 +1 Barkchannel Pathway|KHM +1 Botanical Sanctum|KLD +1 Breeding Pool|SLD +1 Dreamroot Cascade|VOW +1 Hinterland Harbor|ISD +1 Temple of Mystery|M20 +1 Spara's Headquarters|SNC +1 Raffine's Tower|SNC +1 Xander's Lounge|SNC +1 Ziatora's Proving Ground|SNC +1 Jetmir's Garden|SNC +1 Savai Triome|IKO +1 Ketria Triome|IKO +1 Indatha Triome|IKO +1 Raugrin Triome|IKO +1 Zagoth Triome|IKO +1 Boseiju, Who Endures|NEO +1 Eiganjo, Seat of the Empire|NEO +1 Fabled Passage|ELD +1 Field of the Dead|M20 +1 Forsaken Crossroads|YMID +1 Otawara, Soaring City|NEO +1 Sokenzan, Crucible of Defiance|NEO +1 Takenuma, Abandoned Mire|NEO +1 Temple of the Dragon Queen|AFR +1 Thriving Bluff|NCC +1 Thriving Grove|AFC +1 Thriving Heath|NCC +1 Thriving Isle|NCC +1 Thriving Moor|JMP diff --git a/forge-gui/res/cube/The_Arena_Cube_December13_-_January1.dck b/forge-gui/res/cube/The_Arena_Cube_December13_-_January1.dck index 2c3573a7e8f..feeb3dc6762 100644 --- a/forge-gui/res/cube/The_Arena_Cube_December13_-_January1.dck +++ b/forge-gui/res/cube/The_Arena_Cube_December13_-_January1.dck @@ -1,5 +1,5 @@ [metadata] -Name=The Arena Cube , December 13–January 1 +Name=The Arena Cube December 13 – January 1 [Main] 1 Benalish Knight-Counselor|YDMU 1 Dauntless Bodyguard|DOM @@ -550,4 +550,4 @@ Name=The Arena Cube , December 13–January 1 1 Forsaken Crossroads|YMID 1 Hive of the Eye Tyrant|AFR 1 Mutavault|CLB -1 Otawara, Soaring City|NEO +1 Otawara, Soaring City|NEO \ No newline at end of file diff --git a/forge-gui/res/draft/MTGArena_Chromatic_Cube.draft b/forge-gui/res/draft/MTGArena_Chromatic_Cube.draft deleted file mode 100644 index 93bd913aedd..00000000000 --- a/forge-gui/res/draft/MTGArena_Chromatic_Cube.draft +++ /dev/null @@ -1,6 +0,0 @@ -Name:MTGArena Chromatic Cube -DeckFile:MTGArena Chromatic Cube -Singleton:True - -Booster: 15 Any -NumPacks: 3 \ No newline at end of file diff --git a/forge-gui/res/draft/MTGArena_Chromatic_Cube_June_24.draft b/forge-gui/res/draft/MTGArena_Chromatic_Cube_June_24.draft new file mode 100644 index 00000000000..43a7735719b --- /dev/null +++ b/forge-gui/res/draft/MTGArena_Chromatic_Cube_June_24.draft @@ -0,0 +1,6 @@ +Name:MTGArena Chromatic Cube June 24 +DeckFile:MTGArena Chromatic Cube June 24 +Singleton:True + +Booster: 15 Any +NumPacks: 3 \ No newline at end of file diff --git a/forge-gui/res/draft/The_Arena_Cube_December13_-_January1.draft b/forge-gui/res/draft/The_Arena_Cube_December13_-_January1.draft index d9a8f48888c..723295478d4 100644 --- a/forge-gui/res/draft/The_Arena_Cube_December13_-_January1.draft +++ b/forge-gui/res/draft/The_Arena_Cube_December13_-_January1.draft @@ -1,5 +1,5 @@ -Name:The Arena Cube December13 - January1 -DeckFile:The Arena Cube December13 - January1 +Name:The Arena Cube December 13 - January 1 +DeckFile:The Arena Cube December 13 - January 1 Singleton:True Booster: 15 Any From f664b001a2e3c2fd56deb1c37222a80425f939d0 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 22 Feb 2023 06:38:01 +0800 Subject: [PATCH 5/5] update SpellSmithScene selectbox --- .../src/forge/adventure/scene/SpellSmithScene.java | 9 ++++++++- .../src/forge/adventure/util/Controls.java | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java b/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java index f94739f9312..47495508232 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java @@ -117,6 +117,7 @@ public class SpellSmithScene extends UIScene { ui.onButtonPress("BResetEdition", () -> { editionList.setColor(Color.WHITE); edition = ""; + editionList.setUserObject(edition); filterResults(); }); } @@ -267,7 +268,6 @@ public class SpellSmithScene extends UIScene { loadEditions(); //just to be safe since it's preloaded, if somehow edition is null, then reload it editionList.clearListeners(); editionList.clearItems(); - editionList.showScrollPane(); editionList.setItems(editions.toArray(new CardEdition[editions.size()])); editionList.addListener(new ChangeListener() { @Override @@ -278,6 +278,12 @@ public class SpellSmithScene extends UIScene { filterResults(); } }); + editionList.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + editionList.showScrollPane(); + } + }); editionList.setColor(Color.WHITE); filterResults(); super.enter(); @@ -357,6 +363,7 @@ public class SpellSmithScene extends UIScene { pullUsingShards.setText("Pull: " + currentShardPrice + "[+shards]"); pullUsingGold.setDisabled(!(cardPool.size() > 0) || Current.player().getGold() < totalCost); pullUsingShards.setDisabled(!(cardPool.size() > 0) || Current.player().getShards() < currentShardPrice); + editionList.setUserObject(edition); } public void pullCard(boolean usingShards) { diff --git a/forge-gui-mobile/src/forge/adventure/util/Controls.java b/forge-gui-mobile/src/forge/adventure/util/Controls.java index c3f612fca6a..617f92a5775 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Controls.java +++ b/forge-gui-mobile/src/forge/adventure/util/Controls.java @@ -2,7 +2,9 @@ package forge.adventure.util; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.Rectangle; @@ -148,6 +150,15 @@ public class Controls { return (this.isOver() || hasKeyboardFocus()) && this.getStyle().backgroundOver != null ? this.getStyle().backgroundOver : this.getStyle().background; } } + + @Override + protected GlyphLayout drawItem(Batch batch, BitmapFont font, T item, float x, float y, float width) { + if (getUserObject() != null && getUserObject() instanceof String) { //currently this is used on spellsmith... + if (((String) getUserObject()).isEmpty()) + return super.drawItem(batch, font, (T) Forge.getLocalizer().getMessage("lblSelectingFilter"), x, y, width); + } + return super.drawItem(batch, font, item, x, y, width); + } }; } static public SelectBox newComboBox(Float[] text, float item, Function func) {