diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 23f5b9e7b31..94e600d655e 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -208,7 +208,7 @@ public class AiAttackController {
*/
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat, final GameEntity defender) {
// if the attacker will die when attacking don't attack
- if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
+ if (attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true) <= 0) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index 89fdf85963d..d8728803d39 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -17,19 +17,11 @@
*/
package forge.ai;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-
import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi;
import forge.ai.ability.LearnAi;
@@ -40,39 +32,17 @@ import forge.card.mana.ManaCost;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
-import forge.game.CardTraitBase;
-import forge.game.CardTraitPredicates;
-import forge.game.Direction;
-import forge.game.Game;
-import forge.game.GameActionUtil;
-import forge.game.GameEntity;
-import forge.game.GlobalRuleChange;
+import forge.game.*;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.SpellApiBased;
-import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.card.CardCollectionView;
-import forge.game.card.CardFactoryUtil;
-import forge.game.card.CardLists;
-import forge.game.card.CardPlayOption;
-import forge.game.card.CardPredicates;
+import forge.game.card.*;
import forge.game.card.CardPredicates.Accessors;
import forge.game.card.CardPredicates.Presets;
-import forge.game.card.CardUtil;
-import forge.game.card.CounterEnumType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
-import forge.game.cost.Cost;
-import forge.game.cost.CostAdjustment;
-import forge.game.cost.CostDiscard;
-import forge.game.cost.CostPart;
-import forge.game.cost.CostPayEnergy;
-import forge.game.cost.CostPayLife;
-import forge.game.cost.CostPutCounter;
-import forge.game.cost.CostRemoveCounter;
-import forge.game.cost.CostSacrifice;
+import forge.game.cost.*;
import forge.game.keyword.Keyword;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
@@ -82,15 +52,7 @@ import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
-import forge.game.spellability.AbilitySub;
-import forge.game.spellability.LandAbility;
-import forge.game.spellability.OptionalCost;
-import forge.game.spellability.OptionalCostValue;
-import forge.game.spellability.Spell;
-import forge.game.spellability.SpellAbility;
-import forge.game.spellability.SpellAbilityCondition;
-import forge.game.spellability.SpellAbilityPredicates;
-import forge.game.spellability.SpellPermanent;
+import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.trigger.Trigger;
@@ -106,6 +68,9 @@ import forge.util.collect.FCollectionView;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
+import java.util.*;
+import java.util.Map.Entry;
+
/**
*
* AiController class.
@@ -1968,6 +1933,38 @@ public class AiController {
// Whims of the Fates {all, 0, 0}
result.addAll(pool);
break;
+ case FlipOntoBattlefield:
+ if ("DamageCreatures".equals(sa.getParam("AILogic"))) {
+ int maxToughness = Integer.valueOf(sa.getSubAbility().getParam("NumDmg"));
+ CardCollectionView rightToughness = CardLists.filter(pool, new Predicate() {
+ @Override
+ public boolean apply(Card card) {
+ return card.getController().isOpponentOf(sa.getActivatingPlayer())
+ && card.getNetToughness() <= maxToughness
+ && card.canBeDestroyed();
+ }
+ });
+ Card bestCreature = ComputerUtilCard.getBestCreatureAI(rightToughness.isEmpty() ? pool : rightToughness);
+ if (bestCreature != null) {
+ result.add(bestCreature);
+ } else {
+ result.add(Aggregates.random(pool)); // should ideally never get here
+ }
+ } else {
+ CardCollectionView viableOptions = CardLists.filter(pool, Predicates.and(CardPredicates.isControlledByAnyOf(sa.getActivatingPlayer().getOpponents())),
+ new Predicate() {
+ @Override
+ public boolean apply(Card card) {
+ return card.canBeDestroyed();
+ }
+ });
+ Card best = ComputerUtilCard.getBestAI(viableOptions);
+ if (best == null) {
+ best = Aggregates.random(pool); // should ideally never get here either
+ }
+ result.add(best);
+ }
+ break;
default:
CardCollection editablePool = new CardCollection(pool);
for (int i = 0; i < max; i++) {
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index af792a1b66b..538064d763f 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -40,7 +40,6 @@ import forge.game.card.CardFactory;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
-import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
@@ -905,7 +904,7 @@ public class ComputerUtilCard {
}
for (final Card crd : list) {
- ColorSet color = CardUtil.getColors(crd);
+ ColorSet color = crd.determineColor();
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));
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
index a841b338cd3..ab6f76c8477 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
@@ -1478,7 +1478,7 @@ public class ComputerUtilCombat {
// DealDamage triggers
if (ApiType.DealDamage.equals(sa.getApi())) {
- if ("TriggeredAttacker".equals(sa.getParam("Defined"))) {
+ if (!sa.hasParam("Defined") || !sa.getParam("Defined").startsWith("TriggeredAttacker")) {
continue;
}
int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa);
diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
index f0829816f72..87c71ebbdf1 100644
--- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
@@ -87,6 +87,7 @@ public enum SpellApiToAi {
.put(ApiType.Explore, ExploreAi.class)
.put(ApiType.Fight, FightAi.class)
.put(ApiType.FlipACoin, FlipACoinAi.class)
+ .put(ApiType.FlipOntoBattlefield, FlipOntoBattlefieldAi.class)
.put(ApiType.Fog, FogAi.class)
.put(ApiType.GainControl, ControlGainAi.class)
.put(ApiType.GainControlVariant, ControlGainVariantAi.class)
@@ -140,6 +141,7 @@ public enum SpellApiToAi {
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
.put(ApiType.RemoveFromGame, AlwaysPlayAi.class)
+ .put(ApiType.RemoveFromMatch, AlwaysPlayAi.class)
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
.put(ApiType.Repeat, RepeatAi.class)
.put(ApiType.RepeatEach, RepeatEachAi.class)
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
index 11e96df2fbd..f6680775cab 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
@@ -260,8 +260,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
// minimum card advantage unless the hand will be fully reloaded
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
+ boolean noDiscard = logic.contains(".noDiscard");
- if (numExiledWithSrc > curHandSize) {
+ if (numExiledWithSrc > curHandSize || (noDiscard && numExiledWithSrc > 0)) {
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
// Try to gain some card advantage if the card will die anyway
// TODO: ideally, should evaluate the hand value and not discard good hands to it
@@ -269,7 +270,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
}
}
- return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
+ return (curHandSize + minAdv - 1 < numExiledWithSrc) || (!noDiscard && numExiledWithSrc >= ai.getMaxHandSize());
}
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
index e76505383bd..7bcc6b80dfb 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
@@ -244,7 +244,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
//if Iona does prevent from casting, allow it to draw
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
- if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
+ if (imprinted.determineColor().hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
return allow;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
index a07e8b2f9bd..f5b182846ce 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
@@ -303,6 +303,8 @@ public class DestroyAi extends SpellAbilityAi {
|| ai.getLife() <= 5)) {
// Basic ai logic for Lethal Vapors
return false;
+ } else if ("Always".equals(logic)) {
+ return true;
}
if (list.isEmpty()
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
index 8faeefc8e7b..331acd2a61a 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
@@ -51,8 +51,8 @@ public class DigUntilAi extends SpellAbilityAi {
// until opponent's end of turn phase!
// But we still want more (and want to fill grave) if nothing better to do then
// This is important for Replenish/Living Death type decks
- if (!((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN))
- && (!ai.getGame().getPhaseHandler().isPlayerTurn(ai)))) {
+ if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
+ && !ai.getGame().getPhaseHandler().isPlayerTurn(ai)) {
return false;
}
}
@@ -75,7 +75,7 @@ public class DigUntilAi extends SpellAbilityAi {
}
final String num = sa.getParam("Amount");
- if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
+ if (num != null && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
SpellAbility root = sa.getRootAbility();
if (root.getXManaCostPaid() == null) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java b/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java
new file mode 100644
index 00000000000..dfce114c0a0
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java
@@ -0,0 +1,48 @@
+package forge.ai.ability;
+
+import com.google.common.base.Predicate;
+import forge.ai.SpellAbilityAi;
+import forge.game.card.Card;
+import forge.game.card.CardCollectionView;
+import forge.game.card.CardLists;
+import forge.game.phase.PhaseHandler;
+import forge.game.phase.PhaseType;
+import forge.game.player.Player;
+import forge.game.player.PlayerActionConfirmMode;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+
+public class FlipOntoBattlefieldAi extends SpellAbilityAi {
+ @Override
+ protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
+ PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler();
+ String logic = sa.getParamOrDefault("AILogic", "");
+
+ if (!SpellAbilityAi.isSorcerySpeed(sa) && sa.getPayCosts().hasManaCost()) {
+ return ph.is(PhaseType.END_OF_TURN);
+ }
+
+ if ("DamageCreatures".equals(logic)) {
+ int maxToughness = Integer.valueOf(sa.getSubAbility().getParam("NumDmg"));
+ CardCollectionView rightToughness = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), new Predicate() {
+ @Override
+ public boolean apply(Card card) {
+ return card.getNetToughness() <= maxToughness && card.canBeDestroyed();
+ }
+ });
+ return !rightToughness.isEmpty();
+ }
+
+ return !aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield).isEmpty();
+ }
+
+ @Override
+ protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
+ return canPlayAI(aiPlayer, sa) || mandatory;
+ }
+
+ @Override
+ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
+ return true;
+ }
+}
diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java
index 35c38076fcc..1bd2a565e0d 100644
--- a/forge-core/src/main/java/forge/StaticData.java
+++ b/forge-core/src/main/java/forge/StaticData.java
@@ -165,11 +165,10 @@ public class StaticData {
return this.editions;
}
- public final CardEdition.Collection getCustomEditions(){
+ public final CardEdition.Collection getCustomEditions() {
return this.customEditions;
}
-
private List sortedEditions;
public final List getSortedEditions() {
if (sortedEditions == null) {
@@ -189,13 +188,13 @@ public class StaticData {
}
private TreeMap> editionsTypeMap;
- public final Map> getEditionsTypeMap(){
- if (editionsTypeMap == null){
+ public final Map> getEditionsTypeMap() {
+ if (editionsTypeMap == null) {
editionsTypeMap = new TreeMap<>();
- for (CardEdition.Type editionType : CardEdition.Type.values()){
+ for (CardEdition.Type editionType : CardEdition.Type.values()) {
editionsTypeMap.put(editionType, new ArrayList<>());
}
- for (CardEdition edition : this.getSortedEditions()){
+ for (CardEdition edition : this.getSortedEditions()) {
CardEdition.Type key = edition.getType();
List editionsOfType = editionsTypeMap.get(key);
editionsOfType.add(edition);
@@ -204,9 +203,9 @@ public class StaticData {
return editionsTypeMap;
}
- public CardEdition getCardEdition(String setCode){
+ public CardEdition getCardEdition(String setCode) {
CardEdition edition = this.editions.get(setCode);
- if (edition == null) // try custom editions
+ if (edition == null) // try custom editions
edition = this.customEditions.get(setCode);
return edition;
}
@@ -231,8 +230,7 @@ public class StaticData {
public void attemptToLoadCard(String cardName){
this.attemptToLoadCard(cardName, null);
}
-
- public void attemptToLoadCard(String cardName, String setCode){
+ public void attemptToLoadCard(String cardName, String setCode) {
CardRules rules = cardReader.attemptToLoadCard(cardName);
CardRules customRules = null;
if (customCardReader != null) {
@@ -257,7 +255,7 @@ public class StaticData {
* @param collectorNumber Card's collector Number
* @return PaperCard instance found in one of the available CardDb databases, or null if not found.
*/
- public PaperCard fetchCard(final String cardName, final String setCode, final String collectorNumber){
+ public PaperCard fetchCard(final String cardName, final String setCode, final String collectorNumber) {
PaperCard card = null;
for (CardDb db : this.getAvailableDatabases().values()) {
card = db.getCard(cardName, setCode, collectorNumber);
@@ -345,7 +343,7 @@ public class StaticData {
return result;
}
- private CardDb matchTargetCardDb(final String cardName){
+ private CardDb matchTargetCardDb(final String cardName) {
// NOTE: any foil request in cardName is NOT taken into account here.
// It's a private method, so it's a fair assumption.
for (CardDb targetDb : this.getAvailableDatabases().values()){
@@ -360,7 +358,7 @@ public class StaticData {
* @param cardName Name of the Card to verify (CASE SENSITIVE)
* @return True if a card with the given input string can be found. False otherwise.
*/
- public boolean isMTGCard(final String cardName){
+ public boolean isMTGCard(final String cardName) {
if (cardName == null || cardName.trim().length() == 0)
return false;
CardDb.CardRequest cr = CardDb.CardRequest.fromString(cardName); // accounts for any foil request ending with +
@@ -450,10 +448,6 @@ public class StaticData {
public Predicate getBrawlPredicate() { return brawlPredicate; }
- public void setFilteredHandsEnabled(boolean filteredHandsEnabled){
- this.filteredHandsEnabled = filteredHandsEnabled;
- }
-
/**
* Get an alternative card print for the given card wrt. the input setReleaseDate.
* The reference release date will be used to retrieve the alternative art, according
@@ -501,7 +495,7 @@ public class StaticData {
*/
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate,
boolean isCardArtPreferenceLatestArt,
- boolean cardArtPreferenceHasFilter){
+ boolean cardArtPreferenceHasFilter) {
Date searchReferenceDate = getReferenceDate(setReleaseDate, isCardArtPreferenceLatestArt);
CardDb.CardArtPreference searchCardArtStrategy = getSearchStrategyForAlternativeCardArt(isCardArtPreferenceLatestArt,
cardArtPreferenceHasFilter);
@@ -537,7 +531,6 @@ public class StaticData {
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt,
boolean cardArtPreferenceHasFilter,
boolean preferCandidatesFromExpansionSets, boolean preferModernFrame) {
-
PaperCard altCard = this.getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt,
cardArtPreferenceHasFilter);
if (altCard == null)
@@ -618,7 +611,7 @@ public class StaticData {
private PaperCard tryToGetCardPrintFromExpansionSet(PaperCard altCard,
boolean isCardArtPreferenceLatestArt,
- boolean preferModernFrame){
+ boolean preferModernFrame) {
CardEdition altCardEdition = editions.get(altCard.getEdition());
if (altCardEdition.getType() == CardEdition.Type.EXPANSION)
return null; // Nothing to do here!
@@ -628,7 +621,7 @@ public class StaticData {
CardDb.CardArtPreference searchStrategy = getSearchStrategyForAlternativeCardArt(searchStrategyFlag,
true);
PaperCard altCandidate = altCard;
- while (altCandidate != null){
+ while (altCandidate != null) {
Date referenceDate = editions.get(altCandidate.getEdition()).getDate();
altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame,
referenceDate, searchStrategy);
@@ -645,7 +638,7 @@ public class StaticData {
private PaperCard tryToGetCardPrintWithMatchingFrame(PaperCard altCard,
boolean isCardArtPreferenceLatestArt,
boolean cardArtHasFilter,
- boolean preferModernFrame){
+ boolean preferModernFrame) {
CardEdition altCardEdition = editions.get(altCard.getEdition());
boolean frameIsCompliantAlready = (altCardEdition.isModern() == preferModernFrame);
if (frameIsCompliantAlready)
@@ -654,7 +647,7 @@ public class StaticData {
CardDb.CardArtPreference searchStrategy = getSearchStrategyForAlternativeCardArt(searchStrategyFlag,
cardArtHasFilter);
PaperCard altCandidate = altCard;
- while (altCandidate != null){
+ while (altCandidate != null) {
Date referenceDate = editions.get(altCandidate.getEdition()).getDate();
altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame,
referenceDate, searchStrategy);
@@ -677,7 +670,7 @@ public class StaticData {
* @param card Instance of target PaperCard
* @return The number of available arts for the given card in the corresponding set, or 0 if not found.
*/
- public int getCardArtCount(PaperCard card){
+ public int getCardArtCount(PaperCard card) {
Collection databases = this.getAvailableDatabases().values();
for (CardDb db: databases){
int artCount = db.getArtCount(card.getName(), card.getEdition());
@@ -687,9 +680,12 @@ public class StaticData {
return 0;
}
- public boolean getFilteredHandsEnabled(){
+ public boolean getFilteredHandsEnabled() {
return filteredHandsEnabled;
}
+ public void setFilteredHandsEnabled(boolean filteredHandsEnabled) {
+ this.filteredHandsEnabled = filteredHandsEnabled;
+ }
public void setMulliganRule(MulliganDefs.MulliganRule rule) {
mulliganRule = rule;
@@ -699,21 +695,21 @@ public class StaticData {
return mulliganRule;
}
- public void setCardArtPreference(boolean latestArt, boolean coreExpansionOnly){
+ public void setCardArtPreference(boolean latestArt, boolean coreExpansionOnly) {
this.commonCards.setCardArtPreference(latestArt, coreExpansionOnly);
this.variantCards.setCardArtPreference(latestArt, coreExpansionOnly);
this.customCards.setCardArtPreference(latestArt, coreExpansionOnly);
}
- public String getCardArtPreferenceName(){
+ public String getCardArtPreferenceName() {
return this.commonCards.getCardArtPreference().toString();
}
- public CardDb.CardArtPreference getCardArtPreference(){
+ public CardDb.CardArtPreference getCardArtPreference() {
return this.commonCards.getCardArtPreference();
}
- public CardDb.CardArtPreference getCardArtPreference(boolean latestArt, boolean coreExpansionOnly){
+ public CardDb.CardArtPreference getCardArtPreference(boolean latestArt, boolean coreExpansionOnly) {
if (latestArt){
return coreExpansionOnly ? CardDb.CardArtPreference.LATEST_ART_CORE_EXPANSIONS_REPRINT_ONLY : CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS;
}
@@ -721,15 +717,15 @@ public class StaticData {
}
- public boolean isCoreExpansionOnlyFilterSet(){ return this.commonCards.getCardArtPreference().filterSets; }
+ public boolean isCoreExpansionOnlyFilterSet() { return this.commonCards.getCardArtPreference().filterSets; }
- public boolean cardArtPreferenceIsLatest(){
+ public boolean cardArtPreferenceIsLatest() {
return this.commonCards.getCardArtPreference().latestFirst;
}
// === MOBILE APP Alternative Methods (using String Labels, not yet localised!!) ===
// Note: only used in mobile
- public String[] getCardArtAvailablePreferences(){
+ public String[] getCardArtAvailablePreferences() {
CardDb.CardArtPreference[] preferences = CardDb.CardArtPreference.values();
String[] preferences_avails = new String[preferences.length];
for (int i = 0; i < preferences.length; i++)
@@ -745,17 +741,16 @@ public class StaticData {
return label.toString().trim();
}
- public void setCardArtPreference(String artPreference){
+ public void setCardArtPreference(String artPreference) {
this.commonCards.setCardArtPreference(artPreference);
this.variantCards.setCardArtPreference(artPreference);
this.customCards.setCardArtPreference(artPreference);
}
- //
- public boolean isEnabledCardArtSmartSelection(){
+ public boolean isEnabledCardArtSmartSelection() {
return this.enableSmartCardArtSelection;
}
- public void setEnableSmartCardArtSelection(boolean isEnabled){
+ public void setEnableSmartCardArtSelection(boolean isEnabled) {
this.enableSmartCardArtSelection = isEnabled;
}
diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java
index 92d6da1feab..cc05b8422db 100644
--- a/forge-core/src/main/java/forge/deck/CardPool.java
+++ b/forge-core/src/main/java/forge/deck/CardPool.java
@@ -53,7 +53,7 @@ public class CardPool extends ItemPool {
public void add(final String cardRequest, final int amount) {
CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest);
- this.add(request.cardName, request.edition, request.artIndex, amount);
+ this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount);
}
public void add(final String cardName, final String setCode) {
@@ -100,7 +100,7 @@ public class CardPool extends ItemPool {
paperCard = StaticData.instance().getCommonCards().getCard(cardName);
selectedDbName = "Common";
}
- if (paperCard == null){
+ if (paperCard == null) {
// after all still null
System.err.println("An unsupported card was requested: \"" + cardName + "\" from \"" + setCode + "\". \n");
paperCard = StaticData.instance().getCommonCards().createUnsupportedCard(cardName);
@@ -130,7 +130,6 @@ public class CardPool extends ItemPool {
}
}
-
/**
* Add all from a List of CardPrinted.
*
@@ -222,10 +221,10 @@ public class CardPool extends ItemPool {
*
* @see CardPool#getCardEditionStatistics(boolean)
*/
- public Map getCardEditionTypeStatistics(boolean includeBasicLands){
+ public Map getCardEditionTypeStatistics(boolean includeBasicLands) {
Map editionTypeStats = new HashMap<>();
Map editionStatistics = this.getCardEditionStatistics(includeBasicLands);
- for(Entry entry : editionStatistics.entrySet()) {
+ for (Entry entry : editionStatistics.entrySet()) {
CardEdition edition = entry.getKey();
int count = entry.getValue();
CardEdition.Type key = edition.getType();
@@ -242,11 +241,11 @@ public class CardPool extends ItemPool {
*
* @return The most frequent CardEdition.Type in the pool, or null if the Pool is empty
*/
- public CardEdition.Type getTheMostFrequentEditionType(){
+ public CardEdition.Type getTheMostFrequentEditionType() {
Map editionTypeStats = this.getCardEditionTypeStatistics(false);
Integer mostFrequentType = 0;
List mostFrequentEditionTypes = new ArrayList<>();
- for (Map.Entry entry : editionTypeStats.entrySet()){
+ for (Map.Entry entry : editionTypeStats.entrySet()) {
if (entry.getValue() > mostFrequentType) {
mostFrequentType = entry.getValue();
mostFrequentEditionTypes.add(entry.getKey());
@@ -271,11 +270,11 @@ public class CardPool extends ItemPool {
* If the count of Modern and PreModern cards is tied, the return value is determined
* by the preferred Card Art Preference settings, namely True if Latest Art, False otherwise.
*/
- public boolean isModern(){
+ public boolean isModern() {
int modernEditionsCount = 0;
int preModernEditionsCount = 0;
Map editionStats = this.getCardEditionStatistics(false);
- for (Map.Entry entry: editionStats.entrySet()){
+ for (Map.Entry entry: editionStats.entrySet()) {
CardEdition edition = entry.getKey();
if (edition.isModern())
modernEditionsCount += entry.getValue();
@@ -364,7 +363,7 @@ public class CardPool extends ItemPool {
private static int getMedianFrequency(List frequencyValues, float meanFrequency) {
int medianFrequency = frequencyValues.get(0);
float refDelta = Math.abs(meanFrequency - medianFrequency);
- for (int i = 1; i < frequencyValues.size(); i++){
+ for (int i = 1; i < frequencyValues.size(); i++) {
int currentFrequency = frequencyValues.get(i);
float delta = Math.abs(meanFrequency - currentFrequency);
if (delta < refDelta) {
@@ -411,7 +410,7 @@ public class CardPool extends ItemPool {
return pool;
}
- public static List> processCardList(final Iterable lines){
+ public static List> processCardList(final Iterable lines) {
List> cardRequests = new ArrayList<>();
if (lines == null)
return cardRequests; // empty list
@@ -466,7 +465,7 @@ public class CardPool extends ItemPool {
public CardPool getFilteredPool(Predicate predicate) {
CardPool filteredPool = new CardPool();
Iterator cardsInPool = this.items.keySet().iterator();
- while (cardsInPool.hasNext()){
+ while (cardsInPool.hasNext()) {
PaperCard c = cardsInPool.next();
if (predicate.apply(c))
filteredPool.add(c, this.items.get(c));
@@ -479,12 +478,12 @@ public class CardPool extends ItemPool {
* @param predicate the Predicate to apply to this CardPool
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
*/
- public CardPool getFilteredPoolWithCardsCount(Predicate predicate){
+ public CardPool getFilteredPoolWithCardsCount(Predicate predicate) {
CardPool filteredPool = new CardPool();
- for(Entry entry : this.items.entrySet()){
+ for (Entry entry : this.items.entrySet()) {
PaperCard pc = entry.getKey();
int count = entry.getValue();
- if(predicate.apply(pc))
+ if (predicate.apply(pc))
filteredPool.add(pc, count);
}
return filteredPool;
diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java
index 139d80183fb..c72c6ef732a 100644
--- a/forge-core/src/main/java/forge/deck/Deck.java
+++ b/forge-core/src/main/java/forge/deck/Deck.java
@@ -25,6 +25,7 @@ import forge.card.CardDb;
import forge.card.CardEdition;
import forge.item.IPaperCard;
import forge.item.PaperCard;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
@@ -216,7 +217,7 @@ public class Deck extends DeckBase implements Iterable(this.deferredSections);
- }
- else
+ } else
referenceDeckLoadingMap = new HashMap<>(loadedSections);
loadedSections = new HashMap<>();
@@ -255,7 +255,7 @@ public class Deck extends DeckBase implements Iterable cardsInSection = s.getValue();
ArrayList cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection);
- if (cardNamesWithNoEdition.size() > 0){
+ if (cardNamesWithNoEdition.size() > 0) {
includeCardsFromUnspecifiedSet = true;
if (smartCardArtSelection)
cardsWithNoEdition.put(sec, cardNamesWithNoEdition);
@@ -267,10 +267,9 @@ public class Deck extends DeckBase implements Iterable cardsInSection = s.getValue();
+ List> originalCardRequests = CardPool.processCardList(cardsInSection);
CardPool pool = CardPool.fromCardList(cardsInSection);
if (pool.countDistinct() == 0)
continue; // pool empty, no card has been found!
@@ -301,13 +301,12 @@ public class Deck extends DeckBase implements Iterable();
for (Entry entry : filteredPool) {
- String poolRequest = getPoolRequest(entry);
+ String poolRequest = getPoolRequest(entry, originalCardRequests);
whiteList.add(poolRequest);
}
validatedSections.put(s.getKey(), whiteList);
- if (filteredPool.countDistinct() != pool.countDistinct()){
-
+ if (filteredPool.countDistinct() != pool.countDistinct()) {
CardPool blackList = pool.getFilteredPoolWithCardsCount(new Predicate() {
@Override
public boolean apply(PaperCard input) {
@@ -317,7 +316,7 @@ public class Deck extends DeckBase implements Iterable entry : blackList) {
DeckSection cardSection = DeckSection.matchingSection(entry.getKey());
- String poolRequest = getPoolRequest(entry);
+ String poolRequest = getPoolRequest(entry, originalCardRequests);
List sectionCardList = validatedSections.getOrDefault(cardSection.name(), null);
if (sectionCardList == null)
sectionCardList = new ArrayList<>();
@@ -331,11 +330,26 @@ public class Deck extends DeckBase implements Iterable entry) {
+ private String getPoolRequest(Entry entry, List> originalCardRequests) {
PaperCard card = entry.getKey();
int amount = entry.getValue();
- String cardRequest = CardDb.CardRequest.compose(card.getName(), card.getEdition(), card.getArtIndex());
- return String.format("%d %s", amount, cardRequest);
+ String poolCardRequest = CardDb.CardRequest.compose(
+ card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(),
+ card.getEdition(), card.getArtIndex());
+ String originalRequestCandidate = null;
+ for (Pair originalRequest : originalCardRequests){
+ String cardRequest = originalRequest.getLeft();
+ if (!StringUtils.startsWithIgnoreCase(poolCardRequest, cardRequest))
+ continue;
+ originalRequestCandidate = cardRequest;
+ int cardAmount = originalRequest.getRight();
+ if (amount == cardAmount)
+ return String.format("%d %s", cardAmount, cardRequest);
+ }
+ // This is just in case, it should never happen as we're
+ if (originalRequestCandidate != null)
+ return String.format("%d %s", amount, originalRequestCandidate);
+ return String.format("%d %s", amount, poolCardRequest);
}
private ArrayList getAllCardNamesWithNoSpecifiedEdition(List cardsInSection) {
@@ -356,9 +370,9 @@ public class Deck extends DeckBase implements Iterable part : parts.entrySet()) {
+ for (Entry part : parts.entrySet()) {
DeckSection deckSection = part.getKey();
- if(deckSection == DeckSection.Planes || deckSection == DeckSection.Schemes || deckSection == DeckSection.Avatar)
+ if (deckSection != DeckSection.Main && deckSection != DeckSection.Sideboard && deckSection != DeckSection.Commander)
continue;
// == 0. First Off, check if there is anything at all to do for the current section
@@ -432,7 +446,7 @@ public class Deck extends DeckBase implements Iterable FN_NAME_SELECTOR = new Function() {
@Override
public String apply(Deck arg1) {
diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java
index 95d0b8a8f2e..6685bc7c153 100644
--- a/forge-core/src/main/java/forge/item/PaperCard.java
+++ b/forge-core/src/main/java/forge/item/PaperCard.java
@@ -123,6 +123,14 @@ public final class PaperCard implements Comparable, InventoryItemFro
}
return this.foiledVersion;
}
+ public PaperCard getUnFoiled() {
+ if (!this.foil)
+ return this;
+
+ PaperCard unFoiledVersion = new PaperCard(this.rules, this.edition, this.rarity,
+ this.artIndex, false, String.valueOf(collectorNumber), this.artist);
+ return unFoiledVersion;
+ }
// @Override
// public String getImageKey() {
@@ -260,7 +268,6 @@ public final class PaperCard implements Comparable, InventoryItemFro
return sortableCNKey;
}
-
@Override
public int compareTo(final IPaperCard o) {
final int nameCmp = name.compareToIgnoreCase(o.getName());
diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java
index 8015ed28220..4ef0be2278e 100644
--- a/forge-game/src/main/java/forge/game/Game.java
+++ b/forge-game/src/main/java/forge/game/Game.java
@@ -830,7 +830,7 @@ public class Game {
c.removeTempController(p);
// return stolen spells
if (c.isInZone(ZoneType.Stack)) {
- SpellAbilityStackInstance si = getStack().getInstanceFromSpellAbility(c.getCastSA());
+ SpellAbilityStackInstance si = getStack().getInstanceMatchingSpellAbilityID(c.getCastSA());
si.setActivatingPlayer(c.getController());
}
if (c.getController().equals(p)) {
@@ -854,7 +854,7 @@ public class Game {
if (p != null && p.isMonarch()) {
// if the player who lost was the Monarch, someone else will be the monarch
- if(p.equals(getPhaseHandler().getPlayerTurn())) {
+ if (p.equals(getPhaseHandler().getPlayerTurn())) {
getAction().becomeMonarch(getNextPlayerAfter(p), null);
} else {
getAction().becomeMonarch(getPhaseHandler().getPlayerTurn(), null);
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 427e372557b..9d3a3454e2d 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -997,6 +997,9 @@ public class GameAction {
}
public void ceaseToExist(Card c, boolean skipTrig) {
+ if (c.isInZone(ZoneType.Stack)) {
+ c.getGame().getStack().remove(c);
+ }
c.getZone().remove(c);
// CR 603.6c other players LTB triggers should work
diff --git a/forge-game/src/main/java/forge/game/Match.java b/forge-game/src/main/java/forge/game/Match.java
index 6177720f34f..beac2accbb4 100644
--- a/forge-game/src/main/java/forge/game/Match.java
+++ b/forge-game/src/main/java/forge/game/Match.java
@@ -1,24 +1,7 @@
package forge.game;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multiset;
+import com.google.common.collect.*;
import com.google.common.eventbus.EventBus;
-
import forge.LobbyPlayer;
import forge.deck.CardPool;
import forge.deck.Deck;
@@ -40,7 +23,11 @@ import forge.util.Localizer;
import forge.util.MyRandom;
import forge.util.collect.FCollectionView;
+import java.util.*;
+import java.util.Map.Entry;
+
public class Match {
+ private static List removedCards = Lists.newArrayList();
private final List players;
private final GameRules rules;
private final String title;
@@ -198,6 +185,12 @@ public class Match {
return myRemovedAnteCards;
}
+ public static List getRemovedCards() { return removedCards; }
+
+ public void removeCard(PaperCard c) {
+ removedCards.add(c);
+ }
+
private static void preparePlayerZone(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil) {
PlayerZone library = player.getZone(zoneType);
List newLibrary = new ArrayList<>();
@@ -257,6 +250,23 @@ public class Match {
}
Deck toChange = psc.getDeck();
+ if (!getRemovedCards().isEmpty()) {
+ CardPool main = new CardPool();
+ main.addAll(toChange.get(DeckSection.Main));
+ CardPool sideboard = new CardPool();
+ sideboard.addAll(toChange.getOrCreate(DeckSection.Sideboard));
+ for (PaperCard c : removedCards) {
+ if (main.contains(c)) {
+ main.remove(c, 1);
+ } else if (sideboard.contains(c)) {
+ sideboard.remove(c, 1);
+ }
+ }
+ toChange.getMain().clear();
+ toChange.getMain().addAll(main);
+ toChange.get(DeckSection.Sideboard).clear();
+ toChange.get(DeckSection.Sideboard).addAll(sideboard);
+ }
List newMain = person.sideboard(toChange, rules.getGameType(), player.getName());
if (null != newMain) {
CardPool allCards = new CardPool();
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index e0969ec98a9..dae0902bba0 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -1789,7 +1789,7 @@ public class AbilityUtils {
if (sq[0].contains("HasNumChosenColors")) {
int sum = 0;
for (Card card : getDefinedCards(c, sq[1], sa)) {
- sum += CardUtil.getColors(card).getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors();
+ sum += card.determineColor().getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors();
}
return sum;
}
@@ -1990,7 +1990,7 @@ public class AbilityUtils {
// Count$CardMulticolor..
if (sq[0].contains("CardMulticolor")) {
- final boolean isMulti = CardUtil.getColors(c).isMulticolor();
+ final boolean isMulti = c.determineColor().isMulticolor();
return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), expr, c, ctb);
}
// Count$Madness..
@@ -2046,7 +2046,7 @@ public class AbilityUtils {
}
if (sq[0].contains("CardNumColors")) {
- return doXMath(CardUtil.getColors(c).countColors(), expr, c, ctb);
+ return doXMath(c.determineColor().countColors(), expr, c, ctb);
}
if (sq[0].contains("CardNumAttacksThisTurn")) {
return doXMath(c.getDamageHistory().getCreatureAttacksThisTurn(), expr, c, ctb);
@@ -3766,7 +3766,7 @@ public class AbilityUtils {
someCards = CardLists.filter(someCards, new Predicate() {
@Override
public boolean apply(final Card c) {
- return CardUtil.getColors(c).isMulticolor();
+ return c.determineColor().isMulticolor();
}
});
}
@@ -3775,7 +3775,7 @@ public class AbilityUtils {
someCards = CardLists.filter(someCards, new Predicate() {
@Override
public boolean apply(final Card c) {
- return CardUtil.getColors(c).isMonoColor();
+ return c.determineColor().isMonoColor();
}
});
}
diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java
index 3aa8a5cda27..af91ff5d542 100644
--- a/forge-game/src/main/java/forge/game/ability/ApiType.java
+++ b/forge-game/src/main/java/forge/game/ability/ApiType.java
@@ -85,6 +85,7 @@ public enum ApiType {
Explore (ExploreEffect.class),
Fight (FightEffect.class),
FlipACoin (FlipCoinEffect.class),
+ FlipOntoBattlefield (FlipOntoBattlefieldEffect.class),
Fog (FogEffect.class),
GainControl (ControlGainEffect.class),
GainControlVariant (ControlGainVariantEffect.class),
@@ -139,6 +140,7 @@ public enum ApiType {
RemoveCounterAll (CountersRemoveAllEffect.class),
RemoveFromCombat (RemoveFromCombatEffect.class),
RemoveFromGame (RemoveFromGameEffect.class),
+ RemoveFromMatch (RemoveFromMatchEffect.class),
ReorderZone (ReorderZoneEffect.class),
Repeat (RepeatEffect.class),
RepeatEach (RepeatEachEffect.class),
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
index b249d308327..990139f880d 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
@@ -8,6 +8,7 @@ import com.google.common.collect.Lists;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
+import forge.game.card.CardUtil;
import forge.game.event.GameEventCardModeChosen;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -81,6 +82,19 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
chosenSAs = p.getController().chooseSpellAbilitiesForEffect(abilities, sa, prompt, amount, ImmutableMap.of());
}
+ for (SpellAbility chosenSA : chosenSAs) {
+ if (sa.hasParam("AtRandom") && sa.getParam("AtRandom").equals("Urza") && chosenSA.usesTargeting()) {
+ List validTargets = CardUtil.getValidCardsToTarget(chosenSA.getTargetRestrictions(), sa);
+ if (validTargets.isEmpty()) {
+ List newChosenSAs = Lists.newArrayList();
+ Aggregates.random(abilities, amount, newChosenSAs);
+ chosenSAs = newChosenSAs;
+ } else {
+ p.getController().chooseTargetsFor(chosenSA);
+ }
+ }
+ }
+
if (!chosenSAs.isEmpty()) {
for (SpellAbility chosenSA : chosenSAs) {
String chosenValue = chosenSA.getDescription();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
index 4b1d514d412..9e4d5a07cba 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
@@ -42,8 +42,9 @@ public class DigEffect extends SpellAbilityEffect {
final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
final List tgtPlayers = getTargetPlayers(sa);
- sb.append(host.getController()).append(" looks at the top ");
- sb.append(Lang.nounWithAmount(numToDig, "card")).append(" of ");
+ sb.append(host.getController()).append(sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")
+ ? " reveals " : " looks at ").append("the top ");
+ sb.append(numToDig == 1 ? "card" : (Lang.getNumeral(numToDig) + " cards")).append(" of ");
if (tgtPlayers.contains(host.getController())) {
sb.append("their ");
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java
index 68d3fabfe48..81f41238da0 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java
@@ -179,12 +179,12 @@ public class DigUntilEffect extends SpellAbilityEffect {
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) {
continue;
}
+ Map moveParams = AbilityKey.newMap();
+ moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
+ moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
Card m = null;
if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) {
c.setController(sa.getActivatingPlayer(), game.getNextTimestamp());
- Map moveParams = AbilityKey.newMap();
- moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
- moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa, moveParams);
if (sa.hasParam("Tapped")) {
c.setTapped(true);
@@ -195,7 +195,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
} else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) {
//Don't do anything
} else {
- m = game.getAction().moveTo(foundDest, c, foundLibPos, sa);
+ m = game.getAction().moveTo(foundDest, c, foundLibPos, sa, moveParams);
}
revealed.remove(c);
if (m != null && !origin.equals(m.getZone().getZoneType())) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java
new file mode 100644
index 00000000000..8f37045d119
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java
@@ -0,0 +1,130 @@
+package forge.game.ability.effects;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import forge.game.Game;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.card.Card;
+import forge.game.card.CardCollection;
+import forge.game.card.CardCollectionView;
+import forge.game.card.CardLists;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+import forge.util.Aggregates;
+import forge.util.Localizer;
+import forge.util.MyRandom;
+
+import java.util.ArrayList;
+
+public class FlipOntoBattlefieldEffect extends SpellAbilityEffect {
+ @Override
+ public void resolve(SpellAbility sa) {
+ // Basic parameters defining the chances
+ final float chanceToFlip = 0.85f;
+ final int maxFlipTimes = 2;
+ final float chanceToHit = 0.70f;
+ final float chanceToHitTwoCards = 0.20f;
+
+ final Card host = sa.getHostCard();
+ final Player p = sa.getActivatingPlayer();
+ final Game game = host.getGame();
+ boolean flippedOnce = false;
+
+ // TODO: allow to make a bounding box of sorts somehow, ideally - upgrade to a full system allowing to actually target by location
+ CardCollectionView tgtBox = p.getController().chooseCardsForEffect(game.getCardsIn(ZoneType.Battlefield), sa, Localizer.getInstance().getMessage("lblChooseDesiredLocation"), 1, 1, sa.hasParam("AllowRandom"), null);
+
+ Card tgtLoc = tgtBox.getFirst();
+
+ Card lhsNeighbor = getNeighboringCard(tgtLoc, -1);
+ Card rhsNeighbor = getNeighboringCard(tgtLoc, 1);
+
+ CardCollection randChoices = new CardCollection();
+ randChoices.add(tgtLoc);
+ if (lhsNeighbor != null) {
+ randChoices.add(lhsNeighbor);
+ } else if (rhsNeighbor != null) {
+ randChoices.add(rhsNeighbor);
+ }
+
+ // TODO: would be fun to add a small chance (e.g. 3-5%) to land unpredictably on some random target?
+
+ flippedOnce = MyRandom.getRandom().nextFloat() <= chanceToFlip; // 20% chance that the card won't flip even once
+ if (!flippedOnce) {
+ sa.setSVar("TimesFlipped", "0");
+ game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblDidNotFlipOver"), null);
+ return;
+ } else {
+ int flippedTimes = MyRandom.getRandom().nextInt(maxFlipTimes) + 1;
+ sa.setSVar("TimesFlipped", String.valueOf(flippedTimes)); // Currently the exact # of times is unused
+ game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblFlippedOver", flippedTimes), null);
+ }
+
+ // Choose what was hit
+ CardCollection hit = new CardCollection();
+ float outcome = MyRandom.getRandom().nextFloat();
+ if (outcome <= chanceToHitTwoCards) {
+ hit.addAll(Aggregates.random(randChoices, randChoices.size() > 1 ? 2 : 1));
+ if (hit.size() == 2) {
+ game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblLandedOnTwoCards", hit.getFirst(), hit.getLast()), null);
+ } else {
+ game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblLandedOnOneCard", hit.getFirst()), null);
+ }
+ }
+ else if (outcome <= chanceToHit) {
+ hit.add(Aggregates.random(randChoices));
+ game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblLandedOnOneCard", hit.getFirst()), null);
+ } else {
+ game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblDidNotLandOnCards"), null);
+ }
+
+ // Remember whatever was hit
+ for (Card c : hit) {
+ host.addRemembered(c);
+ }
+ }
+
+ @Override
+ protected String getStackDescription(SpellAbility sa) {
+ final StringBuilder sb = new StringBuilder();
+ final Card host = sa.getHostCard();
+ final Player p = sa.getActivatingPlayer();
+
+ sb.append("Flip ");
+ sb.append(host.toString());
+ sb.append(" onto the battlefield from a height of at least one foot.");
+
+ return sb.toString();
+ }
+
+ private Card getNeighboringCard(Card c, int direction) {
+ // Currently gets the nearest (in zone order) card to the left or to the right of the designated one by type
+ Player controller = c.getController();
+ ArrayList cardsOTB = Lists.newArrayList(CardLists.filter(
+ controller.getCardsIn(ZoneType.Battlefield), new Predicate() {
+ @Override
+ public boolean apply(Card card) {
+ if (c.isCreature()) {
+ return card.isCreature();
+ } else if (c.isPlaneswalker() || c.isArtifact() || (c.isEnchantment() && !c.isAura())) {
+ return card.isPlaneswalker() || card.isArtifact() || (c.isEnchantment() && !c.isAura());
+ } else if (c.isLand()) {
+ return card.isLand();
+ } else if (c.isAttachedToEntity()) {
+ return card.isAttachedToEntity(c.getEntityAttachedTo()) || c.equals(card.getAttachedTo());
+ }
+ return card.sharesCardTypeWith(c);
+ }
+ }
+ ));
+
+ int loc = cardsOTB.indexOf(c);
+ if (direction < 0 && loc > 0) {
+ return cardsOTB.get(loc - 1);
+ } else if (loc < cardsOTB.size() - 1) {
+ return cardsOTB.get(loc + 1);
+ }
+
+ return c;
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
index 3b0e4e33212..d56de13f3fb 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
@@ -99,12 +99,8 @@ public class MillEffect extends SpellAbilityEffect {
} else if (dest.equals(ZoneType.Ante)) {
sb.append("antes ");
}
- sb.append(numCards);
- sb.append(" card");
- if (numCards != 1) {
- sb.append("s");
- }
- sb.append(".");
+
+ sb.append(numCards == 1 ? "a card" : (Lang.getNumeral(numCards) + " cards")).append(".");
return sb.toString();
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java
index c82b353a5a0..5269c48def3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java
@@ -15,7 +15,6 @@ import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardFactory;
-import forge.game.card.CardUtil;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.item.PaperCard;
@@ -35,7 +34,7 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
}
// current color of source card
- final ColorSet color = CardUtil.getColors(source);
+ final ColorSet color = source.determineColor();
if (color.isColorless()) {
return;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java
index f7e111cb6ef..475a3801502 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java
@@ -15,7 +15,6 @@ import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
-import forge.game.card.CardUtil;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -65,7 +64,7 @@ public class ProtectAllEffect extends SpellAbilityEffect {
}
} else if (sa.getParam("Gains").equals("TargetedCardColor")) {
for (final Card c : sa.getSATargetingCard().getTargets().getTargetCards()) {
- ColorSet cs = CardUtil.getColors(c);
+ ColorSet cs = c.determineColor();
for (byte col : MagicColor.WUBRG) {
if (cs.hasAnyColor(col))
gains.add(MagicColor.toLongString(col).toLowerCase());
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java
new file mode 100644
index 00000000000..f06bc534c39
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java
@@ -0,0 +1,37 @@
+package forge.game.ability.effects;
+
+import forge.game.ability.AbilityUtils;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.card.Card;
+import forge.game.card.CardCollection;
+import forge.game.event.GameEventRandomLog;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+import forge.item.PaperCard;
+
+public class RemoveFromMatchEffect extends SpellAbilityEffect {
+ @Override
+ public void resolve(SpellAbility sa) {
+ final Card host = sa.getHostCard();
+ CardCollection toRemove;
+
+ if (sa.hasParam("RemoveType")) {
+ CardCollection cards = (CardCollection) host.getOwner().getGame().getCardsInGame();
+ if (sa.hasParam("IncludeSideboard")) {
+ CardCollection sideboard = (CardCollection) host.getGame().getCardsIn(ZoneType.Sideboard);
+ cards.addAll(sideboard);
+ }
+ toRemove = (CardCollection) AbilityUtils.filterListByType(cards, sa.getParam("RemoveType"), sa);
+ } else {
+ toRemove = getTargetCards(sa);
+ }
+ String logMessage = sa.getParamOrDefault("LogMessage", "Removed from match");
+ String remove = toRemove.toString().replace("[","").replace("]","");
+ host.getController().getGame().fireEvent(new GameEventRandomLog(logMessage + ": " + remove));
+ for (final Card tgtC : toRemove) {
+ tgtC.getGame().getAction().ceaseToExist(tgtC, true);
+ PaperCard rem = (PaperCard) tgtC.getPaperCard();
+ host.getGame().getMatch().removeCard(rem);
+ }
+ }
+}
\ No newline at end of file
diff --git a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java
index 746d6190d3e..869198b340b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java
@@ -51,7 +51,7 @@ public class TwoPilesEffect extends SpellAbilityEffect {
zone = ZoneType.smartValueOf(sa.getParam("Zone"));
}
- final String valid = sa.getParamOrDefault("ValidCards", "");
+ final String valid = sa.getParamOrDefault("ValidCards", "Card");
final TargetRestrictions tgt = sa.getTargetRestrictions();
final List tgtPlayers = getTargetPlayers(sa);
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index cd0dce80ae2..634ab28de32 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -4937,17 +4937,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
view.updateEmblem(this);
}
- /*
- * there are easy checkers for Color. The CardUtil functions should be made
- * part of the Card class, so calling out is not necessary
- */
- public final boolean isOfColor(final String col) { return CardUtil.getColors(this).hasAnyColor(MagicColor.fromName(col)); }
- public final boolean isBlack() { return CardUtil.getColors(this).hasBlack(); }
- public final boolean isBlue() { return CardUtil.getColors(this).hasBlue(); }
- public final boolean isRed() { return CardUtil.getColors(this).hasRed(); }
- public final boolean isGreen() { return CardUtil.getColors(this).hasGreen(); }
- public final boolean isWhite() { return CardUtil.getColors(this).hasWhite(); }
- public final boolean isColorless() { return CardUtil.getColors(this).isColorless(); }
+ public final boolean isOfColor(final String col) { return determineColor().hasAnyColor(MagicColor.fromName(col)); }
+ public final boolean isBlack() { return determineColor().hasBlack(); }
+ public final boolean isBlue() { return determineColor().hasBlue(); }
+ public final boolean isRed() { return determineColor().hasRed(); }
+ public final boolean isGreen() { return determineColor().hasGreen(); }
+ public final boolean isWhite() { return determineColor().hasWhite(); }
+ public final boolean isColorless() { return determineColor().isColorless(); }
public final boolean sharesNameWith(final Card c1) {
// in a corner case where c1 is null, there is no name to share with.
@@ -5891,6 +5887,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
@Override
public final boolean canBeTargetedBy(final SpellAbility sa) {
+ if (getOwner().hasLost()) {
+ return false;
+ }
+
if (sa == null) {
return true;
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java
index 1ffae8fd159..1c619716ffa 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactory.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactory.java
@@ -104,6 +104,11 @@ public class CardFactory {
out.setCommander(in.isRealCommander());
//out.setFaceDown(in.isFaceDown());
+ int foil = in.getCurrentState().getFoil();
+ if (foil > 0) {
+ out.setFoil(foil);
+ }
+
return out;
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index 36e2479e4e2..11166d7fad0 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -338,7 +338,7 @@ public class CardFactoryUtil {
}
for (final Card crd : list) {
- ColorSet color = CardUtil.getColors(crd);
+ ColorSet color = crd.determineColor();
for (int i = 0; i < cntColors; i++) {
if (color.hasAnyColor(MagicColor.WUBRG[i]))
map[i]++;
@@ -376,7 +376,7 @@ public class CardFactoryUtil {
}
for (final Card crd : list) {
- ColorSet color = CardUtil.getColors(crd);
+ ColorSet color = crd.determineColor();
for (int i = 0; i < cntColors; i++) {
if (color.hasAnyColor(MagicColor.WUBRG[i]))
map[i]++;
@@ -407,7 +407,7 @@ public class CardFactoryUtil {
}
for (final Card crd : list) {
- ColorSet color = CardUtil.getColors(crd);
+ ColorSet color = crd.determineColor();
for (int i = 0; i < cntColors; i++) {
if (color.hasAnyColor(colorRestrictions.get(i))) {
map[i]++;
diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java
index d8712ecacab..55912ec3aa8 100644
--- a/forge-game/src/main/java/forge/game/card/CardPredicates.java
+++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java
@@ -264,7 +264,7 @@ public final class CardPredicates {
return new Predicate() {
@Override
public boolean apply(final Card c) {
- return CardUtil.getColors(c).hasAnyColor(color);
+ return c.determineColor().hasAnyColor(color);
}
};
} // getColor()
@@ -273,7 +273,7 @@ public final class CardPredicates {
return new Predicate() {
@Override
public boolean apply(final Card c) {
- return CardUtil.getColors(c).hasExactlyColor(color);
+ return c.determineColor().hasExactlyColor(color);
}
};
}
@@ -282,7 +282,7 @@ public final class CardPredicates {
return new Predicate() {
@Override
public boolean apply(final Card c) {
- return CardUtil.getColors(c).isColorless();
+ return c.determineColor().isColorless();
}
};
}
diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java
index 95bbfd2f933..2025be855c7 100644
--- a/forge-game/src/main/java/forge/game/card/CardProperty.java
+++ b/forge-game/src/main/java/forge/game/card/CardProperty.java
@@ -689,7 +689,7 @@ public class CardProperty {
break;
case "MostProminentColor":
byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield));
- if (!CardUtil.getColors(card).hasAnyColor(mask))
+ if (!card.determineColor().hasAnyColor(mask))
return false;
break;
case "LastCastThisTurn":
@@ -703,7 +703,7 @@ public class CardProperty {
if (castSA == null) {
return false;
}
- if (!CardUtil.getColors(card).hasAnyColor(castSA.getPayingColors().getColor())) {
+ if (!card.determineColor().hasAnyColor(castSA.getPayingColors().getColor())) {
return false;
}
break;
diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java
index d5b2df735df..b833f3543fd 100644
--- a/forge-game/src/main/java/forge/game/card/CardUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardUtil.java
@@ -85,10 +85,6 @@ public final class CardUtil {
return false;
}
- public static ColorSet getColors(final Card c) {
- return c.determineColor();
- }
-
public static boolean isStackingKeyword(final String keyword) {
String kw = keyword;
if (kw.startsWith("HIDDEN")) {
@@ -158,7 +154,6 @@ public final class CardUtil {
public static List getLastTurnCast(final String valid, final Card src, final CardTraitBase ctb) {
return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, src.getController(), src, ctb);
-
}
public static List getLKICopyList(final Iterable in, Map cachedMap) {
@@ -343,7 +338,7 @@ public final class CardUtil {
byte combinedColor = 0;
for (Card tgt : tgts) {
- ColorSet cs = CardUtil.getColors(tgt);
+ ColorSet cs = tgt.determineColor();
for (byte color : MagicColor.WUBRG) {
if(!cs.hasAnyColor(color))
continue;
diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
index cd2a209d345..03338e6ee8c 100644
--- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
+++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
@@ -158,7 +158,7 @@ public class TokenInfo {
if (!colorMap.isEmpty()) {
if (!result.isColorless()) {
// change Token Colors
- byte color = CardUtil.getColors(result).getColor();
+ byte color = result.determineColor().getColor();
for (final Map.Entry e : colorMap.entrySet()) {
byte v = MagicColor.fromName(e.getValue());
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java
index 44f8535dcfe..1ba5c680b6b 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java
@@ -34,7 +34,6 @@ import forge.game.GameObjectPredicates;
import forge.game.GameType;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
-import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -300,9 +299,9 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
if (first == null) {
return false;
}
- byte firstColor = CardUtil.getColors(first).getColor();
+ byte firstColor = first.determineColor().getColor();
for (Card c : tgts) {
- if (CardUtil.getColors(c).getColor() != firstColor) {
+ if (c.determineColor().getColor() != firstColor) {
return false;
}
}
diff --git a/forge-gui-desktop/src/main/config/forge.sh b/forge-gui-desktop/src/main/config/forge.sh
index 1e3165f2ed0..3edf69ceec2 100644
--- a/forge-gui-desktop/src/main/config/forge.sh
+++ b/forge-gui-desktop/src/main/config/forge.sh
@@ -1,3 +1,52 @@
-#!/bin/sh
+#!/bin/bash
+
+# returns the JDK version.
+# 8 for 1.8.0_nn, 9 for 9-ea etc, and "no_java" for undetected
+# Based on the code from this source: https://eed3si9n.com/detecting-java-version-bash
+jdk_version() {
+ local result
+ local java_cmd
+ if [[ -n $(type -p java) ]]
+ then
+ java_cmd=java
+ elif [[ (-n "$JAVA_HOME") && (-x "$JAVA_HOME/bin/java") ]]
+ then
+ java_cmd="$JAVA_HOME/bin/java"
+ fi
+ local IFS=$'\n'
+ # remove \r for Cygwin
+ local lines=$("$java_cmd" -Xms32M -Xmx32M -version 2>&1 | tr '\r' '\n')
+ if [[ -z $java_cmd ]]
+ then
+ result=no_java
+ else
+ for line in $lines; do
+ if [[ (-z $result) && ($line = *"version \""*) ]]
+ then
+ local ver=$(echo $line | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q')
+ # on macOS, sed doesn't support '?'
+ if [[ $ver = "1."* ]]
+ then
+ result=$(echo $ver | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q')
+ else
+ result=$(echo $ver | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q')
+ fi
+ fi
+ done
+ fi
+ echo "$result"
+}
+v="$(jdk_version)"
+
+SHAREDPARAMS='-Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$'
cd $(dirname "${0}")
-java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
+
+if [[ $v -ge 17 ]]
+then
+ java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED $SHAREDPARAMS
+elif [[ $v -ge 11 ]]
+then
+ java --illegal-access=permit $SHAREDPARAMS
+else
+ java $SHAREDPARAMS
+fi
diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java
index 67fde1fa346..51b720726d1 100644
--- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java
+++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java
@@ -371,7 +371,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
@Override
public void deckTypeSelected(final DecksComboBoxEvent ev) {
if (ev.getDeckType() == DeckType.NET_ARCHIVE_STANDARD_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -398,7 +398,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
return;
} else if (ev.getDeckType() == DeckType.NET_ARCHIVE_PIONEER_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -424,7 +424,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
return;
} else if (ev.getDeckType() == DeckType.NET_ARCHIVE_MODERN_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -450,7 +450,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
return;
} else if (ev.getDeckType() == DeckType.NET_ARCHIVE_PAUPER_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -476,7 +476,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
return;
} else if (ev.getDeckType() == DeckType.NET_ARCHIVE_LEGACY_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -502,7 +502,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
return;
} else if (ev.getDeckType() == DeckType.NET_ARCHIVE_VINTAGE_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -528,7 +528,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
return;
} else if (ev.getDeckType() == DeckType.NET_ARCHIVE_BLOCK_DECK && !refreshingDeckType) {
- if(lstDecks.getGameType() != GameType.Constructed)
+ if (lstDecks.getGameType() != GameType.Constructed)
return;
FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks
@Override
@@ -712,7 +712,6 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
prefs.save();
}
-
private String getState() {
final StringBuilder state = new StringBuilder();
DeckType selectedDeckType = this.selectedDeckType; // decksComboBox.getDeckType()
@@ -827,7 +826,6 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
}
}
-
private List getSelectedDecksFromSavedState(final String savedState) {
try {
if (StringUtils.isBlank(savedState)) {
diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java
index 09d36720e7b..62ea4e41347 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java
@@ -216,7 +216,6 @@ public abstract class ACEditorBase itemEntry : itemsToAdd) {
-
final TItem item = itemEntry.getKey();
final PaperCard card = item instanceof PaperCard ? (PaperCard)item : null;
int qty = itemEntry.getValue();
@@ -224,8 +223,7 @@ public abstract class ACEditorBase {
/**
* Load deck from file or clipboard
*/
- public void loadDeck(Deck deck){
+ public void loadDeck(Deck deck) {
this.loadDeck(deck, true);
}
-
public void loadDeck(Deck deck, boolean substituteCurrentDeck) {
boolean isStored;
if (view.getCatalogManager().isInfinite()) {
@@ -159,7 +158,6 @@ public class DeckController {
}
private void pickFromCatalog(Map countByName, CardPool catalog, CardPool targetSection) {
-
CardPool catalogClone = new CardPool(catalog); // clone to iterate modified collection
for (Map.Entry entry : catalogClone) {
PaperCard availableCard = entry.getKey();
@@ -218,7 +216,6 @@ public class DeckController {
public void setModel(final T document) {
setModel(document, false);
}
-
private void setModel(final T document, final boolean isStored) {
model = document;
onModelChanged(isStored);
@@ -234,8 +231,7 @@ public class DeckController {
if (isStored) {
if (isModelInSyncWithFolder()) {
setSaved(true);
- }
- else {
+ } else {
notifyModelChanged();
}
} else { //TODO: Make this smarter
@@ -317,8 +313,7 @@ public class DeckController {
final T newModel = currentFolder.get(name);
if (newModel != null) {
setModel((T) newModel.copyTo(name), true);
- }
- else {
+ } else {
setSaved(true);
}
}
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java
index 3052d37dfa1..369aaac808b 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuDownloaders.java
@@ -8,7 +8,6 @@ import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
@@ -96,7 +95,6 @@ public enum VSubmenuDownloaders implements IVSubmenu {
pnlContent.setOpaque(false);
if (javaRecentEnough()) {
-
// With Blacksmith we would upload the releases and the /latest would redirect to the right URL
// That currently doesn't happen so lets comment out this button for now
// pnlContent.add(btnCheckForUpdates, constraintsBTN);
@@ -122,9 +120,7 @@ public enum VSubmenuDownloaders implements IVSubmenu {
pnlContent.add(btnDownloadSkins, constraintsBTN);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadSkins")), constraintsLBL);
-
} else {
-
String text = localizer.getMessage("lblYourVersionOfJavaIsTooOld");
FLabel label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build();
pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 30px 3px");
@@ -137,7 +133,6 @@ public enum VSubmenuDownloaders implements IVSubmenu {
text = text + " . " + localizer.getMessage("lblYouNeedAtLeastJavaVersion") ;
label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build();
pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 0 36px");
-
}
pnlContent.add(btnListImageData, constraintsBTN);
@@ -154,15 +149,12 @@ public enum VSubmenuDownloaders implements IVSubmenu {
pnlContent.add(btnLicensing, constraintsBTN);
pnlContent.add(_makeLabel(localizer.getMessage("lblLicensing")), constraintsLBL);
-
}
private boolean javaRecentEnough() {
-
RuntimeVersion javaVersion = RuntimeVersion.of(System.getProperty("java.version"));
return javaVersion.getMajor() >= 9 || (javaVersion.getMajor() >= 1 && (javaVersion.getMinor() > 8 || (javaVersion.getMinor() == 8 && javaVersion.getUpdate() >= 101)));
-
}
/* (non-Javadoc)
@@ -199,7 +191,7 @@ public enum VSubmenuDownloaders implements IVSubmenu {
public void setHowToPlayCommand(UiCommand command) { btnHowToPlay.setCommand(command); }
public void setDownloadPricesCommand(UiCommand command) { btnDownloadPrices.setCommand(command); }
public void setLicensingCommand(UiCommand command) { btnLicensing.setCommand(command); }
- public void setDownloadSkinsCommand(UiCommand command) { btnDownloadSkins.setCommand(command); }
+ public void setDownloadSkinsCommand(UiCommand command) { btnDownloadSkins.setCommand(command); }
public void focusTopButton() {
btnDownloadPics.requestFocusInWindow();
@@ -269,7 +261,6 @@ public enum VSubmenuDownloaders implements IVSubmenu {
String imagePath;
int artIndex = 1;
- ArrayList cis = new ArrayList<>();
HashMap> cardCount = new HashMap<>();
for (CardInSet c : e.getAllCardsInSet()) {
@@ -302,10 +293,7 @@ public enum VSubmenuDownloaders implements IVSubmenu {
continue;
}
-
- //
// check the front image
- //
imagePath = ImageUtil.getImageRelativePath(cp, false, true, false);
if (imagePath != null) {
File file = ImageKeys.getImageFile(imagePath);
@@ -319,9 +307,7 @@ public enum VSubmenuDownloaders implements IVSubmenu {
}
}
- //
// check the back face
- //
if (cp.hasBackFace()) {
imagePath = ImageUtil.getImageRelativePath(cp, true, true, false);
if (imagePath != null) {
@@ -372,7 +358,6 @@ public enum VSubmenuDownloaders implements IVSubmenu {
nifSB.append("\n");
}
-
String totalStats = "Missing images: " + missingCount + "\nUnimplemented cards: " + notImplementedCount + "\n";
cniSB.append("\n-----------\n");
cniSB.append(totalStats);
@@ -424,7 +409,6 @@ public enum VSubmenuDownloaders implements IVSubmenu {
}
});
}
-
public void showLicensing() {
String license = "Forge License Information
"
diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java
index 54c41a01543..b2d4ebd7252 100644
--- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java
+++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java
@@ -23,7 +23,7 @@ public class IntegerConstraint {
@Override
public String toString() {
- if( min == max ) {
+ if (min == max) {
return String.valueOf( min );
}
return "between " + min + " and " + max;
diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
index 1fbb5bb5a59..80fda456699 100644
--- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java
+++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
@@ -339,17 +339,14 @@ public class FDeckEditor extends TabPageScreen {
//hide deck header on while drafting
setDeck(new Deck());
deckHeader.setVisible(false);
- }
- else {
+ } else {
if (newDeck == null) {
editorType.getController().newModel();
- }
- else {
+ } else {
editorType.getController().setDeck(newDeck);
}
}
- }
- else {
+ } else {
if (editorType == EditorType.Draft || editorType == EditorType.QuestDraft) {
tabPages[0].hideTab(); //hide Draft Pack page if editing existing draft deck
}
@@ -897,8 +894,7 @@ public class FDeckEditor extends TabPageScreen {
public void handleEvent(FEvent e) {
if (max == 1) {
callback.run(max);
- }
- else {
+ } else {
final Localizer localizer = Localizer.getInstance();
GuiChoose.getInteger(cardManager.getSelectedItem() + " - " + verb + " " + localizer.getMessage("lblHowMany"), 1, max, 20, callback);
}
@@ -1150,8 +1146,7 @@ public class FDeckEditor extends TabPageScreen {
default:
// Do nothing
}
- }
- else {
+ } else {
//if a commander has been set, only show cards that match its color identity
switch (editorType) {
case Commander:
@@ -1176,8 +1171,7 @@ public class FDeckEditor extends TabPageScreen {
default:
if (cardManager.getWantUnique()) {
cardManager.setPool(editorType.applyCardFilter(FModel.getUniqueCardsNoAlt(), additionalFilter), true);
- }
- else {
+ } else {
cardManager.setPool(editorType.applyCardFilter(FModel.getAllCardsNoAlt(), additionalFilter), true);
}
break;
@@ -1250,8 +1244,7 @@ public class FDeckEditor extends TabPageScreen {
CardPreferences.save();
}
}));
- }
- else {
+ } else {
menu.addItem(new FMenuItem(localizer.getMessage("lblRemoveFavorites"), Forge.hdbuttons ? FSkinImage.HDSTAR_OUTLINE : FSkinImage.STAR_OUTLINE, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
@@ -1376,8 +1369,7 @@ public class FDeckEditor extends TabPageScreen {
protected void updateCaption() {
if (deckSection == DeckSection.Commander) {
caption = captionPrefix; //don't display count for commander section since it won't be more than 1
- }
- else {
+ } else {
caption = captionPrefix + " (" + parentScreen.getDeck().get(deckSection).countAll() + ")";
}
}
@@ -1767,8 +1759,7 @@ public class FDeckEditor extends TabPageScreen {
if (isStored) {
if (isModelInSyncWithFolder()) {
setSaved(true);
- }
- else {
+ } else {
notifyModelChanged();
}
}
@@ -1779,8 +1770,7 @@ public class FDeckEditor extends TabPageScreen {
}
if (model != null) {
editor.setDeck(model.getHumanDeck());
- }
- else {
+ } else {
editor.setDeck(null);
}
diff --git a/forge-gui-mobile/src/forge/itemmanager/ItemManager.java b/forge-gui-mobile/src/forge/itemmanager/ItemManager.java
index 0885dd95fd9..6a059d9581d 100644
--- a/forge-gui-mobile/src/forge/itemmanager/ItemManager.java
+++ b/forge-gui-mobile/src/forge/itemmanager/ItemManager.java
@@ -525,6 +525,9 @@ public abstract class ItemManager extends FContainer im
}
public void addItem(final T item, int qty) {
+ if (pool == null) {
+ return;
+ }
pool.add(item, qty);
if (isUnfiltered()) {
model.addItem(item, qty);
diff --git a/forge-gui/res/cardsfolder/b/bag_of_holding.txt b/forge-gui/res/cardsfolder/b/bag_of_holding.txt
index 501646deb76..1833b22d7f3 100644
--- a/forge-gui/res/cardsfolder/b/bag_of_holding.txt
+++ b/forge-gui/res/cardsfolder/b/bag_of_holding.txt
@@ -6,6 +6,6 @@ SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Des
A:AB$ Draw | Cost$ 2 T | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
AI:RemoveDeck:All
-A:AB$ ChangeZoneAll | Cost$ 4 T Sac<1/CARDNAME> | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | AILogic$ DiscardAllAndRetExiled.minAdv2 | SpellDescription$ Return all cards exiled with CARDNAME to their owner's hand.
+A:AB$ ChangeZoneAll | Cost$ 4 T Sac<1/CARDNAME> | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | AILogic$ DiscardAllAndRetExiled.noDiscard.minAdv2 | SpellDescription$ Return all cards exiled with CARDNAME to their owner's hand.
AI:RemoveDeck:Random
Oracle:Whenever you discard a card, exile that card from your graveyard.\n{2}, {T}: Draw a card, then discard a card.\n{4}, {T}, Sacrifice Bag of Holding: Return all cards exiled with Bag of Holding to their owner's hand.
diff --git a/forge-gui/res/cardsfolder/b/blacker_lotus.txt b/forge-gui/res/cardsfolder/b/blacker_lotus.txt
new file mode 100644
index 00000000000..1dbe01751b9
--- /dev/null
+++ b/forge-gui/res/cardsfolder/b/blacker_lotus.txt
@@ -0,0 +1,6 @@
+Name:Blacker Lotus
+ManaCost:0
+Types:Artifact
+A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 4 | AILogic$ BlackLotus | SubAbility$ DBTearUpAndRemove | SpellDescription$ Tear CARDNAME into pieces. Add four mana of any one color. Remove the pieces from the game.
+SVar:DBTearUpAndRemove:DB$ RemoveFromMatch | Defined$ Self | LogMessage$ Torn to pieces and removed
+Oracle:{T}: Tear Blacker Lotus into pieces. Add four mana of any one color. Remove the pieces from the game.
diff --git a/forge-gui/res/cardsfolder/c/chaos_orb.txt b/forge-gui/res/cardsfolder/c/chaos_orb.txt
new file mode 100644
index 00000000000..bb504ed4944
--- /dev/null
+++ b/forge-gui/res/cardsfolder/c/chaos_orb.txt
@@ -0,0 +1,8 @@
+Name:Chaos Orb
+ManaCost:2
+Types:Artifact
+A:AB$ FlipOntoBattlefield | Cost$ 1 T | SubAbility$ DBDestroyTouched | ActivationZone$ Battlefield | SpellDescription$ If CARDNAME is on the battlefield, flip CARDNAME onto the battlefield from a height of at least one foot. If CARDNAME turns over completely at least once during the flip, destroy all nontoken permanents it touches. Then destroy CARDNAME. | StackDescription$ SpellDescription
+SVar:DBDestroyTouched:DB$ DestroyAll | ValidCards$ Card.IsRemembered+nonToken | SubAbility$ DBDestroyChaosOrb | AILogic$ Always | StackDescription$ None
+SVar:DBDestroyChaosOrb:DB$ Destroy | Defined$ Self | SubAbility$ DBCleanup | AILogic$ Always | StackDescription$ None
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+Oracle:{1}, {T}: If Chaos Orb is on the battlefield, flip Chaos Orb onto the battlefield from a height of at least one foot. If Chaos Orb turns over completely at least once during the flip, destroy all nontoken permanents it touches. Then destroy Chaos Orb.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/f/fact_or_fiction.txt b/forge-gui/res/cardsfolder/f/fact_or_fiction.txt
index e0112217fb5..fbf2cd9d7a7 100644
--- a/forge-gui/res/cardsfolder/f/fact_or_fiction.txt
+++ b/forge-gui/res/cardsfolder/f/fact_or_fiction.txt
@@ -1,10 +1,9 @@
Name:Fact or Fiction
ManaCost:3 U
Types:Instant
-A:SP$ Dig | Cost$ 3 U | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBTwoPiles | SpellDescription$ Reveal the top five cards of your library. An opponent separates those cards into two piles. Put one pile into your hand and the other into your graveyard.
-SVar:DBTwoPiles:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBHand | UnchosenPile$ DBGrave
+A:SP$ Dig | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBTwoPiles | SpellDescription$ Reveal the top five cards of your library. An opponent separates those cards into two piles. Put one pile into your hand and the other into your graveyard.
+SVar:DBTwoPiles:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBHand | UnchosenPile$ DBGrave | StackDescription$ An opponent separates those cards into two piles. {p:You} puts one pile into their hand and the other into their graveyard.
SVar:DBHand:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand | SubAbility$ DBCleanup
SVar:DBGrave:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Graveyard | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
-SVar:Picture:http://www.wizards.com/global/images/magic/general/fact_or_fiction.jpg
Oracle:Reveal the top five cards of your library. An opponent separates those cards into two piles. Put one pile into your hand and the other into your graveyard.
diff --git a/forge-gui/res/cardsfolder/f/falling_star.txt b/forge-gui/res/cardsfolder/f/falling_star.txt
new file mode 100644
index 00000000000..73af8782e11
--- /dev/null
+++ b/forge-gui/res/cardsfolder/f/falling_star.txt
@@ -0,0 +1,7 @@
+Name:Falling Star
+ManaCost:2 R
+Types:Sorcery
+A:SP$ FlipOntoBattlefield | SubAbility$ DBDamageTouched | AILogic$ DamageCreatures | SpellDescription$ Flip CARDNAME onto the playing area from a height of at least one foot. CARDNAME deals 3 damage to each creature it lands on. Tap all creatures dealt damage by CARDNAME. If CARDNAME doesn't turn completely over at least once during the flip, it has no effect. | StackDescription$ SpellDescription
+SVar:DBDamageTouched:DB$ DamageAll | ValidCards$ Creature.IsRemembered | ValidDescription$ each creature touched. | NumDmg$ 3 | SubAbility$ DBCleanup | StackDescription$ None
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+Oracle:Flip Falling Star onto the playing area from a height of at least one foot. Falling Star deals 3 damage to each creature it lands on. Tap all creatures dealt damage by Falling Star. If Falling Star doesn't turn completely over at least once during the flip, it has no effect.
diff --git a/forge-gui/res/cardsfolder/f/fury.txt b/forge-gui/res/cardsfolder/f/fury.txt
index db186bdd38c..fa813ffe401 100644
--- a/forge-gui/res/cardsfolder/f/fury.txt
+++ b/forge-gui/res/cardsfolder/f/fury.txt
@@ -5,5 +5,5 @@ PT:3/3
K:Double Strike
K:Evoke:ExileFromHand<1/Card.Red+Other/red card>
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 4 damage divided as you choose among any number of target creatures and/or planeswalkers.
-SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker to distribute damage to | NumDmg$ 4 | TargetMin$ 1 | TargetMax$ 4 | DividedAsYouChoose$ 4
+SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker to distribute damage to | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4
Oracle:Double strike\nWhen Fury enters the battlefield, it deals 4 damage divided as you choose among any number of target creatures and/or planeswalkers.\nEvoke—Exile a red card from your hand.
diff --git a/forge-gui/res/cardsfolder/g/green_dragon.txt b/forge-gui/res/cardsfolder/g/green_dragon.txt
index 03d417b5691..6416e776783 100644
--- a/forge-gui/res/cardsfolder/g/green_dragon.txt
+++ b/forge-gui/res/cardsfolder/g/green_dragon.txt
@@ -7,4 +7,5 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
SVar:TrigEffect:DB$ Effect | Triggers$ TrigDamageDone
SVar:TrigDamageDone:Mode$ DamageDone | ValidTarget$ Creature.OppCtrl | Execute$ TrigDestroy | TriggerDescription$ Poison Breath — Until end of turn, whenever a creature an opponent controls is dealt damage, destroy it.
SVar:TrigDestroy:DB$ Destroy | Defined$ TriggeredTarget
+SVar:PlayMain1:TRUE
Oracle:Flying\nPoison Breath — When Green Dragon enters the battlefield, until end of turn, whenever a creature an opponent controls is dealt damage, destroy it.
diff --git a/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt b/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt
index 9cb1efb0157..87ad40e4b44 100644
--- a/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt
+++ b/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt
@@ -14,5 +14,5 @@ ManaCost:1 G
Types:Legendary Artifact
K:ETBReplacement:Other:ChooseCT
SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type.
-A:AB$ Mana | Cost$ T | Produced$ G | RestrictValid$ Creature.ChosenType,Creature.Legendary | SpellDescription$ {T}: Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell.
+A:AB$ Mana | Cost$ T | Produced$ G | RestrictValid$ Creature.ChosenType,Creature.Legendary | SpellDescription$ Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell.
Oracle:As The Ringhart Crest enters the battlefield, choose a creature type.\n{T}: Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell.
diff --git a/forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt b/forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt
new file mode 100644
index 00000000000..0f526ba3bc0
--- /dev/null
+++ b/forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt
@@ -0,0 +1,6 @@
+Name:Look at Me, I'm the DCI
+ManaCost:5 W W
+Types:Sorcery
+A:SP$ NameCard | Defined$ You | ValidCards$ Card.nonBasic | SelectPrompt$ Choose a card other than a basic land card to ban | SubAbility$ DBRemove | AILogic$ MostProminentInHumanDeck | StackDescription$ None | SpellDescription$ Ban a card other than a basic land card for the rest of the match. (All cards with that name in any zone or sideboard are removed from the match.)
+SVar:DBRemove:DB$ RemoveFromMatch | RemoveType$ Card.NamedCard | IncludeSideboard$ True | StackDescription$ Ban a card other than a basic land card for the rest of the match. (All cards with that name in any zone or sideboard are removed from the match.)
+Oracle:Ban a card other than a basic land card for the rest of the match. (All cards with that name in any zone or sideboard are removed from the match.)
diff --git a/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt b/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt
index 2da49610f6d..420a6924e29 100644
--- a/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt
+++ b/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt
@@ -3,6 +3,6 @@ ManaCost:1 U R
Types:Legendary Creature Human Wizard
PT:1/4
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player | Execute$ DrawDamageOther | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn.
-SVar:DrawDamageOther:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 1 | ConditionPlayerDefined$ Player.Other | ConditionPlayerContains$ Player.wasDealtDamageThisTurn | OptionalDecider$ True
+SVar:DrawDamageOther:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 1 | ConditionPlayerDefined$ Player.Other | ConditionPlayerContains$ Player.LostLifeThisTurn | OptionalDecider$ True
K:Partner
Oracle:At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn.\nPartner (You can have two commanders if both have partner.)
diff --git a/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt b/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt
index a225cfdd26c..f8658c62e44 100644
--- a/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt
+++ b/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt
@@ -2,10 +2,10 @@ Name:Sorin, Lord of Innistrad
ManaCost:2 W B
Types:Legendary Planeswalker Sorin
Loyalty:3
-A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | TokenAmount$ 1 | TokenScript$ b_1_1_vampire_lifelink | TokenOwner$ You | LegacyImage$ b 1 1 vampire lifelink dka | Planeswalker$ True | SpellDescription$ Create a 1/1 black Vampire creature token with lifelink.
+A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | TokenAmount$ 1 | TokenScript$ b_1_1_vampire_lifelink | Planeswalker$ True | SpellDescription$ Create a 1/1 black Vampire creature token with lifelink.
A:AB$ Effect | Cost$ SubCounter<2/LOYALTY> | Name$ Emblem - Sorin, Lord of Innistrad | Image$ emblem_sorin_lord_of_innistrad | StaticAbilities$ STPump | Planeswalker$ True | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control get +1/+0."
SVar:STPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddPower$ 1
-A:AB$ Destroy | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature,Planeswalker.Other | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select target creatures or other planeswalkers | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ DBChangeZoneAll | SpellDescription$ Destroy up to 3 target creatures and/or other planeswalkers. Return each card put into a graveyard this way to the battlefield under your control.
+A:AB$ Destroy | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature,Planeswalker.Other | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select target creatures or other planeswalkers | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ DBChangeZoneAll | SpellDescription$ Destroy up to three target creatures and/or other planeswalkers. Return each card put into a graveyard this way to the battlefield under your control.
SVar:DBChangeZoneAll:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/sorin_lord_of_innistrad.jpg
diff --git a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt
new file mode 100644
index 00000000000..717b3f178f8
--- /dev/null
+++ b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt
@@ -0,0 +1,113 @@
+Name:Urza, Academy Headmaster
+ManaCost:W U B R G
+Types:Legendary Planeswalker Urza
+Loyalty:4
+A:AB$ GenericChoice | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | AtRandom$ Urza | ShowChoice$ Description | Choices$ Pump1,PutCounter2,Destroy3,Token4,Token5,Token6,Dig7,MustAttack8,PutCounter9,Effect10,Exile11,Dig12,Animate13,Draw14,Animate15,PumpAll16,Dig17,DealDamage18,Animate19,Mana20 | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click +1.
+SVar:Pump1:DB$ Pump | TargetMin$ 0 | TargetMax$ 1 | KW$ First Strike & Vigilance & Lifelink | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +1 | NumDef$ +1 | KW$ First Strike & Vigilance & Lifelink | SpellDescription$ Until end of turn, up to one target creature gets +1/+1 and gains first strike, vigilance, and lifelink.
+SVar:PutCounter2:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control to distribute counters to | CounterType$ P1P1 | CounterNum$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ Distribute three +1/+1 counters among one, two, or three target creatures you control.
+SVar:Destroy3:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select a noncreature permanent | SpellDescription$ Destroy target noncreature permanent.
+SVar:Token4:DB$ Token | TokenAmount$ 2 | TokenScript$ r_3_1_elemental_haste | AtEOT$ Exile | SpellDescription$ Create two 3/1 red Elemental creature tokens with haste. Exile them at the beginning of the next end step.
+SVar:Token5:DB$ Token | TokenAmount$ 3 | TokenScript$ w_1_1_soldier | SpellDescription$ Create three 1/1 white Soldier creature tokens.
+SVar:Token6:DB$ Token | TokenScript$ b_3_3_beast_deathtouch | SpellDescription$ Create a 3/3 black Beast creature token with deathtouch.
+SVar:Dig7:DB$ Dig | DigNum$ 5 | Reveal$ True | ChangeNum$ All | ChangeValid$ Creature | SpellDescription$ Reveal the top five cards of your library. Put all creature cards revealed this way into your hand and the rest on the bottom of your library in any order.
+SVar:MustAttack8:DB$ MustAttack | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Defender$ Self | SpellDescription$ During target opponent's next turn, creatures that player controls attack NICKNAME if able.
+SVar:PutCounter9:DB$ PutCounter | Defined$ Self | CounterType$ LOYALTY | CounterNum$ X9 | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | StackDescription$ SpellDescription | SpellDescription$ Put a loyalty counter on NICKNAME for each creature target opponent controls.
+SVar:X9:TargetedPlayer$CreaturesInPlay
+SVar:Effect10:DB$ Effect | Triggers$ TrigAttack10 | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn.
+SVar:TrigAttack10:Mode$ Attacks | ValidCard$ Creature.OppCtrl | TriggerZones$ Command | Execute$ DebuffAttacker10 | TriggerDescription$ Whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn.
+SVar:DebuffAttacker10:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ -1
+SVar:Exile11:DB$ ChangeZone | ValidTgts$ Player | TgtPrompt$ Select target player | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | Chooser$ Targeted | Hidden$ True | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player exiles a card from their hand.
+SVar:Dig12:DB$ Dig | DigNum$ 1 | Reveal$ True | ChangeNum$ All | ChangeValid$ Land | DestinationZone$ Battlefield | DestinationZone2$ Hand | SpellDescription$ Reveal the top card of your library. If it's a land card, put it onto the battlefield. Otherwise, put it into your hand.
+SVar:Animate13:DB$ Animate | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | Power$ 4 | Toughness$ 4 | Types$ Creature,Elemental | Duration$ Permanent | Keywords$ Trample | SpellDescription$ Target land you control becomes a 4/4 Elemental creature with trample. It's still a land.
+SVar:Draw14:DB$ Draw | Defined$ You | SubAbility$ DBMana14 | SpellDescription$ Draw a card, then add one mana of any color.
+SVar:DBMana14:DB$ Mana | Produced$ Any | AILogic$ MostProminentInComputerHand
+SVar:Animate15:DB$ Animate | Power$ 4 | Toughness$ 4 | Types$ Creature,Legendary,Dragon | Colors$ Red | OverwriteColors$ True | RemoveCardTypes$ True | Keywords$ Flying & Indestructible & Haste | SpellDescription$ Until end of turn, NICKNAME becomes a legendary 4/4 red Dragon creature with flying, indestructible, and haste. (He doesn't lose loyalty while he's not a planeswalker.)
+SVar:PumpAll16:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | KW$ Lifelink | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, creatures you control get +1/+0 and gain lifelink.
+SVar:Dig17:DB$ Dig | DigNum$ 5 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Artifact | SpellDescription$ Look at the top five cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in any order.
+SVar:DealDamage18:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SpellDescription$ NICKNAME deals 3 damage to any target.
+SVar:Animate19:DB$ Animate | Defined$ Self | Triggers$ DamageTrig19 | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, whenever a creature deals combat damage to NICKNAME, destroy that creature. | StackDescription$ SpellDescription
+SVar:DamageTrig19:Mode$ DamageDone | ValidSource$ Creature | ValidTarget$ Card.Self | TriggerZones$ Battlefield | CombatDamage$ True | Execute$ TrigDestroy19 | TriggerDescription$ Whenever a creature deals combat damage to NICKNAME, destroy that creature.
+SVar:TrigDestroy19:DB$ Destroy | Defined$ TriggeredSourceLKICopy
+SVar:Mana20:DB$ Mana | Produced$ Combo Any | Amount$ X20 | SpellDescription$ Add X mana in any combination of colors, where X is the number of creatures you control.
+SVar:X20:Count$Valid Creature.YouCtrl
+A:AB$ GenericChoice | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | AtRandom$ Urza | ShowChoice$ Description | Choices$ DamageAll1M,GainControl2M,DealDamage3M,Destroy4M,Effect5M,ChangeZone6M,Draw7M,Mill8M,Dig9M,Exile10M,Reveal11M,Tutor12M,Sacrifice13M,Token14M,Token15M,SetLife16M,Destroy17M,Return18M,Token19M,Draw20M | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -1.
+SVar:DamageAll1M:DB$ DamageAll | NumDmg$ 3 | ValidCards$ Creature | SpellDescription$ NICKNAME deals 3 damage to each creature.
+SVar:GainControl2M:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Gain control of target creature.
+SVar:DealDamage3M:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SubAbility$ DBGainLife3M | SpellDescription$ NICKNAME deals 4 damage to any target and you gain 4 life.
+SVar:DBGainLife3M:DB$ GainLife | Defined$ You | LifeAmount$ 4
+SVar:Destroy4M:DB$ Destroy | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBGainLife4M | SpellDescription$ Destroy target creature. You gain life equal to its toughness.
+SVar:DBGainLife4M:DB$ GainLife | Defined$ You | LifeAmount$ X4M
+SVar:X4M:TargetedLKI$CardToughness
+SVar:Effect5M:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (5M) | StaticAbilities$ STPump5M | Duration$ Permanent | SpellDescription$ You get an emblem with "Creatures you control get +1/+1."
+SVar:STPump5M:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control get +1/+1.
+SVar:ChangeZone6M:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ 1 | SpellDescription$ You may put a creature card from your hand onto the battlefield.
+SVar:Draw7M:DB$ Draw | NumCards$ 3 | SubAbility$ DBChangeZone7M | SpellDescription$ Draw three cards, then put a card from your hand on top of your library.
+SVar:DBChangeZone7M:DB$ ChangeZone | Origin$ Hand | Destination$ Library | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SelectPrompt$ Select a card from your hand to put on top of your library
+SVar:Mill8M:DB$ Mill | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ 10 | SpellDescription$ Target player puts the top ten cards of their library into their graveyard.
+SVar:Dig9M:DB$ Dig | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBTwoPiles9M | SpellDescription$ Reveal the top five cards of your library. An opponent separates them into two piles. Put one pile into your hand and the other on the bottom of your library in any order.
+SVar:DBTwoPiles9M:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBHand9M | UnchosenPile$ DBLibraryBottom9M
+SVar:DBHand9M:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand
+SVar:DBLibraryBottom9M:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | SubAbility$ DBCleanup
+SVar:Exile10M:DB$ ChangeZone | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target permanent.
+SVar:Reveal11M:DB$ Dig | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBChangeCreatures11M | SpellDescription$ Reveal the top five cards of your library. You may put all creature cards and/or land cards from among them into your hand. Put the rest into your graveyard.
+SVar:DBChangeCreatures11M:DB$ ChangeZoneAll | ChangeType$ Card.Creature+IsRemembered | Origin$ Library | Destination$ Hand | Optional$ True | OptionQuestion$ Put all creature cards into your hand? | ForgetChanged$ True | SubAbility$ DBChangeLands11M
+SVar:DBChangeLands11M:DB$ ChangeZoneAll | ChangeType$ Card.Land+IsRemembered | Origin$ Library | Destination$ Hand | Optional$ True | OptionQuestion$ Put all land cards into your hand? | ForgetChanged$ True | SubAbility$ DBChangeRest11M
+SVar:DBChangeRest11M:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Graveyard | ForgetChanged$ True
+SVar:Tutor12M:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library.
+SVar:Sacrifice13M:DB$ Sacrifice | ValidTgts$ Player | Amount$ 2 | SacValid$ Creature | SpellDescription$ Target player sacrifices two creatures.
+SVar:Token14M:DB$ Token | TokenScript$ b_5_5_demon_flying | SubAbility$ DBLoselife14M | SpellDescription$ Create a 5/5 black Demon creature token with flying. You lose 2 life.
+SVar:DBLoselife14M:DB$ LoseLife | LifeAmount$ 2
+SVar:Token15M:DB$ Token | TokenScript$ c_4_4_dragon_flying | SpellDescription$ Create a 4/4 gold Dragon creature token with flying.
+SVar:SetLife16M:DB$ SetLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ 10 | SpellDescription$ Target player's life total becomes 10.
+SVar:Destroy17M:DB$ Destroy | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select a nonland permanent | SpellDescription$ Destroy target nonland permanent.
+SVar:Return18M:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | TgtPrompt$ Select target permanent in a graveyard | ValidTgts$ Permanent | SpellDescription$ Return target permanent from a graveyard to the battlefield under your control.
+SVar:Token19M:DB$ Token | TokenAmount$ 2 | TokenScript$ g_3_3_beast | SpellDescription$ Create two 3/3 green Beast creature tokens.
+SVar:Draw20M:DB$ Draw | NumCards$ 4 | SubAbility$ DBDiscard20M | SpellDescription$ Draw four cards, then discard two cards.
+SVar:DBDiscard20M:DB$ Discard | Defined$ You | NumCards$ 2 | Mode$ TgtChoose
+A:AB$ GenericChoice | Cost$ SubCounter<6/LOYALTY> | AtRandom$ Urza | ShowChoice$ Description | Choices$ DealDamage1U,Effect2U,DestroyAll3U,Token4U,LifeGain5U,Damage6U,Effect7U,Effect8U,Token9U,Shuffle10U,Destroy11U,Effect12U,Effect13U,ControlPlayer14U,ExileLibrary15U,Token16U,Raise17U,LifeDraw18U,FlipTurns19U | Planeswalker$ True | Ultimate$ True | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -6.
+SVar:DealDamage1U:DB$ DealDamage | ValidTgts$ Player,Planeswalker | SubAbility$ DBDiscard1U | SpellDescription$ NICKNAME deals 7 damage to target player or planeswalker. That player or that planeswalker's controller discards seven cards, then sacrifices seven permanents.
+SVar:DBDiscard1U:DB$ Discard | Defined$ TargetedOrController | NumCards$ 7 | Mode$ TgtChoose | SubAbility$ DBSac1U
+SVar:DBSac1U:DB$Sacrifice | Defined$ TargetedOrController | SacValid$ Permanent | Amount$ 7
+SVar:Effect2U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (2U) | ReplacementEffects$ RPreventDamage | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage."
+SVar:RPreventDamage:Event$ DamageDone | ValidTarget$ You,Planeswalker.YouCtrl | ReplaceWith$ PreventDmg | PreventionEffect$ True | Description$ If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage.
+SVar:PreventDmg:DB$ ReplaceDamage | Amount$ ShieldAmount
+SVar:ShieldAmount:ReplaceCount$DamageAmount/Minus.1
+SVar:DestroyAll3U:DB$ DestroyAll | ValidTgts$ Player | TgtPrompt$ Select target player | ValidCards$ Land | SpellDescription$ Destroy all lands target player controls.
+SVar:Token4U:DB$ Token | TokenAmount$ X4U | TokenScript$ w_2_2_cat | SpellDescription$ Create X 2/2 white Cat creature tokens, where X is your life total.
+SVar:X4U:Count$YourLifeTotal
+SVar:LifeGain5U:DB$ GainLife | LifeAmount$ 100 | SpellDescription$ You gain 100 life.
+SVar:Damage6U:DB$ DealDamage | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select a player or planeswalker | NumDmg$ 10 | SubAbility$ DmgAll6U | DamageMap$ True | SpellDescription$ NICKNAME deals 10 damage to target player or planeswalker and each creature that player or that planeswalker's controller controls.
+SVar:DmgAll6U:DB$ DamageAll | NumDmg$ 10 | ValidCards$ Creature.ControlledBy TargetedOrController | SubAbility$ DBDamageResolve6U
+SVar:DBDamageResolve6U:DB$ DamageResolve
+SVar:Effect7U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (7U) | StaticAbilities$ ST7U | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control have double strike, trample, hexproof and haste."
+SVar:ST7U:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddKeyword$ Double Strike & Trample & Hexproof & Haste | Description$ Creatures you control have double strike, trample, hexproof and haste.
+SVar:Effect8U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (8U) | StaticAbilities$ STIndestructible8U | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Artifacts, creatures, enchantments, and lands you control have indestructible."
+SVar:STIndestructible8U:Mode$ Continuous | EffectZone$ Command | Affected$ Artifact.YouCtrl,Creature.YouCtrl,Enchantment.YouCtrl,Land.YouCtrl | AffectedZone$ Battlefield | AddKeyword$ Indestructible | Description$ Artifacts, creatures, enchantments, and lands you control are indestructible.
+SVar:Token9U:DB$ Token | TokenAmount$ Lands | TokenScript$ g_6_6_wurm | SpellDescription$ Create a 6/6 green Wurm creature token for each land you control.
+SVar:Lands:Count$Valid Land.YouCtrl
+SVar:Shuffle10U:DB$ ChangeZoneAll | ChangeType$ Card | Origin$ Hand,Graveyard | Destination$ Library | Shuffle$ True | SubAbility$ DBDraw10U | UseAllOriginZones$ True | SpellDescription$ Each player shuffles their hand and graveyard into their library. You draw seven cards.
+SVar:DBDraw10U:DB$ Draw | Defined$ You | NumCards$ 7
+SVar:Destroy11U:DB$ Destroy | ValidTgts$ Creature,Planeswalker.Other | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures and/or other planeswalkers | RememberTargets$ True | SubAbility$ DBChangeZoneAll11U | SpellDescription$ Destroy up to three target creatures and/or other planeswalkers. Return each card put into a graveyard this way to the battlefield under your control.
+SVar:DBChangeZoneAll11U:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SubAbility$ DBCleanup
+SVar:Effect12U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (12U) | Triggers$ TrigSpellCast12U | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Whenever you cast a spell, exile target permanent."
+SVar:TrigSpellCast12U:Mode$ SpellCast | ValidActivatingPlayer$ You | Execute$ EffSpellCast12U | TriggerDescription$ Whenever you cast a spell, exile target permanent.
+SVar:EffSpellCast12U:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent
+SVar:Effect13U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (13U) | Triggers$ TrigFight13U | Duration$ Permanent | AILogic$ Always | SubAbility$ DBToken13U | SpellDescription$ You get an emblem with "Whenever a creature enters the battlefield under your control, you may have it fight target creature." Then create three 8/8 blue Octopus creature tokens.
+SVar:TrigFight13U:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | Execute$ DBFight13U | OptionalDecider$ You | TriggerZones$ Command | TriggerDescription$ Whenever a creature enters the battlefield under your control, you may have it fight target creature.
+SVar:DBFight13U:DB$ Fight | Defined$ TriggeredCardLKICopy | ValidTgts$ Creature | TgtPrompt$ Choose target creature
+SVar:DBToken13U:DB$ Token | TokenAmount$ 3 | TokenScript$ u_8_8_octopus
+SVar:ControlPlayer14U:DB$ ControlPlayer | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ You control target player during that player's next turn.
+SVar:ExileLibrary15U:DB$ ChangeZoneAll | Origin$ Library | Destination$ Exile | ValidTgts$ Player | TgtPrompt$ Select target player | SubAbility$ DBChangeZone15U | Planeswalker$ True | Ultimate$ True | SpellDescription$ Exile all cards from target player's library, then that player shuffles their hand into their library.
+SVar:DBChangeZone15U:DB$ ChangeZoneAll | Origin$ Hand | Destination$ Library | Defined$ Targeted | ChangeType$ Card | Shuffle$ True
+SVar:Token16U:DB$ Token | TokenAmount$ 3 | TokenScript$ b_1_1_assassin_lose_con | SpellDescription$ Create three 1/1 black Assassin creature tokens with "Whenever this creature deals combat damage to a player, that player loses the game."
+SVar:Raise17U:DB$ ChangeZoneAll | ChangeType$ Creature | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SpellDescription$ Put all creature cards in all graveyards onto the battlefield under your control.
+SVar:LifeDraw18U:DB$ GainLife | LifeAmount$ Lands | SubAbility$ DBDraw18U | SpellDescription$ You gain X life and draw X cards, where X is the number of lands you control.
+SVar:DBDraw18U:DB$ Draw | NumCards$ Lands
+SVar:FlipTurns19U:DB$ FlipACoin | Amount$ 5 | NoCall$ True | HeadsSubAbility$ DBAddTurn19U | StackDescription$ SpellDescription | SpellDescription$ Flip 5 coins. Take an extra turn after this one for each coin that comes up heads.
+SVar:DBAddTurn19U:DB$ AddTurn | Defined$ You | NumTurns$ 1
+SVar:LifeDrawPut20U:DB$ GainLife | LifeAmount$ 7 | SubAbility$ DBDraw20U | SpellDescription$ You gain 7 life, draw seven cards, then put up to seven permanent cards from your hand onto the battlefield.
+SVar:DBDraw20U:DB$ Draw | NumCards$ 7 | SubAbility$ DBChangeZone20U
+SVar:DBChangeZone20U:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Permanent | ChangeNum$ 7
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
+DeckHas:Ability$Counters & Ability$Token & Ability$LifeGain & Ability$Discard
+Oracle:[+1]: Head to AskUrza.com and click +1.\n[-1]: Head to AskUrza.com and click -1.\n[-6]: Head to AskUrza.com and click -6.
diff --git a/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck b/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck
index b7537a1b872..70c1b20d229 100644
--- a/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck
+++ b/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck
@@ -244,7 +244,7 @@ Eternal Warrior|LEG
Eureka|LEG
Evil Eye of Orms-by-Gore|LEG
Fallen Angel|LEG
-#Falling Star|LEG
+Falling Star|LEG
Feint|LEG
Field of Dreams|LEG
Fire Sprites|LEG
@@ -504,7 +504,7 @@ Camouflage|LEB
Castle|LEB
Celestial Prism|LEB
Channel|LEB
-#Chaos Orb|LEB
+Chaos Orb|LEB
Chaoslace|LEB
Circle of Protection: Black|LEB
Circle of Protection: Blue|LEB
diff --git a/forge-gui/res/editions/Innistrad Crimson Vow Commander.txt b/forge-gui/res/editions/Innistrad Crimson Vow Commander.txt
new file mode 100644
index 00000000000..d4b8bb45002
--- /dev/null
+++ b/forge-gui/res/editions/Innistrad Crimson Vow Commander.txt
@@ -0,0 +1,14 @@
+[metadata]
+Code=VOC
+Date=2021-11-19
+Name=Innistrad: Crimson Vow Commander
+Type=Commander
+ScryfallCode=VOC
+
+[cards]
+1 M Millicent, Restless Revenant @
+2 M Strefan, Maurer Progenitor @
+32 M Wedding Ring @
+39 M Millicent, Restless Revenant @
+40 M Strefan, Maurer Progenitor @
+70 M Wedding Ring @
diff --git a/forge-gui/res/editions/Innistrad Crimson Vow.txt b/forge-gui/res/editions/Innistrad Crimson Vow.txt
new file mode 100644
index 00000000000..a46d8a6a4cc
--- /dev/null
+++ b/forge-gui/res/editions/Innistrad Crimson Vow.txt
@@ -0,0 +1,90 @@
+[metadata]
+Code=VOW
+Date=2021-11-19
+Name=Innistrad: Crimson Vow
+Code2=VOW
+MciCode=vow
+Type=Expansion
+ScryfallCode=VOW
+
+[cards]
+5 R By Invitation Only @
+10 C Drogskol Infantry @
+15 C Gryff Rider @
+34 M Savior of Ollenbock @
+36 R Sigarda's Summons @
+38 R Thalia, Guardian of Thraben @
+45 R Wedding Announcement @
+60 U Geistlight Snare @
+71 R Overcharged Amalgam @
+103 R Demonic Bargain @
+112 U Fell Stinger @
+114 C Gluttonous Guest @
+129 C Rot-Tide Gargantua @
+131 M Sorin the Mirthless @
+137 R Voldaren Bloodcaster @
+154 R Dominating Vampire @
+184 C Weary Prisoner @
+185 C Apprentice Sharpshooter @
+197 R Dig Up @
+208 C Massive Might @
+235 R Dorothea, Vengeful Victim @
+238 R Grolnok, the Omnivore @
+245 M Olivia, Crimson Bride @
+260 C Wedding Invitation @
+261 R Deathcap Glade @
+262 R Dreamroot Cascade @
+264 R Shattered Sanctum @
+265 R Stormcarved Coast @
+266 R Sundown Pass @
+267 R Voldaren Estate @
+268 L Plains @
+269 L Plains @
+270 L Island @
+271 L Island @
+272 L Swamp @
+273 L Swamp @
+274 L Mountain @
+275 L Mountain @
+276 L Forest @
+277 L Forest @
+278 M Sorin the Mirthless @
+281 R Deathcap Glade @
+282 R Dreamroot Cascade @
+283 R Shattered Sanctum @
+284 R Stormcarved Coast @
+285 R Sundown Pass @
+292 C Gluttonous Guest @
+297 M Sorin the Mirthless @
+298 R Voldaren Bloodcaster @
+305 R Dominating Vampire @
+315 M Olivia, Crimson Bride @
+318 R Thalia, Guardian of Thraben @
+322 R Dorothea, Vengeful Victim @
+324 R Grolnok, the Omnivore @
+330 M Savior of Ollenbock @
+331 R Thalia, Guardian of Thraben @
+337 M Sorin the Mirthless @
+338 R Voldaren Bloodcaster @
+343 M Olivia, Crimson Bride @
+346 R By Invitation Only @
+352 M Savior of Ollenbock @
+353 R Sigarda's Summons @
+355 R Wedding Announcement @
+363 R Overcharged Amalgam @
+368 R Demonic Bargain @
+387 R Dig Up @
+397 R Voldaren Estate @
+398 L Plains @
+399 L Island @
+400 L Swamp @
+401 L Mountain @
+402 L Forest @
+403 R Voldaren Estate @
+404 R Sigarda's Summons @
+405 U Geistlight Snare @
+406 U Fell Stinger @
+407 R Dominating Vampire @
+
+[tokens]
+c_a_blood_draw
diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties
index 62755f5d7f0..db2f2362579 100644
--- a/forge-gui/res/languages/de-DE.properties
+++ b/forge-gui/res/languages/de-DE.properties
@@ -1938,6 +1938,13 @@ lblTails=Zahl
lblCallCoinFlip=Kopf oder Zahl
lblWin=Gewonnen
lblLose=Verloren
+#FlipOntoBattlefieldEffect.java
+lblChooseDesiredLocation=Wähle eine Karte als Mittelpunkt der gewünschten Landezone.
+lblDidNotFlipOver=Die Karte hat sich NICHT überschlagen.
+lblFlippedOver=Die Karte überschlug sich {0} mal.
+lblDidNotLandOnCards=Die Karte landete auf keiner Karte.
+lblLandedOnOneCard=Die Karte landete auf {0}.
+lblLandedOnTwoCards=Die Karte landete auf {0} und {1}.
#InvestigateEffect.java
lblWouldYouLikeInvestigate=Möchtest du Nachforschungen anstellen?
#LifeSetEffect.java
diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties
index 53fddb7ae66..bcda90abb88 100644
--- a/forge-gui/res/languages/en-US.properties
+++ b/forge-gui/res/languages/en-US.properties
@@ -1939,6 +1939,13 @@ lblTails=tails
lblCallCoinFlip=Call coin flip
lblWin=win
lblLose=lose
+#FlipOntoBattlefieldEffect.java
+lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location.
+lblDidNotFlipOver=The card did not flip over.
+lblFlippedOver=The card flipped over {0} time(s).
+lblDidNotLandOnCards=The card did not land on any cards.
+lblLandedOnOneCard=The card landed on {0}.
+lblLandedOnTwoCards=The card landed on {0} and {1}.
#InvestigateEffect.java
lblWouldYouLikeInvestigate=Do you want to investigate?
#LifeSetEffect.java
diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties
index 33914a44b69..cb3047d8e3c 100644
--- a/forge-gui/res/languages/es-ES.properties
+++ b/forge-gui/res/languages/es-ES.properties
@@ -1937,6 +1937,13 @@ lblTails=cruz
lblCallCoinFlip=Llamar al lanzamiento de la moneda
lblWin=gana
lblLose=pierde
+#FlipOntoBattlefieldEffect.java
+lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location.
+lblDidNotFlipOver=The card did not flip over.
+lblFlippedOver=The card flipped over {0} time(s).
+lblDidNotLandOnCards=The card did not land on any cards.
+lblLandedOnOneCard=The card landed on {0}.
+lblLandedOnTwoCards=The card landed on {0} and {1}.
#InvestigateEffect.java
lblWouldYouLikeInvestigate=¿Quieres investigar?
#LifeSetEffect.java
diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties
index 884e47b7c49..98de6dd4347 100644
--- a/forge-gui/res/languages/it-IT.properties
+++ b/forge-gui/res/languages/it-IT.properties
@@ -1936,6 +1936,13 @@ lblTails=croce
lblCallCoinFlip=Scegli testa o croce
lblWin=hai vinto
lblLose=hai perso
+#FlipOntoBattlefieldEffect.java
+lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location.
+lblDidNotFlipOver=The card did not flip over.
+lblFlippedOver=The card flipped over {0} time(s).
+lblDidNotLandOnCards=The card did not land on any cards.
+lblLandedOnOneCard=The card landed on {0}.
+lblLandedOnTwoCards=The card landed on {0} and {1}.
#InvestigateEffect.java
lblWouldYouLikeInvestigate=Do you want to investigate?
#LifeSetEffect.java
diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties
index 5dd856e3578..5949d490ed0 100644
--- a/forge-gui/res/languages/ja-JP.properties
+++ b/forge-gui/res/languages/ja-JP.properties
@@ -1936,6 +1936,13 @@ lblTails=裏
lblCallCoinFlip=コイン投げを予想
lblWin=勝ち
lblLose=負け
+#FlipOntoBattlefieldEffect.java
+lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location.
+lblDidNotFlipOver=The card did not flip over.
+lblFlippedOver=The card flipped over {0} time(s).
+lblDidNotLandOnCards=The card did not land on any cards.
+lblLandedOnOneCard=The card landed on {0}.
+lblLandedOnTwoCards=The card landed on {0} and {1}.
#InvestigateEffect.java
lblWouldYouLikeInvestigate=Do you want to investigate?
#LifeSetEffect.java
diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties
index 0c6a32e70c9..c28b53f03e4 100644
--- a/forge-gui/res/languages/zh-CN.properties
+++ b/forge-gui/res/languages/zh-CN.properties
@@ -1940,6 +1940,13 @@ lblTails=背面
lblCallCoinFlip=掷骰子
lblWin=赢
lblLose=输
+#FlipOntoBattlefieldEffect.java
+lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location.
+lblDidNotFlipOver=The card did not flip over.
+lblFlippedOver=The card flipped over {0} time(s).
+lblDidNotLandOnCards=The card did not land on any cards.
+lblLandedOnOneCard=The card landed on {0}.
+lblLandedOnTwoCards=The card landed on {0} and {1}.
#InvestigateEffect.java
lblWouldYouLikeInvestigate=你想要探查吗?
#LifeSetEffect.java
diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt
index 7c21a195edb..0f0c14cfd53 100644
--- a/forge-gui/res/lists/TypeLists.txt
+++ b/forge-gui/res/lists/TypeLists.txt
@@ -374,6 +374,7 @@ Tezzeret
Tibalt
Tyvar
Ugin
+Urza
Venser
Vivien
Vraska
diff --git a/forge-gui/res/lists/net-decks-archive-legacy.txt b/forge-gui/res/lists/net-decks-archive-legacy.txt
index ad1e107d7c1..bceafd34daa 100644
--- a/forge-gui/res/lists/net-decks-archive-legacy.txt
+++ b/forge-gui/res/lists/net-decks-archive-legacy.txt
@@ -2130,3 +2130,11 @@
2021-10-11 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-11-legacy-challenge.zip
2021-10-12 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-12-legacy-preliminary.zip
2021-10-16 Legacy League (49 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-16-legacy-league.zip
+2021-10-18 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-18-legacy-challenge.zip
+2021-10-20 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-20-legacy-preliminary.zip
+2021-10-22 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-22-legacy-preliminary.zip
+2021-10-23 Legacy League (49 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-23-legacy-league.zip
+2021-10-23 Legacy Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-23-legacy-preliminary.zip
+2021-10-24 Legacy Premier (16 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-24-legacy-premier.zip
+2021-10-25 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-25-legacy-challenge.zip
+2021-10-26 Legacy Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-26-legacy-preliminary.zip
diff --git a/forge-gui/res/lists/net-decks-archive-modern.txt b/forge-gui/res/lists/net-decks-archive-modern.txt
index c78ae61a63f..79266b9f498 100644
--- a/forge-gui/res/lists/net-decks-archive-modern.txt
+++ b/forge-gui/res/lists/net-decks-archive-modern.txt
@@ -2798,3 +2798,17 @@
2021-10-16 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-16-modern-preliminary.zip
2021-10-16 Modern Premier (16 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-16-modern-premier.zip
2021-10-17 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-17-modern-challenge.zip
+2021-10-18 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-18-modern-challenge.zip
+2021-10-18 Modern Premier (16 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-18-modern-premier.zip
+2021-10-19 Modern League (56 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-19-modern-league.zip
+2021-10-19 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-19-modern-preliminary.zip
+2021-10-20 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-20-modern-preliminary.zip
+2021-10-21 Modern Preliminary (10 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-21-modern-preliminary.zip
+2021-10-22 Modern League (56 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-22-modern-league.zip
+2021-10-22 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-22-modern-preliminary.zip
+2021-10-23 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-23-modern-preliminary.zip
+2021-10-24 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-24-modern-challenge.zip
+2021-10-25 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-25-modern-challenge.zip
+2021-10-25 Modern Premier (16 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-25-modern-premier.zip
+2021-10-26 Modern League (65 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-26-modern-league.zip
+2021-10-26 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-26-modern-preliminary.zip
diff --git a/forge-gui/res/lists/net-decks-archive-pauper.txt b/forge-gui/res/lists/net-decks-archive-pauper.txt
index 3af79ec6fef..3f55845b47c 100644
--- a/forge-gui/res/lists/net-decks-archive-pauper.txt
+++ b/forge-gui/res/lists/net-decks-archive-pauper.txt
@@ -1815,3 +1815,8 @@
2021-10-11 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-11-pauper-challenge.zip
2021-10-13 Pauper League (30 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-13-pauper-league.zip
2021-10-17 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-17-pauper-challenge.zip
+2021-10-18 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-18-pauper-challenge.zip
+2021-10-20 Pauper League (25 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-20-pauper-league.zip
+2021-10-24 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-24-pauper-challenge.zip
+2021-10-25 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-25-pauper-challenge.zip
+Pauper League (20 decks) | https://downloads.cardforge.org/decks/archive/pauper/pauper-league.zip
diff --git a/forge-gui/res/lists/net-decks-archive-pioneer.txt b/forge-gui/res/lists/net-decks-archive-pioneer.txt
index 3660d22784f..915a340af8b 100644
--- a/forge-gui/res/lists/net-decks-archive-pioneer.txt
+++ b/forge-gui/res/lists/net-decks-archive-pioneer.txt
@@ -755,3 +755,10 @@
2021-10-11 Pioneer League (18 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-11-pioneer-league.zip
2021-10-14 Pioneer League (15 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-14-pioneer-league.zip
2021-10-17 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-17-pioneer-challenge.zip
+2021-10-18 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-18-pioneer-challenge.zip
+2021-10-18 Pioneer League (14 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-18-pioneer-league.zip
+2021-10-21 Pioneer League (17 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-21-pioneer-league.zip
+2021-10-23 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-23-pioneer-preliminary.zip
+2021-10-24 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-24-pioneer-challenge.zip
+2021-10-25 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-25-pioneer-challenge.zip
+2021-10-25 Pioneer League (19 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-25-pioneer-league.zip
diff --git a/forge-gui/res/lists/net-decks-archive-standard.txt b/forge-gui/res/lists/net-decks-archive-standard.txt
index b0275a8de88..02745e35a0c 100644
--- a/forge-gui/res/lists/net-decks-archive-standard.txt
+++ b/forge-gui/res/lists/net-decks-archive-standard.txt
@@ -2515,3 +2515,10 @@
2021-10-14 Standard League (12 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-14-standard-league.zip
2021-10-15 Standard Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-15-standard-preliminary.zip
2021-10-17 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-17-standard-challenge.zip
+2021-10-18 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-18-standard-challenge.zip
+2021-10-18 Standard League (15 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-18-standard-league.zip
+2021-10-20 Standard Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-20-standard-preliminary.zip
+2021-10-21 Standard League (5 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-21-standard-league.zip
+2021-10-24 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-24-standard-challenge.zip
+2021-10-25 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-25-standard-challenge.zip
+2021-10-25 Standard League (8 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-25-standard-league.zip
diff --git a/forge-gui/res/lists/net-decks-archive-vintage.txt b/forge-gui/res/lists/net-decks-archive-vintage.txt
index 95a314c2d0c..70032f261ca 100644
--- a/forge-gui/res/lists/net-decks-archive-vintage.txt
+++ b/forge-gui/res/lists/net-decks-archive-vintage.txt
@@ -1535,3 +1535,8 @@
2021-10-11 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-11-vintage-challenge.zip
2021-10-17 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-17-vintage-challenge.zip
2021-10-17 Vintage League (12 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-17-vintage-league.zip
+2021-10-18 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-18-vintage-challenge.zip
+2021-10-23 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-23-vintage-preliminary.zip
+2021-10-24 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-24-vintage-challenge.zip
+2021-10-24 Vintage League (9 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-24-vintage-league.zip
+2021-10-25 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-25-vintage-challenge.zip
diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java
index 248f9d393e0..b7012d86fff 100644
--- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java
+++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java
@@ -13,7 +13,6 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
-import forge.game.card.CardUtil;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -74,7 +73,7 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany
if (improvise) {
chosenColor = ManaCostShard.COLORLESS.getColorMask();
} else {
- ColorSet colors = CardUtil.getColors(card);
+ ColorSet colors = card.determineColor();
if (colors.isMulticolor()) {
//if card is multicolor, strip out any colors which can't be paid towards remaining cost
colors = ColorSet.fromMask(colors.getColor() & remainingCost.getUnpaidColors());
diff --git a/forge-gui/src/main/java/forge/gui/FThreads.java b/forge-gui/src/main/java/forge/gui/FThreads.java
index f90221c0c0e..e463f3ead90 100644
--- a/forge-gui/src/main/java/forge/gui/FThreads.java
+++ b/forge-gui/src/main/java/forge/gui/FThreads.java
@@ -30,8 +30,7 @@ public class FThreads {
public static void invokeInEdtNowOrLater(final Runnable proc) {
if (isGuiThread()) {
GuiBase.getInterface().invokeInEdtNow(proc);
- }
- else {
+ } else {
GuiBase.getInterface().invokeInEdtLater(proc);
}
}
diff --git a/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java b/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java
index 03180147d4d..5e026e3021a 100644
--- a/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java
+++ b/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java
@@ -80,8 +80,7 @@ public final class ItemManagerModel {
if (data.count(item0) > 0) {
if (isInfinite()) {
data.removeAll(item0);
- }
- else {
+ } else {
data.remove(item0, qty);
}
isListInSync = false;