consumer, Cost... costs) {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
this.staticText = rule;
this.effects.addAll(effects);
this.targets.addAll(targets);
Collections.addAll(this.costs, costs);
this.useAttachedCost = attachedCost;
+ this.consumer = consumer;
this.generateGainAbilityDependencies(makeAbility(null, null), null);
}
@@ -51,6 +59,7 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
this.targets.addAll(effect.targets);
this.costs.addAll(effect.costs);
this.useAttachedCost = effect.useAttachedCost == null ? null : effect.useAttachedCost.copy();
+ this.consumer = effect.consumer;
}
@Override
@@ -94,7 +103,7 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
}
protected Ability makeAbility(Game game, Ability source) {
- Ability ability = new SimpleActivatedAbility(null, null);
+ ActivatedAbility ability = new SimpleActivatedAbility(null, null);
for (Effect effect : effects) {
if (effect == null) {
continue;
@@ -116,6 +125,9 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
if (source != null && game != null) {
ability.addCost(useAttachedCost.copy().setMageObjectReference(source, game));
}
+ if (consumer != null) {
+ consumer.accept(ability);
+ }
return ability;
}
}
diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java
index 0ece62d0101..b4885a2a020 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/SwitchPowerToughnessTargetEffect.java
@@ -10,6 +10,8 @@ import mage.abilities.effects.ContinuousEffectImpl;
import mage.game.Game;
import mage.game.permanent.Permanent;
+import java.util.UUID;
+
/**
* @author ayratn
*/
@@ -30,13 +32,15 @@ public class SwitchPowerToughnessTargetEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
- Permanent target = game.getPermanent(this.getTargetPointer().getFirst(game, source));
- if (target == null) {
- return false;
+ int affectedTargets = 0;
+ for (UUID uuid : getTargetPointer().getTargets(game, source)) {
+ Permanent target = game.getPermanent(uuid);
+ if (target != null) {
+ target.switchPowerToughness();
+ affectedTargets++;
+ }
}
-
- target.switchPowerToughness();
- return true;
+ return affectedTargets > 0;
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java
index c2f35e9449b..86e32efe3ff 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddPoisonCounterTargetEffect.java
@@ -51,8 +51,8 @@ public class AddPoisonCounterTargetEffect extends OneShotEffect {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
- return getTargetPointer().describeTargets(mode.getTargets(), "it") +
+ return getTargetPointer().describeTargets(mode.getTargets(), "that player") +
(getTargetPointer().isPlural(mode.getTargets()) ? " get " : " gets ") +
- CardUtil.getSimpleCountersText(amount, "a", "poison");
+ CardUtil.getSimpleCountersText(amount, "a", "poison");
}
}
diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/CardMorEnteringTappedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/CardMorEnteringTappedEffect.java
new file mode 100644
index 00000000000..718bc120090
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/CardMorEnteringTappedEffect.java
@@ -0,0 +1,66 @@
+package mage.abilities.effects.common.replacement;
+
+import mage.MageObjectReference;
+import mage.abilities.Ability;
+import mage.abilities.effects.ReplacementEffectImpl;
+import mage.cards.Card;
+import mage.constants.Duration;
+import mage.constants.Outcome;
+import mage.game.Game;
+import mage.game.events.EntersTheBattlefieldEvent;
+import mage.game.events.GameEvent;
+import mage.game.permanent.Permanent;
+
+/**
+ * Used to affect a card that will enter the battlefield (land played, or card put into play by effect).
+ * The permanent it becomes enters tapped.
+ *
+ * Short-lived replacement effect, auto-cleanup if the mor is no longer current.
+ *
+ * @author Susucr
+ */
+public class CardMorEnteringTappedEffect extends ReplacementEffectImpl {
+
+ private final MageObjectReference mor;
+
+ public CardMorEnteringTappedEffect(MageObjectReference mor) {
+ super(Duration.OneUse, Outcome.Tap);
+ this.staticText = "That permanent enters the battlefield tapped";
+ this.mor = mor;
+ }
+
+ private CardMorEnteringTappedEffect(final CardMorEnteringTappedEffect effect) {
+ super(effect);
+ this.mor = effect.mor;
+ }
+
+ @Override
+ public CardMorEnteringTappedEffect copy() {
+ return new CardMorEnteringTappedEffect(this);
+ }
+
+ @Override
+ public boolean checksEventType(GameEvent event, Game game) {
+ return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
+ }
+
+ @Override
+ public boolean applies(GameEvent event, Ability source, Game game) {
+ Card morCard = mor.getCard(game);
+ if (morCard == null) {
+ // cleanup if something other than resolving happens to the spell.
+ discard();
+ return false;
+ }
+ return event.getTargetId().equals(morCard.getId());
+ }
+
+ @Override
+ public boolean replaceEvent(GameEvent event, Ability source, Game game) {
+ Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
+ if (permanent != null) {
+ permanent.setTapped(true);
+ }
+ return false;
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/SpellMorEnteringTappedEffect.java
similarity index 85%
rename from Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java
rename to Mage/src/main/java/mage/abilities/effects/common/replacement/SpellMorEnteringTappedEffect.java
index 66bc00ef654..8e1b23a279a 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/SpellMorEnteringTappedEffect.java
@@ -19,24 +19,24 @@ import mage.game.stack.Spell;
*
* @author Susucr
*/
-public class MorEnteringTappedEffect extends ReplacementEffectImpl {
+public class SpellMorEnteringTappedEffect extends ReplacementEffectImpl {
private final MageObjectReference mor;
- public MorEnteringTappedEffect(MageObjectReference mor) {
+ public SpellMorEnteringTappedEffect(MageObjectReference mor) {
super(Duration.OneUse, Outcome.Tap);
this.staticText = "That permanent enters the battlefield tapped";
this.mor = mor;
}
- private MorEnteringTappedEffect(final MorEnteringTappedEffect effect) {
+ private SpellMorEnteringTappedEffect(final SpellMorEnteringTappedEffect effect) {
super(effect);
this.mor = effect.mor;
}
@Override
- public MorEnteringTappedEffect copy() {
- return new MorEnteringTappedEffect(this);
+ public SpellMorEnteringTappedEffect copy() {
+ return new SpellMorEnteringTappedEffect(this);
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java b/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java
index f596d3c5904..7abb548a02c 100644
--- a/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java
+++ b/Mage/src/main/java/mage/abilities/hint/common/CreaturesYouControlHint.java
@@ -12,7 +12,7 @@ import mage.game.Game;
public enum CreaturesYouControlHint implements Hint {
instance;
- private static final Hint hint = new ValueHint("Creatures you control", CreaturesYouControlCount.instance);
+ private static final Hint hint = new ValueHint("Creatures you control", CreaturesYouControlCount.PLURAL);
@Override
public String getText(Game game, Ability ability) {
diff --git a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java
index 99909e27fd1..2035e57cfcf 100644
--- a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java
@@ -20,8 +20,11 @@ import mage.watchers.common.CommanderPlaysCountWatcher;
*/
public class CommanderStormAbility extends TriggeredAbilityImpl {
- public CommanderStormAbility() {
- super(Zone.STACK, new CommanderStormEffect());
+ public CommanderStormAbility(boolean newTargetsText) {
+ super(Zone.STACK, new CommanderStormEffect().setText("copy it for each time you've "
+ + "cast your commander from the command zone this game."
+ + (newTargetsText ? " You may choose new targets for the copies." : "")));
+ this.setTriggerPhrase("When you cast this spell, ");
this.setRuleAtTheTop(true);
}
@@ -54,13 +57,6 @@ public class CommanderStormAbility extends TriggeredAbilityImpl {
}
return true;
}
-
- @Override
- public String getRule() {
- return "When you cast this spell, copy it for each time you've "
- + "cast your commander from the command zone this game. "
- + "You may choose new targets for the copies.";
- }
}
class CommanderStormEffect extends OneShotEffect {
diff --git a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java
index 7c068c514b4..7108aa802e1 100644
--- a/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/ProvokeAbility.java
@@ -6,12 +6,12 @@ import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.constants.Duration;
-import mage.filter.common.FilterCreaturePermanent;
-import mage.filter.predicate.permanent.ControllerIdPredicate;
+import mage.constants.SetTargetPointer;
+import mage.filter.StaticFilters;
import mage.game.Game;
-import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
+import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import java.util.UUID;
@@ -31,27 +31,16 @@ public class ProvokeAbility extends AttacksTriggeredAbility {
}
public ProvokeAbility(String text) {
- super(new UntapTargetEffect(), true, text);
+ super(new UntapTargetEffect(), true, text, SetTargetPointer.PLAYER);
this.addEffect(new ProvokeRequirementEffect());
+ this.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE));
+ this.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
}
protected ProvokeAbility(final ProvokeAbility ability) {
super(ability);
}
- @Override
- public boolean checkTrigger(GameEvent event, Game game) {
- if (super.checkTrigger(event, game)) {
- FilterCreaturePermanent filter = new FilterCreaturePermanent("creature defending player controls");
- UUID defendingPlayerId = game.getCombat().getDefendingPlayerId(sourceId, game);
- filter.add(new ControllerIdPredicate(defendingPlayerId));
- this.getTargets().clear();
- this.addTarget(new TargetPermanent(filter));
- return true;
- }
- return false;
- }
-
@Override
public ProvokeAbility copy() {
return new ProvokeAbility(this);
diff --git a/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java b/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java
index c1b08e6947e..6a4eb980ccd 100644
--- a/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/SoulshiftAbility.java
@@ -2,19 +2,15 @@
package mage.abilities.keyword;
-import mage.constants.ComparisonType;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
+import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.filter.FilterCard;
-import mage.filter.predicate.mageobject.ManaValuePredicate;
-import mage.game.Game;
-import mage.game.events.GameEvent;
import mage.target.common.TargetCardInYourGraveyard;
-
-import java.util.UUID;
+import mage.target.targetadjustment.ManaValueTargetAdjuster;
/**
* 702.45. Soulshift
@@ -38,6 +34,10 @@ public class SoulshiftAbility extends DiesSourceTriggeredAbility {
public SoulshiftAbility(DynamicValue amount) {
super(new ReturnToHandTargetEffect());
this.amount = amount;
+ FilterCard filter = new FilterCard("Spirit card from your graveyard");
+ filter.add(SubType.SPIRIT.getPredicate());
+ this.addTarget(new TargetCardInYourGraveyard(filter));
+ this.setTargetAdjuster(new ManaValueTargetAdjuster(amount, ComparisonType.OR_LESS));
}
protected SoulshiftAbility(final SoulshiftAbility ability) {
@@ -45,17 +45,6 @@ public class SoulshiftAbility extends DiesSourceTriggeredAbility {
this.amount = ability.amount;
}
- @Override
- public void trigger(Game game, UUID controllerId, GameEvent triggeringEvent) {
- this.getTargets().clear();
- int intValue = amount.calculate(game, this, null);
- FilterCard filter = new FilterCard("Spirit card with mana value " + intValue + " or less from your graveyard");
- filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, intValue + 1));
- filter.add(SubType.SPIRIT.getPredicate());
- this.addTarget(new TargetCardInYourGraveyard(filter));
- super.trigger(game, controllerId, triggeringEvent);
- }
-
@Override
public SoulshiftAbility copy() {
return new SoulshiftAbility(this);
diff --git a/Mage/src/main/java/mage/abilities/keyword/StationAbility.java b/Mage/src/main/java/mage/abilities/keyword/StationAbility.java
new file mode 100644
index 00000000000..e918dc283d2
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/keyword/StationAbility.java
@@ -0,0 +1,91 @@
+package mage.abilities.keyword;
+
+import mage.abilities.Ability;
+import mage.abilities.common.SimpleActivatedAbility;
+import mage.abilities.costs.common.TapTargetCost;
+import mage.abilities.effects.OneShotEffect;
+import mage.constants.Outcome;
+import mage.constants.TimingRule;
+import mage.constants.Zone;
+import mage.counters.CounterType;
+import mage.filter.common.FilterControlledCreaturePermanent;
+import mage.filter.common.FilterControlledPermanent;
+import mage.filter.predicate.mageobject.AnotherPredicate;
+import mage.filter.predicate.permanent.TappedPredicate;
+import mage.game.Game;
+import mage.game.events.GameEvent;
+import mage.game.permanent.Permanent;
+
+import java.util.List;
+
+/**
+ * @author TheElk801
+ */
+public class StationAbility extends SimpleActivatedAbility {
+
+ private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("another creature you control");
+
+ static {
+ filter.add(AnotherPredicate.instance);
+ filter.add(TappedPredicate.UNTAPPED);
+ }
+
+ public StationAbility() {
+ super(Zone.BATTLEFIELD, new StationAbilityEffect(), new TapTargetCost(filter));
+ this.timing = TimingRule.SORCERY;
+ }
+
+ private StationAbility(final StationAbility ability) {
+ super(ability);
+ }
+
+ @Override
+ public StationAbility copy() {
+ return new StationAbility(this);
+ }
+
+ @Override
+ public String getRule() {
+ return "Station (Tap another creature you control: Put charge counters equal to its power on {this}. Station only as a sorcery.)";
+ }
+}
+
+class StationAbilityEffect extends OneShotEffect {
+
+ StationAbilityEffect() {
+ super(Outcome.Benefit);
+ }
+
+ private StationAbilityEffect(final StationAbilityEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public StationAbilityEffect copy() {
+ return new StationAbilityEffect(this);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Permanent permanent = source.getSourcePermanentIfItStillExists(game);
+ if (permanent == null) {
+ return false;
+ }
+ List creatures = (List) getValue("tappedPermanents");
+ if (creatures == null) {
+ return false;
+ }
+ int power = 0;
+ for (Permanent creature : creatures) {
+ GameEvent event = GameEvent.getEvent(
+ GameEvent.EventType.STATION_PERMANENT, creature.getId(),
+ source, source.getControllerId(), creature.getPower().getValue()
+ );
+ if (game.replaceEvent(event)) {
+ continue;
+ }
+ power += event.getAmount();
+ }
+ return power > 0 && permanent.addCounters(CounterType.CHARGE.createInstance(power), source, game);
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/keyword/StationLevelAbility.java b/Mage/src/main/java/mage/abilities/keyword/StationLevelAbility.java
new file mode 100644
index 00000000000..20791ca75f9
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/keyword/StationLevelAbility.java
@@ -0,0 +1,159 @@
+package mage.abilities.keyword;
+
+import mage.abilities.Ability;
+import mage.abilities.StaticAbility;
+import mage.abilities.effects.ContinuousEffectImpl;
+import mage.constants.*;
+import mage.counters.CounterType;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+import mage.util.CardUtil;
+
+import java.util.stream.Collectors;
+
+/**
+ * @author TheElk801
+ */
+public class StationLevelAbility extends StaticAbility {
+
+ private final int level;
+
+ public StationLevelAbility(int level) {
+ super(Zone.BATTLEFIELD, null);
+ this.level = level;
+ }
+
+ private StationLevelAbility(final StationLevelAbility ability) {
+ super(ability);
+ this.level = ability.level;
+ }
+
+ @Override
+ public StationLevelAbility copy() {
+ return new StationLevelAbility(this);
+ }
+
+ public StationLevelAbility withLevelAbility(Ability ability) {
+ this.addEffect(new StationLevelAbilityEffect(ability, level));
+ return this;
+ }
+
+ public StationLevelAbility withPT(int power, int toughness) {
+ this.addEffect(new StationLevelCreatureEffect(power, toughness, level));
+ return this;
+ }
+
+ @Override
+ public String getRule() {
+ return "STATION " + level + "+
" + this
+ .getEffects()
+ .stream()
+ .map(effect -> effect.getText(this.getModes().getMode()))
+ .map(CardUtil::getTextWithFirstCharUpperCase)
+ .collect(Collectors.joining("
"));
+ }
+
+ public boolean hasPT() {
+ return this.getEffects().stream().anyMatch(StationLevelCreatureEffect.class::isInstance);
+ }
+}
+
+class StationLevelAbilityEffect extends ContinuousEffectImpl {
+
+ private final Ability ability;
+ private final int level;
+
+ StationLevelAbilityEffect(Ability ability, int level) {
+ super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
+ this.ability = ability;
+ this.level = level;
+ this.ability.setRuleVisible(false);
+ this.staticText = ability.getRule();
+ }
+
+ private StationLevelAbilityEffect(final StationLevelAbilityEffect effect) {
+ super(effect);
+ this.ability = effect.ability;
+ this.level = effect.level;
+ }
+
+ @Override
+ public StationLevelAbilityEffect copy() {
+ return new StationLevelAbilityEffect(this);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Permanent permanent = source.getSourcePermanentIfItStillExists(game);
+ if (permanent == null || permanent.getCounters(game).getCount(CounterType.CHARGE) < level) {
+ return false;
+ }
+ permanent.addAbility(ability, source.getSourceId(), game);
+ return true;
+ }
+}
+
+class StationLevelCreatureEffect extends ContinuousEffectImpl {
+
+ private final int power;
+ private final int toughness;
+ private final int level;
+
+ StationLevelCreatureEffect(int power, int toughness, int level) {
+ super(Duration.WhileOnBattlefield, Outcome.BecomeCreature);
+ this.power = power;
+ this.toughness = toughness;
+ this.level = level;
+ staticText = power + "/" + toughness;
+ }
+
+ private StationLevelCreatureEffect(final StationLevelCreatureEffect effect) {
+ super(effect);
+ this.power = effect.power;
+ this.toughness = effect.toughness;
+ this.level = effect.level;
+ }
+
+ @Override
+ public StationLevelCreatureEffect copy() {
+ return new StationLevelCreatureEffect(this);
+ }
+
+ @Override
+ public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
+ Permanent permanent = source.getSourcePermanentIfItStillExists(game);
+ if (permanent == null || permanent.getCounters(game).getCount(CounterType.CHARGE) < level) {
+ return false;
+ }
+ switch (layer) {
+ case TypeChangingEffects_4:
+ permanent.addCardType(game, CardType.ARTIFACT, CardType.CREATURE);
+ return true;
+ case PTChangingEffects_7:
+ if (sublayer != SubLayer.SetPT_7b) {
+ return false;
+ }
+ permanent.getPower().setModifiedBaseValue(power);
+ permanent.getToughness().setModifiedBaseValue(toughness);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ return false;
+ }
+
+ @Override
+ public boolean hasLayer(Layer layer) {
+ switch (layer) {
+ case TypeChangingEffects_4:
+ case PTChangingEffects_7:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/keyword/WarpAbility.java b/Mage/src/main/java/mage/abilities/keyword/WarpAbility.java
new file mode 100644
index 00000000000..f3de58b05e7
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/keyword/WarpAbility.java
@@ -0,0 +1,157 @@
+package mage.abilities.keyword;
+
+import mage.MageIdentifier;
+import mage.abilities.Ability;
+import mage.abilities.SpellAbility;
+import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
+import mage.abilities.condition.Condition;
+import mage.abilities.costs.mana.ManaCostsImpl;
+import mage.abilities.effects.OneShotEffect;
+import mage.cards.Card;
+import mage.constants.*;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+import mage.players.Player;
+import mage.util.CardUtil;
+
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @author TheElk801
+ */
+public class WarpAbility extends SpellAbility {
+
+ public static final String WARP_ACTIVATION_VALUE_KEY = "warpActivation";
+ private final boolean allowGraveyard;
+
+ public WarpAbility(Card card, String manaString) {
+ this(card, manaString, false);
+ }
+
+ public WarpAbility(Card card, String manaString, boolean allowGraveyard) {
+ super(card.getSpellAbility());
+ this.newId();
+ this.setCardName(card.getName() + " with Warp");
+ this.zone = Zone.HAND;
+ this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
+ this.timing = TimingRule.SORCERY;
+ this.clearManaCosts();
+ this.clearManaCostsToPay();
+ this.addCost(new ManaCostsImpl<>(manaString));
+ this.setAdditionalCostsRuleVisible(false);
+ this.allowGraveyard = allowGraveyard;
+ }
+
+ private WarpAbility(final WarpAbility ability) {
+ super(ability);
+ this.allowGraveyard = ability.allowGraveyard;
+ }
+
+ // The ability sets up a delayed trigger which can't be set up using the cost tag system
+ public static void addDelayedTrigger(SpellAbility spellAbility, Game game) {
+ if (spellAbility instanceof WarpAbility) {
+ game.addDelayedTriggeredAbility(
+ new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new WarpExileEffect()), spellAbility
+ );
+ }
+ }
+
+ @Override
+ public ActivationStatus canActivate(UUID playerId, Game game) {
+ switch (game.getState().getZone(getSourceId())) {
+ case GRAVEYARD:
+ if (!allowGraveyard) {
+ break;
+ }
+ case HAND:
+ return super.canActivate(playerId, game);
+ }
+ return ActivationStatus.getFalse();
+ }
+
+ @Override
+ public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) {
+ if (!super.activate(game, allowedIdentifiers, noMana)) {
+ return false;
+ }
+ this.setCostsTag(WARP_ACTIVATION_VALUE_KEY, null);
+ return true;
+ }
+
+ @Override
+ public WarpAbility copy() {
+ return new WarpAbility(this);
+ }
+
+ @Override
+ public String getRule() {
+ StringBuilder sb = new StringBuilder("Warp");
+ if (getCosts().isEmpty()) {
+ sb.append(' ');
+ } else {
+ sb.append("—");
+ }
+ sb.append(getManaCosts().getText());
+ if (!getCosts().isEmpty()) {
+ sb.append(", ");
+ sb.append(getCosts().getText());
+ sb.append('.');
+ }
+ return sb.toString();
+ }
+
+ public static String makeWarpString(UUID playerId) {
+ return playerId + "- Warped";
+ }
+}
+
+class WarpExileEffect extends OneShotEffect {
+
+ private static class WarpCondition implements Condition {
+
+ private final int turnNumber;
+
+ WarpCondition(Game game) {
+ this.turnNumber = game.getTurnNum();
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ return game.getTurnNum() > turnNumber;
+ }
+ }
+
+ WarpExileEffect() {
+ super(Outcome.Benefit);
+ staticText = "exile this creature if it was cast for its warp cost";
+ }
+
+ private WarpExileEffect(final WarpExileEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public WarpExileEffect copy() {
+ return new WarpExileEffect(this);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Player player = game.getPlayer(source.getControllerId());
+ Permanent permanent = game.getPermanent(source.getSourceId());
+ if (permanent == null || permanent.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + 1) {
+ return false;
+ }
+ player.moveCardsToExile(
+ permanent, source, game, true,
+ CardUtil.getExileZoneId(WarpAbility.makeWarpString(player.getId()), game),
+ "Warped by " + player.getLogName()
+ );
+ CardUtil.makeCardPlayable(
+ game, source, permanent.getMainCard(), true,
+ Duration.Custom, false, player.getId(), new WarpCondition(game)
+ );
+ return true;
+ }
+}
diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java
index 2edbf43c918..90acef7ccce 100644
--- a/Mage/src/main/java/mage/cards/CardImpl.java
+++ b/Mage/src/main/java/mage/cards/CardImpl.java
@@ -918,27 +918,41 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) {
+ boolean canAttach = true;
for (ProtectionAbility ability : this.getAbilities(game).getProtectionAbilities()) {
if ((!attachment.hasSubtype(SubType.AURA, game) || ability.removesAuras())
&& (!attachment.hasSubtype(SubType.EQUIPMENT, game) || ability.removesEquipment())
&& !attachment.getId().equals(ability.getAuraIdNotToBeRemoved())
&& !ability.canTarget(attachment, game)) {
- return !ability.getDoesntRemoveControlled() || Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId()));
+ canAttach &= ability.getDoesntRemoveControlled() && Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId()));
}
}
- boolean canAttach = true;
- Permanent attachmentPermanent = game.getPermanent(attachment.getId());
// If attachment is an aura, ensures this permanent can still be legally enchanted, according to the enchantment's Enchant ability
- if (attachment.hasSubtype(SubType.AURA, game)
- && attachmentPermanent != null
- && attachmentPermanent.getSpellAbility() != null
- && !attachmentPermanent.getSpellAbility().getTargets().isEmpty()) {
- // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583
- // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl).
- canAttach = attachmentPermanent.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(attachmentPermanent.getControllerId(), this.getId(), source, game);
+ if (attachment.hasSubtype(SubType.AURA, game)) {
+ SpellAbility spellAbility = null;
+ UUID controller = null;
+ Permanent attachmentPermanent = game.getPermanent(attachment.getId());
+ if (attachmentPermanent != null) {
+ spellAbility = attachmentPermanent.getSpellAbility(); // Permanent's SpellAbility might be modified, so if possible use that one
+ controller = attachmentPermanent.getControllerId();
+ } else { // Used for checking if it can be attached from the graveyard, such as Unfinished Business
+ Card attachmentCard = game.getCard(attachment.getId());
+ if (attachmentCard != null) {
+ spellAbility = attachmentCard.getSpellAbility();
+ if (source != null) {
+ controller = source.getControllerId();
+ } else {
+ controller = attachmentCard.getControllerOrOwnerId();
+ }
+ }
+ }
+ if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()){
+ // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583
+ // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl).
+ canAttach &= spellAbility.getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(controller, this.getId(), source, game);
+ }
}
-
return !canAttach || game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode);
}
diff --git a/Mage/src/main/java/mage/cards/repository/TokenRepository.java b/Mage/src/main/java/mage/cards/repository/TokenRepository.java
index 75ff94d16bf..65d80a97c93 100644
--- a/Mage/src/main/java/mage/cards/repository/TokenRepository.java
+++ b/Mage/src/main/java/mage/cards/repository/TokenRepository.java
@@ -270,6 +270,7 @@ public enum TokenRepository {
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 10, "https://api.scryfall.com/cards/tdsk/1/en?format=image"));
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 11, "https://api.scryfall.com/cards/tacr/1/en?format=image"));
res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 12, "https://api.scryfall.com/cards/tpip/1/en?format=image"));
+ res.add(createXmageToken(XMAGE_IMAGE_NAME_COPY, 13, "https://api.scryfall.com/cards/teoc/1/en?format=image"));
// City's Blessing
// https://scryfall.com/search?q=type%3Atoken+include%3Aextras+unique%3Aprints+City%27s+Blessing+&unique=cards&as=grid&order=name
diff --git a/Mage/src/main/java/mage/collectors/DataCollector.java b/Mage/src/main/java/mage/collectors/DataCollector.java
new file mode 100644
index 00000000000..db3471a4acd
--- /dev/null
+++ b/Mage/src/main/java/mage/collectors/DataCollector.java
@@ -0,0 +1,68 @@
+package mage.collectors;
+
+import mage.game.Game;
+import mage.game.Table;
+
+import java.util.UUID;
+
+/**
+ * Data collection for better debugging. Can collect server/table/game events and process related data.
+ *
+ * Supported features:
+ * - [x] collect and print game logs in server output, including unit tests
+ * - [x] collect and save full games history and decks
+ * - [ ] collect and print performance metrics like ApplyEffects calc time or inform players time (pings)
+ * - [ ] collect and send metrics to third party tools like prometheus + grafana
+ * - [ ] prepare "attachable" game data for bug reports
+ * - [ ] record game replays data (GameView history)
+ *
+ * How-to enable or disable:
+ * - use java params like -Dxmage.dataCollectors.saveGameHistory=true
+ *
+ * How-to add new service:
+ * - create new class and extends EmptyDataCollector
+ * - each service must use unique service code
+ * - override only needed events
+ * - modify DataCollectorServices.init with new class
+ * - make sure it's fast and never raise errors
+ *
+ * @author JayDi85
+ */
+public interface DataCollector {
+
+ /**
+ * Return unique service code to enable by command line
+ */
+ String getServiceCode();
+
+ /**
+ * Show some hints on service enabled, e.g. root folder path
+ */
+ String getInitInfo();
+
+ void onServerStart();
+
+ void onTableStart(Table table);
+
+ void onTableEnd(Table table);
+
+ void onGameStart(Game game);
+
+ void onGameLog(Game game, String message);
+
+ void onGameEnd(Game game);
+
+ /**
+ * @param userName can be null for system messages
+ */
+ void onChatRoom(UUID roomId, String userName, String message);
+
+ void onChatTourney(UUID tourneyId, String userName, String message);
+
+ void onChatTable(UUID tableId, String userName, String message);
+
+ /**
+ * @param gameId chat sessings don't have full game access, so use onGameStart event to find game's ID before chat
+ */
+ void onChatGame(UUID gameId, String userName, String message);
+}
diff --git a/Mage/src/main/java/mage/collectors/DataCollectorServices.java b/Mage/src/main/java/mage/collectors/DataCollectorServices.java
new file mode 100644
index 00000000000..dc2740f078a
--- /dev/null
+++ b/Mage/src/main/java/mage/collectors/DataCollectorServices.java
@@ -0,0 +1,143 @@
+package mage.collectors;
+
+import mage.collectors.services.PrintGameLogsDataCollector;
+import mage.collectors.services.SaveGameHistoryDataCollector;
+import mage.game.Game;
+import mage.game.Table;
+import org.apache.log4j.Logger;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Not a real data collector. It's a global service to inject and collect data all around the code.
+ *
+ * @author JayDi85
+ */
+final public class DataCollectorServices implements DataCollector {
+
+ // usage example: -Dxmage.dataCollectors.saveGameHistory=true
+ private static final String COMMAND_LINE_DATA_COLLECTORS_PREFIX = "xmage.dataCollectors.";
+
+ private static final Logger logger = Logger.getLogger(DataCollectorServices.class);
+
+ private static DataCollectorServices instance = null;
+
+ // fill on server startup, so it's thread safe
+ Set allServices = new LinkedHashSet<>();
+ Set activeServices = new LinkedHashSet<>();
+
+ public static DataCollectorServices getInstance() {
+ if (instance == null) {
+ instance = new DataCollectorServices();
+ }
+ return instance;
+ }
+
+ /**
+ * Init data service on server's startup
+ *
+ * @param enablePrintGameLogs use for unit tests to enable additional logs for better debugging
+ * @param enableSaveGameHistory use to save full game history with logs, decks, etc
+ */
+ public static void init(boolean enablePrintGameLogs, boolean enableSaveGameHistory) {
+ if (instance != null) {
+ // unit tests: init on first test run, all other will use same process
+ // real server: init on server startup
+ return;
+ }
+
+ // fill all possible services
+ getInstance().allServices.add(new PrintGameLogsDataCollector());
+ getInstance().allServices.add(new SaveGameHistoryDataCollector());
+ logger.info(String.format("Data collectors: found %d services", getInstance().allServices.size()));
+
+ // enable only needed
+ getInstance().allServices.forEach(service -> {
+ boolean isDefault = false;
+ isDefault |= enablePrintGameLogs && service.getServiceCode().equals(PrintGameLogsDataCollector.SERVICE_CODE);
+ isDefault |= enableSaveGameHistory && service.getServiceCode().equals(SaveGameHistoryDataCollector.SERVICE_CODE);
+ boolean isEnable = isServiceEnable(service.getServiceCode(), isDefault);
+ if (isEnable) {
+ getInstance().activeServices.add(service);
+ }
+ String info = isEnable ? String.format(" (%s)", service.getInitInfo()) : "";
+ logger.info(String.format("Data collectors: %s - %s%s", service.getServiceCode(), isEnable ? "enabled" : "disabled", info));
+ });
+ }
+
+ private static boolean isServiceEnable(String dataCollectorCode, boolean isEnableByDefault) {
+ String needCommand = COMMAND_LINE_DATA_COLLECTORS_PREFIX + dataCollectorCode;
+ boolean isEnable;
+ if (System.getProperty(needCommand) != null) {
+ isEnable = System.getProperty(needCommand, "false").equals("true");
+ } else {
+ isEnable = isEnableByDefault;
+ }
+ return isEnable;
+ }
+
+ @Override
+ public String getServiceCode() {
+ throw new IllegalStateException("Wrong code usage. Use it by static methods only");
+ }
+
+ @Override
+ public String getInitInfo() {
+ throw new IllegalStateException("Wrong code usage. Use it by static methods only");
+ }
+
+ @Override
+ public void onServerStart() {
+ activeServices.forEach(DataCollector::onServerStart);
+ }
+
+ @Override
+ public void onTableStart(Table table) {
+ activeServices.forEach(c -> c.onTableStart(table));
+ }
+
+ @Override
+ public void onTableEnd(Table table) {
+ activeServices.forEach(c -> c.onTableEnd(table));
+ }
+
+ @Override
+ public void onGameStart(Game game) {
+ if (game.isSimulation()) return;
+ activeServices.forEach(c -> c.onGameStart(game));
+ }
+
+ @Override
+ public void onGameLog(Game game, String message) {
+ if (game.isSimulation()) return;
+ activeServices.forEach(c -> c.onGameLog(game, message));
+ }
+
+ @Override
+ public void onGameEnd(Game game) {
+ if (game.isSimulation()) return;
+ activeServices.forEach(c -> c.onGameEnd(game));
+ }
+
+ @Override
+ public void onChatRoom(UUID roomId, String userName, String message) {
+ activeServices.forEach(c -> c.onChatRoom(roomId, userName, message));
+ }
+
+ @Override
+ public void onChatTourney(UUID tourneyId, String userName, String message) {
+ activeServices.forEach(c -> c.onChatTourney(tourneyId, userName, message));
+ }
+
+ @Override
+ public void onChatTable(UUID tableId, String userName, String message) {
+ activeServices.forEach(c -> c.onChatTable(tableId, userName, message));
+ }
+
+ @Override
+ public void onChatGame(UUID gameId, String userName, String message) {
+ activeServices.forEach(c -> c.onChatGame(gameId, userName, message));
+ }
+}
diff --git a/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java b/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java
new file mode 100644
index 00000000000..2e5f1793e81
--- /dev/null
+++ b/Mage/src/main/java/mage/collectors/services/EmptyDataCollector.java
@@ -0,0 +1,70 @@
+package mage.collectors.services;
+
+import mage.collectors.DataCollector;
+import mage.game.Game;
+import mage.game.Table;
+
+import java.util.UUID;
+
+/**
+ * Base implementation of Data Collector, do nothing. Use it to implement own or simple collectors, e.g. chats only collectors
+ *
+ * @author JayDi85
+ */
+public abstract class EmptyDataCollector implements DataCollector {
+
+ @Override
+ public String getInitInfo() {
+ return "";
+ }
+
+ @Override
+ public void onServerStart() {
+ // nothing
+ }
+
+ @Override
+ public void onTableStart(Table table) {
+ // nothing
+ }
+
+ @Override
+ public void onTableEnd(Table table) {
+ // nothing
+ }
+
+ @Override
+ public void onGameStart(Game game) {
+ // nothing
+ }
+
+ @Override
+ public void onGameLog(Game game, String message) {
+ // nothing
+ }
+
+ @Override
+ public void onGameEnd(Game game) {
+ // nothing
+ }
+
+ @Override
+ public void onChatRoom(UUID roomId, String userName, String message) {
+ // nothing
+ }
+
+ @Override
+ public void onChatTourney(UUID tourneyId, String userName, String message) {
+ // nothing
+ }
+
+ @Override
+ public void onChatTable(UUID tableId, String userName, String message) {
+ // nothing
+ }
+
+ @Override
+ public void onChatGame(UUID gameId, String userName, String message) {
+ // nothing
+ }
+}
diff --git a/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java b/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java
new file mode 100644
index 00000000000..f8fc9a8966a
--- /dev/null
+++ b/Mage/src/main/java/mage/collectors/services/PrintGameLogsDataCollector.java
@@ -0,0 +1,48 @@
+package mage.collectors.services;
+
+import mage.game.Game;
+import mage.util.CardUtil;
+import org.apache.log4j.Logger;
+import org.jsoup.Jsoup;
+
+/**
+ * Data collector to print game logs in console output. Used for better unit tests debugging.
+ *
+ * @author JayDi85
+ */
+public class PrintGameLogsDataCollector extends EmptyDataCollector {
+
+ private static final Logger logger = Logger.getLogger(PrintGameLogsDataCollector.class);
+ public static final String SERVICE_CODE = "printGameLogs";
+
+ private void writeLog(String message) {
+ logger.info(message);
+ }
+
+ private void writeLog(String category, String event, String details) {
+ writeLog(String.format("[%s][%s] %s",
+ category,
+ event,
+ details
+ ));
+ }
+
+ @Override
+ public String getServiceCode() {
+ return SERVICE_CODE;
+ }
+
+ @Override
+ public String getInitInfo() {
+ return "print game logs in server logs";
+ }
+
+ @Override
+ public void onGameLog(Game game, String message) {
+ String needMessage = Jsoup.parse(message).text();
+ writeLog("GAME", "LOG", String.format("%s: %s",
+ CardUtil.getTurnInfo(game),
+ needMessage
+ ));
+ }
+}
diff --git a/Mage/src/main/java/mage/collectors/services/SaveGameHistoryDataCollector.java b/Mage/src/main/java/mage/collectors/services/SaveGameHistoryDataCollector.java
new file mode 100644
index 00000000000..7d6212f0fbf
--- /dev/null
+++ b/Mage/src/main/java/mage/collectors/services/SaveGameHistoryDataCollector.java
@@ -0,0 +1,383 @@
+package mage.collectors.services;
+
+import mage.cards.decks.DeckFormats;
+import mage.constants.TableState;
+import mage.game.Game;
+import mage.game.Table;
+import mage.game.match.MatchPlayer;
+import mage.players.Player;
+import mage.util.CardUtil;
+import org.apache.log4j.Logger;
+import org.jsoup.Jsoup;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
+
+/**
+ * Data collector that can collect and save whole games history and store it in disk system.
+ *
+ * WARNING, it's not production ready yet, use for load tests only (see todos below)
+ *
+ * Possible use cases:
+ * - load tests debugging to find freeze AI games;
+ * - public server debugging to find human freeze games;
+ * - AI learning data collection;
+ * - fast access to saved decks for public tourneys, e.g. after draft
+ * Tasks:
+ * - TODO: drafts - save picks history per player;
+ * - TODO: tourneys - fix chat logs
+ * - TODO: tourneys - fix miss end events on table or server quite (active table/game freeze bug)
+ *
+ * Data structure example:
+ * - gamesHistory
+ * - 2025-07-04
+ * - tables_active
+ * - tables_done
+ * - table 1 - UUID
+ * - table_logs.txt
+ * - games_done
+ * - games_active
+ * - game 1 - UUID
+ * - game 2 - UUID
+ * - game_logs.html
+ * - chat_logs.html
+ * - deck_player_1.dck
+ * - deck_player_2.dck
+ *
+ * @author JayDi85
+ */
+public class SaveGameHistoryDataCollector extends EmptyDataCollector {
+
+ private static final Logger logger = Logger.getLogger(SaveGameHistoryDataCollector.class);
+ public static final String SERVICE_CODE = "saveGameHistory";
+
+ private static final String DIR_NAME_ROOT = "gamesHistory";
+ private static final SimpleDateFormat DIR_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+ private static final String DIR_NAME_TABLES_ACTIVE = "tables_active";
+ private static final String DIR_NAME_TABLES_DONE = "tables_done";
+ private static final String DIR_NAME_GAMES_ACTIVE = "games_active";
+ private static final String DIR_NAME_GAMES_DONE = "games_done";
+
+ private static final String TABLE_LOGS_FILE_NAME = "table_logs.txt";
+ private static final String TABLE_CHAT_FILE_NAME = "table_chat.txt";
+ private static final String DECK_FILE_NAME_FORMAT = "deck_player_%d.dck";
+ private static final String GAME_LOGS_FILE_NAME = "game_logs.html";
+ private static final String GAME_CHAT_FILE_NAME = "game_chat.txt";
+
+ private static final UUID NO_TABLE_ID = UUID.randomUUID();
+ private static final String NO_TABLE_NAME = "SINGLE"; // need for unit tests
+
+ // global switch
+ boolean enabled;
+
+ // prepared dirs for each table or game - if it returns empty string then logs will be disabled, e.g. on too many data
+ Map tableDirs = new ConcurrentHashMap<>();
+ Map gameDirs = new ConcurrentHashMap<>();
+
+ // all write operations must be done in single thread
+ // TODO: analyse load tests performance and split locks per table/game
+ // TODO: limit file sizes for possible game freeze?
+ ReentrantLock writeLock = new ReentrantLock();
+
+ public SaveGameHistoryDataCollector() {
+ // prepare
+ Path root = Paths.get(DIR_NAME_ROOT);
+ if (!Files.exists(root)) {
+ try {
+ Files.createDirectories(root);
+ } catch (IOException e) {
+ logger.error("Can't create root dir, games history data collector will be disabled - " + e, e);
+ this.enabled = false;
+ return;
+ }
+ }
+
+ this.enabled = true;
+ }
+
+ @Override
+ public String getServiceCode() {
+ return SERVICE_CODE;
+ }
+
+ @Override
+ public String getInitInfo() {
+ return "save all game history to " + Paths.get(DIR_NAME_ROOT, DIR_DATE_FORMAT.format(new Date())).toAbsolutePath();
+ }
+
+ @Override
+ public void onTableStart(Table table) {
+ if (!this.enabled) return;
+ writeToTableLogsFile(table, new Date() + " [START] " + table.getId() + ", " + table);
+ }
+
+ @Override
+ public void onTableEnd(Table table) {
+ if (!this.enabled) return;
+ writeToTableLogsFile(table, new Date() + " [END] " + table.getId() + ", " + table);
+
+ // good end - move all files to done folder and change dir refs for possible game and other logs
+ writeLock.lock();
+ try {
+ String oldFolder = getOrCreateTableDir(table.getId(), table.getParentTableId(), table.getTableIndex(), false);
+ if (oldFolder.contains(DIR_NAME_TABLES_ACTIVE)) {
+ // move files
+ String newFolder = oldFolder.replace(DIR_NAME_TABLES_ACTIVE, DIR_NAME_TABLES_DONE);
+ Files.createDirectories(Paths.get(newFolder));
+ Files.move(Paths.get(oldFolder), Paths.get(newFolder), StandardCopyOption.REPLACE_EXISTING);
+
+ // update all refs (table and games)
+ this.tableDirs.put(table.getId(), newFolder);
+ this.gameDirs.replaceAll((gameId, gameFolder) ->
+ gameFolder.startsWith(oldFolder)
+ ? gameFolder.replace(oldFolder, newFolder)
+ : gameFolder
+ );
+ innerDirDeleteEmptyParent(oldFolder);
+ }
+ } catch (IOException e) {
+ logger.error("Can't move table files to done folder: " + e, e);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public void onGameStart(Game game) {
+ if (!this.enabled) return;
+ writeToGameLogsFile(game, new Date() + " [START] " + game.getId() + ", " + game);
+
+ // save deck files
+ writeLock.lock();
+ try {
+ String gameDir = getOrCreateGameDir(game, isActive(game));
+ if (gameDir.isEmpty()) {
+ return;
+ }
+ int playerNum = 0;
+ for (Player player : game.getPlayers().values()) {
+ playerNum++;
+ MatchPlayer matchPlayer = player.getMatchPlayer();
+ if (matchPlayer != null && matchPlayer.getDeck() != null) {
+ String deckFile = Paths.get(gameDir, String.format(DECK_FILE_NAME_FORMAT, playerNum)).toString();
+ DeckFormats.XMAGE.getExporter().writeDeck(deckFile, matchPlayer.getDeckForViewer().prepareCardsOnlyDeck());
+ }
+ }
+ } catch (IOException e) {
+ logger.error("Can't write deck file for game " + game.getId() + ": " + e, e);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public void onGameLog(Game game, String message) {
+ if (!this.enabled) return;
+ writeToGameLogsFile(game, new Date() + " [LOG] " + CardUtil.getTurnInfo(game) + ": " + message);
+ }
+
+ @Override
+ public void onGameEnd(Game game) {
+ if (!this.enabled) return;
+ writeToGameLogsFile(game, new Date() + " [END] " + game.getId() + ", " + game);
+
+ // good end - move game data to done folder
+ writeLock.lock();
+ try {
+ String oldFolder = getOrCreateGameDir(game, false);
+ if (oldFolder.contains(DIR_NAME_GAMES_ACTIVE)) {
+ // move files
+ String newFolder = oldFolder.replace(DIR_NAME_GAMES_ACTIVE, DIR_NAME_GAMES_DONE);
+ Files.createDirectories(Paths.get(newFolder));
+ Files.move(Paths.get(oldFolder), Paths.get(newFolder), StandardCopyOption.REPLACE_EXISTING);
+
+ // update all refs
+ this.gameDirs.replaceAll((gameId, gameFolder) ->
+ gameFolder.startsWith(oldFolder)
+ ? gameFolder.replace(oldFolder, newFolder)
+ : gameFolder
+ );
+ innerDirDeleteEmptyParent(oldFolder);
+ }
+ } catch (IOException e) {
+ logger.error("Can't move game files to done folder: " + e, e);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public void onChatTourney(UUID tourneyId, String userName, String message) {
+ // TODO: implement?
+ }
+
+ @Override
+ public void onChatTable(UUID tableId, String userName, String message) {
+ if (!this.enabled) return;
+ String needMessage = Jsoup.parse(message).text(); // convert html to txt format, so users can't break something
+ writeToTableChatFile(tableId, new Date() + " [CHAT] " + (userName == null ? "system" : userName) + ": " + needMessage);
+ }
+
+ @Override
+ public void onChatGame(UUID gameId, String userName, String message) {
+ if (!this.enabled) return;
+ String needMessage = Jsoup.parse(message).text(); // convert html to txt format, so users can't break something
+ writeToGameChatFile(gameId, new Date() + " [CHAT] " + (userName == null ? "system" : userName) + ": " + needMessage);
+ }
+
+
+ private String getOrCreateTableDir(UUID tableId) {
+ return this.tableDirs.getOrDefault(tableId, "");
+ }
+
+ private String getOrCreateTableDir(UUID tableId, UUID parentTableId, Integer tableIndex, boolean isActive) {
+ // unit tests don't have tables, so write it to custom table
+ String needName;
+ UUID needId;
+ if (tableId == null) {
+ needId = NO_TABLE_ID;
+ needName = String.format("table %s", NO_TABLE_NAME);
+ } else {
+ needId = tableId;
+ if (parentTableId == null) {
+ needName = String.format("table %s - %s", tableIndex, tableId);
+ } else {
+ needName = String.format("table %s - %s_%s", tableIndex, parentTableId, tableId);
+ }
+ }
+
+ String needDate = DIR_DATE_FORMAT.format(new Date());
+ String needStatus = isActive ? DIR_NAME_TABLES_ACTIVE : DIR_NAME_TABLES_DONE;
+
+ String tableDir = Paths.get(
+ DIR_NAME_ROOT,
+ needDate,
+ needStatus,
+ needName
+ ).toString();
+
+ return this.tableDirs.computeIfAbsent(needId, x -> innerDirCreate(tableDir));
+ }
+
+ private String getOrCreateGameDir(UUID gameId) {
+ // some events don't have full game info and must come after real game start
+ return this.gameDirs.getOrDefault(gameId, "");
+ }
+
+ private String getOrCreateGameDir(Game game, boolean isActive) {
+ AtomicBoolean isNewDir = new AtomicBoolean(false);
+ String res = this.gameDirs.computeIfAbsent(game.getId(), x -> {
+ isNewDir.set(true);
+ // if you find "table 0 - UUID" folders with real server then game logs come before table then
+ // it's almost impossible and nothing to do with it
+ String tableDir = getOrCreateTableDir(game.getTableId(), null, 0, true);
+ if (tableDir.isEmpty()) {
+ // disabled
+ return "";
+ }
+
+ String needStatus = isActive ? DIR_NAME_GAMES_ACTIVE : DIR_NAME_GAMES_DONE;
+ String needName = String.format("game %s - %s", game.getGameIndex(), game.getId());
+ String gameDir = Paths.get(
+ tableDir,
+ needStatus,
+ needName
+ ).toString();
+
+ return innerDirCreate(gameDir);
+ });
+
+ // workaround for good background color in html logs
+ if (isNewDir.get()) {
+ writeToGameLogsFile(game, "");
+ }
+
+ return res;
+ }
+
+ private String innerDirCreate(String destFileOrDir) {
+ Path dir = Paths.get(destFileOrDir);
+ if (!Files.exists(dir)) {
+ try {
+ Files.createDirectories(dir);
+ } catch (IOException ignore) {
+ return "";
+ }
+ }
+ return dir.toString();
+ }
+
+ private void innerDirDeleteEmptyParent(String dir) throws IOException {
+ // delete empty parent folder, e.g. after move all games from active to done folder
+ Path parentPath = Paths.get(dir).getParent();
+ if (Files.exists(parentPath) && Files.isDirectory(parentPath)) {
+ try (Stream entries = Files.list(parentPath)) {
+ if (!entries.findAny().isPresent()) {
+ Files.delete(parentPath);
+ }
+ }
+ }
+ }
+
+ private void writeToTableLogsFile(Table table, String data) {
+ String tableDir = getOrCreateTableDir(table.getId(), table.getParentTableId(), table.getTableIndex(), isActive(table));
+ if (tableDir.isEmpty()) {
+ return;
+ }
+ writeToFile(Paths.get(tableDir, TABLE_LOGS_FILE_NAME).toString(), data, "\n");
+ }
+
+ private void writeToTableChatFile(UUID tableId, String data) {
+ String gameDir = getOrCreateTableDir(tableId);
+ if (gameDir.isEmpty()) {
+ return;
+ }
+ writeToFile(Paths.get(gameDir, TABLE_CHAT_FILE_NAME).toString(), data, "\n");
+ }
+
+ private void writeToGameLogsFile(Game game, String data) {
+ String gameDir = getOrCreateGameDir(game, isActive(game));
+ if (gameDir.isEmpty()) {
+ return;
+ }
+ writeToFile(Paths.get(gameDir, GAME_LOGS_FILE_NAME).toString(), data, "\n
\n");
+ }
+
+ private void writeToGameChatFile(UUID gameId, String data) {
+ String gameDir = getOrCreateGameDir(gameId);
+ if (gameDir.isEmpty()) {
+ return;
+ }
+ writeToFile(Paths.get(gameDir, GAME_CHAT_FILE_NAME).toString(), data, "\n");
+ }
+
+ private void writeToFile(String destFile, String data, String newLine) {
+ writeLock.lock();
+ try {
+ try {
+ String newData = newLine + data;
+ Files.write(Paths.get(destFile), newData.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
+ } catch (IOException ignore) {
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ private boolean isActive(Table table) {
+ return !TableState.FINISHED.equals(table.getState());
+ }
+
+ private boolean isActive(Game game) {
+ return game.getState() == null || !game.getState().isGameOver();
+ }
+}
diff --git a/Mage/src/main/java/mage/constants/AbilityWord.java b/Mage/src/main/java/mage/constants/AbilityWord.java
index 6e79b03c841..c8044d3c38c 100644
--- a/Mage/src/main/java/mage/constants/AbilityWord.java
+++ b/Mage/src/main/java/mage/constants/AbilityWord.java
@@ -63,6 +63,7 @@ public enum AbilityWord {
THRESHOLD("Threshold"),
UNDERGROWTH("Undergrowth"),
VALIANT("Valiant"),
+ VOID("Void"),
WILL_OF_THE_COUNCIL("Will of the council"),
WILL_OF_THE_PLANESWALKERS("Will of the planeswalkers");
diff --git a/Mage/src/main/java/mage/constants/AffinityType.java b/Mage/src/main/java/mage/constants/AffinityType.java
index a49fe5f9bfc..de6fb741683 100644
--- a/Mage/src/main/java/mage/constants/AffinityType.java
+++ b/Mage/src/main/java/mage/constants/AffinityType.java
@@ -41,6 +41,7 @@ public enum AffinityType {
LIZARDS(new FilterControlledPermanent(SubType.LIZARD, "Lizards")),
BIRDS(new FilterControlledPermanent(SubType.BIRD, "Birds")),
CITIZENS(new FilterControlledPermanent(SubType.CITIZEN, "Citizens")),
+ SLIVERS(new FilterControlledPermanent(SubType.SLIVER, "Slivers")),
TOWNS(new FilterControlledPermanent(SubType.TOWN, "Towns")),
GATES(new FilterControlledPermanent(SubType.GATE, "Gates"), GatesYouControlHint.instance),
SNOW_LANDS(AffinityFilters.SNOW_LANDS),
diff --git a/Mage/src/main/java/mage/constants/ModeChoice.java b/Mage/src/main/java/mage/constants/ModeChoice.java
index 1759d2dece8..329a086285d 100644
--- a/Mage/src/main/java/mage/constants/ModeChoice.java
+++ b/Mage/src/main/java/mage/constants/ModeChoice.java
@@ -18,7 +18,7 @@ public enum ModeChoice {
SULTAI("Sultai"),
MIRRAN("Mirran"),
- PHYREXIAN("Phyrexian "),
+ PHYREXIAN("Phyrexian"),
ODD("odd"),
EVEN("even"),
diff --git a/Mage/src/main/java/mage/constants/Outcome.java b/Mage/src/main/java/mage/constants/Outcome.java
index 1e976f9ad7b..c6bdd119cd2 100644
--- a/Mage/src/main/java/mage/constants/Outcome.java
+++ b/Mage/src/main/java/mage/constants/Outcome.java
@@ -14,14 +14,14 @@ public enum Outcome {
LoseLife(false),
ExtraTurn(true),
BecomeCreature(true),
- PutCreatureInPlay(true),
- PutCardInPlay(true),
- PutLandInPlay(true),
+ PutCreatureInPlay(true, true),
+ PutCardInPlay(true, true),
+ PutLandInPlay(true, true),
GainControl(false),
DrawCard(true),
Discard(false),
Sacrifice(false),
- PlayForFree(true),
+ PlayForFree(true, true),
ReturnToHand(false),
Exile(false),
Protect(true),
@@ -46,7 +46,7 @@ public enum Outcome {
// AI sorting targets by priorities (own or opponents) and selects most valueable or weakest
private final boolean good;
- // no different between own or opponent targets (example: copy must choose from all permanents)
+ // no different between own or opponent targets (example: copy must choose from all permanents, free cast from selected cards, etc)
private boolean anyTargetHasSameValue;
Outcome(boolean good) {
diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java
index 78d376b1d53..b354e9dca7f 100644
--- a/Mage/src/main/java/mage/constants/SubType.java
+++ b/Mage/src/main/java/mage/constants/SubType.java
@@ -31,6 +31,7 @@ public enum SubType {
GATE("Gate", SubTypeSet.NonBasicLandType),
LAIR("Lair", SubTypeSet.NonBasicLandType),
LOCUS("Locus", SubTypeSet.NonBasicLandType),
+ PLANET("Planet", SubTypeSet.NonBasicLandType),
SPHERE("Sphere", SubTypeSet.NonBasicLandType),
URZAS("Urza's", SubTypeSet.NonBasicLandType),
MINE("Mine", SubTypeSet.NonBasicLandType),
@@ -61,8 +62,10 @@ public enum SubType {
GOLD("Gold", SubTypeSet.ArtifactType),
INCUBATOR("Incubator", SubTypeSet.ArtifactType),
JUNK("Junk", SubTypeSet.ArtifactType),
+ LANDER("Lander", SubTypeSet.ArtifactType),
MAP("Map", SubTypeSet.ArtifactType),
POWERSTONE("Powerstone", SubTypeSet.ArtifactType),
+ SPACECRAFT("Spacecraft", SubTypeSet.ArtifactType),
TREASURE("Treasure", SubTypeSet.ArtifactType),
VEHICLE("Vehicle", SubTypeSet.ArtifactType),
// 205.3m : Creatures and kindreds share their lists of subtypes; these subtypes are called creature types.
@@ -157,6 +160,7 @@ public enum SubType {
DRAGON("Dragon", SubTypeSet.CreatureType),
DRAKE("Drake", SubTypeSet.CreatureType),
DREADNOUGHT("Dreadnought", SubTypeSet.CreatureType),
+ DRIX("Drix", SubTypeSet.CreatureType),
DROID("Droid", SubTypeSet.CreatureType, true), // Star Wars
DRONE("Drone", SubTypeSet.CreatureType),
DRUID("Druid", SubTypeSet.CreatureType),
@@ -186,7 +190,7 @@ public enum SubType {
FROG("Frog", SubTypeSet.CreatureType),
FUNGUS("Fungus", SubTypeSet.CreatureType),
// G
- GAMER("Gamer", SubTypeSet.CreatureType, true), // Un-sets
+ GAMER("Gamer", SubTypeSet.CreatureType),
GAMORREAN("Gamorrean", SubTypeSet.CreatureType, true), // Star Wars
GAND("Gand", SubTypeSet.CreatureType, true), // Star Wars
GARGOYLE("Gargoyle", SubTypeSet.CreatureType),
@@ -261,7 +265,7 @@ public enum SubType {
LICID("Licid", SubTypeSet.CreatureType),
LIZARD("Lizard", SubTypeSet.CreatureType),
LLAMA("Llama", SubTypeSet.CreatureType),
- LOBSTER("Lobster", SubTypeSet.CreatureType, true), // Unglued
+ LOBSTER("Lobster", SubTypeSet.CreatureType),
// M
MANTELLIAN("Mantellian", SubTypeSet.CreatureType, true), // Star Wars
MANTICORE("Manticore", SubTypeSet.CreatureType),
@@ -355,7 +359,7 @@ public enum SubType {
SAPROLING("Saproling", SubTypeSet.CreatureType),
SATYR("Satyr", SubTypeSet.CreatureType),
SCARECROW("Scarecrow", SubTypeSet.CreatureType),
- SCIENTIST("Scientist", SubTypeSet.CreatureType, true), // Unstable
+ SCIENTIST("Scientist", SubTypeSet.CreatureType),
SCION("Scion", SubTypeSet.CreatureType),
SCORPION("Scorpion", SubTypeSet.CreatureType),
SCOUT("Scout", SubTypeSet.CreatureType),
@@ -426,7 +430,7 @@ public enum SubType {
VAMPIRE("Vampire", SubTypeSet.CreatureType),
VARMINT("Varmint", SubTypeSet.CreatureType),
VEDALKEN("Vedalken", SubTypeSet.CreatureType),
- VILLAIN("Villain", SubTypeSet.CreatureType, true), // Unstable
+ VILLAIN("Villain", SubTypeSet.CreatureType),
VOLVER("Volver", SubTypeSet.CreatureType),
// W
WALL("Wall", SubTypeSet.CreatureType),
diff --git a/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java b/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java
index 5592a991ce2..3d0967649a4 100644
--- a/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java
+++ b/Mage/src/main/java/mage/filter/FilterPermanentThisOrAnother.java
@@ -48,14 +48,18 @@ public class FilterPermanentThisOrAnother extends FilterPermanent {
}
protected static String generateFilterMessage(FilterPermanent otherFilter) {
+ StringBuilder sb = new StringBuilder("{this} or another ");
// Remove the indefinite article from the beginning of the message:
- String otherFilterMessage = otherFilter.getMessage();
- if (otherFilterMessage.startsWith("a ")) {
- otherFilterMessage = otherFilterMessage.substring(2);
- } else if (otherFilterMessage.startsWith("an ")) {
- otherFilterMessage = otherFilterMessage.substring(3);
+ String message = otherFilter.getMessage();
+ if (message.startsWith("a ")) {
+ sb.append(message.substring(2));
+ } else if (message.startsWith("an ")) {
+ sb.append(message.substring(3));
+ } else if (message.startsWith("another ")) {
+ sb.append(message.substring(8));
+ } else {
+ sb.append(message);
}
-
- return "{this} or another " + otherFilterMessage;
+ return sb.toString();
}
}
diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java
index 6680018c2fb..0a5b6fdf79a 100644
--- a/Mage/src/main/java/mage/filter/StaticFilters.java
+++ b/Mage/src/main/java/mage/filter/StaticFilters.java
@@ -1,6 +1,7 @@
package mage.filter;
import mage.ObjectColor;
+import mage.abilities.keyword.FlyingAbility;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.common.*;
@@ -412,6 +413,13 @@ public final class StaticFilters {
FILTER_CONTROLLED_A_PERMANENT.setLockedFilter(true);
}
+ public static final FilterControlledPermanent FILTER_CONTROLLED_UNTAPPED_PERMANENT = new FilterControlledPermanent("an untapped permanent you control");
+
+ static {
+ FILTER_CONTROLLED_UNTAPPED_PERMANENT.add(TappedPredicate.UNTAPPED);
+ FILTER_CONTROLLED_UNTAPPED_PERMANENT.setLockedFilter(true);
+ }
+
public static final FilterControlledPermanent FILTER_CONTROLLED_ANOTHER_PERMANENT = new FilterControlledPermanent("another permanent you control");
static {
@@ -909,6 +917,13 @@ public final class StaticFilters {
FILTER_PERMANENT_BATTLES.setLockedFilter(true);
}
+ public static final FilterPermanent FILTER_PERMANENT_NON_CREATURE = new FilterPermanent("noncreature permanent");
+
+ static {
+ FILTER_PERMANENT_NON_CREATURE.add(Predicates.not(CardType.CREATURE.getPredicate()));
+ FILTER_PERMANENT_NON_CREATURE.setLockedFilter(true);
+ }
+
public static final FilterNonlandPermanent FILTER_PERMANENT_NON_LAND = new FilterNonlandPermanent();
static {
@@ -1145,6 +1160,13 @@ public final class StaticFilters {
FILTER_CREATURES_NON_TOKEN.setLockedFilter(true);
}
+ public static final FilterCreaturePermanent FILTER_CREATURE_FLYING = new FilterCreaturePermanent("creature with flying");
+
+ static {
+ FILTER_CREATURE_FLYING.add(new AbilityPredicate(FlyingAbility.class));
+ FILTER_CREATURE_FLYING.setLockedFilter(true);
+ }
+
public static final FilterControlledCreaturePermanent FILTER_A_CONTROLLED_CREATURE_P1P1 = new FilterControlledCreaturePermanent("a creature you control with a +1/+1 counter on it");
static {
diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java
index 13846b795b8..ad0cd9232d4 100644
--- a/Mage/src/main/java/mage/game/Game.java
+++ b/Mage/src/main/java/mage/game/Game.java
@@ -48,6 +48,11 @@ import java.util.stream.Collectors;
public interface Game extends MageItem, Serializable, Copyable {
+ /**
+ * Return global game index (used for better logs and history)
+ */
+ Integer getGameIndex();
+
MatchType getGameType();
int getNumPlayers();
@@ -137,6 +142,7 @@ public interface Game extends MageItem, Serializable, Copyable {
Map getPermanentsEntering();
Map> getLKI();
+
Map> getPermanentCostsTags();
/**
@@ -171,9 +177,9 @@ public interface Game extends MageItem, Serializable, Copyable {
PlayerList getPlayerList();
/**
- * Returns opponents list in range for the given playerId. Use it to interate by starting turn order.
- *
- * Warning, it will return leaved players until end of turn. For dialogs and one shot effects use excludeLeavedPlayers
+ * Returns opponents list in range for the given playerId. Use it to interate by starting turn order.
+ *
+ * Warning, it will return leaved players until end of turn. For dialogs and one shot effects use excludeLeavedPlayers
*/
// TODO: check usage of getOpponents in cards and replace with correct call of excludeLeavedPlayers, see #13289
default Set getOpponents(UUID playerId) {
@@ -181,8 +187,8 @@ public interface Game extends MageItem, Serializable, Copyable {
}
/**
- * Returns opponents list in range for the given playerId. Use it to interate by starting turn order.
- * Warning, it will return dead players until end of turn.
+ * Returns opponents list in range for the given playerId. Use it to interate by starting turn order.
+ * Warning, it will return dead players until end of turn.
*
* @param excludeLeavedPlayers exclude dead player immediately without waiting range update on next turn
*/
@@ -245,7 +251,7 @@ public interface Game extends MageItem, Serializable, Copyable {
/**
* Id of the player the current turn it is.
- *
+ *
* Player can be under control of another player, so search a real GUI's controller by Player->getTurnControlledBy
*
* @return
@@ -563,14 +569,15 @@ public interface Game extends MageItem, Serializable, Copyable {
*/
void processAction();
- @Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead)
+ @Deprecated
+ // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead)
boolean checkStateAndTriggered();
/**
* Play priority by all players
*
* @param activePlayerId starting priority player
- * @param resuming false to reset passed priority and ask it again
+ * @param resuming false to reset passed priority and ask it again
*/
void playPriority(UUID activePlayerId, boolean resuming);
@@ -600,6 +607,7 @@ public interface Game extends MageItem, Serializable, Copyable {
/**
* TODO: remove logic changed, must research each usage of removeBookmark and replace it with new code
+ *
* @param bookmark
*/
void removeBookmark_v2(int bookmark);
@@ -620,6 +628,7 @@ public interface Game extends MageItem, Serializable, Copyable {
// game cheats (for tests only)
void cheat(UUID ownerId, Map commands);
+
void cheat(UUID ownerId, List library, List hand, List battlefield, List graveyard, List command, List exiled);
// controlling the behaviour of replacement effects while permanents entering the battlefield
@@ -811,4 +820,8 @@ public interface Game extends MageItem, Serializable, Copyable {
boolean isGameStopped();
boolean isTurnOrderReversed();
+
+ UUID getTableId();
+
+ void setTableId(UUID tableId);
}
diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java
index e48ef287395..e3f59becee3 100644
--- a/Mage/src/main/java/mage/game/GameImpl.java
+++ b/Mage/src/main/java/mage/game/GameImpl.java
@@ -27,6 +27,7 @@ import mage.cards.*;
import mage.cards.decks.Deck;
import mage.cards.decks.DeckCardInfo;
import mage.choices.Choice;
+import mage.collectors.DataCollectorServices;
import mage.constants.*;
import mage.counters.CounterType;
import mage.counters.Counters;
@@ -94,6 +95,8 @@ import java.util.stream.Collectors;
*/
public abstract class GameImpl implements Game {
+ private final static AtomicInteger GLOBAL_INDEX = new AtomicInteger();
+
private static final int ROLLBACK_TURNS_MAX = 4;
private static final String UNIT_TESTS_ERROR_TEXT = "Error in unit tests";
private static final Logger logger = Logger.getLogger(GameImpl.class);
@@ -108,6 +111,8 @@ public abstract class GameImpl implements Game {
protected AtomicInteger totalErrorsCount = new AtomicInteger(); // for debug only: error stats
protected final UUID id;
+ protected final Integer gameIndex; // for better logs and history
+ protected UUID tableId = null;
protected boolean ready;
protected transient TableEventSource tableEventSource = new TableEventSource();
@@ -171,6 +176,7 @@ public abstract class GameImpl implements Game {
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int minimumDeckSize, int startingLife, int startingHandSize) {
this.id = UUID.randomUUID();
+ this.gameIndex = GLOBAL_INDEX.incrementAndGet();
this.range = range;
this.mulligan = mulligan;
this.attackOption = attackOption;
@@ -191,6 +197,8 @@ public abstract class GameImpl implements Game {
this.checkPlayableState = game.checkPlayableState;
this.id = game.id;
+ this.gameIndex = game.gameIndex;
+ this.tableId = game.tableId;
this.totalErrorsCount.set(game.totalErrorsCount.get());
this.ready = game.ready;
@@ -250,6 +258,11 @@ public abstract class GameImpl implements Game {
*/
}
+ @Override
+ public Integer getGameIndex() {
+ return this.gameIndex;
+ }
+
@Override
public boolean isSimulation() {
return simulation;
@@ -887,6 +900,12 @@ public abstract class GameImpl implements Game {
if (state.isGameOver()) {
return true;
}
+
+ // stop on game thread ended by third party tools or AI's timeout
+ if (Thread.currentThread().isInterrupted()) {
+ return true;
+ }
+
int remainingPlayers = 0;
int numLosers = 0;
for (Player player : state.getPlayers().values()) {
@@ -897,7 +916,10 @@ public abstract class GameImpl implements Game {
numLosers++;
}
}
- if (remainingPlayers <= 1 || numLosers >= state.getPlayers().size() - 1) {
+
+ // stop on no more active players
+ boolean noMorePlayers = remainingPlayers <= 1 || numLosers >= state.getPlayers().size() - 1;
+ if (noMorePlayers) {
end();
if (remainingPlayers == 0 && logger.isDebugEnabled()) {
logger.debug("DRAW for gameId: " + getId());
@@ -1038,6 +1060,7 @@ public abstract class GameImpl implements Game {
@Override
public void start(UUID choosingPlayerId) {
startTime = new Date();
+ DataCollectorServices.getInstance().onGameStart(this);
if (state.getPlayers().values().iterator().hasNext()) {
init(choosingPlayerId);
play(startingPlayerId);
@@ -1553,6 +1576,11 @@ public abstract class GameImpl implements Game {
endTime = new Date();
state.endGame();
+ // cancel all player dialogs/feedbacks
+ for (Player player : state.getPlayers().values()) {
+ player.abort();
+ }
+
// inform players about face down cards
state.getBattlefield().getAllPermanents()
.stream()
@@ -1572,10 +1600,7 @@ public abstract class GameImpl implements Game {
.sorted()
.forEach(this::informPlayers);
- // cancel all player dialogs/feedbacks
- for (Player player : state.getPlayers().values()) {
- player.abort();
- }
+ DataCollectorServices.getInstance().onGameEnd(this);
}
}
@@ -1782,7 +1807,7 @@ public abstract class GameImpl implements Game {
// count total errors
Player activePlayer = this.getPlayer(getActivePlayerId());
- if (activePlayer != null && !activePlayer.isTestsMode()) {
+ if (activePlayer != null && !activePlayer.isTestMode() && !activePlayer.isFastFailInTestMode()) {
// real game - try to continue
priorityErrorsCount++;
continue;
@@ -2794,7 +2819,7 @@ public abstract class GameImpl implements Game {
if (attachedTo != null) {
for (Ability ability : perm.getAbilities(this)) {
if (ability instanceof AttachableToRestrictedAbility) {
- if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo, null, this)) {
+ if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), null, this)) {
attachedTo = null;
break;
}
@@ -3164,6 +3189,8 @@ public abstract class GameImpl implements Game {
@Override
public void informPlayers(String message) {
+ DataCollectorServices.getInstance().onGameLog(this, message);
+
// Uncomment to print game messages
// System.out.println(message.replaceAll("\\<.*?\\>", ""));
if (simulation) {
@@ -4239,4 +4266,14 @@ public abstract class GameImpl implements Game {
.append(this.getState().isGameOver() ? "; FINISHED: " + this.getWinner() : "");
return sb.toString();
}
+
+ @Override
+ public UUID getTableId() {
+ return this.tableId;
+ }
+
+ @Override
+ public void setTableId(UUID tableId) {
+ this.tableId = tableId;
+ }
}
diff --git a/Mage/src/main/java/mage/game/Table.java b/Mage/src/main/java/mage/game/Table.java
index a7c84529f82..1126d199058 100644
--- a/Mage/src/main/java/mage/game/Table.java
+++ b/Mage/src/main/java/mage/game/Table.java
@@ -3,7 +3,10 @@ package mage.game;
import java.io.Serializable;
import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
import mage.cards.decks.DeckValidator;
+import mage.collectors.DataCollectorServices;
import mage.constants.TableState;
import mage.game.draft.Draft;
import mage.game.events.Listener;
@@ -20,7 +23,10 @@ import mage.players.PlayerType;
*/
public class Table implements Serializable {
+ private final static AtomicInteger GLOBAL_INDEX = new AtomicInteger();
+
private UUID tableId;
+ private Integer tableIndex; // for better logs and history
private UUID roomId;
private String name;
private String controllerName;
@@ -54,17 +60,23 @@ public class Table implements Serializable {
this.tournament = tournament;
this.isTournament = true;
setState(TableState.WAITING);
+
+ DataCollectorServices.getInstance().onTableStart(this);
}
public Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List playerTypes, TableRecorder recorder, Match match, Set bannedUsernames, boolean isPlaneChase) {
this(roomId, gameType, name, controllerName, validator, playerTypes, recorder, bannedUsernames, isPlaneChase);
this.match = match;
+ this.match.setTableId(this.getId());
this.isTournament = false;
setState(TableState.WAITING);
+
+ DataCollectorServices.getInstance().onTableStart(this);
}
protected Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List playerTypes, TableRecorder recorder, Set bannedUsernames, boolean isPlaneChase) {
- tableId = UUID.randomUUID();
+ this.tableId = UUID.randomUUID();
+ this.tableIndex = GLOBAL_INDEX.incrementAndGet();
this.roomId = roomId;
this.numSeats = playerTypes.size();
this.gameType = gameType;
@@ -91,6 +103,10 @@ public class Table implements Serializable {
return tableId;
}
+ public Integer getTableIndex() {
+ return tableIndex;
+ }
+
public UUID getParentTableId() {
return parentTableId;
}
@@ -132,6 +148,8 @@ public class Table implements Serializable {
setState(TableState.FINISHED); // otherwise the table can be removed completely
}
this.validator = null;
+
+ DataCollectorServices.getInstance().onTableEnd(this);
}
/**
diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java
index 333e786b95a..1fa776ffc2f 100644
--- a/Mage/src/main/java/mage/game/combat/Combat.java
+++ b/Mage/src/main/java/mage/game/combat/Combat.java
@@ -695,7 +695,8 @@ public class Combat implements Serializable, Copyable {
// real game: send warning
// test: fast fail
game.informPlayers(controller.getLogName() + ": WARNING - AI can't find good blocker combination and will skip it - report your battlefield to github - " + game.getCombat());
- if (controller.isTestsMode()) {
+ if (controller.isTestMode() && controller.isFastFailInTestMode()) {
+ // fast fail in tests
// how-to fix: AI code must support failed abilities or use cases
throw new IllegalArgumentException("AI can't find good blocker combination");
}
diff --git a/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java b/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java
index 746ea66785b..fc9f282fefa 100644
--- a/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/AurraSingBaneOfJediEmblem.java
@@ -13,7 +13,7 @@ public final class AurraSingBaneOfJediEmblem extends Emblem {
// Whenever a nontoken creature you control leaves the battlefied, discard a card.
public AurraSingBaneOfJediEmblem() {
- super("Emblem Aurra Sing, Bane of Jedi");
+ super("Emblem Aurra Sing");
getAbilities().add(new LeavesBattlefieldAllTriggeredAbility(Zone.COMMAND, new DiscardControllerEffect(1), StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN, false));
}
diff --git a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java
index 7f72bf4b36c..c401b0d2bfb 100644
--- a/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/RadiationEmblem.java
@@ -1,5 +1,6 @@
package mage.game.command.emblems;
+import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.effects.OneShotEffect;
@@ -34,6 +35,13 @@ public class RadiationEmblem extends Emblem {
Zone.ALL, TargetController.YOU, new RadiationEffect(), false
).withInterveningIf(RadiationCondition.instance).setTriggerPhrase("At the beginning of each player's precombat main phase, "));
+ // radiation don't have source, so image can be initialized immediately
+ setSourceObjectAndInitImage(null);
+ }
+
+ @Override
+ public void setSourceObjectAndInitImage(MageObject sourceObject) {
+ this.sourceObject = sourceObject;
TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_RADIATION, null);
if (foundInfo != null) {
this.setExpansionSetCode(foundInfo.getSetCode());
diff --git a/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java b/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java
index 3d30bdf66ce..50b64a0d6c7 100644
--- a/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/SephirothOneWingedAngelEmblem.java
@@ -2,7 +2,7 @@ package mage.game.command.emblems;
import mage.abilities.Ability;
import mage.abilities.common.DiesCreatureTriggeredAbility;
-import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
+import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.constants.Zone;
import mage.filter.StaticFilters;
@@ -21,7 +21,7 @@ public final class SephirothOneWingedAngelEmblem extends Emblem {
Zone.COMMAND, new LoseLifeTargetEffect(1), false,
StaticFilters.FILTER_PERMANENT_A_CREATURE, false
);
- ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and"));
+ ability.addEffect(new GainLifeEffect(1).concatBy("and"));
ability.addTarget(new TargetOpponent());
this.getAbilities().add(ability);
}
diff --git a/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java b/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java
index e5fb21ccf99..225f5f55f23 100644
--- a/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/TheRingEmblem.java
@@ -1,5 +1,6 @@
package mage.game.command.emblems;
+import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility;
@@ -40,11 +41,21 @@ public final class TheRingEmblem extends Emblem {
filter.add(TheRingEmblemPredicate.instance);
}
+ public TheRingEmblem() {
+ this((UUID) null); // require for verify test
+ }
+
public TheRingEmblem(UUID controllerId) {
super("The Ring");
this.setControllerId(controllerId);
// ring don't have source, so image can be initialized immediately
+ setSourceObjectAndInitImage(null);
+ }
+
+ @Override
+ public void setSourceObjectAndInitImage(MageObject sourceObject) {
+ this.sourceObject = sourceObject;
TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_THE_RING, null);
if (foundInfo != null) {
this.setExpansionSetCode(foundInfo.getSetCode());
diff --git a/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java b/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java
index d4f396adaa8..d051f212ef6 100644
--- a/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/XmageHelperEmblem.java
@@ -1,5 +1,6 @@
package mage.game.command.emblems;
+import mage.MageObject;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.hint.Hint;
@@ -20,6 +21,13 @@ public class XmageHelperEmblem extends Emblem {
super("Helper Emblem");
this.frameStyle = FrameStyle.M15_NORMAL;
+ // helper don't have source, so image can be initialized immediately
+ setSourceObjectAndInitImage(null);
+ }
+
+ @Override
+ public void setSourceObjectAndInitImage(MageObject sourceObject) {
+ this.sourceObject = sourceObject;
TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForXmage(TokenRepository.XMAGE_IMAGE_NAME_HELPER_EMBLEM, null);
if (foundInfo != null) {
this.setExpansionSetCode(foundInfo.getSetCode());
diff --git a/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java b/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java
index c816a1051eb..a128a6df188 100644
--- a/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/YodaEmblem.java
@@ -18,7 +18,7 @@ public final class YodaEmblem extends Emblem {
// You get an emblem with "Hexproof, you and your creatures have."
public YodaEmblem() {
- super("Emblem Yoda, Jedi Master");
+ super("Emblem Yoda");
Effect effect = new GainAbilityControllerEffect(HexproofAbility.getInstance(), Duration.EndOfGame);
effect.setText("Hexproof, you");
Ability ability = new SimpleStaticAbility(Zone.COMMAND, effect);
diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java
index 9a12cc41e44..632c2ede065 100644
--- a/Mage/src/main/java/mage/game/events/GameEvent.java
+++ b/Mage/src/main/java/mage/game/events/GameEvent.java
@@ -40,6 +40,7 @@ public class GameEvent implements Serializable {
PREVENT_DAMAGE, PREVENTED_DAMAGE,
//Turn-based events
PLAY_TURN, EXTRA_TURN,
+ BEGIN_TURN, // event fired on actual begin of turn.
CHANGE_PHASE, PHASE_CHANGED,
CHANGE_STEP, STEP_CHANGED,
BEGINNING_PHASE, BEGINNING_PHASE_PRE, BEGINNING_PHASE_POST, // The normal beginning phase -- at the beginning of turn
@@ -227,6 +228,13 @@ public class GameEvent implements Serializable {
sourceId sourceId of the mount
playerId the id of the controlling player
*/
+ STATION_PERMANENT,
+ /* STATION_PERMANENT
+ targetId the id of the creature stationing
+ sourceId sourceId of the spaceship or planet
+ playerId the id of the controlling player
+ amount how many counters are being added
+ */
CAST_SPELL,
CAST_SPELL_LATE,
/* SPELL_CAST, CAST_SPELL_LATE
diff --git a/Mage/src/main/java/mage/game/events/MageEvent.java b/Mage/src/main/java/mage/game/events/MageEvent.java
deleted file mode 100644
index 540890dc3e7..00000000000
--- a/Mage/src/main/java/mage/game/events/MageEvent.java
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-package mage.game.events;
-
-import java.io.Serializable;
-
-/**
- *
- * @author BetaSteward_at_googlemail.com
- */
-public interface MageEvent extends Serializable {
-
-}
diff --git a/Mage/src/main/java/mage/game/match/Match.java b/Mage/src/main/java/mage/game/match/Match.java
index 76e74e71683..7f080674871 100644
--- a/Mage/src/main/java/mage/game/match/Match.java
+++ b/Mage/src/main/java/mage/game/match/Match.java
@@ -10,7 +10,7 @@ import mage.game.GameException;
import mage.game.GameInfo;
import mage.game.events.Listener;
import mage.game.events.TableEvent;
-import mage.game.result.ResultProtos.MatchProto;
+import mage.game.result.ResultProtos.MatchProto; // on unknown package error must run and auto-generate proto classes by "mvn install -DskipTests"
import mage.players.Player;
/**
diff --git a/Mage/src/main/java/mage/game/match/MatchImpl.java b/Mage/src/main/java/mage/game/match/MatchImpl.java
index 3fb5def5119..0e0e2b1d119 100644
--- a/Mage/src/main/java/mage/game/match/MatchImpl.java
+++ b/Mage/src/main/java/mage/game/match/MatchImpl.java
@@ -192,6 +192,7 @@ public abstract class MatchImpl implements Match {
}
protected void initGame(Game game) throws GameException {
+ game.setTableId(this.tableId);
addGame(); // raises only the number
shufflePlayers();
for (MatchPlayer matchPlayer : this.players) {
@@ -291,6 +292,9 @@ public abstract class MatchImpl implements Match {
@Override
public void setTableId(UUID tableId) {
this.tableId = tableId;
+
+ // sync tableId with all games (needs for data collectors)
+ this.games.forEach(game -> game.setTableId(tableId));
}
@Override
diff --git a/Mage/src/main/java/mage/game/permanent/token/DroneToken2.java b/Mage/src/main/java/mage/game/permanent/token/DroneToken2.java
new file mode 100644
index 00000000000..99afb0362fd
--- /dev/null
+++ b/Mage/src/main/java/mage/game/permanent/token/DroneToken2.java
@@ -0,0 +1,34 @@
+package mage.game.permanent.token;
+
+import mage.MageInt;
+import mage.abilities.common.CanBlockOnlyFlyingAbility;
+import mage.abilities.keyword.FlyingAbility;
+import mage.constants.CardType;
+import mage.constants.SubType;
+
+/**
+ * @author TheElk801
+ */
+public final class DroneToken2 extends TokenImpl {
+
+ public DroneToken2() {
+ super("Drone Token", "1/1 colorless Drone artifact creature token with flying and \"This token can block only creatures with flying.\"");
+ cardType.add(CardType.ARTIFACT);
+ cardType.add(CardType.CREATURE);
+ subtype.add(SubType.DRONE);
+ power = new MageInt(1);
+ toughness = new MageInt(1);
+
+ addAbility(FlyingAbility.getInstance());
+ addAbility(new CanBlockOnlyFlyingAbility());
+ }
+
+ private DroneToken2(final DroneToken2 token) {
+ super(token);
+ }
+
+ @Override
+ public DroneToken2 copy() {
+ return new DroneToken2(this);
+ }
+}
diff --git a/Mage/src/main/java/mage/game/permanent/token/LanderToken.java b/Mage/src/main/java/mage/game/permanent/token/LanderToken.java
new file mode 100644
index 00000000000..06d780af86c
--- /dev/null
+++ b/Mage/src/main/java/mage/game/permanent/token/LanderToken.java
@@ -0,0 +1,45 @@
+package mage.game.permanent.token;
+
+import mage.abilities.Ability;
+import mage.abilities.common.SimpleActivatedAbility;
+import mage.abilities.costs.common.SacrificeSourceCost;
+import mage.abilities.costs.common.TapSourceCost;
+import mage.abilities.costs.mana.GenericManaCost;
+import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
+import mage.constants.CardType;
+import mage.constants.SubType;
+import mage.filter.StaticFilters;
+import mage.target.common.TargetCardInLibrary;
+
+/**
+ * @author TheElk801
+ */
+public final class LanderToken extends TokenImpl {
+
+ public static String getReminderText() {
+ return "(It's an artifact with \"{2}, {T}, Sacrifice this token: "
+ + "Search your library for a basic land card, put it onto the "
+ + "battlefield tapped, then shuffle.\")";
+ }
+
+ public LanderToken() {
+ super("Lander Token", "Lander token");
+ cardType.add(CardType.ARTIFACT);
+ subtype.add(SubType.LANDER);
+
+ Ability ability = new SimpleActivatedAbility(new SearchLibraryPutInPlayEffect(
+ new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND_A), true
+ ), new GenericManaCost(2));
+ ability.addCost(new TapSourceCost());
+ ability.addCost(new SacrificeSourceCost());
+ this.addAbility(ability);
+ }
+
+ private LanderToken(final LanderToken token) {
+ super(token);
+ }
+
+ public LanderToken copy() {
+ return new LanderToken(this);
+ }
+}
diff --git a/Mage/src/main/java/mage/game/permanent/token/MunitionsToken.java b/Mage/src/main/java/mage/game/permanent/token/MunitionsToken.java
new file mode 100644
index 00000000000..846c32a77ec
--- /dev/null
+++ b/Mage/src/main/java/mage/game/permanent/token/MunitionsToken.java
@@ -0,0 +1,30 @@
+package mage.game.permanent.token;
+
+import mage.abilities.Ability;
+import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
+import mage.abilities.effects.common.DamageTargetEffect;
+import mage.constants.CardType;
+import mage.target.common.TargetAnyTarget;
+
+/**
+ * @author TheElk801
+ */
+public final class MunitionsToken extends TokenImpl {
+
+ public MunitionsToken() {
+ super("Munitions", "colorless artifact token named Munitions with \"When this token leaves the battlefield, it deals 2 damage to any target.\"");
+ cardType.add(CardType.ARTIFACT);
+
+ Ability ability = new LeavesBattlefieldTriggeredAbility(new DamageTargetEffect(2, "it"));
+ ability.addTarget(new TargetAnyTarget());
+ this.addAbility(ability);
+ }
+
+ private MunitionsToken(final MunitionsToken token) {
+ super(token);
+ }
+
+ public MunitionsToken copy() {
+ return new MunitionsToken(this);
+ }
+}
diff --git a/Mage/src/main/java/mage/game/permanent/token/Spirit31Token.java b/Mage/src/main/java/mage/game/permanent/token/Spirit31Token.java
new file mode 100644
index 00000000000..75e905ac67a
--- /dev/null
+++ b/Mage/src/main/java/mage/game/permanent/token/Spirit31Token.java
@@ -0,0 +1,32 @@
+package mage.game.permanent.token;
+
+import mage.MageInt;
+import mage.abilities.keyword.FlyingAbility;
+import mage.constants.CardType;
+import mage.constants.SubType;
+
+/**
+ * @author TheElk801
+ */
+public final class Spirit31Token extends TokenImpl {
+
+ public Spirit31Token() {
+ super("Spirit Token", "3/1 white Spirit creature token with flying");
+ cardType.add(CardType.CREATURE);
+ color.setWhite(true);
+ subtype.add(SubType.SPIRIT);
+ power = new MageInt(3);
+ toughness = new MageInt(1);
+
+ addAbility(FlyingAbility.getInstance());
+ }
+
+ private Spirit31Token(final Spirit31Token token) {
+ super(token);
+ }
+
+ @Override
+ public Spirit31Token copy() {
+ return new Spirit31Token(this);
+ }
+}
diff --git a/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java b/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java
index 875a723aa46..27022d0b08c 100644
--- a/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java
+++ b/Mage/src/main/java/mage/game/permanent/token/VoiceOfResurgenceToken.java
@@ -6,7 +6,6 @@ import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect;
import mage.constants.CardType;
import mage.constants.SubType;
-import mage.constants.Zone;
/**
* @author spjspj
@@ -25,7 +24,7 @@ public final class VoiceOfResurgenceToken extends TokenImpl {
// This creature's power and toughness are each equal to the number of creatures you control.
this.addAbility(new SimpleStaticAbility(new SetBasePowerToughnessSourceEffect(
- CreaturesYouControlCount.instance)));
+ CreaturesYouControlCount.PLURAL)));
}
private VoiceOfResurgenceToken(final VoiceOfResurgenceToken token) {
diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java
index 7cc905df6b6..70e66ba58ce 100644
--- a/Mage/src/main/java/mage/game/stack/Spell.java
+++ b/Mage/src/main/java/mage/game/stack/Spell.java
@@ -8,6 +8,7 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.BestowAbility;
import mage.abilities.keyword.PrototypeAbility;
import mage.abilities.keyword.TransformAbility;
+import mage.abilities.keyword.WarpAbility;
import mage.cards.*;
import mage.constants.*;
import mage.counters.Counter;
@@ -421,6 +422,7 @@ public class Spell extends StackObjectImpl implements Card {
} else {
MageObjectReference mor = new MageObjectReference(getSpellAbility());
game.storePermanentCostsTags(mor, getSpellAbility());
+ WarpAbility.addDelayedTrigger(getSpellAbility(), game);
return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
}
}
diff --git a/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java b/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java
index 2f19c2c2d97..7fdc106b767 100644
--- a/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java
+++ b/Mage/src/main/java/mage/game/tournament/MultiplayerRound.java
@@ -42,14 +42,11 @@ public class MultiplayerRound {
return this.roundNum;
}
- public void setMatch (Match match) {
+ public void setMatchAndTable(Match match, UUID tableId) {
this.match = match;
- }
-
- public void setTableId (UUID tableId) {
this.tableId = tableId;
}
-
+
public boolean isRoundOver() {
boolean roundIsOver = true;
if (this.match != null) {
diff --git a/Mage/src/main/java/mage/game/tournament/TournamentPairing.java b/Mage/src/main/java/mage/game/tournament/TournamentPairing.java
index 3939ce40f0c..986887c6711 100644
--- a/Mage/src/main/java/mage/game/tournament/TournamentPairing.java
+++ b/Mage/src/main/java/mage/game/tournament/TournamentPairing.java
@@ -42,8 +42,9 @@ public class TournamentPairing {
return match;
}
- public void setMatch(Match match) {
+ public void setMatchAndTable(Match match, UUID tableId) {
this.match = match;
+ this.tableId = tableId;
}
/**
@@ -88,10 +89,6 @@ public class TournamentPairing {
return tableId;
}
- public void setTableId(UUID tableId) {
- this.tableId = tableId;
- }
-
public boolean isAlreadyPublished() {
return alreadyPublished;
}
diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java
index 995c0b3c48f..d6597719b75 100644
--- a/Mage/src/main/java/mage/game/turn/Turn.java
+++ b/Mage/src/main/java/mage/game/turn/Turn.java
@@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.constants.PhaseStep;
import mage.constants.TurnPhase;
import mage.game.Game;
+import mage.game.events.GameEvent;
import mage.game.events.PhaseChangedEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
@@ -106,6 +107,8 @@ public class Turn implements Serializable {
checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
game.getPlayer(activePlayer.getId()).beginTurn(game);
+ GameEvent event = new GameEvent(GameEvent.EventType.BEGIN_TURN, null, null, activePlayer.getId());
+ game.fireEvent(event);
for (Phase phase : phases) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java
index eb4fe3f69c0..5e3e89f8a8d 100644
--- a/Mage/src/main/java/mage/players/Player.java
+++ b/Mage/src/main/java/mage/players/Player.java
@@ -79,7 +79,13 @@ public interface Player extends MageItem, Copyable {
*/
boolean isHuman();
- boolean isTestsMode();
+ boolean isTestMode();
+
+ void setTestMode(boolean value);
+
+ boolean isFastFailInTestMode();
+
+ void setFastFailInTestMode(boolean value);
/**
* Current player is AI. Use it in card's code and all other places.
@@ -398,8 +404,6 @@ public interface Player extends MageItem, Copyable {
*/
void setGameUnderYourControl(Game game, boolean value, boolean fullRestore);
- void setTestMode(boolean value);
-
void setAllowBadMoves(boolean allowBadMoves);
/**
diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java
index e830b0d70bb..94d126e11f5 100644
--- a/Mage/src/main/java/mage/players/PlayerImpl.java
+++ b/Mage/src/main/java/mage/players/PlayerImpl.java
@@ -147,6 +147,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected Set inRange = new HashSet<>(); // players list in current range of influence (updates each turn due rules)
protected boolean isTestMode = false;
+ protected boolean isFastFailInTestMode = true;
protected boolean canGainLife = true;
protected boolean canLoseLife = true;
protected PayLifeCostLevel payLifeCostLevel = PayLifeCostLevel.allAbilities;
@@ -2833,8 +2834,10 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
- public boolean canRespond() { // abort is checked here to get out of player requests (as example: after disconnect)
- return isInGame() && !abort;
+ public boolean canRespond() {
+ // abort is checked here to get out of player requests (as example: after disconnect)
+ // thread is checked here to get out of AI game simulations or close by third party tools
+ return isInGame() && !abort && !Thread.currentThread().isInterrupted();
}
@Override
@@ -4602,7 +4605,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
- public boolean isTestsMode() {
+ public boolean isTestMode() {
return isTestMode;
}
@@ -4611,6 +4614,16 @@ public abstract class PlayerImpl implements Player, Serializable {
this.isTestMode = value;
}
+ @Override
+ public boolean isFastFailInTestMode() {
+ return isFastFailInTestMode;
+ }
+
+ @Override
+ public void setFastFailInTestMode(boolean value) {
+ this.isFastFailInTestMode = value;
+ }
+
@Override
public boolean isTopCardRevealed() {
return topCardRevealed;
diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java
index b95d18c8550..c04395b2cc5 100644
--- a/Mage/src/main/java/mage/target/Target.java
+++ b/Mage/src/main/java/mage/target/Target.java
@@ -14,6 +14,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
/**
* @author BetaSteward_at_googlemail.com
@@ -63,6 +64,13 @@ public interface Target extends Copyable, Serializable {
*/
Set possibleTargets(UUID sourceControllerId, Ability source, Game game);
+ default Set possibleTargets(UUID sourceControllerId, Ability source, Game game, Set cards) {
+ // do not override
+ return possibleTargets(sourceControllerId, source, game).stream()
+ .filter(id -> cards == null || cards.contains(id))
+ .collect(Collectors.toSet());
+ }
+
/**
* Priority method to make a choice from cards and other places, not a player.chooseXXX
*/
diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java
index 118ca88ed9e..3abc11591dc 100644
--- a/Mage/src/main/java/mage/target/TargetCard.java
+++ b/Mage/src/main/java/mage/target/TargetCard.java
@@ -110,6 +110,12 @@ public class TargetCard extends TargetObject {
possibleTargets += countPossibleTargetInCommandZone(game, player, sourceControllerId, source,
filter, isNotTarget(), this.minNumberOfTargets - possibleTargets);
break;
+ case ALL:
+ possibleTargets += countPossibleTargetInAnyZone(game, player, sourceControllerId, source,
+ filter, isNotTarget(), this.minNumberOfTargets - possibleTargets);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported TargetCard zone: " + zone);
}
if (possibleTargets >= this.minNumberOfTargets) {
return true;
@@ -214,6 +220,25 @@ public class TargetCard extends TargetObject {
return possibleTargets;
}
+ /**
+ * count up to N possible target cards in ANY zone
+ */
+ protected static int countPossibleTargetInAnyZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) {
+ UUID sourceId = source != null ? source.getSourceId() : null;
+ int possibleTargets = 0;
+ for (Card card : game.getCards()) {
+ if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) {
+ if (filter.match(card, game)) {
+ possibleTargets++;
+ if (possibleTargets >= countUpTo) {
+ return possibleTargets; // early return for faster computation.
+ }
+ }
+ }
+ }
+ return possibleTargets;
+ }
+
@Override
public Set possibleTargets(UUID sourceControllerId, Game game) {
return possibleTargets(sourceControllerId, null, game);
@@ -245,6 +270,11 @@ public class TargetCard extends TargetObject {
case COMMAND:
possibleTargets.addAll(getAllPossibleTargetInCommandZone(game, player, sourceControllerId, source, filter, isNotTarget()));
break;
+ case ALL:
+ possibleTargets.addAll(getAllPossibleTargetInAnyZone(game, player, sourceControllerId, source, filter, isNotTarget()));
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported TargetCard zone: " + zone);
}
}
}
@@ -332,6 +362,22 @@ public class TargetCard extends TargetObject {
return possibleTargets;
}
+ /**
+ * set of all matching target in ANY zone
+ */
+ protected static Set getAllPossibleTargetInAnyZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) {
+ Set possibleTargets = new HashSet<>();
+ UUID sourceId = source != null ? source.getSourceId() : null;
+ for (Card card : game.getCards()) {
+ if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) {
+ if (filter.match(card, sourceControllerId, source, game)) {
+ possibleTargets.add(card.getId());
+ }
+ }
+ }
+ return possibleTargets;
+ }
+
// TODO: check all class targets, if it override canTarget then make sure it override ALL 3 METHODS with canTarget and possibleTargets (method with cards doesn't need)
@Override
diff --git a/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java
index a9e90993042..3536e38a3f3 100644
--- a/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java
+++ b/Mage/src/main/java/mage/target/targetpointer/EachTargetPointer.java
@@ -120,6 +120,7 @@ public class EachTargetPointer extends TargetPointerImpl {
@Override
public String describeTargets(Targets targets, String defaultDescription) {
+ if (targetDescription != null) return targetDescription;
if (targets.isEmpty()) {
return defaultDescription;
}
diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java
index 1a3b3d926bd..8d97aa4b5a1 100644
--- a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java
+++ b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java
@@ -40,6 +40,7 @@ public class FixedTargets extends TargetPointerImpl {
this(token.getLastAddedTokenIds()
.stream()
.map(game::getPermanent)
+ .filter(Objects::nonNull)
.collect(Collectors.toList()), game);
}
diff --git a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java
index cb79e86e0a5..d4da5b9f329 100644
--- a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java
+++ b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java
@@ -148,6 +148,7 @@ public abstract class NthTargetPointer extends TargetPointerImpl {
@Override
public String describeTargets(Targets targets, String defaultDescription) {
+ if (targetDescription != null) return targetDescription;
if (targets.size() <= this.targetIndex) {
// TODO: need research, is it used for non setup targets ?!
// Typical usage example: trigger sets fixed target pointer
diff --git a/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java
index 2dcff217576..fab982b18e9 100644
--- a/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java
+++ b/Mage/src/main/java/mage/target/targetpointer/TargetPointer.java
@@ -57,9 +57,13 @@ public interface TargetPointer extends Serializable, Copyable {
/**
* Describes the appropriate subset of targets for ability text.
*/
- default String describeTargets(Targets targets, String defaultDescription) {
- return defaultDescription;
- }
+ String describeTargets(Targets targets, String defaultDescription);
+
+ /**
+ * Overwrite the default target description
+ */
+ void setTargetDescription(String description);
+ String getTargetDescription();
default boolean isPlural(Targets targets) {
return false;
diff --git a/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java b/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java
index 857061689ad..ae593646a90 100644
--- a/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java
+++ b/Mage/src/main/java/mage/target/targetpointer/TargetPointerImpl.java
@@ -5,6 +5,7 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
+import mage.target.Targets;
import java.util.HashMap;
import java.util.Map;
@@ -18,6 +19,7 @@ public abstract class TargetPointerImpl implements TargetPointer {
private Map data;
private boolean initialized = false;
+ protected String targetDescription = null;
protected TargetPointerImpl() {
super();
@@ -30,6 +32,7 @@ public abstract class TargetPointerImpl implements TargetPointer {
this.data.putAll(targetPointer.data);
}
this.initialized = targetPointer.initialized;
+ this.targetDescription = targetPointer.targetDescription;
}
@Override
@@ -74,4 +77,18 @@ public abstract class TargetPointerImpl implements TargetPointer {
return this;
}
+ @Override
+ public String describeTargets(Targets targets, String defaultDescription) {
+ return targetDescription != null ? targetDescription : defaultDescription;
+ }
+
+ @Override
+ public void setTargetDescription(String description) {
+ targetDescription = description;
+ }
+ @Override
+ public String getTargetDescription() {
+ return targetDescription;
+ }
+
}
diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java
index d4c968bd4dd..abc4f0bbe53 100644
--- a/Mage/src/main/java/mage/util/CardUtil.java
+++ b/Mage/src/main/java/mage/util/CardUtil.java
@@ -1556,7 +1556,7 @@ public final class CardUtil {
return result;
}
- private static boolean checkForPlayable(Cards cards, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) {
+ private static boolean cardsHasCastableParts(Cards cards, FilterCard filter, Ability source, Player player, Game game, SpellCastTracker spellCastTracker, boolean playLand) {
return cards
.getCards(game)
.stream()
@@ -1580,19 +1580,40 @@ public final class CardUtil {
CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter);
return;
}
- int spellsCast = 0;
+ int castCount = 0;
+ int maxCastCount = Integer.min(cards.size(), maxSpells);
cards.removeZone(Zone.STACK, game);
- while (player.canRespond() && spellsCast < maxSpells && !cards.isEmpty()) {
- if (CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker, playLand)) {
- spellsCast++;
- cards.removeZone(Zone.STACK, game);
- } else if (!checkForPlayable(
- cards, filter, source, player, game, spellCastTracker, playLand
- ) || !player.chooseUse(
- Outcome.PlayForFree, "Continue casting spells?", source, game
- )) {
+ if (!cardsHasCastableParts(cards, filter, source, player, game, spellCastTracker, playLand)) {
+ return;
+ }
+
+ while (player.canRespond()) {
+ boolean wasCast = CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter, spellCastTracker, playLand);
+
+ // nothing to cast
+ cards.removeZone(Zone.STACK, game);
+ if (cards.isEmpty() || !cardsHasCastableParts(cards, filter, source, player, game, spellCastTracker, playLand)) {
break;
}
+
+ if (wasCast) {
+ // no more tries to cast
+ castCount++;
+ if (castCount >= maxCastCount) {
+ break;
+ }
+ } else {
+ // player want to cancel
+ if (player.isComputer()) {
+ // AI can't choose good spell, so stop
+ break;
+ } else {
+ // Human can choose wrong spell part, so allow to continue
+ if (!player.chooseUse(Outcome.PlayForFree, "Continue casting spells?", source, game)) {
+ break;
+ }
+ }
+ }
}
}
diff --git a/Mage/src/main/java/mage/util/DebugUtil.java b/Mage/src/main/java/mage/util/DebugUtil.java
index c76f83243eb..fcad86ea034 100644
--- a/Mage/src/main/java/mage/util/DebugUtil.java
+++ b/Mage/src/main/java/mage/util/DebugUtil.java
@@ -17,6 +17,12 @@ public class DebugUtil {
public static boolean AI_ENABLE_DEBUG_MODE = false;
public static boolean AI_SHOW_TARGET_OPTIMIZATION_LOGS = false; // works with target amount
+ // SERVER
+ // data collectors - enable additional logs and data collection for better AI and human games debugging
+ public static boolean TESTS_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY = false; // WARNING, for debug only, can generate too much files
+ public static boolean SERVER_DATA_COLLECTORS_ENABLE_PRINT_GAME_LOGS = false;
+ public static boolean SERVER_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY = false;
+
// GAME
// print detail target info for activate/cast/trigger only, not a single choose dialog
// can be useful to debug unit tests, auto-choose or AI
diff --git a/Mage/src/main/java/mage/util/FuzzyTestsUtil.java b/Mage/src/main/java/mage/util/FuzzyTestsUtil.java
index 1523c9040a5..1c7f29a43b6 100644
--- a/Mage/src/main/java/mage/util/FuzzyTestsUtil.java
+++ b/Mage/src/main/java/mage/util/FuzzyTestsUtil.java
@@ -58,7 +58,7 @@ public class FuzzyTestsUtil {
return;
}
Player samplePlayer = game.getPlayers().values().stream().findFirst().orElse(null);
- if (samplePlayer == null || !samplePlayer.isTestsMode()) {
+ if (samplePlayer == null || !samplePlayer.isTestMode()) {
return;
}
diff --git a/Mage/src/main/java/mage/watchers/common/VoidWatcher.java b/Mage/src/main/java/mage/watchers/common/VoidWatcher.java
new file mode 100644
index 00000000000..879387dd50c
--- /dev/null
+++ b/Mage/src/main/java/mage/watchers/common/VoidWatcher.java
@@ -0,0 +1,60 @@
+package mage.watchers.common;
+
+import mage.abilities.keyword.WarpAbility;
+import mage.constants.WatcherScope;
+import mage.constants.Zone;
+import mage.game.Game;
+import mage.game.events.GameEvent;
+import mage.game.events.ZoneChangeEvent;
+import mage.game.stack.Spell;
+import mage.watchers.Watcher;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @author TheElk801
+ */
+public class VoidWatcher extends Watcher {
+
+ // need to track separately for each player as it needs to be in range
+ private final Set players = new HashSet<>();
+
+ public VoidWatcher() {
+ super(WatcherScope.GAME);
+ }
+
+ @Override
+ public void watch(GameEvent event, Game game) {
+ switch (event.getType()) {
+ case SPELL_CAST:
+ Spell spell = game.getSpell(event.getTargetId());
+ if (spell != null && spell.getSpellAbility() instanceof WarpAbility) {
+ players.addAll(game.getState().getPlayersInRange(spell.getControllerId(), game));
+ }
+ return;
+ case ZONE_CHANGE:
+ ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
+ if (Zone.BATTLEFIELD.match(zEvent.getFromZone())
+ && zEvent.getTarget() != null
+ && !zEvent.getTarget().isLand(game)) {
+ players.addAll(game.getState().getPlayersInRange(zEvent.getTarget().getControllerId(), game));
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ players.clear();
+ }
+
+ public static boolean checkPlayer(UUID playerId, Game game) {
+ return game
+ .getState()
+ .getWatcher(VoidWatcher.class)
+ .players
+ .contains(playerId);
+ }
+}
diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt
index df62e048268..8d0dacf120a 100644
--- a/Mage/src/main/resources/tokens-database.txt
+++ b/Mage/src/main/resources/tokens-database.txt
@@ -73,6 +73,8 @@
|Generate|EMBLEM:MD1|Emblem Elspeth|||ElspethKnightErrantEmblem|
|Generate|EMBLEM:SWS|Emblem Obi-Wan Kenobi|||ObiWanKenobiEmblem|
|Generate|EMBLEM:SWS|Emblem Luke Skywalker|||LukeSkywalkerEmblem|
+|Generate|EMBLEM:SWS|Emblem Yoda|||YodaEmblem|
+|Generate|EMBLEM:SWS|Emblem Aurra Sing|||AurraSingBaneOfJediEmblem|
|Generate|EMBLEM:RIX|Emblem Huatli|||HuatliRadiantChampionEmblem|
|Generate|EMBLEM:RNA|Emblem Domri|||DomriChaosBringerEmblem|
|Generate|EMBLEM:WAR|Emblem Nissa|||NissaWhoShakesTheWorldEmblem|
@@ -146,6 +148,7 @@
|Generate|EMBLEM:INR|Emblem Wrenn|||WrennAndSevenEmblem|
|Generate|EMBLEM:DFT|Emblem Chandra|||ChandraSparkHunterEmblem|
|Generate|EMBLEM:FIN|Emblem Sephiroth|||SephirothOneWingedAngelEmblem|
+|Generate|EMBLEM:EOE|Emblem Tezzeret|||TezzeretCruelCaptainEmblem|
# ALL PLANES
@@ -1527,12 +1530,18 @@
|Generate|TOK:SLD|Food|3||FoodToken|
|Generate|TOK:SLD|Food|4||FoodToken|
|Generate|TOK:SLD|Food|5||FoodToken|
+|Generate|TOK:SLD|Food|6||FoodToken|
|Generate|TOK:SLD|Goblin|||GoblinToken|
|Generate|TOK:SLD|Hydra|||ZaxaraTheExemplaryHydraToken|
|Generate|TOK:SLD|Icingdeath, Frost Tongue|||IcingdeathFrostTongueToken|
|Generate|TOK:SLD|Marit Lage|||MaritLageToken|
|Generate|TOK:SLD|Mechtitan|||MechtitanToken|
+|Generate|TOK:SLD|Myr|||MyrToken|
|Generate|TOK:SLD|Saproling|||SaprolingToken|
+|Generate|TOK:SLD|Shapeshifter|1||ShapeshifterBlueToken|
+|Generate|TOK:SLD|Shapeshifter|2||ShapeshifterBlueToken|
+|Generate|TOK:SLD|Shapeshifter|3||ShapeshifterBlueToken|
+|Generate|TOK:SLD|Shapeshifter|4||ShapeshifterBlueToken|
|Generate|TOK:SLD|Shrine|||ShrineToken|
|Generate|TOK:SLD|Spirit|1||SpiritWhiteToken|
|Generate|TOK:SLD|Spirit|2||SpiritToken|
@@ -1541,6 +1550,8 @@
|Generate|TOK:SLD|Treasure|2||TreasureToken|
|Generate|TOK:SLD|Treasure|3||TreasureToken|
|Generate|TOK:SLD|Treasure|4||TreasureToken|
+|Generate|TOK:SLD|Treasure|5||TreasureToken|
+|Generate|TOK:SLD|Treasure|6||TreasureToken|
|Generate|TOK:SLD|Walker|1||WalkerToken|
|Generate|TOK:SLD|Walker|2||WalkerToken|
|Generate|TOK:SLD|Walker|3||WalkerToken|
@@ -2227,22 +2238,15 @@
|Generate|TOK:WOE|Young Hero|||YoungHeroRoleToken|
# WOC
-|Generate|TOK:WOC|Cat|1||CatToken2|
-|Generate|TOK:WOC|Cat|2||CatToken|
-|Generate|TOK:WOC|Elephant|||ElephantToken|
|Generate|TOK:WOC|Faerie|||FaerieToken|
|Generate|TOK:WOC|Faerie Rogue|1||FaerieRogueToken|
|Generate|TOK:WOC|Faerie Rogue|2||OonaQueenFaerieRogueToken|
-|Generate|TOK:WOC|Human Monk|||HumanMonkToken|
|Generate|TOK:WOC|Human Soldier|||HumanSoldierToken|
|Generate|TOK:WOC|Monster|||MonsterRoleToken|
-|Generate|TOK:WOC|Ox|||OxToken|
|Generate|TOK:WOC|Pegasus|||PegasusToken2|
|Generate|TOK:WOC|Pirate|||NettlingNuisancePirateToken|
|Generate|TOK:WOC|Royal|||RoyalRoleToken|
|Generate|TOK:WOC|Saproling|||SaprolingToken|
-|Generate|TOK:WOC|Sorcerer|||SorcererRoleToken|
-|Generate|TOK:WOC|Spirit|||WhiteBlackSpiritToken|
|Generate|TOK:WOC|Virtuous|||VirtuousRoleToken|
# WHO
@@ -2786,7 +2790,8 @@
|Generate|TOK:DSK|Primo, the Indivisible|||PrimoTheIndivisibleToken|
|Generate|TOK:DSK|Shard|||ShardToken|
|Generate|TOK:DSK|Spider|||Spider22Token|
-|Generate|TOK:DSK|Spirit|||SpiritBlueToken|
+|Generate|TOK:DSK|Spirit|1||Spirit31Token|
+|Generate|TOK:DSK|Spirit|2||SpiritBlueToken|
|Generate|TOK:DSK|Treasure|||TreasureToken|
# FIN
@@ -2824,7 +2829,7 @@
|Generate|TOK:FIN|Treasure|1||TreasureToken|
|Generate|TOK:FIN|Treasure|2||TreasureToken|
-# FIN
+# FIC
|Generate|TOK:FIC|Human Soldier|||HumanSoldierToken|
|Generate|TOK:FIC|Soldier|||SoldierToken|
|Generate|TOK:FIC|Spirit|||SpiritWhiteToken|
@@ -2835,6 +2840,36 @@
|Generate|TOK:FIC|The Blackjack|||TheBlackjackToken|
|Generate|TOK:FIC|Clue|||ClueArtifactToken|
+#EOE
+|Generate|TOK:EOE|Drone|||DroneToken2|
+|Generate|TOK:EOE|Human Soldier|||HumanSoldierToken|
+|Generate|TOK:EOE|Lander|1||LanderToken|
+|Generate|TOK:EOE|Lander|2||LanderToken|
+|Generate|TOK:EOE|Lander|3||LanderToken|
+|Generate|TOK:EOE|Lander|4||LanderToken|
+|Generate|TOK:EOE|Lander|5||LanderToken|
+|Generate|TOK:EOE|Munitions|||MunitionsToken|
+|Generate|TOK:EOE|Robot|||RobotToken|
+|Generate|TOK:EOE|Sliver|||SliverToken|
+
+#EOC
+|Generate|TOK:EOC|Beast|1||BeastToken|
+|Generate|TOK:EOC|Beast|2||BeastToken2|
+|Generate|TOK:EOC|Bird|||SwanSongBirdToken|
+|Generate|TOK:EOC|Clue|||ClueArtifactToken|
+|Generate|TOK:EOC|Elemental|1||TitaniaProtectorOfArgothElementalToken|
+|Generate|TOK:EOC|Elemental|2||OmnathElementalToken|
+|Generate|TOK:EOC|Gnome|||GnomeToken|
+|Generate|TOK:EOC|Golem|1||GolemToken|
+|Generate|TOK:EOC|Golem|2||HammerOfPurphorosGolemToken|
+|Generate|TOK:EOC|Golem|3||TitanForgeGolemToken|
+|Generate|TOK:EOC|Incubator|||IncubatorToken|
+|Generate|TOK:EOC|Insect|||XiraBlackInsectToken|
+|Generate|TOK:EOC|Pest|||Pest11GainLifeToken|
+|Generate|TOK:EOC|Phyrexian|||Phyrexian00Token|
+|Generate|TOK:EOC|Shapeshifter|||ShapeshifterDeathtouchToken|
+|Generate|TOK:EOC|Thopter|||ThopterColorlessToken|
+
# JVC
|Generate|TOK:JVC|Elemental Shaman|||ElementalShamanToken|
diff --git a/Utils/keywords.txt b/Utils/keywords.txt
index ea6b857b8ce..5dd4945cf5d 100644
--- a/Utils/keywords.txt
+++ b/Utils/keywords.txt
@@ -128,6 +128,7 @@ Spectacle|card, cost|
Spree|card|
Squad|cost|
Start your engines!|new|
+Station|new|
Storm|new|
Sunburst|new|
Suspend|number, cost, card|
@@ -147,4 +148,5 @@ Unleash|new|
Vanishing|number|
Vigilance|instance|
Ward|cost|
+Warp|card, manaString|
Wither|instance|
diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt
index 9ee840f5d31..a8509dc657a 100644
--- a/Utils/mtg-cards-data.txt
+++ b/Utils/mtg-cards-data.txt
@@ -59108,34 +59108,595 @@ Green Goblin, Nemesis|Marvel's Spider-Man Eternal|23|R|{2}{B}{R}|Legendary Creat
Doc Ock, Evil Inventor|Marvel's Spider-Man Eternal|24|R|{5}{U}{B}|Legendary Creature - Human Scientist Villain|8|8|At the beginning of combat on your turn, target noncreature artifact you control becomes an 8/8 Robot Villain artifact creature in addition to its other types.|
Sensational Spider-Man|Marvel's Spider-Man Eternal|25|R|{1}{W}{U}|Legendary Creature - Spider Human Hero|3|3|Whenever Sensational Spider-Man attacks, tap target creature defending player controls and put a stun counter on it. Then you may remove up to three stun counters from among all permanents. Draw cards equal to the number of stun counters removed this way.|
Pumpkin Bombs|Marvel's Spider-Man Eternal|26|R|{1}{R}|Artifact|||{T}, Discard two cards: Draw three cards, then put a fuse counter on this artifact. It deals damage equal to the number of fuse counters on it to target opponent. They gain control of this artifact.|
-Tezzeret, Cruel Captain|Edge of Eternities|2|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it in your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."|
+Anticausal Vestige|Edge of Eternities|1|R|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}|
+Tezzeret, Cruel Captain|Edge of Eternities|2|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."|
+All-Fates Stalker|Edge of Eternities|3|U|{3}{W}|Creature - Drix Assassin|2|3|When this creature enters, exile up to one target non-Assassin creature until this creature leaves the battlefield.$Warp {1}{W}|
+Astelli Reclaimer|Edge of Eternities|4|R|{3}{W}{W}|Creature - Angel Warrior|5|4|Flying$When this creature enters, return target noncreature, nonland permanent card with mana value X or less from your graveyard to the battlefield, where X is the amount of mana spent to cast this creature.$Warp {2}{W}|
+Auxiliary Boosters|Edge of Eternities|5|C|{4}{W}|Artifact - Equipment|||When this Equipment enters, create a 2/2 colorless Robot artifact creature token and attach this Equipment to it.$Equipped creature gets +1/+2 and has flying.$Equip {3}|
+Banishing Light|Edge of Eternities|6|C|{2}{W}|Enchantment|||When this enchantment enters, exile target nonland permanent an opponent controls until this enchantment leaves the battlefield.|
+Beyond the Quiet|Edge of Eternities|7|R|{3}{W}{W}|Sorcery|||Exile all creatures and Spacecraft.|
+Brightspear Zealot|Edge of Eternities|8|C|{2}{W}|Creature - Human Soldier|2|4|Vigilance$This creature gets +2/+0 as long as you've cast two or more spells this turn.|
+Cosmogrand Zenith|Edge of Eternities|9|M|{2}{W}|Creature - Human Soldier|2|4|Whenever you cast your second spell each turn, choose one --$* Create two 1/1 white Human Soldier creature tokens.$* Put a +1/+1 counter on each creature you control.|
+Dawnstrike Vanguard|Edge of Eternities|10|U|{5}{W}|Creature - Human Knight|4|5|Lifelink$At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on each creature you control other than this creature.|
+Dockworker Drone|Edge of Eternities|11|C|{1}{W}|Artifact Creature - Robot|1|1|This creature enters with a +1/+1 counter on it.$When this creature dies, put its counters on target creature you control.|
+Dual-Sun Adepts|Edge of Eternities|12|U|{2}{W}|Creature - Human Soldier|2|2|Double strike${5}: Creatures you control get +1/+1 until end of turn.|
+Dual-Sun Technique|Edge of Eternities|13|U|{1}{W}|Instant|||Target creature you control gains double strike until end of turn. If it has a +1/+1 counter on it, draw a card.|
+Emergency Eject|Edge of Eternities|14|U|{2}{W}|Instant|||Destroy target nonland permanent. Its controller creates a Lander token.|
+Exalted Sunborn|Edge of Eternities|15|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}|
+Exosuit Savior|Edge of Eternities|16|C|{2}{W}|Creature - Human Soldier|2|2|Flying$When this creature enters, return up to one other target permanent you control to its owner's hand.|
+Flight-Deck Coordinator|Edge of Eternities|17|C|{2}{W}|Creature - Human Soldier|3|3|At the beginning of your end step, if you control two or more tapped creatures, you gain 2 life.|
+Focus Fire|Edge of Eternities|18|C|{W}|Instant|||Focus Fire deals X damage to target attacking or blocking creature, where X is 2 plus the number of creatures and/or Spacecraft you control.|
+Haliya, Guided by Light|Edge of Eternities|19|R|{2}{W}|Legendary Creature - Human Soldier|3|3|Whenever Haliya or another creature or artifact you control enters, you gain 1 life.$At the beginning of your end step, draw a card if you've gained 3 or more life this turn.$Warp {W}|
+Hardlight Containment|Edge of Eternities|20|R|{W}|Enchantment - Aura|||Enchant artifact you control$When this Aura enters, exile target creature an opponent controls until this Aura leaves the battlefield.$Enchanted permanent has ward {1}.|
+Honor|Edge of Eternities|21|U|{W}|Sorcery|||Put a +1/+1 counter on target creature.$Draw a card.|
+Honored Knight-Captain|Edge of Eternities|22|U|{1}{W}|Creature - Human Advisor Knight|1|1|When this creature enters, create a 1/1 white Human Soldier creature token.${4}{W}{W}, Sacrifice this creature: Search your library for an Equipment card, put it onto the battlefield, then shuffle.|
+Knight Luminary|Edge of Eternities|23|C|{3}{W}|Creature - Human Knight|3|2|When this creature enters, create a 1/1 white Human Soldier creature token.$Warp {1}{W}|
+Lightstall Inquisitor|Edge of Eternities|24|R|{W}|Creature - Angel Wizard|2|1|Vigilance$When this creature enters, each opponent exiles a card from their hand and may play that card for as long as it remains exiled. Each spell cast this way costs {1} more to cast. Each land played this way enters tapped.|
+Lumen-Class Frigate|Edge of Eternities|25|R|{1}{W}|Artifact - Spacecraft|||Station$STATION 2+$Other creatures you control get +1/+1.$STATION 12+$Flying, lifelink$3/5|
+Luxknight Breacher|Edge of Eternities|26|C|{3}{W}|Creature - Human Knight|2|2|This creature enters with a +1/+1 counter on it for each other creature and/or artifact you control.|
+Pinnacle Starcage|Edge of Eternities|27|R|{1}{W}{W}|Artifact|||When this artifact enters, exile all artifacts and creatures with mana value 2 or less until this artifact leaves the battlefield.${6}{W}{W}: Put each card exiled with this artifact into its owner's graveyard, then create a 2/2 colorless Robot artifact token for each card put into a graveyard this way. Sacrifice this artifact.|
+Pulsar Squadron Ace|Edge of Eternities|28|U|{1}{W}|Creature - Human Pilot|1|2|When this creature enters, look at the top five cards of your library. You may reveal a Spacecraft card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. If you didn't put a card into your hand this way, put a +1/+1 counter on this creature.|
+Radiant Strike|Edge of Eternities|29|C|{3}{W}|Instant|||Destroy target artifact or tapped creature. You gain 3 life.|
+Rayblade Trooper|Edge of Eternities|30|U|{2}{W}|Creature - Human Soldier|2|2|When this creature enters, put a +1/+1 counter on target creature you control.$Whenever a nontoken creature you control with a +1/+1 counter on it dies, create a 1/1 white Human Soldier creature token.$Warp {1}{W}|
+Reroute Systems|Edge of Eternities|31|U|{W}|Instant|||Choose one --$* Target artifact or creature gains indestructible until end of turn.$* Reroute Systems deals 2 damage to target tapped creature.|
+Rescue Skiff|Edge of Eternities|32|U|{5}{W}|Artifact - Spacecraft|||When this Spacecraft enters, return target creature or enchantment card from your graveyard to the battlefield.$Station$STATION 10+$Flying$5/6|
+Scout for Survivors|Edge of Eternities|33|U|{2}{W}|Sorcery|||Return up to three target creature cards with total mana value 3 or less from your graveyard to the battlefield. Put a +1/+1 counter on each of them.|
+Seam Rip|Edge of Eternities|34|U|{W}|Enchantment|||When this enchantment enters, exile target nonland permanent an opponent controls with mana value 2 or less until this enchantment leaves the battlefield.|
The Seriema|Edge of Eternities|35|R|{1}{W}{W}|Legendary Artifact - Spacecraft|||When The Seriema enters, search your library for a legendary creature card, reveal it, put it into your hand, then shuffle.$Station$STATION 7+$Flying$Other tapped legendary creatures you control have indestructible.$5/5|
+Squire's Lightblade|Edge of Eternities|36|C|{W}|Artifact - Equipment|||Flash$When this Equipment enters, attach it to target creature you control. That creature gains first strike until end of turn.$Equipped creature gets +1/+0.$Equip {3}|
+Starfield Shepherd|Edge of Eternities|37|U|{3}{W}{W}|Creature - Angel|3|2|Flying$When this creature enters, search your library for a basic Plains card or a creature card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$Warp {1}{W}|
+Starfighter Pilot|Edge of Eternities|38|C|{1}{W}|Creature - Human Pilot|2|2|Whenever this creature becomes tapped, surveil 1.|
+Starport Security|Edge of Eternities|39|C|{W}|Artifact Creature - Robot Soldier|1|1|{3}{W}, {T}: Tap another target creature. This ability costs {2} less to activate if you control a creature with a +1/+1 counter on it.|
+Sunstar Chaplain|Edge of Eternities|40|R|{1}{W}|Creature - Human Cleric|3|2|At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on target creature you control.${2}, Remove a +1/+1 counter from a creature you control: Tap target artifact or creature.|
+Sunstar Expansionist|Edge of Eternities|41|U|{1}{W}|Creature - Human Knight|2|3|When this creature enters, if an opponent controls more lands than you, create a Lander token.$Landfall -- Whenever a land you control enters, this creature gets +1/+0 until end of turn.|
+Sunstar Lightsmith|Edge of Eternities|42|U|{3}{W}|Creature - Human Artificer|3|3|Whenever you cast your second spell each turn, put a +1/+1 counter on this creature and draw a card.|
+Wedgelight Rammer|Edge of Eternities|43|U|{3}{W}|Artifact - Spacecraft|||When this Spacecraft enters, create a 2/2 colorless Robot artifact creature token.$Station$STATION 9+$Flying, first strike$3/4|
+Weftblade Enhancer|Edge of Eternities|44|C|{5}{W}|Creature - Drix Artificer|3|4|When this creature enters, put a +1/+1 counter on each of up to two target creatures.$Warp {2}{W}|
+Zealous Display|Edge of Eternities|45|C|{2}{W}|Instant|||Creatures you control get +2/+0 until end of turn. If it's not your turn, untap those creatures.|
+Annul|Edge of Eternities|46|U|{U}|Instant|||Counter target artifact or enchantment spell.|
+Atomic Microsizer|Edge of Eternities|47|U|{U}|Artifact - Equipment|||Equipped creature gets +1/+0.$Whenever equipped creature attacks, choose up to one target creature. That creature can't be blocked this turn and has base power and toughness 1/1 until end of turn.$Equip {2}|
+Cerebral Download|Edge of Eternities|48|U|{4}{U}|Instant|||Surveil X, where X is the number of artifacts you control. Then draw three cards.|
+Cloudsculpt Technician|Edge of Eternities|49|C|{2}{U}|Creature - Jellyfish Artificer|1|4|Flying$As long as you control an artifact, this creature gets +1/+0.|
+Codecracker Hound|Edge of Eternities|50|U|{2}{U}|Creature - Dog|2|1|When this creature enters, look at the top two cards of your library. Put one into your hand and the other into your graveyard.$Warp {2}{U}|
+Consult the Star Charts|Edge of Eternities|51|R|{1}{U}|Instant|||Kicker {1}{U}$Look at the top X cards of your library, where X is the number of lands you control. Put one of those cards into your hand. If this spell was kicked, put two of those cards into your hand instead. Put the rest on the bottom of your library in a random order.|
+Cryogen Relic|Edge of Eternities|52|C|{1}{U}|Artifact|||When this artifact enters or leaves the battlefield, draw a card.${1}{U}, Sacrifice this artifact: Put a stun counter on up to one target tapped creature.|
+Cryoshatter|Edge of Eternities|53|C|{U}|Enchantment - Aura|||Enchant creature$Enchanted creature gets -5/-0.$When enchanted creature becomes tapped or is dealt damage, destroy it.|
+Desculpting Blast|Edge of Eternities|54|U|{1}{U}|Instant|||Return target nonland permanent to its owner's hand. If it was attacking, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."|
+Divert Disaster|Edge of Eternities|55|C|{1}{U}|Instant|||Counter target spell unless its controller pays {2}. If they do, you create a Lander token.|
+Emissary Escort|Edge of Eternities|56|R|{1}{U}|Artifact Creature - Robot Soldier|0|4|This creature gets +X/+0, where X is the greatest mana value among other artifacts you control.|
+Gigastorm Titan|Edge of Eternities|57|U|{4}{U}|Creature - Elemental|4|4|This spell costs {3} less to cast if you've cast another spell this turn.|
+Illvoi Galeblade|Edge of Eternities|58|C|{U}|Creature - Jellyfish Warrior|1|1|Flash$Flying${2}, Sacrifice this creature: Draw a card.|
+Illvoi Infiltrator|Edge of Eternities|59|U|{2}{U}|Creature - Jellyfish Rogue|1|3|This creature can't be blocked if you've cast two or more spells this turn.$Whenever this creature deals combat damage to a player, draw a card.|
+Illvoi Light Jammer|Edge of Eternities|60|C|{1}{U}|Artifact - Equipment|||Flash$When this Equipment enters, attach it to target creature you control. That creature gains hexproof until end of turn.$Equipped creature gets +1/+2.$Equip {3}|
+Illvoi Operative|Edge of Eternities|61|C|{1}{U}|Creature - Jellyfish Rogue|2|1|Whenever you cast your second spell each turn, put a +1/+1 counter on this creature.|
+Lost in Space|Edge of Eternities|62|C|{3}{U}|Instant|||Target artifact or creature's owner puts it on their choice of the top or bottom of their library. Surveil 1.|
+Mechan Assembler|Edge of Eternities|63|U|{4}{U}|Artifact Creature - Robot Artificer|4|4|Whenever another artifact you control enters, create a 2/2 colorless Robot artifact creature token. This ability triggers only once each turn.|
+Mechan Navigator|Edge of Eternities|64|U|{1}{U}|Artifact Creature - Robot Pilot|2|1|Whenever this creature becomes tapped, draw a card, then discard a card.|
+Mechan Shieldmate|Edge of Eternities|65|C|{1}{U}|Artifact Creature - Robot Soldier|3|2|Defender$As long as an artifact entered the battlefield under your control this turn, this creature can attack as though it didn't have defender.|
+Mechanozoa|Edge of Eternities|66|C|{4}{U}{U}|Artifact Creature - Robot Jellyfish|5|5|When this creature enters, tap target artifact or creature an opponent controls and put a stun counter on it.$Warp {2}{U}|
+Mental Modulation|Edge of Eternities|67|C|{1}{U}|Instant|||This spell costs {1} less to cast during your turn.$Tap target artifact or creature.$Draw a card.|
+Mm'menon, the Right Hand|Edge of Eternities|68|R|{3}{U}{U}|Legendary Creature - Jellyfish Advisor|3|4|Flying$You may look at the top card of your library any time.$You may cast artifact spells from the top of your library.$Artifacts you control have "{T}: Add {U}. Spend this mana only to cast a spell from anywhere other than your hand."|
+Moonlit Meditation|Edge of Eternities|69|R|{2}{U}|Enchantment - Aura|||Enchant artifact or creature you control$The first time you would create one or more tokens each turn, you may instead create that many tokens that are copies of enchanted permanent.|
+Mouth of the Storm|Edge of Eternities|70|U|{6}{U}|Creature - Elemental|6|6|Flying$Ward {2}$When this creature enters, creatures your opponents control get -3/-0 until your next turn.|
+Nanoform Sentinel|Edge of Eternities|71|C|{2}{U}|Artifact Creature - Robot|3|2|Whenever this creature becomes tapped, untap another target permanent. This ability triggers only once each turn.|
+Quantum Riddler|Edge of Eternities|72|M|{3}{U}{U}|Creature - Sphinx|4|6|Flying$When this creature enters, draw a card.$As long as you have one or fewer cards in hand, if you would draw one or more cards, you draw that many cards plus one instead.$Warp {1}{U}|
+Scour for Scrap|Edge of Eternities|73|U|{3}{U}|Instant|||Choose one or both --$* Search your library for an artifact card, reveal it, put it into your hand, then shuffle.$* Return target artifact card from your graveyard to your hand.|
+Selfcraft Mechan|Edge of Eternities|74|C|{3}{U}|Artifact Creature - Robot Artificer|3|4|When this creature enters, you may sacrifice an artifact. When you do, put a +1/+1 counter on target creature and draw a card.|
+Sinister Cryologist|Edge of Eternities|75|C|{2}{U}|Creature - Jellyfish Wizard|2|3|When this creature enters, target creature an opponent controls gets -3/-0 until end of turn.$Warp {U}|
+Specimen Freighter|Edge of Eternities|76|U|{5}{U}|Artifact - Spacecraft|||When this Spacecraft enters, return up to two target non-Spacecraft creatures to their owners' hands.$Station$STATION 9+$Flying$Whenever this Spacecraft attacks, defending player mills four cards.$4/7|
+Starbreach Whale|Edge of Eternities|77|C|{4}{U}|Creature - Whale|3|5|Flying$When this creature enters, surveil 2.$Warp {1}{U}|
+Starfield Vocalist|Edge of Eternities|78|R|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}|
+Starwinder|Edge of Eternities|79|R|{5}{U}{U}|Creature - Leviathan|7|7|Whenever a creature you control deals combat damage to a player, you may draw that many cards.$Warp {2}{U}{U}|
+Steelswarm Operator|Edge of Eternities|80|U|{1}{U}|Artifact Creature - Robot Soldier|1|1|Flying${T}: Add {U}. Spend this mana only to cast an artifact spell.${T}: Add {U}{U}. Spend this mana only to activate abilities of artifact sources.|
+Synthesizer Labship|Edge of Eternities|81|R|{U}|Artifact - Spacecraft|||Station$STATION 2+$At the beginning of combat on your turn, up to one other target artifact you control becomes an artifact creature with base power and toughness 2/2 and gains flying until end of turn.$STATION 9+$Flying, vigilance$4/4|
+Tractor Beam|Edge of Eternities|82|U|{2}{U}{U}|Enchantment - Aura|||Enchant creature or Spacecraft$When this Aura enters, tap enchanted permanent.$You control enchanted permanent.$Enchanted permanent doesn't untap during its controller's untap step.|
+Unravel|Edge of Eternities|83|U|{1}{U}{U}|Instant|||Counter target spell. If the amount of mana spent to cast that spell was less than its mana value, you draw a card.|
+Uthros Psionicist|Edge of Eternities|84|U|{2}{U}|Creature - Jellyfish Scientist|2|4|The second spell you cast each turn costs {2} less to cast.|
+Uthros Scanship|Edge of Eternities|85|U|{3}{U}|Artifact - Spacecraft|||When this Spacecraft enters, draw two cards, then discard a card.$Station$STATION 8+$Flying$4/4|
+Weftwalking|Edge of Eternities|86|M|{4}{U}{U}|Enchantment|||When this enchantment enters, if you cast it, shuffle your hand and graveyard into your library, then draw seven cards.$The first spell each player casts during each of their turns may be cast without paying its mana cost.|
+Alpharael, Stonechosen|Edge of Eternities|87|M|{3}{B}{B}|Legendary Creature - Human Cleric|3|3|Ward--Discard a card at random.$Void -- Whenever Alpharael attacks, if a nonland permanent left the battlefield this turn or a spell was warped this turn, defending player loses half their life, rounded up.|
+Archenemy's Charm|Edge of Eternities|88|R|{B}{B}{B}|Instant|||Choose one --$* Exile target creature or planeswalker.$* Return one or two target creature and/or planeswalker cards from your graveyard to your hand.$* Put two +1/+1 counters on target creature you control. It gains lifelink until end of turn.|
+Beamsaw Prospector|Edge of Eternities|89|C|{1}{B}|Creature - Human Artificer|2|1|When this creature dies, create a Lander token.|
+Blade of the Swarm|Edge of Eternities|90|U|{3}{B}|Creature - Insect Assassin|3|1|When this creature enters, choose one --$* Put two +1/+1 counters on this creature.$* Put target exiled card with warp on the bottom of its owner's library.|
+Chorale of the Void|Edge of Eternities|91|R|{3}{B}|Enchantment - Aura|||Enchant creature you control$Whenever enchanted creature attacks, put target creature card from defending player's graveyard onto the battlefield under your control tapped and attacking.$Void -- At the beginning of your end step, sacrifice this Aura unless a nonland permanent left the battlefield this turn or a spell was warped this turn.|
+Comet Crawler|Edge of Eternities|92|C|{2}{B}|Creature - Insect Horror|2|3|Lifelink$Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, this creature gets +2/+0 until end of turn.|
+Dark Endurance|Edge of Eternities|93|C|{1}{B}|Instant|||This spell costs {1} less to cast if it targets a blocking creature.$Target creature gets +2/+0 and gains indestructible until end of turn.|
+Decode Transmissions|Edge of Eternities|94|C|{2}{B}|Sorcery|||You draw two cards and lose 2 life.$Void -- If a nonland permanent left the battlefield this turn or a spell was warped this turn, instead you draw two cards and each opponent loses 2 life.|
+Depressurize|Edge of Eternities|95|C|{1}{B}|Instant|||Target creature gets -3/-0 until end of turn. Then if that creature's power is 0 or less, destroy it.|
+Dubious Delicacy|Edge of Eternities|96|U|{2}{B}|Artifact - Food|||Flash$When this artifact enters, up to one target creature gets -3/-3 until end of turn.${2}, {T}, Sacrifice this artifact: You gain 3 life.${2}, {T}, Sacrifice this artifact: Target opponent loses 3 life.|
+Elegy Acolyte|Edge of Eternities|97|R|{2}{B}{B}|Creature - Human Cleric|4|4|Lifelink$Whenever one or more creatures you control deal combat damage to a player, you draw a card and lose 1 life.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, create a 2/2 colorless Robot artifact creature token.|
+Embrace Oblivion|Edge of Eternities|98|C|{B}|Sorcery|||As an additional cost to cast this spell, sacrifice an artifact or creature.$Destroy target creature or Spacecraft.|
+Entropic Battlecruiser|Edge of Eternities|99|R|{3}{B}|Artifact - Spacecraft|||Station$STATION 1+$Whenever an opponent discards a card, they lose 3 life.$STATION 8+$Flying, deathtouch$Whenever this Spacecraft attacks, each opponent discards a card. Each opponent who doesn't loses 3 life.$3/10|
+Faller's Faithful|Edge of Eternities|100|U|{2}{B}|Creature - Human Wizard|3|1|When this creature enters, destroy up to one other target creature. If that creature wasn't dealt damage this turn, its controller draws two cards.|
+Fell Gravship|Edge of Eternities|101|U|{2}{B}|Artifact - Spacecraft|||When this Spacecraft enters, mill three cards, then return a creature or Spacecraft card from your graveyard to your hand.$Station$STATION 8+$Flying, lifelink$3/2|
+Gravblade Heavy|Edge of Eternities|102|C|{3}{B}|Creature - Human Soldier|3|4|As long as you control an artifact, this creature gets +1/+0 and has deathtouch.|
+Gravkill|Edge of Eternities|103|C|{3}{B}|Instant|||Exile target creature or Spacecraft.|
+Gravpack Monoist|Edge of Eternities|104|C|{2}{B}|Creature - Human Scout|2|1|Flying$When this creature dies, create a tapped 2/2 colorless Robot artifact creature token.|
+Hullcarver|Edge of Eternities|105|C|{B}|Artifact Creature - Robot Assassin|1|1|Deathtouch|
+Hylderblade|Edge of Eternities|106|U|{B}|Artifact - Equipment|||Equipped creature gets +3/+1.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, attach this Equipment to target creature you control.$Equip {4}|
+Hymn of the Faller|Edge of Eternities|107|U|{1}{B}|Sorcery|||Surveil 1, then you draw a card and lose 1 life.$Void -- If a nonland permanent left the battlefield this turn or a spell was warped this turn, draw another card.|
+Insatiable Skittermaw|Edge of Eternities|108|C|{2}{B}|Creature - Insect Horror|2|2|Menace$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature.|
+Lightless Evangel|Edge of Eternities|109|U|{1}{B}|Creature - Vampire Cleric|2|2|Whenever you sacrifice another creature or artifact, put a +1/+1 counter on this creature.|
+Monoist Circuit-Feeder|Edge of Eternities|110|U|{4}{B}{B}|Artifact Creature - Nautilus|4|4|Flying$When this creature enters, until end of turn, target creature you control gets +X/+0 and target creature an opponent controls gets -0/-X, where X is the number of artifacts you control.|
+Monoist Sentry|Edge of Eternities|111|U|{B}|Artifact Creature - Robot|4|1|Defender|
+Perigee Beckoner|Edge of Eternities|112|C|{4}{B}|Creature - Horror|4|5|When this creature enters, until end of turn, another target creature you control gets +2/+0 and gains "When this creature dies, return it to the battlefield tapped under its owner's control."$Warp {1}{B}|
+Requiem Monolith|Edge of Eternities|113|R|{2}{B}|Artifact|||{T}: Until end of turn, target creature gains "Whenever this creature is dealt damage, you draw that many cards and lose that much life." That creature's controller may have this artifact deal 1 damage to it. Activate only as a sorcery.|
+Scrounge for Eternity|Edge of Eternities|114|U|{2}{B}|Sorcery|||As an additional cost to cast this spell, sacrifice an artifact or creature.$Return target creature or Spacecraft card with mana value 5 or less from your graveyard to the battlefield. Then create a Lander token.|
Sothera, the Supervoid|Edge of Eternities|115|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.|
+Sunset Saboteur|Edge of Eternities|116|R|{1}{B}|Creature - Human Rogue|4|1|Menace$Ward--Discard a card.$Whenever this creature attacks, put a +1/+1 counter on target creature an opponent controls.|
+Susurian Dirgecraft|Edge of Eternities|117|U|{4}{B}|Artifact - Spacecraft|||When this Spacecraft enters, each opponent sacrifices a nontoken creature.$Station$STATION 7+$Flying$4/3|
+Susurian Voidborn|Edge of Eternities|118|U|{2}{B}|Creature - Vampire Soldier|2|2|Whenever this creature or another creature or artifact you control dies, target opponent loses 1 life and you gain 1 life.$Warp {B}|
+Swarm Culler|Edge of Eternities|119|C|{3}{B}|Creature - Insect Warrior|2|4|Flying$Whenever this creature becomes tapped, you may sacrifice another creature or artifact. If you do, draw a card.|
+Temporal Intervention|Edge of Eternities|120|C|{2}{B}|Sorcery|||Void -- This spell costs {2} less to cast if a nonland permanent left the battlefield this turn or a spell was warped this turn.$Target opponent reveals their hand. You choose a nonland card from it. That player discards that card.|
+Timeline Culler|Edge of Eternities|121|U|{B}{B}|Creature - Drix Warlock|2|2|Haste$You may cast this card from your graveyard using its warp ability.$Warp--{B}, Pay 2 life.|
+Tragic Trajectory|Edge of Eternities|122|U|{B}|Sorcery|||Target creature gets -2/-2 until end of turn.$Void -- That creature gets -10/-10 until end of turn instead if a nonland permanent left the battlefield this turn or a spell was warped this turn.|
+Umbral Collar Zealot|Edge of Eternities|123|U|{1}{B}|Creature - Human Cleric|3|2|Sacrifice another creature or artifact: Surveil 1.|
+Virus Beetle|Edge of Eternities|124|C|{1}{B}|Artifact Creature - Insect|1|1|When this creature enters, each opponent discards a card.|
+Voidforged Titan|Edge of Eternities|125|U|{4}{B}|Artifact Creature - Robot Warrior|5|4|Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, you draw a card and lose 1 life.|
+Vote Out|Edge of Eternities|126|U|{3}{B}|Sorcery|||Convoke$Destroy target creature.|
+Xu-Ifit, Osteoharmonist|Edge of Eternities|127|R|{1}{B}{B}|Legendary Creature - Human Wizard|2|3|{T}: Return target creature card from your graveyard to the battlefield. It's a Skeleton in addition to its other types and has no abilities. Activate only as a sorcery.|
+Zero Point Ballad|Edge of Eternities|128|R|{X}{B}|Sorcery|||Destroy all creatures with toughness X or less. You lose X life. If X is 6 or more, return a creature card put into a graveyard this way to the battlefield under your control.|
+Bombard|Edge of Eternities|129|C|{2}{R}|Instant|||Bombard deals 4 damage to target creature.|
+Cut Propulsion|Edge of Eternities|130|U|{2}{R}|Instant|||Target creature deals damage to itself equal to its power. If that creature has flying, it deals twice that much damage to itself instead.|
+Debris Field Crusher|Edge of Eternities|131|U|{4}{R}|Artifact - Spacecraft|||When this Spacecraft enters, it deals 3 damage to any target.$Station$STATION 8+$Flying${1}{R}: This Spacecraft gets +2/+0 until end of turn.$1/5|
+Devastating Onslaught|Edge of Eternities|132|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.|
+Drill Too Deep|Edge of Eternities|133|C|{1}{R}|Instant|||Choose one --$* Put five charge counters on target Spacecraft or Planet you control.$* Destroy target artifact.|
+Frontline War-Rager|Edge of Eternities|134|C|{2}{R}|Creature - Kavu Soldier|2|3|At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on this creature.|
+Full Bore|Edge of Eternities|135|U|{R}|Instant|||Target creature you control gets +3/+2 until end of turn. If that creature was cast for its warp cost, it also gains trample and haste until end of turn.|
+Galvanizing Sawship|Edge of Eternities|136|U|{5}{R}|Artifact - Spacecraft|||Station$STATION 3+$Flying, haste$6/5|
+Invasive Maneuvers|Edge of Eternities|137|U|{1}{R}|Instant|||Invasive Maneuvers deals 3 damage to target creature. It deals 5 damage instead if you control a Spacecraft.|
+Kav Landseeker|Edge of Eternities|138|C|{3}{R}|Creature - Kavu Soldier|4|3|Menace$When this creature enters, create a Lander token. At the beginning of the end step on your next turn, sacrifice that token.|
+Kavaron Harrier|Edge of Eternities|139|U|{R}|Artifact Creature - Robot Soldier|2|1|Whenever this creature attacks, you may pay {2}. If you do, create a 2/2 colorless Robot artifact creature token that's tapped and attacking. Sacrifice that token at end of combat.|
+Kavaron Skywarden|Edge of Eternities|140|C|{4}{R}|Creature - Kavu Soldier|4|5|Reach$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature.|
+Kavaron Turbodrone|Edge of Eternities|141|C|{2}{R}|Artifact Creature - Robot Scout|2|3|{T}: Target creature you control gets +1/+1 and gains haste until end of turn. Activate only as a sorcery.|
+Lithobraking|Edge of Eternities|142|U|{2}{R}|Instant|||Create a Lander token. Then you may sacrifice an artifact. When you do, Lithobraking deals 2 damage to each creature.|
+Melded Moxite|Edge of Eternities|143|C|{1}{R}|Artifact|||When this artifact enters, you may discard a card. If you do, draw two cards.${3}, Sacrifice this artifact: Create a tapped 2/2 colorless Robot artifact creature token.|
+Memorial Team Leader|Edge of Eternities|144|U|{3}{R}|Creature - Kavu Soldier|4|3|During your turn, other creatures you control get +1/+0.$Warp {1}{R}|
+Memorial Vault|Edge of Eternities|145|R|{3}{R}|Artifact|||{T}, Sacrifice another artifact: Exile the top X cards of your library, where X is one plus the mana value of the sacrificed artifact. You may play those cards this turn.|
+Molecular Modifier|Edge of Eternities|146|U|{2}{R}|Creature - Kavu Artificer|2|2|At the beginning of combat on your turn, target creature you control gets +1/+0 and gains first strike until end of turn.|
+Nebula Dragon|Edge of Eternities|147|C|{6}{R}|Creature - Dragon|4|4|Flying$When this creature enters, it deals 3 damage to any target.|
+Nova Hellkite|Edge of Eternities|148|R|{3}{R}{R}|Creature - Dragon|4|5|Flying, haste$When this creature enters, it deals 1 damage to target creature an opponent controls.$Warp {2}{R}|
+Orbital Plunge|Edge of Eternities|149|C|{3}{R}|Sorcery|||Orbital Plunge deals 6 damage to target creature. If excess damage was dealt this way, create a Lander token.|
+Oreplate Pangolin|Edge of Eternities|150|C|{1}{R}|Artifact Creature - Robot Pangolin|2|2|Whenever another artifact you control enters, you may pay {1}. If you do, put a +1/+1 counter on this creature.|
+Pain for All|Edge of Eternities|151|R|{2}{R}|Enchantment - Aura|||Enchant creature you control$When this Aura enters, enchanted creature deals damage equal to its power to any other target.$Whenever enchanted creature is dealt damage, it deals that much damage to each opponent.|
+Plasma Bolt|Edge of Eternities|152|C|{R}|Sorcery|||Plasma Bolt deals 2 damage to any target.$Void -- Plasma Bolt deals 3 damage instead if a nonland permanent left the battlefield this turn or a spell was warped this turn.|
+Possibility Technician|Edge of Eternities|153|R|{2}{R}|Creature - Kavu Artificer|3|3|Whenever this creature or another Kavu you control enters, exile the top card of your library. For as long as that card remains exiled, you may play it if you control a Kavu.$Warp {1}{R}|
+Red Tiger Mechan|Edge of Eternities|154|C|{3}{R}|Artifact Creature - Robot Cat|3|3|Haste$Warp {1}{R}|
+Remnant Elemental|Edge of Eternities|155|U|{1}{R}|Creature - Elemental|0|4|Reach$Landfall -- Whenever a land you control enters, this creature gets +2/+0 until end of turn.|
+Rig for War|Edge of Eternities|156|C|{1}{R}|Instant|||Target creature gets +3/+0 and gains first strike and reach until end of turn.|
+Roving Actuator|Edge of Eternities|157|U|{3}{R}|Artifact Creature - Robot|3|4|Void -- When this creature enters, if a nonland permanent left the battlefield this turn or a spell was warped this turn, exile up to one target instant or sorcery card with mana value 2 or less from your graveyard. Copy it. You may cast the copy without paying its mana cost.|
+Ruinous Rampage|Edge of Eternities|158|U|{1}{R}{R}|Sorcery|||Choose one --$* Ruinous Rampage deals 3 damage to each opponent.$* Exile all artifacts with mana value 3 or less.|
+Rust Harvester|Edge of Eternities|159|R|{R}|Artifact Creature - Robot|1|1|Menace${2}, {T}, Exile an artifact card from your graveyard: Put a +1/+1 counter on this creature, then it deals damage equal to its power to any target.|
+Slagdrill Scrapper|Edge of Eternities|160|C|{R}|Artifact Creature - Robot Scout|1|2|{2}, {T}, Sacrifice another artifact or land: Draw a card.|
+Systems Override|Edge of Eternities|161|U|{2}{R}|Sorcery|||Gain control of target artifact or creature until end of turn. Untap that permanent. It gains haste until end of turn. If it's a Spacecraft, put ten charge counters on it. If you do, remove ten charge counters from it at the beginning of the next end step.|
+Tannuk, Steadfast Second|Edge of Eternities|162|M|{2}{R}{R}|Legendary Creature - Kavu Pilot|3|5|Other creatures you control have haste.$Artifact cards and red creature cards in your hand have warp {2}{R}.|
+Terminal Velocity|Edge of Eternities|163|R|{4}{R}{R}|Sorcery|||You may put an artifact or creature card from your hand onto the battlefield. That permanent gains haste, "When this permanent leaves the battlefield, it deals damage equal to its mana value to each creature," and "At the beginning of your end step, sacrifice this permanent."|
+Terrapact Intimidator|Edge of Eternities|164|U|{1}{R}|Creature - Kavu Scout|2|1|When this creature enters, target opponent may have you create two Lander tokens. If they don't, put two +1/+1 counters on this creature.|
+Territorial Bruntar|Edge of Eternities|165|U|{4}{R}{R}|Creature - Beast|6|6|Reach$Landfall -- Whenever a land you control enters, exile cards from the top of your library until you exile a nonland card. You may cast that card this turn.|
+Vaultguard Trooper|Edge of Eternities|166|U|{4}{R}|Creature - Kavu Soldier|5|5|At the beginning of your end step, if you control two or more tapped creatures, you may discard your hand. If you do, draw two cards.|
+Warmaker Gunship|Edge of Eternities|167|R|{2}{R}|Artifact - Spacecraft|||When this Spacecraft enters, it deals damage equal to the number of artifacts you control to target creature an opponent controls.$Station$STATION 6+$Flying$4/3|
+Weapons Manufacturing|Edge of Eternities|168|R|{1}{R}|Enchantment|||Whenever a nontoken artifact you control enters, create a colorless artifact token named Munitions with "When this token leaves the battlefield, it deals 2 damage to any target."|
+Weftstalker Ardent|Edge of Eternities|169|U|{2}{R}|Creature - Drix Artificer|2|3|Whenever another creature or artifact you control enters, this creature deals 1 damage to each opponent.$Warp {R}|
+Zookeeper Mechan|Edge of Eternities|170|C|{1}{R}|Artifact Creature - Robot|1|3|{T}: Add {R}.${6}{R}: Target creature you control gets +4/+0 until end of turn. Activate only as a sorceery.|
+Atmospheric Greenhouse|Edge of Eternities|171|U|{4}{G}|Artifact - Spacecraft|||When this Spacecraft enters, put a +1/+1 counter on each creature you control.$Station$STATION 8+$Flying, trample$5/4|
+Bioengineered Future|Edge of Eternities|172|R|{1}{G}{G}|Enchantment|||When this enchantment enters, create a Lander token.$Each creature you control enters with an additional +1/+1 counter on it for each land that entered the battlefield under your control this turn.|
+Biosynthic Burst|Edge of Eternities|173|C|{1}{G}|Instant|||Put a +1/+1 counter on target creature you control. It gains reach, trample, and indestructible until end of turn. Untap it.|
+Blooming Stinger|Edge of Eternities|174|C|{1}{G}|Creature - Plant Scorpion|2|2|Deathtouch$When this creature enters, another target creature you control gains deathtouch until end of turn.|
+Broodguard Elite|Edge of Eternities|175|U|{X}{G}{G}|Creature - Insect Knight|0|0|This creature enters with X +1/+1 counters on it.$When this creature leaves the battlefield, put its counters on target creature you control.$Warp {X}{G}|
+Close Encounter|Edge of Eternities|176|U|{1}{G}|Instant|||As an additional cost to cast this spell, choose a creature you control or a warped creature card you own in exile.$Close Encounter deals damage equal to the power of the chosen creature or card to target creature.|
+Diplomatic Relations|Edge of Eternities|177|C|{2}{G}|Instant|||Target creature gets +1/+0 and gains vigilance until end of turn. It deals damage equal to its power to target creature an opponent controls.|
+Drix Fatemaker|Edge of Eternities|178|C|{3}{G}|Creature - Drix Wizard|3|2|When this creature enters, put a +1/+1 counter on target creature.$Each creature you control with a +1/+1 counter on it has trample.$Warp {1}{G}|
+Edge Rover|Edge of Eternities|179|U|{G}|Artifact Creature - Robot Scout|2|2|Reach$When this creature dies, each player creates a Lander token.|
+Eumidian Terrabotanist|Edge of Eternities|180|U|{1}{G}|Creature - Insect Druid|2|3|Landfall -- Whenever a land you control enters, you gain 1 life.|
+Eusocial Engineering|Edge of Eternities|181|U|{3}{G}{G}|Enchantment|||Landfall -- Whenever a land you control enters, create a 2/2 colorless Robot artifact creature token.$Warp {1}{G}|
+Famished Worldsire|Edge of Eternities|182|M|{5}{G}{G}{G}|Creature - Leviathan|0|0|Ward {3}$Devour land 3$When this creature enters, look at the top X cards of your library, where X is this creature's power. Put any number of land cards from among them onto the battlefield tapped, then shuffle.|
+Frenzied Baloth|Edge of Eternities|183|R|{G}{G}|Creature - Beast|3|2|This spell can't be countered.$Trample, haste$Creature spells you control can't be countered.$Combat damage can't be prevented.|
+Fungal Colossus|Edge of Eternities|184|C|{6}{G}|Creature - Fungus Beast|5|5|This spell costs {X} less to cast, where X is the number of differently named lands you control.|
+Galactic Wayfarer|Edge of Eternities|185|C|{2}{G}|Creature - Human Scout|3|3|When this creature enters, create a Lander token.|
+Gene Pollinator|Edge of Eternities|186|C|{G}|Artifact Creature - Robot Insect|1|2|{T}, Tap an untapped permanent you control: Add one mana of any color.|
+Germinating Wurm|Edge of Eternities|187|C|{4}{G}|Creature - Plant Wurm|5|5|When this creature enters, you gain 2 life.$Warp {1}{G}|
+Glacier Godmaw|Edge of Eternities|188|U|{5}{G}{G}|Creature - Leviathan|6|6|Trample$When this creature enters, create a Lander token.$Landfall -- Whenever a land you control enters, creatures you control get +1/+1 and gain vigilance and haste until end of turn.|
Harmonious Grovestrider|Edge of Eternities|189|U|{3}{G}{G}|Creature - Beast|*|*|Ward {2}$This creature's power and toughness are each equal to the number of lands you control.|
-Alpharael, Dreaming Acolyte|Edge of Eternities|212|U|{1}{U}{B}|Legendary Creature - Human Cleric|2|3|When Alpharael enters, draw two cards. Then discard two cards unless you discard an artifact card. During your turn, Alpharael has deathtouch.|
+Hemosymbic Mite|Edge of Eternities|190|U|{G}|Creature - Mite|1|1|Whenever this creature becomes tapped, another target creature you control gets +X/+X until end of turn, where X is this creature's power.|
+Icecave Crasher|Edge of Eternities|191|C|{3}{G}|Creature - Beast|4|4|Trample$Landfall -- Whenever a land you control enters, this creature gets +1/+0 until end of turn.|
+Icetill Explorer|Edge of Eternities|192|R|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.|
+Intrepid Tenderfoot|Edge of Eternities|193|C|{1}{G}|Creature - Insect Citizen|2|2|{3}: Put a +1/+1 counter on this creature. Activate only as a sorcery.|
+Larval Scoutlander|Edge of Eternities|194|U|{2}{G}|Artifact - Spacecraft|||When this Spacecraft enters, you may sacrifice a land or a Lander. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.$Station$STATION 7+$Flying$3/3|
+Lashwhip Predator|Edge of Eternities|195|U|{4}{G}{G}|Creature - Plant Beast|5|7|This spell costs {2} less to cast if your opponents control three or more creatures.$Reach|
+Loading Zone|Edge of Eternities|196|R|{3}{G}|Enchantment|||If one or more counters would be put on a creature, Spacecraft, or Planet you control, twice that many of each of those kinds of counters are put on it instead.$Warp {G}|
+Meltstrider Eulogist|Edge of Eternities|197|U|{2}{G}|Creature - Insect Soldier|3|3|Whenever a creature you control with a +1/+1 counter on it dies, draw a card.|
+Meltstrider's Gear|Edge of Eternities|198|C|{G}|Artifact - Equipment|||When this Equipment enters, attach it to target creature you control.$Equipped creature gets +2/+1 and has reach.$Equip {5}|
+Meltstrider's Resolve|Edge of Eternities|199|U|{G}|Enchantment - Aura|||Enchant creature you control$When this Aura enters, enchanted creature fights up to one target creature an opponent controls.$Enchanted creature gets +0/+2 and can't be blocked by more than one creature.|
+Mightform Harmonizer|Edge of Eternities|200|R|{2}{G}{G}|Creature - Insect Druid|4|4|Landfall -- Whenever a land you control enters, double the power of target creature you control until end of turn.$Warp {2}{G}|
+Ouroboroid|Edge of Eternities|201|M|{2}{G}{G}|Creature - Plant Wurm|1|3|At the beginning of combat on your turn, put X +1/+1 counters on each creature you control, where X is this creature's power.|
+Pull Through the Weft|Edge of Eternities|202|U|{3}{G}{G}|Sorcery|||Return up to two target nonland permanent cards from your graveyard to your hand, then return up to two target land cards from your graveyard to the battlefield tapped.|
+Sami's Curiosity|Edge of Eternities|203|C|{G}|Sorcery|||You gain 2 life. Create a Lander token.|
+Seedship Agrarian|Edge of Eternities|204|U|{3}{G}|Creature - Insect Scientist|3|3|Whenever this creature becomes tapped, create a Lander token.$Landfall -- Whenever a land you control enters, put a +1/+1 counter on this creature.|
+Seedship Impact|Edge of Eternities|205|U|{1}{G}|Instant|||Destroy target artifact or enchantment. If its mana value was 2 or less, create a Lander token.|
+Shattered Wings|Edge of Eternities|206|C|{2}{G}|Sorcery|||Destroy target artifact, enchantment, or creature with flying. Surveil 1.|
+Skystinger|Edge of Eternities|207|C|{2}{G}|Creature - Insect Warrior|3|3|Reach$Whenever this creature blocks a creature with flying, this creature gets +5/+0 until end of turn.|
+Sledge-Class Seedship|Edge of Eternities|208|R|{2}{G}|Artifact - Spacecraft|||Station$STATION 7+$Flying$Whenever this Spacecraft attacks, you may put a creature from your hand onto the battlefield.$4/5|
+Tapestry Warden|Edge of Eternities|209|U|{3}{G}|Artifact Creature - Robot Soldier|3|4|Vigilance$Each creature you control with toughness greater than its power assigns combat damage equal to its toughness rather than its power.$Each creature you control with toughness greater than its power stations permanents using its toughness rather than its power.|
+Terrasymbiosis|Edge of Eternities|210|R|{2}{G}|Enchantment|||Whenever you put one or more +1/+1 counters on a creature you control, you may draw that many cards. Do this only once each turn.|
+Thawbringer|Edge of Eternities|211|C|{2}{G}|Creature - Insect Scout|4|2|When this creature enters or dies, surveil 1.|
+Alpharael, Dreaming Acolyte|Edge of Eternities|212|U|{1}{U}{B}|Legendary Creature - Human Cleric|2|3|When Alpharael enters, draw two cards. Then discard two cards unless you discard an artifact card.$During your turn, Alpharael has deathtouch.|
+Biomechan Engineer|Edge of Eternities|213|U|{G}{U}|Creature - Insect Artificer|2|2|When this creature enters, create a Lander token.${8}: Draw two cards and create a 2/2 colorless Robot artifact creature token.|
+Biotech Specialist|Edge of Eternities|214|R|{R}{G}|Creature - Insect Scientist|1|3|When this creature enters, create a Lander token.$Whenever you sacrifice an artifact, this creature deals 2 damage to target opponent.|
+Cosmogoyf|Edge of Eternities|215|R|{B}{G}|Creature - Elemental Lhurgoyf|*|1+*|This creature's power is equal to the number of cards you own in exile and its toughness is equal to that number plus 1.|
+Dyadrine, Synthesis Amalgam|Edge of Eternities|216|R|{X}{G}{W}|Legendary Artifact Creature - Construct|0|1|Trample$Dyadrine enters with a number of +1/+1 counters on it equal to the amount of mana spent to cast it.$Whenever you attack, you may remove a +1/+1 counter from each of two creatures you control. If you do, draw a card and create a 2/2 colorless Robot artifact creature token.|
+Genemorph Imago|Edge of Eternities|217|R|{G}{U}|Creature - Insect Druid|1|3|Flying$Landfall -- Whenever a land you control enters, target creature has base power and toughness 3/3 until end of turn. If you control six or more lands, that creature has base power and toughness 6/6 until end of turn instead.|
+Haliya, Ascendant Cadet|Edge of Eternities|218|U|{2}{G}{W}{W}|Legendary Creature - Human Soldier|3|3|Whenever Haliya enters or attacks, put a +1/+1 counter on target creature you control.$Whenever one or more creatures you control with +1/+1 counters on them deal combat damage to a player, draw a card.|
+Infinite Guideline Station|Edge of Eternities|219|R|{W}{U}{B}{R}{G}|Legendary Artifact - Spacecraft|||When Infinite Guideline Station enters, create a tapped 2/2 colorless Robot artifact creature token for each multicolored permanent you control.$Station$STATION 12+$Flying$Whenever Infinite Guideline Station attacks, draw a card for each multicolored permanent you control.$7/15|
+Interceptor Mechan|Edge of Eternities|220|U|{2}{B}{R}|Artifact Creature - Robot|2|2|Flying$When this creature enters, return target artifact or creature card from your graveyard to your hand.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, put a +1/+1 counter on this creature.|
+Mm'menon, Uthros Exile|Edge of Eternities|221|U|{1}{U}{R}|Legendary Creature - Jellyfish Advisor|1|3|Flying$Whenever an artifact you control enters, put a +1/+1 counter on target creature.|
+Mutinous Massacre|Edge of Eternities|222|R|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.|
+Pinnacle Emissary|Edge of Eternities|223|R|{1}{U}{R}|Artifact Creature - Robot|3|3|Whenever you cast an artifact spell, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."$Warp {U/R}|
+Ragost, Deft Gastronaut|Edge of Eternities|224|R|{R}{W}|Legendary Creature - Lobster Citizen|2|2|Artifacts you control are Foods in addition to their other types and have "{2}, {T}, Sacrifice this artifact: You gain 3 life."${1}, {T}, Sacrifice a Food: Ragost deals 3 damage to each opponent.$At the beginning of each end step, if you gained life this turn, untap Ragost.|
Sami, Ship's Engineer|Edge of Eternities|225|U|{2}{R}{W}|Legendary Creature - Human Artificer|2|4|At the beginning of your end step, if you control two or more tapped creatures, create a tapped 2/2 colorless Robot artifact creature token.|
-Tannuk, Memorial Ensign|Edge of Eternities|233|U|{1}{R}{G}|Legendary Creature - Kavu Pilot|2|4|Landfall - Whenever a land you control enters, Tannuk deals 1 damage to each opponent. If this is the second time this ability has resolved this turn, draw a card.|
+Sami, Wildcat Captain|Edge of Eternities|226|M|{4}{R}{W}|Legendary Creature - Human Artificer Rogue|4|4|Double strike, vigilance$Spells you cast have affinity for artifacts.|
+Seedship Broodtender|Edge of Eternities|227|U|{B}{G}|Creature - Insect Citizen|2|3|When this creature enters, mill three cards.${3}{B}{G}, Sacrifice this creature: Return target creature or Spacecraft card from your graveyard to the battlefield. Activate only as a sorcery.|
+Singularity Rupture|Edge of Eternities|228|R|{3}{U}{B}{B}|Sorcery|||Destroy all creatures, then any number of target players each mill half their library, rounded down.|
+Space-Time Anomaly|Edge of Eternities|229|R|{2}{W}{U}|Sorcery|||Target player mills cards equal to your life total.|
+Station Monitor|Edge of Eternities|230|U|{W}{U}|Creature - Lizard Artificer|2|2|Whenever you cast your second spell each turn, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."|
+Syr Vondam, Sunstar Exemplar|Edge of Eternities|231|R|{W}{B}|Legendary Creature - Human Knight|2|2|Vigilance, menace$Whenever another creature you control dies or is put into exile, put a +1/+1 counter on Syr Vondam and you gain 1 life.$When Syr Vondam dies or is put into exile while its power is 4 or greater, destroy up to one target nonland permanent.|
+Syr Vondam, the Lucent|Edge of Eternities|232|U|{2}{W}{B}{B}|Legendary Creature - Human Knight|4|4|Deathtouch, lifelink$Whenever Syr Vondam enters or attacks, other creatures you control get +1/+0 and gain deathtouch until end of turn.|
+Tannuk, Memorial Ensign|Edge of Eternities|233|U|{1}{R}{G}|Legendary Creature - Kavu Pilot|2|4|Landfall -- Whenever a land you control enters, Tannuk deals 1 damage to each opponent. If this is the second time this ability has resolved this turn, draw a card.|
+All-Fates Scroll|Edge of Eternities|234|U|{3}|Artifact|||{T}: Add one mana of any color.${7}, {T}, Sacrifice this artifact: Draw X cards, where X is the number of differently named lands you control.|
+Bygone Colossus|Edge of Eternities|235|U|{9}|Artifact Creature - Robot Giant|9|9|Warp {3}|
+Chrome Companion|Edge of Eternities|236|C|{2}|Artifact Creature - Dog|2|1|Whenever this creature becomes tapped, you gain 1 life.${2}, {T}: Put target card from a graveyard on the bottom of its owner's library.|
+Dauntless Scrapbot|Edge of Eternities|237|U|{3}|Artifact Creature - Robot|3|1|When this creature enters, exile each opponent's graveyard. Create a Lander token.|
+Dawnsire, Sunstar Dreadnought|Edge of Eternities|238|M|{5}|Legendary Artifact - Spacecraft|||Station$STATION 10+$Whenever you attack, Dawnsire deals 100 damage to up to one target creature or planeswalker.$STATION 20+$Flying$20/20|
+The Dominion Bracelet|Edge of Eternities|239|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}|
+The Endstone|Edge of Eternities|240|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.|
+The Eternity Elevator|Edge of Eternities|241|R|{5}|Legendary Artifact - Spacecraft|||{T}: Add {C}{C}{C}.$Station$STATION 20+${T}: Add X mana of any one color, where X is the number of charge counters on The Eternity Elevator.|
+Extinguisher Battleship|Edge of Eternities|242|R|{8}|Artifact - Spacecraft|||When this Spacecraft enters, destroy target noncreature permanent. Then this Spacecraft deals 4 damage to each creature.$Station$STATION 5+$Flying, trample$10/10|
+Nutrient Block|Edge of Eternities|243|C|{1}|Artifact - Food|||Indestructible${2}, {T}, Sacrifice this artifact: You gain 3 life.$When this artifact is put into a graveyard from the battlefield, draw a card.|
+Pinnacle Kill-Ship|Edge of Eternities|244|C|{7}|Artifact - Spacecraft|||When this Spacecraft enters, it deals 10 damage to up to one target creature.$Station$STATION 7+$Flying$7/7|
+Survey Mechan|Edge of Eternities|245|U|{4}|Artifact Creature - Robot|1|3|Flying$Hexproof${10}, Sacrifice this creature: It deals 3 damage to any target. Target player draws three cards and gains 3 life. This ability costs {X} less to activate, where X is the number of differently named lands you control.|
+Thaumaton Torpedo|Edge of Eternities|246|C|{1}|Artifact|||{6}, {T}, Sacrifice this artifact: Destroy target nonland permanent. This ability costs {3} less to activate if you attacked with a Spacecraft this turn.|
+Thrumming Hivepool|Edge of Eternities|247|R|{6}|Artifact|||Affinity for Slivers$Slivers you control have double strike and haste.$At the beginning of your upkeep, create two 1/1 colorless Sliver creature tokens.|
+Virulent Silencer|Edge of Eternities|248|U|{3}|Artifact Creature - Robot Assassin|2|3|Whenever a nontoken artifact creature you control deals combat damage to a player, that player gets two poison counters.|
+Wurmwall Sweeper|Edge of Eternities|249|C|{2}|Artifact - Spacecraft|||When this Spacecraft enters, surveil 2.$Station$STATION 4+$Flying$2/2|
+Adagia, Windswept Bastion|Edge of Eternities|250|M||Land - Planet|||This land enters tapped.${T}: Add {W}.$Station$STATION 12+${3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.|
Breeding Pool|Edge of Eternities|251|R||Land - Forest Island|||({T}: Add {G} or {U}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Command Bridge|Edge of Eternities|252|C||Land|||This land enters tapped.$When this land enters, sacrifice it unless you tap an untapped permanent you control.${T}: Add one mana of any color.|
+Evendo, Waking Haven|Edge of Eternities|253|M||Land - Planet|||This land enters tapped.${T}: Add {G}.$Station$STATION 12+${G}, {T}: Add {G} for each creature you control.|
Godless Shrine|Edge of Eternities|254|R||Land - Plains Swamp|||({T}: Add {W} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Kavaron, Memorial World|Edge of Eternities|255|M||Land - Planet|||This land enters tapped.${T}: Add {R}.$Station$STATION 12+${1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn.|
Sacred Foundry|Edge of Eternities|256|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Secluded Starforge|Edge of Eternities|257|R||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.|
Stomping Ground|Edge of Eternities|258|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Susur Secundi, Void Altar|Edge of Eternities|259|M||Land - Planet|||This land enters tapped.${T}: Add {B}.$Station$STATION 12+${1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery.|
+Uthros, Titanic Godcore|Edge of Eternities|260|M||Land - Planet|||This land enters tapped.${T}: Add {U}.$Station$STATION 12+${U}, {T}: Add {U} for each artifact you control.|
Watery Grave|Edge of Eternities|261|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
Plains|Edge of Eternities|262|C||Basic Land - Plains|||({T}: Add {W}.)|
Island|Edge of Eternities|263|C||Basic Land - Island|||({T}: Add {U}.)|
Swamp|Edge of Eternities|264|C||Basic Land - Swamp|||({T}: Add {B}.)|
Mountain|Edge of Eternities|265|C||Basic Land - Mountain|||({T}: Add {R}.)|
Forest|Edge of Eternities|266|C||Basic Land - Forest|||({T}: Add {G}.)|
-Tezzeret, Cruel Captain|Edge of Eternities|287|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it in your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."|
+Plains|Edge of Eternities|267|C||Basic Land - Plains|||({T}: Add {W}.)|
+Plains|Edge of Eternities|268|C||Basic Land - Plains|||({T}: Add {W}.)|
+Island|Edge of Eternities|269|C||Basic Land - Island|||({T}: Add {U}.)|
+Island|Edge of Eternities|270|C||Basic Land - Island|||({T}: Add {U}.)|
+Swamp|Edge of Eternities|271|C||Basic Land - Swamp|||({T}: Add {B}.)|
+Swamp|Edge of Eternities|272|C||Basic Land - Swamp|||({T}: Add {B}.)|
+Mountain|Edge of Eternities|273|C||Basic Land - Mountain|||({T}: Add {R}.)|
+Mountain|Edge of Eternities|274|C||Basic Land - Mountain|||({T}: Add {R}.)|
+Forest|Edge of Eternities|275|C||Basic Land - Forest|||({T}: Add {G}.)|
+Forest|Edge of Eternities|276|C||Basic Land - Forest|||({T}: Add {G}.)|
+Adagia, Windswept Bastion|Edge of Eternities|277|M||Land - Planet|||This land enters tapped.${T}: Add {W}.$Station$STATION 12+${3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.|
+Breeding Pool|Edge of Eternities|278|R||Land - Forest Island|||({T}: Add {G} or {U}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Evendo, Waking Haven|Edge of Eternities|279|M||Land - Planet|||This land enters tapped.${T}: Add {G}.$Station$STATION 12+${G}, {T}: Add {G} for each creature you control.|
+Godless Shrine|Edge of Eternities|280|R||Land - Plains Swamp|||({T}: Add {W} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Kavaron, Memorial World|Edge of Eternities|281|M||Land - Planet|||This land enters tapped.${T}: Add {R}.$Station$STATION 12+${1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn.|
+Sacred Foundry|Edge of Eternities|282|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Stomping Ground|Edge of Eternities|283|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Susur Secundi, Void Altar|Edge of Eternities|284|M||Land - Planet|||This land enters tapped.${T}: Add {B}.$Station$STATION 12+${1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery.|
+Uthros, Titanic Godcore|Edge of Eternities|285|M||Land - Planet|||This land enters tapped.${T}: Add {U}.$Station$STATION 12+${U}, {T}: Add {U} for each artifact you control.|
+Watery Grave|Edge of Eternities|286|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Tezzeret, Cruel Captain|Edge of Eternities|287|M|{3}|Legendary Planeswalker - Tezzeret|4|Whenever an artifact you control enters, put a loyalty counter on Tezzeret.$0: Untap target artifact or creature. If it's an artifact creature, put a +1/+1 counter on it.$-3: Search your library for an artifact card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$-7: You get an emblem with "At the beginning of combat on your turn, put three +1/+1 counters on target artifact you control. If it's not a creature, it becomes a 0/0 Robot artifact creature."|
+Astelli Reclaimer|Edge of Eternities|288|R|{3}{W}{W}|Creature - Angel Warrior|5|4|Flying$When this creature enters, return target noncreature, nonland permanent card with mana value X or less from your graveyard to the battlefield, where X is the amount of mana spent to cast this creature.$Warp {2}{W}|
+Haliya, Guided by Light|Edge of Eternities|289|R|{2}{W}|Legendary Creature - Human Soldier|3|3|Whenever Haliya or another creature or artifact you control enters, you gain 1 life.$At the beginning of your end step, draw a card if you've gained 3 or more life this turn.$Warp {W}|
+Mm'menon, the Right Hand|Edge of Eternities|290|R|{3}{U}{U}|Legendary Creature - Jellyfish Advisor|3|4|Flying$You may look at the top card of your library any time.$You may cast artifact spells from the top of your library.$Artifacts you control have "{T}: Add {U}. Spend this mana only to cast a spell from anywhere other than your hand."|
+Starwinder|Edge of Eternities|291|R|{5}{U}{U}|Creature - Leviathan|7|7|Whenever a creature you control deals combat damage to a player, you may draw that many cards.$Warp {2}{U}{U}|
+Alpharael, Stonechosen|Edge of Eternities|292|M|{3}{B}{B}|Legendary Creature - Human Cleric|3|3|Ward--Discard a card at random.$Void -- Whenever Alpharael attacks, if a nonland permanent left the battlefield this turn or a spell was warped this turn, defending player loses half their life, rounded up.|
+Elegy Acolyte|Edge of Eternities|293|R|{2}{B}{B}|Creature - Human Cleric|4|4|Lifelink$Whenever one or more creatures you control deal combat damage to a player, you draw a card and lose 1 life.$Void -- At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, create a 2/2 colorless Robot artifact creature token.|
+Xu-Ifit, Osteoharmonist|Edge of Eternities|294|R|{1}{B}{B}|Legendary Creature - Human Wizard|2|3|{T}: Return target creature card from your graveyard to the battlefield. It's a Skeleton in addition to its other types and has no abilities. Activate only as a sorcery.|
+Possibility Technician|Edge of Eternities|295|R|{2}{R}|Creature - Kavu Artificer|3|3|Whenever this creature or another Kavu you control enters, exile the top card of your library. For as long as that card remains exiled, you may play it if you control a Kavu.$Warp {1}{R}|
+Tannuk, Steadfast Second|Edge of Eternities|296|M|{2}{R}{R}|Legendary Creature - Kavu Pilot|3|5|Other creatures you control have haste.$Artifact cards and red creature cards in your hand have warp {2}{R}.|
+Mightform Harmonizer|Edge of Eternities|297|R|{2}{G}{G}|Creature - Insect Druid|4|4|Landfall -- Whenever a land you control enters, double the power of target creature you control until end of turn.$Warp {2}{G}|
+Dyadrine, Synthesis Amalgam|Edge of Eternities|298|R|{X}{G}{W}|Legendary Artifact Creature - Construct|0|1|Trample$Dyadrine enters with a number of +1/+1 counters on it equal to the amount of mana spent to cast it.$Whenever you attack, you may remove a +1/+1 counter from each of two creatures you control. If you do, draw a card and create a 2/2 colorless Robot artifact creature token.|
+Genemorph Imago|Edge of Eternities|299|R|{G}{U}|Creature - Insect Druid|1|3|Flying$Landfall -- Whenever a land you control enters, target creature has base power and toughness 3/3 until end of turn. If you control six or more lands, that creature has base power and toughness 6/6 until end of turn instead.|
+Ragost, Deft Gastronaut|Edge of Eternities|300|R|{R}{W}|Legendary Creature - Lobster Citizen|2|2|Artifacts you control are Foods in addition to their other types and have "{2}, {T}, Sacrifice this artifact: You gain 3 life."${1}, {T}, Sacrifice a Food: Ragost deals 3 damage to each opponent.$At the beginning of each end step, if you gained life this turn, untap Ragost.|
+Sami, Wildcat Captain|Edge of Eternities|301|M|{4}{R}{W}|Legendary Creature - Human Artificer Rogue|4|4|Double strike, vigilance$Spells you cast have affinity for artifacts.|
+Syr Vondam, Sunstar Exemplar|Edge of Eternities|302|R|{W}{B}|Legendary Creature - Human Knight|2|2|Vigilance, menace$Whenever another creature you control dies or is put into exile, put a +1/+1 counter on Syr Vondam and you gain 1 life.$When Syr Vondam dies or is put into exile while its power is 4 or greater, destroy up to one target nonland permanent.|
+Beyond the Quiet|Edge of Eternities|303|R|{3}{W}{W}|Sorcery|||Exile all creatures and Spacecraft.|
+Cosmogrand Zenith|Edge of Eternities|304|M|{2}{W}|Creature - Human Soldier|2|4|Whenever you cast your second spell each turn, choose one --$* Create two 1/1 white Human Soldier creature tokens.$* Put a +1/+1 counter on each creature you control.|
+Quantum Riddler|Edge of Eternities|305|M|{3}{U}{U}|Creature - Sphinx|4|6|Flying$When this creature enters, draw a card.$As long as you have one or fewer cards in hand, if you would draw one or more cards, you draw that many cards plus one instead.$Warp {1}{U}|
+Starwinder|Edge of Eternities|306|R|{5}{U}{U}|Creature - Leviathan|7|7|Whenever a creature you control deals combat damage to a player, you may draw that many cards.$Warp {2}{U}{U}|
+Archenemy's Charm|Edge of Eternities|307|R|{B}{B}{B}|Instant|||Choose one --$* Exile target creature or planeswalker.$* Return one or two target creature and/or planeswalker cards from your graveyard to your hand.$* Put two +1/+1 counters on target creature you control. It gains lifelink until end of turn.|
+Devastating Onslaught|Edge of Eternities|308|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.|
+Nova Hellkite|Edge of Eternities|309|R|{3}{R}{R}|Creature - Dragon|4|5|Flying, haste$When this creature enters, it deals 1 damage to target creature an opponent controls.$Warp {2}{R}|
+Rust Harvester|Edge of Eternities|310|R|{R}|Artifact Creature - Robot|1|1|Menace${2}, {T}, Exile an artifact card from your graveyard: Put a +1/+1 counter on this creature, then it deals damage equal to its power to any target.|
+Weapons Manufacturing|Edge of Eternities|311|R|{1}{R}|Enchantment|||Whenever a nontoken artifact you control enters, create a colorless artifact token named Munitions with "When this token leaves the battlefield, it deals 2 damage to any target."|
+Terrasymbiosis|Edge of Eternities|312|R|{2}{G}|Enchantment|||Whenever you put one or more +1/+1 counters on a creature you control, you may draw that many cards. Do this only once each turn.|
+Cosmogoyf|Edge of Eternities|313|R|{B}{G}|Creature - Elemental Lhurgoyf|*|1+*|This creature's power is equal to the number of cards you own in exile and its toughness is equal to that number plus 1.|
+Mutinous Massacre|Edge of Eternities|314|R|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.|
+Space-Time Anomaly|Edge of Eternities|315|R|{2}{W}{U}|Sorcery|||Target player mills cards equal to your life total.|
+Secluded Starforge|Edge of Eternities|316|R||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.|
+Anticausal Vestige|Edge of Eternities|317|R|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}|
+Exalted Sunborn|Edge of Eternities|318|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}|
+Hardlight Containment|Edge of Eternities|319|R|{W}|Enchantment - Aura|||Enchant artifact you control$When this Aura enters, exile target creature an opponent controls until this Aura leaves the battlefield.$Enchanted permanent has ward {1}.|
+Lightstall Inquisitor|Edge of Eternities|320|R|{W}|Creature - Angel Wizard|2|1|Vigilance$When this creature enters, each opponent exiles a card from their hand and may play that card for as long as it remains exiled. Each spell cast this way costs {1} more to cast. Each land played this way enters tapped.|
+Lumen-Class Frigate|Edge of Eternities|321|R|{1}{W}|Artifact - Spacecraft|||Station$STATION 2+$Other creatures you control get +1/+1.$STATION 12+$Flying, lifelink$3/5|
+Pinnacle Starcage|Edge of Eternities|322|R|{1}{W}{W}|Artifact|||When this artifact enters, exile all artifacts and creatures with mana value 2 or less until this artifact leaves the battlefield.${6}{W}{W}: Put each card exiled with this artifact into its owner's graveyard, then create a 2/2 colorless Robot artifact token for each card put into a graveyard this way. Sacrifice this artifact.|
The Seriema|Edge of Eternities|323|R|{1}{W}{W}|Legendary Artifact - Spacecraft|||When The Seriema enters, search your library for a legendary creature card, reveal it, put it into your hand, then shuffle.$Station$STATION 7+$Flying$Other tapped legendary creatures you control have indestructible.$5/5|
+Sunstar Chaplain|Edge of Eternities|324|R|{1}{W}|Creature - Human Cleric|3|2|At the beginning of your end step, if you control two or more tapped creatures, put a +1/+1 counter on target creature you control.${2}, Remove a +1/+1 counter from a creature you control: Tap target artifact or creature.|
+Consult the Star Charts|Edge of Eternities|325|R|{1}{U}|Instant|||Kicker {1}{U}$Look at the top X cards of your library, where X is the number of lands you control. Put one of those cards into your hand. If this spell was kicked, put two of those cards into your hand instead. Put the rest on the bottom of your library in a random order.|
+Emissary Escort|Edge of Eternities|326|R|{1}{U}|Artifact Creature - Robot Soldier|0|4|This creature gets +X/+0, where X is the greatest mana value among other artifacts you control.|
+Moonlit Meditation|Edge of Eternities|327|R|{2}{U}|Enchantment - Aura|||Enchant artifact or creature you control$The first time you would create one or more tokens each turn, you may instead create that many tokens that are copies of enchanted permanent.|
+Starfield Vocalist|Edge of Eternities|328|R|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}|
+Synthesizer Labship|Edge of Eternities|329|R|{U}|Artifact - Spacecraft|||Station$STATION 2+$At the beginning of combat on your turn, up to one other target artifact you control becomes an artifact creature with base power and toughness 2/2 and gains flying until end of turn.$STATION 9+$Flying, vigilance$4/4|
+Weftwalking|Edge of Eternities|330|M|{4}{U}{U}|Enchantment|||When this enchantment enters, if you cast it, shuffle your hand and graveyard into your library, then draw seven cards.$The first spell each player casts during each of their turns may be cast without paying its mana cost.|
+Chorale of the Void|Edge of Eternities|331|R|{3}{B}|Enchantment - Aura|||Enchant creature you control$Whenever enchanted creature attacks, put target creature card from defending player's graveyard onto the battlefield under your control tapped and attacking.$Void -- At the beginning of your end step, sacrifice this Aura unless a nonland permanent left the battlefield this turn or a spell was warped this turn.|
+Entropic Battlecruiser|Edge of Eternities|332|R|{3}{B}|Artifact - Spacecraft|||Station$STATION 1+$Whenever an opponent discards a card, they lose 3 life.$STATION 8+$Flying, deathtouch$Whenever this Spacecraft attacks, each opponent discards a card. Each opponent who doesn't loses 3 life.$3/10|
+Requiem Monolith|Edge of Eternities|333|R|{2}{B}|Artifact|||{T}: Until end of turn, target creature gains "Whenever this creature is dealt damage, you draw that many cards and lose that much life." That creature's controller may have this artifact deal 1 damage to it. Activate only as a sorcery.|
+Sunset Saboteur|Edge of Eternities|334|R|{1}{B}|Creature - Human Rogue|4|1|Menace$Ward--Discard a card.$Whenever this creature attacks, put a +1/+1 counter on target creature an opponent controls.|
+Zero Point Ballad|Edge of Eternities|335|R|{X}{B}|Sorcery|||Destroy all creatures with toughness X or less. You lose X life. If X is 6 or more, return a creature card put into a graveyard this way to the battlefield under your control.|
+Memorial Vault|Edge of Eternities|336|R|{3}{R}|Artifact|||{T}, Sacrifice another artifact: Exile the top X cards of your library, where X is one plus the mana value of the sacrificed artifact. You may play those cards this turn.|
+Pain for All|Edge of Eternities|337|R|{2}{R}|Enchantment - Aura|||Enchant creature you control$When this Aura enters, enchanted creature deals damage equal to its power to any other target.$Whenever enchanted creature is dealt damage, it deals that much damage to each opponent.|
+Terminal Velocity|Edge of Eternities|338|R|{4}{R}{R}|Sorcery|||You may put an artifact or creature card from your hand onto the battlefield. That permanent gains haste, "When this permanent leaves the battlefield, it deals damage equal to its mana value to each creature," and "At the beginning of your end step, sacrifice this permanent."|
+Warmaker Gunship|Edge of Eternities|339|R|{2}{R}|Artifact - Spacecraft|||When this Spacecraft enters, it deals damage equal to the number of artifacts you control to target creature an opponent controls.$Station$STATION 6+$Flying$4/3|
+Bioengineered Future|Edge of Eternities|340|R|{1}{G}{G}|Enchantment|||When this enchantment enters, create a Lander token.$Each creature you control enters with an additional +1/+1 counter on it for each land that entered the battlefield under your control this turn.|
+Famished Worldsire|Edge of Eternities|341|M|{5}{G}{G}{G}|Creature - Leviathan|0|0|Ward {3}$Devour land 3$When this creature enters, look at the top X cards of your library, where X is this creature's power. Put any number of land cards from among them onto the battlefield tapped, then shuffle.|
+Frenzied Baloth|Edge of Eternities|342|R|{G}{G}|Creature - Beast|3|2|This spell can't be countered.$Trample, haste$Creature spells you control can't be countered.$Combat damage can't be prevented.|
+Icetill Explorer|Edge of Eternities|343|R|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.|
+Loading Zone|Edge of Eternities|344|R|{3}{G}|Enchantment|||If one or more counters would be put on a creature, Spacecraft, or Planet you control, twice that many of each of those kinds of counters are put on it instead.$Warp {G}|
+Ouroboroid|Edge of Eternities|345|M|{2}{G}{G}|Creature - Plant Wurm|1|3|At the beginning of combat on your turn, put X +1/+1 counters on each creature you control, where X is this creature's power.|
+Sledge-Class Seedship|Edge of Eternities|346|R|{2}{G}|Artifact - Spacecraft|||Station$STATION 7+$Flying$Whenever this Spacecraft attacks, you may put a creature from your hand onto the battlefield.$4/5|
+Biotech Specialist|Edge of Eternities|347|R|{R}{G}|Creature - Insect Scientist|1|3|When this creature enters, create a Lander token.$Whenever you sacrifice an artifact, this creature deals 2 damage to target opponent.|
+Infinite Guideline Station|Edge of Eternities|348|R|{W}{U}{B}{R}{G}|Legendary Artifact - Spacecraft|||When Infinite Guideline Station enters, create a tapped 2/2 colorless Robot artifact creature token for each multicolored permanent you control.$Station$STATION 12+$Flying$Whenever Infinite Guideline Station attacks, draw a card for each multicolored permanent you control.$7/15|
+Pinnacle Emissary|Edge of Eternities|349|R|{1}{U}{R}|Artifact Creature - Robot|3|3|Whenever you cast an artifact spell, create a 1/1 colorless Drone artifact creature token with flying and "This token can block only creatures with flying."$Warp {U/R}|
+Singularity Rupture|Edge of Eternities|350|R|{3}{U}{B}{B}|Sorcery|||Destroy all creatures, then any number of target players each mill half their library, rounded down.|
+Dawnsire, Sunstar Dreadnought|Edge of Eternities|351|M|{5}|Legendary Artifact - Spacecraft|||Station$STATION 10+$Whenever you attack, Dawnsire deals 100 damage to up to one target creature or planeswalker.$STATION 20+$Flying$20/20|
+The Dominion Bracelet|Edge of Eternities|352|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}|
+The Endstone|Edge of Eternities|353|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.|
+The Eternity Elevator|Edge of Eternities|354|R|{5}|Legendary Artifact - Spacecraft|||{T}: Add {C}{C}{C}.$Station$STATION 20+${T}: Add X mana of any one color, where X is the number of charge counters on The Eternity Elevator.|
+Extinguisher Battleship|Edge of Eternities|355|R|{8}|Artifact - Spacecraft|||When this Spacecraft enters, destroy target noncreature permanent. Then this Spacecraft deals 4 damage to each creature.$Station$STATION 5+$Flying, trample$10/10|
+Thrumming Hivepool|Edge of Eternities|356|R|{6}|Artifact|||Affinity for Slivers$Slivers you control have double strike and haste.$At the beginning of your upkeep, create two 1/1 colorless Sliver creature tokens.|
+Anticausal Vestige|Edge of Eternities|357|M|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}|
+Exalted Sunborn|Edge of Eternities|358|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}|
+Starfield Vocalist|Edge of Eternities|359|M|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}|
Sothera, the Supervoid|Edge of Eternities|360|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.|
+Devastating Onslaught|Edge of Eternities|361|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.|
+Icetill Explorer|Edge of Eternities|362|M|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.|
+Mutinous Massacre|Edge of Eternities|363|M|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.|
+The Dominion Bracelet|Edge of Eternities|364|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}|
+The Endstone|Edge of Eternities|365|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.|
+Secluded Starforge|Edge of Eternities|366|M||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.|
Plains|Edge of Eternities|367|C||Basic Land - Plains|||({T}: Add {W}.)|
Island|Edge of Eternities|368|C||Basic Land - Island|||({T}: Add {U}.)|
Swamp|Edge of Eternities|369|C||Basic Land - Swamp|||({T}: Add {B}.)|
Mountain|Edge of Eternities|370|C||Basic Land - Mountain|||({T}: Add {R}.)|
Forest|Edge of Eternities|371|C||Basic Land - Forest|||({T}: Add {G}.)|
+Adagia, Windswept Bastion|Edge of Eternities|372|M||Land - Planet|||This land enters tapped.${T}: Add {W}.$Station$STATION 12+${3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.|
+Breeding Pool|Edge of Eternities|373|R||Land - Forest Island|||({T}: Add {G} or {U}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Evendo, Waking Haven|Edge of Eternities|374|M||Land - Planet|||This land enters tapped.${T}: Add {G}.$Station$STATION 12+${G}, {T}: Add {G} for each creature you control.|
+Godless Shrine|Edge of Eternities|375|R||Land - Plains Swamp|||({T}: Add {W} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Kavaron, Memorial World|Edge of Eternities|376|M||Land - Planet|||This land enters tapped.${T}: Add {R}.$Station$STATION 12+${1}{R}, {T}, Sacrifice a land: Create a 2/2 colorless Robot artifact creature token, then creatures you control get +1/+0 and gain haste until end of turn.|
+Sacred Foundry|Edge of Eternities|377|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Stomping Ground|Edge of Eternities|378|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
+Susur Secundi, Void Altar|Edge of Eternities|379|M||Land - Planet|||This land enters tapped.${T}: Add {B}.$Station$STATION 12+${1}{B}, {T}, Pay 2 life, Sacrifice a creature: Draw cards equal to the sacrificed creature's power. Activate only as a sorcery.|
+Uthros, Titanic Godcore|Edge of Eternities|380|M||Land - Planet|||This land enters tapped.${T}: Add {U}.$Station$STATION 12+${U}, {T}: Add {U} for each artifact you control.|
+Watery Grave|Edge of Eternities|381|R||Land - Island Swamp|||({T}: Add {U} or {B}.)$As this land enters, you may pay 2 life. If you don't, it enters tapped.|
Sothera, the Supervoid|Edge of Eternities|382|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.|
+Anticausal Vestige|Edge of Eternities|383|M|{6}|Creature - Eldrazi|7|5|When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.$Warp {4}|
+Exalted Sunborn|Edge of Eternities|384|M|{3}{W}{W}|Creature - Angel Wizard|4|5|Flying, lifelink$If one or more tokens would be created under your control, twice that many of those tokens are created instead.$Warp {1}{W}|
+Starfield Vocalist|Edge of Eternities|385|M|{3}{U}|Creature - Human Bard|3|4|If a permanent entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time.$Warp {1}{U}|
Sothera, the Supervoid|Edge of Eternities|386|M|{2}{B}{B}|Legendary Enchantment|||Whenever a creature you control dies, each opponent chooses a creature they control and exiles it.$At the beginning of your end step, if a player controls no creatures, sacrifice Sothera, then put a creature card exiled with it onto the battlefield under your control with two additional +1/+1 counters on it.|
+Devastating Onslaught|Edge of Eternities|387|M|{X}{X}{R}|Sorcery|||Create X tokens that are copies of target artifact or creature you control. Those tokens gain haste until end of turn. Sacrifice them at the beginning of the next end step.|
+Icetill Explorer|Edge of Eternities|388|M|{2}{G}{G}|Creature - Insect Scout|2|4|You may play an additional land on each of your turns.$You may play lands from your graveyard.$Landfall -- Whenever a land you control enters, mill a card.|
+Mutinous Massacre|Edge of Eternities|389|M|{3}{B}{B}{R}{R}|Sorcery|||Choose odd or even. Destroy each creature with mana value of the chosen quality. Then gain control of all creatures until end of turn. Untap them. They gain haste until end of turn.|
+The Dominion Bracelet|Edge of Eternities|390|M|{2}|Legendary Artifact - Equipment|||Equipped creature gets +1/+1 and has "{15}, Exile The Dominion Bracelet: You control target opponent during their next turn. This ability costs {X} less to activate, where X is this creature's power. Activate only as a sorcery."$Equip {1}|
+The Endstone|Edge of Eternities|391|M|{7}|Legendary Artifact|||Whenever you play a land or cast a spell, draw a card.$At the beginning of your end step, your life total becomes half your starting life total, rounded up.|
+Secluded Starforge|Edge of Eternities|392|M||Land|||{T}: Add {C}.${2}, {T}, Tap X untapped artifacts you control: Target creature gets +X/+0 until end of turn. Activate only as a sorcery.${5}, {T}: Create a 2/2 colorless Robot artifact creature token.|
+Starfield Shepherd|Edge of Eternities|393|U|{3}{W}{W}|Creature - Angel|3|2|Flying$When this creature enters, search your library for a basic Plains card or a creature card with mana value 1 or less, reveal it, put it into your hand, then shuffle.$Warp {1}{W}|
+Annul|Edge of Eternities|394|U|{U}|Instant|||Counter target artifact or enchantment spell.|
+Umbral Collar Zealot|Edge of Eternities|395|U|{1}{B}|Creature - Human Cleric|3|2|Sacrifice another creature or artifact: Surveil 1.|
+Kavaron Harrier|Edge of Eternities|396|U|{R}|Artifact Creature - Robot Soldier|2|1|Whenever this creature attacks, you may pay {2}. If you do, create a 2/2 colorless Robot artifact creature token that's tapped and attacking. Sacrifice that token at end of combat.|
+Pull Through the Weft|Edge of Eternities|397|U|{3}{G}{G}|Sorcery|||Return up to two target nonland permanent cards from your graveyard to your hand, then return up to two target land cards from your graveyard to the battlefield tapped.|
+Singularity Rupture|Edge of Eternities|398|R|{3}{U}{B}{B}|Sorcery|||Destroy all creatures, then any number of target players each mill half their library, rounded down.|
+Emissary Escort|Edge of Eternities|399|R|{1}{U}|Artifact Creature - Robot Soldier|0|4|This creature gets +X/+0, where X is the greatest mana value among other artifacts you control.|
+Hearthhull, the Worldseed|Edge of Eternities Commander|1|M|{1}{B}{R}{G}|Legendary Artifact - Spacecraft|||Station$STATION 2+${1}, {T}, Sacrifice a land: Draw two cards. You may play an additional land this turn.$STATION 8+$Flying, vigilance, haste$Whenever you sacrifice a land, each opponent loses 2 life.$6/7|
+Inspirit, Flagship Vessel|Edge of Eternities Commander|2|M|{U}{R}{W}|Legendary Artifact - Spacecraft|||Station$STATION 1+$At the beginning of combat on your turn, put your choice of a +1/+1 counter or two charge counters on up to one other target artifact.$STATION 8+$Flying$Other artifacts you control have hexproof and indestructible.$5/5|
Kilo, Apogee Mind|Edge of Eternities Commander|3|M|{U}{R}{W}|Legendary Artifact Creature - Robot Artificer|3|3|Haste$Whenever Kilo becomes tapped, proliferate.|
Szarel, Genesis Shepherd|Edge of Eternities Commander|4|M|{2}{B}{R}{G}|Legendary Creature - Insect Druid|2|5|Flying$You may play lands from your graveyard.$Whenever you sacrifice another nontoken permanent during your turn, put a number of +1/+1 counters equal to Szarel's power on up to one other target creature.|
+Patrolling Peacemaker|Edge of Eternities Commander|5|R|{2}{W}|Artifact Creature - Robot Soldier|0|0|This creature enters with two +1/+1 counters on it.$Whenever an opponent commits a crime, proliferate.|
+Insight Engine|Edge of Eternities Commander|6|R|{2}{U}|Artifact|||{2}, {T}: Put a charge counter on this artifact, then draw a card for each charge counter on it.|
+Uthros Research Craft|Edge of Eternities Commander|7|R|{2}{U}|Artifact - Spacecraft|||Station$STATION 3+$Whenever you cast an artifact spell, draw a card. Put a charge counter on this Spacecraft.$STATION 12+$Flying$This Spacecraft gets +1/+0 for each artifact you control.$0/8|
+Eumidian Wastewaker|Edge of Eternities Commander|8|R|{2}{B}{B}|Creature - Insect Cleric|4|4|Whenever this creature attacks, you and defending player each discard a card or sacrifice a permanent. You draw a card for each land card put into a graveyard this way.$Encore {6}{B}{B}|
+Depthshaker Titan|Edge of Eternities Commander|9|R|{5}{R}{R}|Artifact Creature - Robot|5|5|When this creature enters, any number of target noncreature artifacts you control become 3/3 artifact creatures. Sacrifice them at the beginning of the next end step.$Each artifact creature you control has melee, trample, and haste.|
+Evendo Brushrazer|Edge of Eternities Commander|10|R|{2}{R}|Creature - Insect Warrior|2|2|Whenever you sacrifice a nontoken permanent, exile the top card of your library.$During your turn, as long as you've sacrificed a nontoken permanent this turn, you may play cards exiled with this creature.${T}, Sacrifice a land: Add {R}{R}.|
+Long-Range Sensor|Edge of Eternities Commander|11|R|{2}{R}|Artifact|||Whenever you attack a player, put a charge counter on this artifact.${1}, Remove two charge counters from this artifact: Discover 4. Activate only as a sorcery.|
+Planetary Annihilation|Edge of Eternities Commander|12|R|{3}{R}{R}|Sorcery|||Each player chooses six lands they control, then sacrifices the rest. Planetary Annihilation deals 6 damage to each creature.|
+Baloth Prime|Edge of Eternities Commander|13|R|{3}{G}|Creature - Beast|10|10|This creature enters tapped with six stun counters on it.$Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature.${4}, Sacrifice a land: You gain 2 life.|
+Exploration Broodship|Edge of Eternities Commander|14|R|{G}|Artifact - Spacecraft|||Station$STATION 3+$You may play an additional land on each of your turns.$STATION 8+$Flying$Once during each of your turns, you may cast a permanent spell from your graveyard by sacrificing a land in addition to paying its other costs.$4/4|
+Horizon Explorer|Edge of Eternities Commander|15|R|{2}{G}|Creature - Insect Scout|2|4|Lands you control enter untapped.$Whenever you attack a player, create a Lander token.|
+Scouring Swarm|Edge of Eternities Commander|16|R|{1}{B}{G}|Creature - Insect|1|1|Flying$Whenever you sacrifice a land, create a tapped token that's a copy of this creature if seven or more land cards are in your graveyard. Otherwise, create a tapped 1/1 black Insect creature token with flying.|
+Moxite Refinery|Edge of Eternities Commander|17|R|{2}|Artifact|||{2}, {T}, Remove X counters from an artifact or creature you control: Choose one. Activate only as a sorcery.$* Put X charge counters on target artifact.$* Put X +1/+1 counters on target creature.|
+Solar Array|Edge of Eternities Commander|18|R|{3}|Artifact|||{T}: Add one mana of any color. When you next cast an artifact spell this turn, that spell gains sunburst.|
+Surge Conductor|Edge of Eternities Commander|19|R|{3}|Artifact Creature - Robot|3|2|Whenever another nontoken artifact you control enters, proliferate.|
+Eumidian Hatchery|Edge of Eternities Commander|20|R||Land|||{T}, Pay 1 life: Add {B}. Put a hatchling counter on this land.$When this land is put into a graveyard from the battlefield, for each hatchling counter on it, create a 1/1 black Insect creature token with flying.|
+Festering Thicket|Edge of Eternities Commander|21|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped.$Cycling {2}|
+Glittering Massif|Edge of Eternities Commander|22|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped.$Cycling {2}|
+Radiant Summit|Edge of Eternities Commander|23|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped unless you control two or more basic lands.|
+Vernal Fen|Edge of Eternities Commander|24|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped unless you control two or more basic lands.|
+Patrolling Peacemaker|Edge of Eternities Commander|25|R|{2}{W}|Artifact Creature - Robot Soldier|0|0|This creature enters with two +1/+1 counters on it.$Whenever an opponent commits a crime, proliferate.|
+Insight Engine|Edge of Eternities Commander|26|R|{2}{U}|Artifact|||{2}, {T}: Put a charge counter on this artifact, then draw a card for each charge counter on it.|
+Uthros Research Craft|Edge of Eternities Commander|27|R|{2}{U}|Artifact - Spacecraft|||Station$STATION 3+$Whenever you cast an artifact spell, draw a card. Put a charge counter on this Spacecraft.$STATION 12+$Flying$This Spacecraft gets +1/+0 for each artifact you control.$0/8|
+Eumidian Wastewaker|Edge of Eternities Commander|28|R|{2}{B}{B}|Creature - Insect Cleric|4|4|Whenever this creature attacks, you and defending player each discard a card or sacrifice a permanent. You draw a card for each land card put into a graveyard this way.$Encore {6}{B}{B}|
+Depthshaker Titan|Edge of Eternities Commander|29|R|{5}{R}{R}|Artifact Creature - Robot|5|5|When this creature enters, any number of target noncreature artifacts you control become 3/3 artifact creatures. Sacrifice them at the beginning of the next end step.$Each artifact creature you control has melee, trample, and haste.|
+Evendo Brushrazer|Edge of Eternities Commander|30|R|{2}{R}|Creature - Insect Warrior|2|2|Whenever you sacrifice a nontoken permanent, exile the top card of your library.$During your turn, as long as you've sacrificed a nontoken permanent this turn, you may play cards exiled with this creature.${T}, Sacrifice a land: Add {R}{R}.|
+Long-Range Sensor|Edge of Eternities Commander|31|R|{2}{R}|Artifact|||Whenever you attack a player, put a charge counter on this artifact.${1}, Remove two charge counters from this artifact: Discover 4. Activate only as a sorcery.|
+Planetary Annihilation|Edge of Eternities Commander|32|R|{3}{R}{R}|Sorcery|||Each player chooses six lands they control, then sacrifices the rest. Planetary Annihilation deals 6 damage to each creature.|
+Baloth Prime|Edge of Eternities Commander|33|R|{3}{G}|Creature - Beast|10|10|This creature enters tapped with six stun counters on it.$Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature.${4}, Sacrifice a land: You gain 2 life.|
+Exploration Broodship|Edge of Eternities Commander|34|R|{G}|Artifact - Spacecraft|||Station$STATION 3+$You may play an additional land on each of your turns.$STATION 8+$Flying$Once during each of your turns, you may cast a permanent spell from your graveyard by sacrificing a land in addition to paying its other costs.$4/4|
+Horizon Explorer|Edge of Eternities Commander|35|R|{2}{G}|Creature - Insect Scout|2|4|Lands you control enter untapped.$Whenever you attack a player, create a Lander token.|
+Scouring Swarm|Edge of Eternities Commander|36|R|{1}{B}{G}|Creature - Insect|1|1|Flying$Whenever you sacrifice a land, create a tapped token that's a copy of this creature if seven or more land cards are in your graveyard. Otherwise, create a tapped 1/1 black Insect creature token with flying.|
+Moxite Refinery|Edge of Eternities Commander|37|R|{2}|Artifact|||{2}, {T}, Remove X counters from an artifact or creature you control: Choose one. Activate only as a sorcery.$* Put X charge counters on target artifact.$* Put X +1/+1 counters on target creature.|
+Solar Array|Edge of Eternities Commander|38|R|{3}|Artifact|||{T}: Add one mana of any color. When you next cast an artifact spell this turn, that spell gains sunburst.|
+Surge Conductor|Edge of Eternities Commander|39|R|{3}|Artifact Creature - Robot|3|2|Whenever another nontoken artifact you control enters, proliferate.|
+Eumidian Hatchery|Edge of Eternities Commander|40|R||Land|||{T}, Pay 1 life: Add {B}. Put a hatchling counter on this land.$When this land is put into a graveyard from the battlefield, for each hatchling counter on it, create a 1/1 black Insect creature token with flying.|
+Festering Thicket|Edge of Eternities Commander|41|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped.$Cycling {2}|
+Glittering Massif|Edge of Eternities Commander|42|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped.$Cycling {2}|
+Radiant Summit|Edge of Eternities Commander|43|R||Land - Mountain Plains|||({T}: Add {R} or {W}.)$This land enters tapped unless you control two or more basic lands.|
+Vernal Fen|Edge of Eternities Commander|44|R||Land - Swamp Forest|||({T}: Add {B} or {G}.)$This land enters tapped unless you control two or more basic lands.|
+Swords to Plowshares|Edge of Eternities Commander|45|U|{W}|Instant|||Exile target creature. Its controller gains life equal to its power.|
+Swan Song|Edge of Eternities Commander|46|R|{U}|Instant|||Counter target enchantment, instant, or sorcery spell. Its controller creates a 2/2 blue Bird creature token with flying.|
+Tezzeret's Gambit|Edge of Eternities Commander|47|U|{3}{U/P}|Sorcery|||({U/P} can be paid with either {U} or 2 life.)$Draw two cards, then proliferate.|
+Thirst for Knowledge|Edge of Eternities Commander|48|U|{2}{U}|Instant|||Draw three cards. Then discard two cards unless you discard an artifact card.|
+Chaos Warp|Edge of Eternities Commander|49|R|{2}{R}|Instant|||The owner of target permanent shuffles it into their library, then reveals the top card of their library. If it's a permanent card, they put it onto the battlefield.|
+Farseek|Edge of Eternities Commander|50|C|{1}{G}|Sorcery|||Search your library for a Plains, Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle.|
+Springbloom Druid|Edge of Eternities Commander|51|C|{2}{G}|Creature - Elf Druid|1|1|When this creature enters, you may sacrifice a land. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.|
+Binding the Old Gods|Edge of Eternities Commander|52|U|{2}{B}{G}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Destroy target nonland permanent an opponent controls.$II -- Search your library for a Forest card, put it onto the battlefield tapped, then shuffle.$III -- Creatures you control gain deathtouch until end of turn.|
+Arcane Signet|Edge of Eternities Commander|53|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.|
+Cloud Key|Edge of Eternities Commander|54|R|{3}|Artifact|||As this artifact enters, choose artifact, creature, enchantment, instant, or sorcery.$Spells you cast of the chosen type cost {1} less to cast.|
+Gavel of the Righteous|Edge of Eternities Commander|55|R|{2}|Artifact - Equipment|||At the beginning of combat on your turn, put a charge counter on this Equipment.$Equipped creature gets +1/+1 for each counter on this Equipment.$As long as this Equipment has four or more counters on it, equipped creature has double strike.$Equip--Pay {3} or remove a counter from this Equipment.|
+Pentad Prism|Edge of Eternities Commander|56|U|{2}|Artifact|||Sunburst$Remove a charge counter from this artifact: Add one mana of any color.|
+Sol Ring|Edge of Eternities Commander|57|U|{1}|Artifact|||{T}: Add {C}{C}.|
+Battlefield Forge|Edge of Eternities Commander|58|R||Land|||{T}: Add {C}.${T}: Add {R} or {W}. This land deals 1 damage to you.|
+Command Tower|Edge of Eternities Commander|59|C||Land|||{T}: Add one mana of any color in your commander's color identity.|
+Fabled Passage|Edge of Eternities Commander|60|R||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if you control four or more lands, untap that land.|
+Mountain Valley|Edge of Eternities Commander|61|U||Land|||This land enters tapped.${T}, Sacrifice this land: Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle.|
+Terramorphic Expanse|Edge of Eternities Commander|62|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.|
+Angel of the Ruins|Edge of Eternities Commander|63|R|{5}{W}{W}|Artifact Creature - Angel|5|7|Flying$When this creature enters, exile up to two target artifacts and/or enchantments.$Plainscycling {2}|
+Dispatch|Edge of Eternities Commander|64|U|{W}|Instant|||Tap target creature.$Metalcraft -- If you control three or more artifacts, exile that creature.|
+Fumigate|Edge of Eternities Commander|65|R|{3}{W}{W}|Sorcery|||Destroy all creatures. You gain 1 life for each creature destroyed this way.|
+Organic Extinction|Edge of Eternities Commander|66|R|{8}{W}{W}|Sorcery|||Improvise$Destroy all nonartifact creatures.|
+Resourceful Defense|Edge of Eternities Commander|67|R|{2}{W}|Enchantment|||Whenever a permanent you control leaves the battlefield, if it had counters on it, put those counters on target permanent you control.${4}{W}: Move any number of counters from target permanent you control to another target permanent you control.|
+Chrome Host Seedshark|Edge of Eternities Commander|68|R|{2}{U}|Creature - Phyrexian Shark|2|4|Flying$Whenever you cast a noncreature spell, incubate X, where X is that spell's mana value.|
+Cyberdrive Awakener|Edge of Eternities Commander|69|R|{5}{U}|Artifact Creature - Construct|4|4|Flying$Other artifact creatures you control have flying.$When this creature enters, until end of turn, each noncreature artifact you control becomes an artifact creature with base power and toughness 4/4.|
+Deepglow Skate|Edge of Eternities Commander|70|R|{4}{U}|Creature - Fish|3|3|When this creature enters, double the number of each kind of counter on any number of target permanents.|
+Emry, Lurker of the Loch|Edge of Eternities Commander|71|R|{2}{U}|Legendary Creature - Merfolk Wizard|1|2|Affinity for artifacts$When Emry enters, mill four cards.${T}: Choose target artifact card in your graveyard. You may cast that card this turn.|
+Etherium Sculptor|Edge of Eternities Commander|72|C|{1}{U}|Artifact Creature - Vedalken Artificer|1|2|Artifact spells you cast cost {1} less to cast.|
+Experimental Augury|Edge of Eternities Commander|73|C|{1}{U}|Instant|||Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. Proliferate.|
+Kappa Cannoneer|Edge of Eternities Commander|74|R|{5}{U}|Artifact Creature - Turtle Warrior|4|4|Improvise$Ward {4}$Whenever this creature or another artifact you control enters, put a +1/+1 counter on this creature and it can't be blocked this turn.|
+Phyrexian Metamorph|Edge of Eternities Commander|75|R|{3}{U/P}|Artifact Creature - Phyrexian Shapeshifter|0|0|({U/P} can be paid with either {U} or 2 life.)$You may have this creature enter as a copy of any artifact or creature on the battlefield, except it's an artifact in addition to its other types.|
+Pull from Tomorrow|Edge of Eternities Commander|76|R|{X}{U}{U}|Instant|||Draw X cards, then discard a card.|
+Ripples of Potential|Edge of Eternities Commander|77|R|{1}{U}|Instant|||Proliferate, then choose any number of permanents you control that had a counter put on them this way. Those permanents phase out.|
+Tekuthal, Inquiry Dominus|Edge of Eternities Commander|78|M|{2}{U}{U}|Legendary Creature - Phyrexian Horror|3|5|Flying$If you would proliferate, proliferate twice instead.${1}{U/P}{U/P}, Remove three counters from among other artifacts, creatures, and planeswalkers you control: Put an indestructible counter on Tekuthal.|
+Thought Monitor|Edge of Eternities Commander|79|R|{6}{U}|Artifact Creature - Construct|2|2|Affinity for artifacts$Flying$When this creature enters, draw two cards.|
+Thrummingbird|Edge of Eternities Commander|80|U|{1}{U}|Creature - Phyrexian Bird Horror|1|1|Flying$Whenever this creature deals combat damage to a player, proliferate.|
+Universal Surveillance|Edge of Eternities Commander|81|R|{X}{U}{U}{U}|Sorcery|||Improvise$Draw X cards.|
+Braids, Arisen Nightmare|Edge of Eternities Commander|82|R|{1}{B}{B}|Legendary Creature - Nightmare|3|3|At the beginning of your end step, you may sacrifice an artifact, creature, enchantment, land, or planeswalker. If you do, each opponent may sacrifice a permanent of their choice that shares a card type with it. For each opponent who doesn't, that player loses 2 life and you draw a card.|
+God-Eternal Bontu|Edge of Eternities Commander|83|M|{3}{B}{B}|Legendary Creature - Zombie God|5|6|Menace$When God-Eternal Bontu enters, sacrifice any number of other permanents, then draw that many cards.$When God-Eternal Bontu dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.|
+Infernal Grasp|Edge of Eternities Commander|84|U|{1}{B}|Instant|||Destroy target creature. You lose 2 life.|
+Night's Whisper|Edge of Eternities Commander|85|C|{1}{B}|Sorcery|||You draw two cards and you lose 2 life.|
+Blasphemous Act|Edge of Eternities Commander|86|R|{8}{R}|Sorcery|||This spell costs {1} less to cast for each creature on the battlefield.$Blasphemous Act deals 13 damage to each creature.|
+Chain Reaction|Edge of Eternities Commander|87|R|{2}{R}{R}|Sorcery|||Chain Reaction deals X damage to each creature, where X is the number of creatures on the battlefield.|
+Hammer of Purphoros|Edge of Eternities Commander|88|R|{1}{R}{R}|Legendary Enchantment Artifact|||Creatures you control have haste.${2}{R}, {T}, Sacrifice a land: Create a 3/3 colorless Golem enchantment artifact creature token.|
+Moraug, Fury of Akoum|Edge of Eternities Commander|89|M|{4}{R}{R}|Legendary Creature - Minotaur Warrior|6|6|Each creature you control gets +1/+0 for each time it has attacked this turn.$Landfall -- Whenever a land you control enters, if it's your main phase, there's an additional combat phase after this phase. At the beginning of that combat, untap all creatures you control.|
+Sprouting Goblin|Edge of Eternities Commander|90|U|{1}{R}|Creature - Goblin Druid|2|2|Kicker {G}$When this creature enters, if it was kicked, search your library for a land card with a basic land type, reveal it, put it into your hand, then shuffle.${R}, {T}, Sacrifice a land: Draw a card.|
+Aftermath Analyst|Edge of Eternities Commander|91|U|{1}{G}|Creature - Elf Detective|1|3|When this creature enters, mill three cards.${3}{G}, Sacrifice this creature: Return all land cards from your graveyard to the battlefield tapped.|
+Augur of Autumn|Edge of Eternities Commander|92|R|{1}{G}{G}|Creature - Human Druid|2|3|You may look at the top card of your library any time.$You may play lands from the top of your library.$Coven -- As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library.|
+Beast Within|Edge of Eternities Commander|93|U|{2}{G}|Instant|||Destroy target permanent. Its controller creates a 3/3 green Beast creature token.|
+Centaur Vinecrasher|Edge of Eternities Commander|94|R|{3}{G}|Creature - Plant Centaur|1|1|Trample$This creature enters with a number of +1/+1 counters on it equal to the number of land cards in all graveyards.$Whenever a land card is put into a graveyard from anywhere, you may pay {G}{G}. If you do, return this card from your graveyard to your hand.|
+Cultivate|Edge of Eternities Commander|95|C|{2}{G}|Sorcery|||Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.|
+Formless Genesis|Edge of Eternities Commander|96|R|{2}{G}|Kindred Sorcery - Shapeshifter|||Changeling$Create an X/X colorless Shapeshifter creature token with changeling and deathtouch, where X is the number of land cards in your graveyard.$Retrace|
+Groundskeeper|Edge of Eternities Commander|97|U|{G}|Creature - Human Druid|1|1|{1}{G}: Return target basic land card from your graveyard to your hand.|
+Harrow|Edge of Eternities Commander|98|C|{2}{G}|Instant|||As an additional cost to cast this spell, sacrifice a land.$Search your library for up to two basic land cards, put them onto the battlefield, then shuffle.|
+Loamcrafter Faun|Edge of Eternities Commander|99|R|{2}{G}|Creature - Satyr Druid|3|3|When this creature enters, you may discard one or more land cards. When you do, return up to that many target nonland permanent cards from your graveyard to your hand.|
+Multani, Yavimaya's Avatar|Edge of Eternities Commander|100|M|{4}{G}{G}|Legendary Creature - Elemental Avatar|0|0|Reach, trample$Multani gets +1/+1 for each land you control and each land card in your graveyard.${1}{G}, Return two lands you control to their owner's hand: Return this card from your graveyard to your hand.|
+Nature's Lore|Edge of Eternities Commander|101|U|{1}{G}|Sorcery|||Search your library for a Forest card, put that card onto the battlefield, then shuffle.|
+Oracle of Mul Daya|Edge of Eternities Commander|102|R|{3}{G}|Creature - Elf Shaman|2|2|You may play an additional land on each of your turns.$Play with the top card of your library revealed.$You may play lands from the top of your library.|
+Pest Infestation|Edge of Eternities Commander|103|R|{X}{X}{G}|Sorcery|||Destroy up to X target artifacts and/or enchantments. Create twice X 1/1 black and green Pest creature tokens with "When this token dies, you gain 1 life."|
+Rampaging Baloths|Edge of Eternities Commander|104|R|{4}{G}{G}|Creature - Beast|6|6|Trample$Landfall -- Whenever a land you control enters, you may create a 4/4 green Beast creature token.|
+Roiling Regrowth|Edge of Eternities Commander|105|U|{2}{G}|Instant|||Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.|
+Satyr Wayfinder|Edge of Eternities Commander|106|C|{1}{G}|Creature - Satyr|1|1|When this creature enters, reveal the top four cards of your library. You may put a land card from among them into your hand. Put the rest into your graveyard.|
+Skyshroud Claim|Edge of Eternities Commander|107|C|{3}{G}|Sorcery|||Search your library for up to two Forest cards, put them onto the battlefield, then shuffle.|
+Splendid Reclamation|Edge of Eternities Commander|108|R|{3}{G}|Sorcery|||Return all land cards from your graveyard to the battlefield tapped.|
+Tear Asunder|Edge of Eternities Commander|109|U|{1}{G}|Instant|||Kicker {1}{B}$Exile target artifact or enchantment. If this spell was kicked, exile target nonland permanent instead.|
+Tireless Tracker|Edge of Eternities Commander|110|R|{2}{G}|Creature - Human Scout|3|2|Landfall -- Whenever a land you control enters, investigate.$Whenever you sacrifice a Clue, put a +1/+1 counter on this creature.|
+Titania, Protector of Argoth|Edge of Eternities Commander|111|M|{3}{G}{G}|Legendary Creature - Elemental|5|3|When Titania enters, return target land card from your graveyard to the battlefield.$Whenever a land you control is put into a graveyard from the battlefield, create a 5/3 green Elemental creature token.|
+World Breaker|Edge of Eternities Commander|112|M|{6}{G}|Creature - Eldrazi|5|7|Devoid$When you cast this spell, exile target artifact, enchantment, or land.$Reach${2}{C}, Sacrifice a land: Return this card from your graveyard to your hand.|
+Alibou, Ancient Witness|Edge of Eternities Commander|113|M|{3}{R}{W}|Legendary Artifact Creature - Golem|4|5|Other artifact creatures you control have haste.$Whenever one or more artifact creatures you control attack, Alibou deals X damage to any target and you scry X, where X is the number of tapped artifacts you control.|
+Enthusiastic Mechanaut|Edge of Eternities Commander|114|U|{U}{R}|Artifact Creature - Goblin Artificer|2|2|Flying$Artifact spells you cast cost {1} less to cast.|
+Escape to the Wilds|Edge of Eternities Commander|115|R|{3}{R}{G}|Sorcery|||Exile the top five cards of your library. You may play cards exiled this way until the end of your next turn.$You may play an additional land this turn.|
+Gaze of Granite|Edge of Eternities Commander|116|R|{X}{B}{B}{G}|Sorcery|||Destroy each nonland permanent with mana value X or less.|
+The Gitrog Monster|Edge of Eternities Commander|117|M|{3}{B}{G}|Legendary Creature - Frog Horror|6|6|Deathtouch$At the beginning of your upkeep, sacrifice The Gitrog Monster unless you sacrifice a land.$You may play an additional land on each of your turns.$Whenever one or more land cards are put into your graveyard from anywhere, draw a card.|
+Jhoira, Weatherlight Captain|Edge of Eternities Commander|118|R|{2}{U}{R}|Legendary Creature - Human Artificer|3|3|Whenever you cast a historic spell, draw a card.|
+Juri, Master of the Revue|Edge of Eternities Commander|119|U|{B}{R}|Legendary Creature - Human Shaman|1|1|Whenever you sacrifice a permanent, put a +1/+1 counter on Juri.$When Juri dies, it deals damage equal to its power to any target.|
+Korvold, Fae-Cursed King|Edge of Eternities Commander|120|M|{2}{B}{R}{G}|Legendary Creature - Dragon Noble|4|4|Flying$Whenever Korvold enters or attacks, sacrifice another permanent.$Whenever you sacrifice a permanent, put a +1/+1 counter on Korvold and draw a card.|
+Mayhem Devil|Edge of Eternities Commander|121|U|{1}{B}{R}|Creature - Devil|3|3|Whenever a player sacrifices a permanent, this creature deals 1 damage to any target.|
+Mazirek, Kraul Death Priest|Edge of Eternities Commander|122|R|{3}{B}{G}|Legendary Creature - Insect Shaman|2|2|Flying$Whenever a player sacrifices another permanent, put a +1/+1 counter on each creature you control.|
+Omnath, Locus of Rage|Edge of Eternities Commander|123|M|{3}{R}{R}{G}{G}|Legendary Creature - Elemental|5|5|Landfall -- Whenever a land you control enters, create a 5/5 red and green Elemental creature token.$Whenever Omnath or another Elemental you control dies, Omnath deals 3 damage to any target.|
+Putrefy|Edge of Eternities Commander|124|U|{1}{B}{G}|Instant|||Destroy target artifact or creature. It can't be regenerated.|
+Rakdos Charm|Edge of Eternities Commander|125|U|{B}{R}|Instant|||Choose one --$* Exile target player's graveyard.$* Destroy target artifact.$* Each creature deals 1 damage to its controller.|
+Soul of Windgrace|Edge of Eternities Commander|126|M|{1}{B}{R}{G}|Legendary Creature - Cat Avatar|5|4|Whenever Soul of Windgrace enters or attacks, you may put a land card from a graveyard onto the battlefield tapped under your control.${G}, Discard a land card: You gain 3 life.${1}{R}, Discard a land card: Draw a card.${2}{B}, Discard a land card: Soul of Windgrace gains indestructible until end of turn. Tap it.|
+Uurg, Spawn of Turg|Edge of Eternities Commander|127|U|{B}{B}{G}|Legendary Creature - Frog Beast|*|5|Uurg's power is equal to the number of land cards in your graveyard.$At the beginning of your upkeep, surveil 1.${B}{G}, Sacrifice a land: You gain 2 life.|
+Wake the Past|Edge of Eternities Commander|128|R|{5}{R}{W}|Sorcery|||Return all artifact cards from your graveyard to the battlefield. They gain haste until end of turn.|
+Windgrace's Judgment|Edge of Eternities Commander|129|R|{3}{B}{G}|Instant|||For any number of opponents, destroy target nonland permanent that player controls.|
+Worldsoul's Rage|Edge of Eternities Commander|130|R|{X}{R}{G}|Sorcery|||Worldsoul's Rage deals X damage to any target. Put up to X land cards from your hand and/or graveyard onto the battlefield tapped.|
+Astral Cornucopia|Edge of Eternities Commander|131|R|{X}{X}{X}|Artifact|||This artifact enters with X charge counters on it.${T}: Choose a color. Add one mana of that color for each charge counter on this artifact.|
+Coretapper|Edge of Eternities Commander|132|U|{2}|Artifact Creature - Myr|1|1|{T}: Put a charge counter on target artifact.$Sacrifice this creature: Put two charge counters on target artifact.|
+Crystalline Crawler|Edge of Eternities Commander|133|R|{4}|Artifact Creature - Construct|1|1|Converge -- This creature enters with a +1/+1 counter on it for each color of mana spent to cast it.$Remove a +1/+1 counter from this creature: Add one mana of any color.${T}: Put a +1/+1 counter on this creature.|
+Darksteel Reactor|Edge of Eternities Commander|134|R|{4}|Artifact|||Indestructible$At the beginning of your upkeep, you may put a charge counter on this artifact.$When this artifact has twenty or more charge counters on it, you win the game.|
+Empowered Autogenerator|Edge of Eternities Commander|135|R|{4}|Artifact|||This artifact enters tapped.${T}: Put a charge counter on this artifact. Add X mana of any one color, where X is the number of charge counters on this artifact.|
+Etched Oracle|Edge of Eternities Commander|136|U|{4}|Artifact Creature - Wizard|0|0|Sunburst${1}, Remove four +1/+1 counters from this creature: Target player draws three cards.|
+Everflowing Chalice|Edge of Eternities Commander|137|U|{0}|Artifact|||Multikicker {2}$This artifact enters with a charge counter on it for each time it was kicked.${T}: Add {C} for each charge counter on this artifact.|
+Golem Foundry|Edge of Eternities Commander|138|C|{3}|Artifact|||Whenever you cast an artifact spell, you may put a charge counter on this artifact.$Remove three charge counters from this artifact: Create a 3/3 colorless Golem artifact creature token.|
+Hangarback Walker|Edge of Eternities Commander|139|R|{X}{X}|Artifact Creature - Construct|0|0|This creature enters with X +1/+1 counters on it.$When this creature dies, create a 1/1 colorless Thopter artifact creature token with flying for each +1/+1 counter on this creature.${1}, {T}: Put a +1/+1 counter on this creature.|
+Lux Artillery|Edge of Eternities Commander|140|R|{4}|Artifact|||Whenever you cast an artifact creature spell, it gains sunburst.$At the beginning of your end step, if there are thirty or more counters among artifacts and creatures you control, this artifact deals 10 damage to each opponent.|
+Lux Cannon|Edge of Eternities Commander|141|R|{4}|Artifact|||{T}: Put a charge counter on this artifact.${T}, Remove three charge counters from this artifact: Destroy target permanent.|
+Mindless Automaton|Edge of Eternities Commander|142|U|{4}|Artifact Creature - Construct|0|0|This creature enters with two +1/+1 counters on it.${1}, Discard a card: Put a +1/+1 counter on this creature.$Remove two +1/+1 counters from this creature: Draw a card.|
+Soul-Guide Lantern|Edge of Eternities Commander|143|U|{1}|Artifact|||When this artifact enters, exile target card from a graveyard.${T}, Sacrifice this artifact: Exile each opponent's graveyard.${1}, {T}, Sacrifice this artifact: Draw a card.|
+Steel Overseer|Edge of Eternities Commander|144|R|{2}|Artifact Creature - Construct|1|1|{T}: Put a +1/+1 counter on each artifact creature you control.|
+Threefold Thunderhulk|Edge of Eternities Commander|145|R|{7}|Artifact Creature - Gnome|0|0|This creature enters with three +1/+1 counters on it.$Whenever this creature enters or attacks, create a number of 1/1 colorless Gnome artifact creature tokens equal to its power.${2}, Sacrifice another artifact: Put a +1/+1 counter on this creature.|
+Titan Forge|Edge of Eternities Commander|146|R|{3}|Artifact|||{3}, {T}: Put a charge counter on this artifact.${T}, Remove three charge counters from this artifact: Create a 9/9 colorless Golem artifact creature token.|
+Adarkar Wastes|Edge of Eternities Commander|147|R||Land|||{T}: Add {C}.${T}: Add {W} or {U}. This land deals 1 damage to you.|
+Ancient Den|Edge of Eternities Commander|148|C||Artifact Land|||{T}: Add {W}.|
+Bojuka Bog|Edge of Eternities Commander|149|C||Land|||This land enters tapped.$When this land enters, exile target player's graveyard.${T}: Add {B}.|
+Buried Ruin|Edge of Eternities Commander|150|U||Land|||{T}: Add {C}.${2}, {T}, Sacrifice this land: Return target artifact card from your graveyard to your hand.|
+Cabaretti Courtyard|Edge of Eternities Commander|151|C||Land|||When this land enters, sacrifice it. When you do, search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle and you gain 1 life.|
+Canyon Slough|Edge of Eternities Commander|152|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$This land enters tapped.$Cycling {2}|
+Cascade Bluffs|Edge of Eternities Commander|153|R||Land|||{T}: Add {C}.${U/R}, {T}: Add {U}{U}, {U}{R}, or {R}{R}.|
+Cinder Glade|Edge of Eternities Commander|154|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$This land enters tapped unless you control two or more basic lands.|
+Clifftop Retreat|Edge of Eternities Commander|155|R||Land|||This land enters tapped unless you control a Mountain or a Plains.${T}: Add {R} or {W}.|
+Dakmor Salvage|Edge of Eternities Commander|156|U||Land|||This land enters tapped.${T}: Add {B}.$Dredge 2|
+Escape Tunnel|Edge of Eternities Commander|157|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.${T}, Sacrifice this land: Target creature with power 2 or less can't be blocked this turn.|
+Evolving Wilds|Edge of Eternities Commander|158|C||Land|||{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.|
+Exotic Orchard|Edge of Eternities Commander|159|R||Land|||{T}: Add one mana of any color that a land an opponent controls could produce.|
+Glacial Fortress|Edge of Eternities Commander|160|R||Land|||This land enters tapped unless you control a Plains or an Island.${T}: Add {W} or {U}.|
+Great Furnace|Edge of Eternities Commander|161|C||Artifact Land|||{T}: Add {R}.|
+Irrigated Farmland|Edge of Eternities Commander|162|R||Land - Plains Island|||({T}: Add {W} or {U}.)$This land enters tapped.$Cycling {2}|
+Karn's Bastion|Edge of Eternities Commander|163|R||Land|||{T}: Add {C}.${4}, {T}: Proliferate.|
+Karplusan Forest|Edge of Eternities Commander|164|R||Land|||{T}: Add {C}.${T}: Add {R} or {G}. This land deals 1 damage to you.|
+Llanowar Wastes|Edge of Eternities Commander|165|R||Land|||{T}: Add {C}.${T}: Add {B} or {G}. This land deals 1 damage to you.|
+Lonely Sandbar|Edge of Eternities Commander|166|C||Land|||This land enters tapped.${T}: Add {U}.$Cycling {U}|
+Maestros Theater|Edge of Eternities Commander|167|C||Land|||When this land enters, sacrifice it. When you do, search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle and you gain 1 life.|
+The Mycosynth Gardens|Edge of Eternities Commander|168|R||Land - Sphere|||{T}: Add {C}.${1}, {T}: Add one mana of any color.${X}, {T}: This land becomes a copy of target nontoken artifact you control with mana value X.|
+Myriad Landscape|Edge of Eternities Commander|169|U||Land|||This land enters tapped.${T}: Add {C}.${2}, {T}, Sacrifice this land: Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle.|
+Mystic Monastery|Edge of Eternities Commander|170|U||Land|||This land enters tapped.${T}: Add {U}, {R}, or {W}.|
+Razortide Bridge|Edge of Eternities Commander|171|C||Artifact Land|||This land enters tapped.$Indestructible${T}: Add {W} or {U}.|
+Riveteers Overlook|Edge of Eternities Commander|172|C||Land|||When this land enters, sacrifice it. When you do, search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle and you gain 1 life.|
+Rocky Tar Pit|Edge of Eternities Commander|173|U||Land|||This land enters tapped.${T}, Sacrifice this land: Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle.|
+Rugged Prairie|Edge of Eternities Commander|174|R||Land|||{T}: Add {C}.${R/W}, {T}: Add {R}{R}, {R}{W}, or {W}{W}.|
+Rustvale Bridge|Edge of Eternities Commander|175|C||Artifact Land|||This land enters tapped.$Indestructible${T}: Add {R} or {W}.|
+Seat of the Synod|Edge of Eternities Commander|176|C||Artifact Land|||{T}: Add {U}.|
+Secluded Steppe|Edge of Eternities Commander|177|C||Land|||This land enters tapped.${T}: Add {W}.$Cycling {W}|
+Sheltered Thicket|Edge of Eternities Commander|178|R||Land - Mountain Forest|||({T}: Add {R} or {G}.)$This land enters tapped.$Cycling {2}|
+Shivan Reef|Edge of Eternities Commander|179|R||Land|||{T}: Add {C}.${T}: Add {U} or {R}. This land deals 1 damage to you.|
+Silverbluff Bridge|Edge of Eternities Commander|180|C||Artifact Land|||This land enters tapped.$Indestructible${T}: Add {U} or {R}.|
+Skycloud Expanse|Edge of Eternities Commander|181|R||Land|||{1}, {T}: Add {W}{U}.|
+Smoldering Marsh|Edge of Eternities Commander|182|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$This land enters tapped unless you control two or more basic lands.|
+Spire of Industry|Edge of Eternities Commander|183|R||Land|||{T}: Add {C}.${T}, Pay 1 life: Add one mana of any color. Activate only if you control an artifact.|
+Sulfur Falls|Edge of Eternities Commander|184|R||Land|||This land enters tapped unless you control an Island or a Mountain.${T}: Add {U} or {R}.|
+Sulfurous Springs|Edge of Eternities Commander|185|R||Land|||{T}: Add {C}.${T}: Add {B} or {R}. This land deals 1 damage to you.|
+Temple of Enlightenment|Edge of Eternities Commander|186|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {W} or {U}.|
+Temple of Epiphany|Edge of Eternities Commander|187|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {U} or {R}.|
+Temple of Triumph|Edge of Eternities Commander|188|R||Land|||This land enters tapped.$When this land enters, scry 1.${T}: Add {R} or {W}.|
+Twilight Mire|Edge of Eternities Commander|189|R||Land|||{T}: Add {C}.${B/G}, {T}: Add {B}{B}, {B}{G}, or {G}{G}.|
+Viridescent Bog|Edge of Eternities Commander|190|R||Land|||{1}, {T}: Add {B}{G}.|
+Wastes|Edge of Eternities Commander|191|C||Basic Land|||{T}: Add {C}.|
Avatar Aang|Avatar: The Last Airbender|363|M|{R}{G}{W}{U}|Legendary Creature - Human Avatar Ally|4|4|Flying, firebending 2$Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang.|
Aang, Master of Elements|Avatar: The Last Airbender|363|M||Legendary Creature - Avatar Ally|6|6|Flying$Spelsl you cast cost {W}{U}{B}{R}{G} less to cast.$At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.|
diff --git a/pom.xml b/pom.xml
index 329e9c45206..e597f31a0f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -353,7 +353,7 @@
com.google.guava
guava
- 30.1.1-jre
+ 33.4.8-jre