diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java
index e07f836566d..f2b976f3008 100644
--- a/forge-core/src/main/java/forge/ImageKeys.java
+++ b/forge-core/src/main/java/forge/ImageKeys.java
@@ -113,7 +113,11 @@ public final class ImageKeys {
}
//try fullborder...
if (filename.contains(".full")) {
- file = findFile(dir, TextUtil.fastReplace(filename, ".full", ".fullborder"));
+ String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
+ file = findFile(dir, fullborderFile);
+ if (file != null) { return file; }
+ // if there's an art variant try without it
+ file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
if (file != null) { return file; }
}
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java
index 322c003b6f2..a27b60adf2c 100644
--- a/forge-core/src/main/java/forge/card/CardRules.java
+++ b/forge-core/src/main/java/forge/card/CardRules.java
@@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics {
public boolean canBeBrawlCommander() {
CardType type = mainPart.getType();
- return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker();
+ return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
+ }
+
+ public boolean canBeTinyLeadersCommander() {
+ CardType type = mainPart.getType();
+ return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
}
public String getMeldWith() {
diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
index 6679b3ad673..66dca47b592 100644
--- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java
+++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
@@ -594,8 +594,10 @@ public final class CardRulesPredicates {
public static final Predicate IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
- public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
- Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
+ public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
+ Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
+ public static final Predicate CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
+ Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
/** The Constant IS_NON_CREATURE_SPELL. **/
public static final Predicate IS_NON_CREATURE_SPELL = com.google.common.base.Predicates
diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java
index 3ea356c6a6d..e369fccc075 100644
--- a/forge-core/src/main/java/forge/deck/DeckFormat.java
+++ b/forge-core/src/main/java/forge/deck/DeckFormat.java
@@ -463,6 +463,9 @@ public enum DeckFormat {
if (this.equals(DeckFormat.Brawl)) {
return rules.canBeBrawlCommander();
}
+ if (this.equals(DeckFormat.TinyLeaders)) {
+ return rules.canBeTinyLeadersCommander();
+ }
return rules.canBeCommander();
}
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index b54c727370f..6d9ed807964 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -22,8 +22,10 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
+import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -32,9 +34,15 @@ import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerController;
+import forge.game.replacement.ReplacementEffect;
+import forge.game.replacement.ReplacementHandler;
+import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.*;
import forge.game.trigger.Trigger;
+import forge.game.trigger.TriggerHandler;
+import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
+import forge.util.Lang;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
@@ -363,10 +371,11 @@ public final class GameActionUtil {
}
SpellAbility result = null;
final Card host = sa.getHostCard();
+ final Game game = host.getGame();
final Player activator = sa.getActivatingPlayer();
final PlayerController pc = activator.getController();
- host.getGame().getAction().checkStaticAbilities(false);
+ game.getAction().checkStaticAbilities(false);
boolean reset = false;
@@ -429,7 +438,60 @@ public final class GameActionUtil {
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);
if (v > 0) {
- host.addReplacementEffect(CardFactoryUtil.makeEtbCounter("etbCounter:P1P1:" + v, host, false));
+
+ final Card eff = new Card(game.nextCardId(), game);
+ eff.setTimestamp(game.getNextTimestamp());
+ eff.setName(c.getName() + "'s Effect");
+ eff.addType("Effect");
+ eff.setToken(true); // Set token to true, so when leaving play it gets nuked
+ eff.setOwner(activator);
+
+ eff.setImageKey(c.getImageKey());
+ eff.setColor(MagicColor.COLORLESS);
+ eff.setImmutable(true);
+ // try to get the SpellAbility from the mana ability
+ //eff.setEffectSource((SpellAbility)null);
+
+ eff.addRemembered(host);
+
+ String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ " + v;
+
+ SpellAbility saAb = AbilityFactory.getAbility(abStr, c);
+
+ CardFactoryUtil.setupETBReplacementAbility(saAb);
+
+ String desc = "It enters the battlefield with ";
+ desc += Lang.nounWithNumeral(v, CounterType.P1P1.getName() + " counter");
+ desc += " on it.";
+
+ String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
+
+ ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
+ re.setLayer(ReplacementLayer.Other);
+ re.setOverridingAbility(saAb);
+
+ eff.addReplacementEffect(re);
+
+ // Forgot Trigger
+ String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
+ String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
+ String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
+ + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
+
+ SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
+ AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
+ saForget.setSubAbility(saExile);
+
+ final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
+ parsedTrigger.setOverridingAbility(saForget);
+ eff.addTrigger(parsedTrigger);
+ eff.updateStateForView();
+
+ // TODO: Add targeting to the effect so it knows who it's dealing with
+ game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
+ game.getAction().moveTo(ZoneType.Command, eff, null);
+ game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
+
if (result == null) {
result = sa.copy();
}
diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java
index e90e6eb9b50..d11afbe776c 100644
--- a/forge-game/src/main/java/forge/game/mana/ManaPool.java
+++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java
@@ -254,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
}
}
if (mana.addsCounters(sa)) {
- mana.getManaAbility().createETBCounters(host);
+ mana.getManaAbility().createETBCounters(host, this.owner);
}
if (mana.triggersWhenSpent()) {
mana.getManaAbility().addTriggersWhenSpent(sa, host);
diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
index f2976852559..510458a6189 100644
--- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
+++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
@@ -6,23 +6,24 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package forge.game.spellability;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
+import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
@@ -35,6 +36,8 @@ import forge.game.replacement.*;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
+import forge.game.zone.ZoneType;
+import forge.util.Lang;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
@@ -47,7 +50,7 @@ import java.util.regex.Pattern;
*
* Abstract AbilityMana class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -79,7 +82,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* Constructor for AbilityMana.
*
- *
+ *
* @param sourceCard
* a {@link forge.game.card.Card} object.
*/
@@ -112,7 +115,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* produceMana.
*
- *
+ *
* @param produced
* a {@link java.lang.String} object.
* @param player
@@ -170,7 +173,7 @@ public class AbilityManaPart implements java.io.Serializable {
* cannotCounterPaidWith.
*
* @param saBeingPaid
- *
+ *
* @return a {@link java.lang.String} object.
*/
public boolean cannotCounterPaidWith(SpellAbility saBeingPaid) {
@@ -187,7 +190,7 @@ public class AbilityManaPart implements java.io.Serializable {
* addKeywords.
*
* @param saBeingPaid
- *
+ *
* @return a {@link java.lang.String} object.
*/
public boolean addKeywords(SpellAbility saBeingPaid) {
@@ -206,7 +209,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* getKeywords.
*
- *
+ *
* @return a {@link java.lang.String} object.
*/
public String getKeywords() {
@@ -218,7 +221,7 @@ public class AbilityManaPart implements java.io.Serializable {
* addsCounters.
*
* @param saBeingPaid
- *
+ *
* @return a {@link java.lang.String} object.
*/
public boolean addsCounters(SpellAbility saBeingPaid) {
@@ -228,10 +231,26 @@ public class AbilityManaPart implements java.io.Serializable {
/**
* createETBCounters
*/
- public void createETBCounters(Card c) {
+ public void createETBCounters(Card c, Player controller) {
String[] parse = this.addsCounters.split("_");
// Convert random SVars if there are other cards with this effect
if (c.isValid(parse[0], c.getController(), c, null)) {
+ final Game game = this.sourceCard.getGame();
+ final Card eff = new Card(game.nextCardId(), game);
+ eff.setTimestamp(game.getNextTimestamp());
+ eff.setName(sourceCard.getName() + "'s Effect");
+ eff.addType("Effect");
+ eff.setToken(true); // Set token to true, so when leaving play it gets nuked
+ eff.setOwner(controller);
+
+ eff.setImageKey(sourceCard.getImageKey());
+ eff.setColor(MagicColor.COLORLESS);
+ eff.setImmutable(true);
+ // try to get the SpellAbility from the mana ability
+ //eff.setEffectSource((SpellAbility)null);
+
+ eff.addRemembered(c);
+
String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + parse[1]
+ " | ETB$ True | CounterNum$ " + parse[2];
@@ -241,15 +260,37 @@ public class AbilityManaPart implements java.io.Serializable {
}
CardFactoryUtil.setupETBReplacementAbility(sa);
- String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
- + " | Secondary$ True | Description$ CARDNAME"
- + " enters the battlefield with " + CounterType.valueOf(parse[1]).getName() + " counters.";
+ String desc = "It enters the battlefield with ";
+ desc += Lang.nounWithNumeral(parse[2], CounterType.valueOf(parse[1]).getName() + " counter");
+ desc += " on it.";
- ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, c, false);
+ String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
+
+ ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
re.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(sa);
- c.addChangedCardTraits(null, null, null, ImmutableList.of(re), null, false, false, false, sa.getHostCard().getGame().getNextTimestamp());
+ eff.addReplacementEffect(re);
+
+ // Forgot Trigger
+ String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
+ String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
+ String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
+ + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
+
+ SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
+ AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
+ saForget.setSubAbility(saExile);
+
+ final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
+ parsedTrigger.setOverridingAbility(saForget);
+ eff.addTrigger(parsedTrigger);
+ eff.updateStateForView();
+
+ // TODO: Add targeting to the effect so it knows who it's dealing with
+ game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
+ game.getAction().moveTo(ZoneType.Command, eff, null);
+ game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}
}
@@ -270,7 +311,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* getManaRestrictions.
*
- *
+ *
* @return a {@link java.lang.String} object.
*/
public String getManaRestrictions() {
@@ -281,7 +322,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* meetsManaRestrictions.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
@@ -297,7 +338,7 @@ public class AbilityManaPart implements java.io.Serializable {
if (restriction.equals("nonSpell")) {
return !sa.isSpell();
}
-
+
if (restriction.equals("CumulativeUpkeep")) {
if (sa.isCumulativeupkeep()) {
return true;
@@ -350,7 +391,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* mana.
*
- *
+ *
* @return a {@link java.lang.String} object.
*/
public final String mana() {
@@ -439,7 +480,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* canProduce.
*
- *
+ *
* @param s
* a {@link java.lang.String} object.
* @return a boolean.
@@ -469,7 +510,7 @@ public class AbilityManaPart implements java.io.Serializable {
*
* isBasic.
*
- *
+ *
* @return a boolean.
*/
public final boolean isBasic() {
@@ -542,7 +583,7 @@ public class AbilityManaPart implements java.io.Serializable {
public Card getSourceCard() {
return sourceCard;
}
-
+
public void setSourceCard(final Card host) {
sourceCard = host;
}
diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java
index eec8c20c98a..0d72be9a4e5 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java
@@ -103,7 +103,7 @@ public final class CEditorConstructed extends CDeckEditor {
case TinyLeaders:
allSections.add(DeckSection.Commander);
- commanderFilter = CardRulesPredicates.Presets.CAN_BE_COMMANDER;
+ commanderFilter = CardRulesPredicates.Presets.CAN_BE_TINY_LEADERS_COMMANDER;
commanderPool = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getAllCards(Predicates.compose(commanderFilter, PaperCard.FN_GET_RULES)), PaperCard.class);
normalPool = ItemPool.createFrom(FModel.getMagicDb().getCommonCards().getAllCards(), PaperCard.class);
diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
index 2f74d266722..ac553fb40ab 100644
--- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java
+++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
@@ -856,6 +856,9 @@ public class FDeckEditor extends TabPageScreen {
case Brawl:
isLegalCommander = card.getRules().canBeBrawlCommander();
break;
+ case TinyLeaders:
+ isLegalCommander = card.getRules().canBeTinyLeadersCommander();
+ break;
case Oathbreaker:
isLegalCommander = card.getRules().canBeOathbreaker();
captionSuffix = localizer.getMessage("lblOathbreaker");
diff --git a/forge-gui/res/blockdata/blocks.txt b/forge-gui/res/blockdata/blocks.txt
index 5d062be3068..67d158df0af 100644
--- a/forge-gui/res/blockdata/blocks.txt
+++ b/forge-gui/res/blockdata/blocks.txt
@@ -84,4 +84,4 @@ Modern Horizons, 3/6/WAR, MH1
Core Set 2020, 3/6/M20, M20
Throne of Eldraine, 3/6/ELD, ELD
Theros Beyond Death, 3/6/THB, THB
-Mystery Booster, 3/6/MB1, MB1
\ No newline at end of file
+Mystery Booster, 3/6/THB, MB1
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/g/ghastly_demise.txt b/forge-gui/res/cardsfolder/g/ghastly_demise.txt
index 78ced7746b3..f04b0f2ac15 100644
--- a/forge-gui/res/cardsfolder/g/ghastly_demise.txt
+++ b/forge-gui/res/cardsfolder/g/ghastly_demise.txt
@@ -1,7 +1,7 @@
Name:Ghastly Demise
ManaCost:B
Types:Instant
-A:SP$ Destroy | Cost$ B | ValidTgts$ Creature.nonBlack+toughnessLEX | TgtPrompt$ Select target nonblack creature with toughness less than or equal to the number of cards in your graveyard. | References$ X | SpellDescription$ Destroy target nonblack creature if its toughness is less than or equal to the number of cards in your graveyard.
+A:SP$ Destroy | Cost$ B | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select target nonblack creature | ConditionCheckSVar$ Y | ConditionSVarCompare$ LEX | References$ X,Y | StackDescription$ SpellDescription | SpellDescription$ Destroy target nonblack creature if its toughness is less than or equal to the number of cards in your graveyard.
+SVar:Y:Targeted$CardToughness
SVar:X:Count$InYourYard
-SVar:Picture:http://www.wizards.com/global/images/magic/general/ghastly_demise.jpg
Oracle:Destroy target nonblack creature if its toughness is less than or equal to the number of cards in your graveyard.
diff --git a/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt b/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt
index 3ad0c877919..f89143a06a0 100644
--- a/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt
+++ b/forge-gui/res/cardsfolder/n/nissas_pilgrimage.txt
@@ -1,9 +1,10 @@
Name:Nissa's Pilgrimage
ManaCost:2 G
Types:Sorcery
-A:SP$ ChangeZone | Cost$ 2 G | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic+Forest | ChangeNum$ 1 | SubAbility$ DBChangeZone1 | NoShuffle$ True | SpellDescription$ Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library. Spell mastery — If there are two or more instant or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two.
-SVar:DBChangeZone1:DB$ChangeZone | Origin$ Library | Destination$ Hand | SubAbility$ DBChangeZone2 | ChangeType$ Land.Basic+Forest | ChangeNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ LT2 | References$ X
-SVar:DBChangeZone2:DB$ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic+Forest | ChangeNum$ 2 | ConditionCheckSVar$ X | ConditionSVarCompare$ GE2 | References$ X
-SVar:X:Count$ValidGraveyard Instant.YouOwn,Sorcery.YouOwn
-SVar:Picture:http://www.wizards.com/global/images/magic/general/nissas_pilgrimage.jpg
+A:SP$ ChangeZone | Cost$ 2 G | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic+Forest | ChangeNum$ X | References$ X,Y | RememberChanged$ True | SubAbility$ DBBattlefield | Shuffle$ False | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library. Spell mastery — If there are two or more instant or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two.
+SVar:DBBattlefield:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | SubAbility$ DBHand | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to go to the battlefield | Shuffle$ False | StackDescription$ None
+SVar:DBHand:DB$ ChangeZone | Origin$ Library | Destination$ Hand | Defined$ Remembered | NoLooking$ True | StackDescription$ None | SubAbility$ DBCleanup
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+SVar:X:Count$Compare Y GE2.3.2
+SVar:Y:Count$ValidGraveyard Instant.YouOwn,Sorcery.YouOwn
Oracle:Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library.\nSpell mastery — If there are two or more instant or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two.
diff --git a/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt b/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt
index 349f1e790d9..27021aac864 100644
--- a/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt
+++ b/forge-gui/res/cardsfolder/t/the_birth_of_meletis.txt
@@ -3,7 +3,7 @@ ManaCost:1 W
Types:Enchantment Saga
K:Saga:3:TrigChange,TrigToken,TrigGainLife
SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Plains+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle your library.
-SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_0_4_wall_defender | TokenOwner$ You | LegacyImage$ c 0 4 wall defender thb | SpellDescription$ Create a 0/4 colorless Wall artifact creature token with defender.
+SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_0_4_a_wall_defender | TokenOwner$ You | LegacyImage$ c 0 4 wall defender thb | SpellDescription$ Create a 0/4 colorless Wall artifact creature token with defender.
SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | SpellDescription$ You gain 2 life.
DeckHas:Ability$LifeGain & Ability$Token
Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle your library.\nII - Create a 0/4 colorless Wall artifact creature token with defender.\nIII - You gain 2 life.
diff --git a/forge-gui/res/editions/Theros Beyond Death.txt b/forge-gui/res/editions/Theros Beyond Death.txt
index b6d93e220f7..6d8b9f91a2d 100644
--- a/forge-gui/res/editions/Theros Beyond Death.txt
+++ b/forge-gui/res/editions/Theros Beyond Death.txt
@@ -377,7 +377,7 @@ Prerelease=6 Boosters, 1 RareMythic+
[tokens]
b_2_2_zombie
-c_0_4_wall_defender
+c_0_4_a_wall_defender
g_1_2_spider_reach
g_2_2_wolf
r_x_1_elemental_trample_haste
diff --git a/forge-gui/res/skins/default/sprite_border.png b/forge-gui/res/skins/default/sprite_border.png
index c6be80f099a..4fe259b55c7 100644
Binary files a/forge-gui/res/skins/default/sprite_border.png and b/forge-gui/res/skins/default/sprite_border.png differ
diff --git a/forge-gui/res/tokenscripts/c_0_4_wall_defender.txt b/forge-gui/res/tokenscripts/c_0_4_a_wall_defender.txt
similarity index 67%
rename from forge-gui/res/tokenscripts/c_0_4_wall_defender.txt
rename to forge-gui/res/tokenscripts/c_0_4_a_wall_defender.txt
index 03a29952252..2b5be99e3e2 100644
--- a/forge-gui/res/tokenscripts/c_0_4_wall_defender.txt
+++ b/forge-gui/res/tokenscripts/c_0_4_a_wall_defender.txt
@@ -1,6 +1,6 @@
Name:Wall
ManaCost:no cost
-Types:Creature Wall
+Types:Artifact Creature Wall
PT:0/4
K:Defender
Oracle:Defender
diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java
index 0ab7b0ed0e8..ef83a64f414 100644
--- a/forge-gui/src/main/java/forge/util/ImageFetcher.java
+++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java
@@ -6,6 +6,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import forge.FThreads;
import forge.ImageKeys;
@@ -50,7 +52,14 @@ public abstract class ImageFetcher {
setDownload.append(ImageUtil.getDownloadUrl(paperCard, backFace));
downloadUrls.add(setDownload.toString());
- int artIndex = Integer.parseInt(imageKey.split("\\|")[2]);
+ int artIndex = 1;
+ final Pattern pattern = Pattern.compile(
+ "^.:([^|]*\\|){2}(\\d+).*$"
+ );
+ Matcher matcher = pattern.matcher(imageKey);
+ if (matcher.matches()) {
+ artIndex = Integer.parseInt(matcher.group(2));
+ }
final StaticData data = StaticData.instance();
final String cardNum = data.getCommonCards().getCardCollectorNumber(paperCard.getName(), paperCard.getEdition(), artIndex);
if (cardNum != null) {