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