From 06a280dd1882c4c22d1f6e2914ff442decad4352 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Wed, 20 Jun 2018 14:25:24 -0400 Subject: [PATCH 01/74] Implement Mirror Golem --- Mage.Sets/src/mage/cards/m/MirrorGolem.java | 156 ++++++++++++++++++++ Mage.Sets/src/mage/sets/Mirrodin.java | 1 + 2 files changed, 157 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MirrorGolem.java diff --git a/Mage.Sets/src/mage/cards/m/MirrorGolem.java b/Mage.Sets/src/mage/cards/m/MirrorGolem.java new file mode 100644 index 00000000000..df65286c7d7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MirrorGolem.java @@ -0,0 +1,156 @@ +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +/** + * + * @author noahg + */ +public final class MirrorGolem extends CardImpl { + + public MirrorGolem(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{6}"); + + this.subtype.add(SubType.GOLEM); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Imprint - When Mirror Golem enters the battlefield, you may exile target card from a graveyard. + Ability ability = new EntersBattlefieldTriggeredAbility(new MirrorGolemImprintEffect(), true, "Imprint — "); + ability.addTarget(new TargetCardInGraveyard()); + this.addAbility(ability); + + // Mirror Golem has protection from each of the exiled card's card types. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MirrorGolemEffect())); + } + + public MirrorGolem(final MirrorGolem card) { + super(card); + } + + @Override + public MirrorGolem copy() { + return new MirrorGolem(this); + } +} + +class MirrorGolemImprintEffect extends OneShotEffect { + + MirrorGolemImprintEffect() { + super(Outcome.Exile); + this.staticText = "you may exile target card from a graveyard"; + } + + MirrorGolemImprintEffect(final MirrorGolemImprintEffect effect) { + super(effect); + } + + @Override + public MirrorGolemImprintEffect copy() { + return new MirrorGolemImprintEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); + if (card != null) { + controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), source.getSourceObject(game).getIdName()); + if (sourcePermanent != null) { + sourcePermanent.imprint(this.getTargetPointer().getFirst(game, source), game); + } + } + return true; + } + return false; + } +} + +class MirrorGolemEffect extends ContinuousEffectImpl { + + public MirrorGolemEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.addDependedToType(DependencyType.AddingAbility); + staticText = "{this} has protection from each of the exiled card's card types."; + } + + public MirrorGolemEffect(final MirrorGolemEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourceObject = game.getPermanent(source.getSourceId()); + + if (sourceObject == null || sourceObject.getImprinted() == null) { + return false; + } + + for (UUID imprinted : sourceObject.getImprinted()){ + if (imprinted != null){ + Card card = game.getCard(imprinted); + if (card != null) { + for (CardType cardType : card.getCardType()){ + FilterCard filterCard; + if (cardType.equals(CardType.SORCERY)){ + filterCard = new FilterCard("sorceries"); + } else if (cardType.equals(CardType.TRIBAL)){ + filterCard = new FilterCard("tribal"); + } else { + filterCard = new FilterCard(cardType.toString()+"s"); + } + filterCard.add(new CardTypePredicate(cardType)); + sourceObject.addAbility(new ProtectionAbility(filterCard)); + } + } + } + } + +// for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { +// Player player = game.getPlayer(playerId); +// if (player != null) { +// UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), sourceObject.getZoneChangeCounter(game)); +// if (exileId != null) { +// for (UUID cardId : game.getState().getExile().getExileZone(exileId)) { +// Card card = game.getCard(cardId); +// if (card != null) { +// for (CardType cardType : card.getCardType()){ +// FilterCard filterCard = new FilterCard(); +// filterCard.add(new CardTypePredicate(cardType)); +// sourceObject.addAbility(new ProtectionAbility(filterCard)); +// } +// } +// } +// } +// } +// } + + return true; + } + + @Override + public MirrorGolemEffect copy() { + return new MirrorGolemEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Mirrodin.java b/Mage.Sets/src/mage/sets/Mirrodin.java index a494c80176f..a32f95c35b7 100644 --- a/Mage.Sets/src/mage/sets/Mirrodin.java +++ b/Mage.Sets/src/mage/sets/Mirrodin.java @@ -169,6 +169,7 @@ public final class Mirrodin extends ExpansionSet { cards.add(new SetCardInfo("Mind's Eye", 205, Rarity.RARE, mage.cards.m.MindsEye.class)); cards.add(new SetCardInfo("Mindslaver", 206, Rarity.RARE, mage.cards.m.Mindslaver.class)); cards.add(new SetCardInfo("Mindstorm Crown", 207, Rarity.UNCOMMON, mage.cards.m.MindstormCrown.class)); + cards.add(new SetCardInfo("Mirror Golem", 208, Rarity.UNCOMMON, mage.cards.m.MirrorGolem.class)); cards.add(new SetCardInfo("Molder Slug", 125, Rarity.RARE, mage.cards.m.MolderSlug.class)); cards.add(new SetCardInfo("Molten Rain", 101, Rarity.COMMON, mage.cards.m.MoltenRain.class)); cards.add(new SetCardInfo("Moriok Scavenger", 68, Rarity.COMMON, mage.cards.m.MoriokScavenger.class)); From ec8a25f2e1bb86a0465684428ba3e1bd975c4c77 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Wed, 20 Jun 2018 23:03:46 -0400 Subject: [PATCH 02/74] Fix Riftsweeper issue --- Mage.Sets/src/mage/cards/m/MirrorGolem.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/m/MirrorGolem.java b/Mage.Sets/src/mage/cards/m/MirrorGolem.java index df65286c7d7..f5f9349ef99 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorGolem.java +++ b/Mage.Sets/src/mage/cards/m/MirrorGolem.java @@ -15,6 +15,7 @@ import mage.cards.CardSetInfo; import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.ExileZone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -102,13 +103,14 @@ class MirrorGolemEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent sourceObject = game.getPermanent(source.getSourceId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source.getSourceId())); if (sourceObject == null || sourceObject.getImprinted() == null) { return false; } for (UUID imprinted : sourceObject.getImprinted()){ - if (imprinted != null){ + if (imprinted != null && exileZone.contains(imprinted)){ Card card = game.getCard(imprinted); if (card != null) { for (CardType cardType : card.getCardType()){ From defed69b70ca3759c880317f688dc2e38aa216f3 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Wed, 20 Jun 2018 23:09:28 -0400 Subject: [PATCH 03/74] Remove commented code --- Mage.Sets/src/mage/cards/m/MirrorGolem.java | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MirrorGolem.java b/Mage.Sets/src/mage/cards/m/MirrorGolem.java index f5f9349ef99..96aa5fb00e7 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorGolem.java +++ b/Mage.Sets/src/mage/cards/m/MirrorGolem.java @@ -128,26 +128,6 @@ class MirrorGolemEffect extends ContinuousEffectImpl { } } } - -// for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { -// Player player = game.getPlayer(playerId); -// if (player != null) { -// UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), sourceObject.getZoneChangeCounter(game)); -// if (exileId != null) { -// for (UUID cardId : game.getState().getExile().getExileZone(exileId)) { -// Card card = game.getCard(cardId); -// if (card != null) { -// for (CardType cardType : card.getCardType()){ -// FilterCard filterCard = new FilterCard(); -// filterCard.add(new CardTypePredicate(cardType)); -// sourceObject.addAbility(new ProtectionAbility(filterCard)); -// } -// } -// } -// } -// } -// } - return true; } From 6a2eda01463b922addf9f5d11eea794a501917a8 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Wed, 20 Jun 2018 22:56:04 -0400 Subject: [PATCH 04/74] Implement Mourner's Shield --- .../src/mage/cards/m/MournersShield.java | 170 ++++++++++++++++++ Mage.Sets/src/mage/sets/Mirrodin.java | 1 + .../mageobject/SharesColorPredicate.java | 39 ++++ 3 files changed, 210 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MournersShield.java create mode 100644 Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorPredicate.java diff --git a/Mage.Sets/src/mage/cards/m/MournersShield.java b/Mage.Sets/src/mage/cards/m/MournersShield.java new file mode 100644 index 00000000000..889e6e77369 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MournersShield.java @@ -0,0 +1,170 @@ +package mage.cards.m; + +import java.util.UUID; + +import mage.MageObject; +import mage.MageObjectReference; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.PreventDamageBySourceEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterObject; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.mageobject.ColorlessPredicate; +import mage.filter.predicate.mageobject.SharesColorPredicate; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetSource; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +/** + * + * @author noahg + */ +public final class MournersShield extends CardImpl { + + public MournersShield(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + + // Imprint - When Mourner's Shield enters the battlefield, you may exile target card from a graveyard. + Ability ability = new EntersBattlefieldTriggeredAbility(new MournersShieldImprintEffect(), true, "Imprint — "); + ability.addTarget(new TargetCardInGraveyard()); + this.addAbility(ability); + + // {2}, {tap}: Prevent all damage that would be dealt this turn by a source of your choice that shares a color with the exiled card. + Ability preventAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MournersShieldEffect(), new GenericManaCost(2)); + preventAbility.addCost(new TapSourceCost()); + this.addAbility(preventAbility); + } + + public MournersShield(final MournersShield card) { + super(card); + } + + @Override + public MournersShield copy() { + return new MournersShield(this); + } +} + +class MournersShieldImprintEffect extends OneShotEffect { + + MournersShieldImprintEffect() { + super(Outcome.Exile); + this.staticText = "you may exile target card from a graveyard"; + } + + MournersShieldImprintEffect(final MournersShieldImprintEffect effect) { + super(effect); + } + + @Override + public MournersShieldImprintEffect copy() { + return new MournersShieldImprintEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card card = game.getCard(this.getTargetPointer().getFirst(game, source)); + if (card != null) { + controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), source.getSourceObject(game).getIdName()); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (sourcePermanent != null) { + sourcePermanent.imprint(this.getTargetPointer().getFirst(game, source), game); + } + } + return true; + } + return false; + } +} + +class MournersShieldEffect extends PreventionEffectImpl { + + private TargetSource target; + private MageObjectReference mageObjectReference; + private boolean noneExiled; + + public MournersShieldEffect() { + super(Duration.EndOfTurn); + noneExiled = false; + this.staticText = "Prevent all damage that would be dealt this turn by a source of your choice that shares a color with the exiled card."; + } + + public MournersShieldEffect(final MournersShieldEffect effect) { + super(effect); + if (effect.target != null) { + this.target = effect.target.copy(); + } else { + this.target = null; + } + this.mageObjectReference = effect.mageObjectReference; + this.noneExiled = effect.noneExiled; + } + + @Override + public MournersShieldEffect copy() { + return new MournersShieldEffect(this); + } + + @Override + public void init(Ability source, Game game) { + ObjectColor colorsAmongImprinted = new ObjectColor(); + Permanent sourceObject = game.getPermanent(source.getSourceId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source.getSourceId())); + if (sourceObject == null || sourceObject.getImprinted() == null) { + noneExiled = true; + return; + } + for (UUID imprinted : sourceObject.getImprinted()){ + if (imprinted != null && exileZone.contains(imprinted)){ + Card card = game.getCard(imprinted); + if (card != null) { + colorsAmongImprinted = colorsAmongImprinted.union(card.getColor(game)); + } + } + } + FilterObject filterObject = new FilterObject("a source of your choice that shares a color with the exiled card"); + filterObject.add(new SharesColorPredicate(colorsAmongImprinted)); + this.target = new TargetSource(filterObject); + this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), game); + if (target.getFirstTarget() != null) { + mageObjectReference = new MageObjectReference(target.getFirstTarget(), game); + } else { + mageObjectReference = null; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (noneExiled || mageObjectReference == null){ + return false; + } + if (super.applies(event, source, game)) { + MageObject mageObject = game.getObject(event.getSourceId()); + return mageObjectReference.refersTo(mageObject, game); + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Mirrodin.java b/Mage.Sets/src/mage/sets/Mirrodin.java index a494c80176f..fa58a2da7ed 100644 --- a/Mage.Sets/src/mage/sets/Mirrodin.java +++ b/Mage.Sets/src/mage/sets/Mirrodin.java @@ -176,6 +176,7 @@ public final class Mirrodin extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 300, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 301, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 302, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mourner's Shield", 209, Rarity.UNCOMMON, mage.cards.m.MournersShield.class)); cards.add(new SetCardInfo("Myr Adapter", 210, Rarity.COMMON, mage.cards.m.MyrAdapter.class)); cards.add(new SetCardInfo("Myr Enforcer", 211, Rarity.COMMON, mage.cards.m.MyrEnforcer.class)); cards.add(new SetCardInfo("Myr Incubator", 212, Rarity.RARE, mage.cards.m.MyrIncubator.class)); diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorPredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorPredicate.java new file mode 100644 index 00000000000..a261bf58fc9 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/SharesColorPredicate.java @@ -0,0 +1,39 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.ObjectColor; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicate; +import mage.game.Game; + +/** + * + * @author noahg + */ + +public class SharesColorPredicate implements Predicate { + + private final ObjectColor color; + + public SharesColorPredicate(ObjectColor color) { + this.color = color; + } + + @Override + public boolean apply(MageObject input, Game game) { + return color != null && input.getColor(game).shares(color); + + } + + @Override + public String toString() { + return "shares a color"; + } +} \ No newline at end of file From 1adcdd3b62d5b0f061797ac7c0d7678f24264656 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Thu, 21 Jun 2018 13:10:08 -0400 Subject: [PATCH 05/74] Implement Thought Dissector --- .../src/mage/cards/t/ThoughtDissector.java | 106 ++++++++++++++++++ Mage.Sets/src/mage/sets/Darksteel.java | 1 + 2 files changed, 107 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/ThoughtDissector.java diff --git a/Mage.Sets/src/mage/cards/t/ThoughtDissector.java b/Mage.Sets/src/mage/cards/t/ThoughtDissector.java new file mode 100644 index 00000000000..523217f2359 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThoughtDissector.java @@ -0,0 +1,106 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.VariableManaCost; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * + * @author noahg + */ +public final class ThoughtDissector extends CardImpl { + + public ThoughtDissector(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + + // {X}, {tap}: Target opponent reveals cards from the top of his or her library until an artifact card or X cards are revealed, whichever comes first. If an artifact card is revealed this way, put it onto the battlefield under your control and sacrifice Thought Dissector. Put the rest of the revealed cards into that player's graveyard. + SimpleActivatedAbility abilitiy = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ThoughtDissectorEffect(), new VariableManaCost()); + abilitiy.addCost(new TapSourceCost()); + abilitiy.addTarget(new TargetOpponent()); + this.addAbility(abilitiy); + } + + public ThoughtDissector(final ThoughtDissector card) { + super(card); + } + + @Override + public ThoughtDissector copy() { + return new ThoughtDissector(this); + } +} + +class ThoughtDissectorEffect extends OneShotEffect { + + private static final ManacostVariableValue amount = new ManacostVariableValue(); + + public ThoughtDissectorEffect() { + super(Outcome.Detriment); + staticText = "Target opponent reveals cards from the top of his or her library until an artifact card or X cards are revealed, whichever comes first. If an artifact card is revealed this way, put it onto the battlefield under your control and sacrifice {this}. Put the rest of the revealed cards into that player's graveyard."; + } + + public ThoughtDissectorEffect(final ThoughtDissectorEffect effect) { + super(effect); + } + + @Override + public ThoughtDissectorEffect copy() { + return new ThoughtDissectorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player targetOpponent = game.getPlayer(targetPointer.getFirst(game, source)); + int max = amount.calculate(game, source, this); + if (targetOpponent != null && controller != null && max > 0) { + int numberOfCard = 0; + Card artifact = null; + CardsImpl nonArtifacts = new CardsImpl(); + CardsImpl reveal = new CardsImpl(); + for (Card card : targetOpponent.getLibrary().getCards(game)) { + reveal.add(card); + if (card.isArtifact()) { + artifact = card; + break; + } else { + numberOfCard++; + if (numberOfCard > max){ + break; + } + nonArtifacts.add(card); + } + } + targetOpponent.revealCards(source, reveal, game); + if (artifact != null) { + game.applyEffects(); + controller.moveCards(artifact, Zone.BATTLEFIELD, source, game); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + if (sourcePermanent != null) { + sourcePermanent.sacrifice(source.getSourceId(), game); + } + } + targetOpponent.moveCards(nonArtifacts, Zone.GRAVEYARD, source, game); + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Darksteel.java b/Mage.Sets/src/mage/sets/Darksteel.java index 7624752521b..dc188d9d8ad 100644 --- a/Mage.Sets/src/mage/sets/Darksteel.java +++ b/Mage.Sets/src/mage/sets/Darksteel.java @@ -169,6 +169,7 @@ public final class Darksteel extends ExpansionSet { cards.add(new SetCardInfo("Tel-Jilad Outrider", 87, Rarity.COMMON, mage.cards.t.TelJiladOutrider.class)); cards.add(new SetCardInfo("Tel-Jilad Wolf", 88, Rarity.COMMON, mage.cards.t.TelJiladWolf.class)); cards.add(new SetCardInfo("Test of Faith", 17, Rarity.UNCOMMON, mage.cards.t.TestOfFaith.class)); + cards.add(new SetCardInfo("Thought Dissector", 152, Rarity.RARE, mage.cards.t.ThoughtDissector.class)); cards.add(new SetCardInfo("Thunderstaff", 153, Rarity.UNCOMMON, mage.cards.t.Thunderstaff.class)); cards.add(new SetCardInfo("Trinisphere", 154, Rarity.RARE, mage.cards.t.Trinisphere.class)); cards.add(new SetCardInfo("Turn the Tables", 18, Rarity.RARE, mage.cards.t.TurnTheTables.class)); From 9b68e0860b2844d73f7bc7b15d0de94c5df2b747 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Thu, 21 Jun 2018 15:29:17 -0400 Subject: [PATCH 06/74] Implement Neurok Transmuter --- .../src/mage/cards/d/DarksteelGarrison.java | 49 +----------- .../src/mage/cards/n/NeurokTransmuter.java | 71 +++++++++++++++++ Mage.Sets/src/mage/sets/Darksteel.java | 1 + .../LoseArtifactTypeTargetEffect.java | 77 +++++++++++++++++++ .../mage/abilities/keyword/EquipAbility.java | 12 ++- .../abilities/keyword/FortifyAbility.java | 42 +++++++++- .../src/main/java/mage/constants/SubType.java | 10 +++ 7 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/n/NeurokTransmuter.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java diff --git a/Mage.Sets/src/mage/cards/d/DarksteelGarrison.java b/Mage.Sets/src/mage/cards/d/DarksteelGarrison.java index dfdba117cd8..78c63b2bfc2 100644 --- a/Mage.Sets/src/mage/cards/d/DarksteelGarrison.java +++ b/Mage.Sets/src/mage/cards/d/DarksteelGarrison.java @@ -12,6 +12,7 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.FortifyAbility; import mage.abilities.keyword.IndestructibleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -53,7 +54,7 @@ public final class DarksteelGarrison extends CardImpl { this.addAbility(ability); // Fortify {3} - this.addAbility(new FortifyAbility(Outcome.AddAbility, new GenericManaCost(3))); + this.addAbility(new FortifyAbility(3)); } @@ -66,49 +67,3 @@ public final class DarksteelGarrison extends CardImpl { return new DarksteelGarrison(this); } } - -class FortifyAbility extends ActivatedAbilityImpl { - - private static final FilterControlledPermanent filter = new FilterControlledPermanent("land you control"); - - static { - filter.add(new CardTypePredicate(CardType.LAND)); - } - - public FortifyAbility(Outcome outcome, Cost cost) { - this(outcome, cost, new TargetControlledPermanent(filter)); - } - - public FortifyAbility(Outcome outcome, Cost cost, Target target) { - super(Zone.BATTLEFIELD, new AttachEffect(outcome, "Fortify"), cost); - this.addTarget(target); - this.timing = TimingRule.SORCERY; - } - - @Override - public ActivationStatus canActivate(UUID playerId, Game game) { - ActivationStatus activationStatus = super.canActivate(playerId, game); - if (activationStatus.canActivate()) { - Permanent permanent = game.getPermanent(sourceId); - if (permanent != null && permanent.hasSubtype(SubType.FORTIFICATION, game)) { - return activationStatus; - } - } - return ActivationStatus.getFalse(); - } - - public FortifyAbility(final FortifyAbility ability) { - super(ability); - } - - @Override - public FortifyAbility copy() { - return new FortifyAbility(this); - } - - @Override - public String getRule() { - return "Fortify " + costs.getText() + manaCosts.getText() + " (" + manaCosts.getText() + ": Attach to target land you control. Fortify only as a sorcery.)"; - } - -} diff --git a/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java b/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java new file mode 100644 index 00000000000..886d089c0b3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NeurokTransmuter.java @@ -0,0 +1,71 @@ +package mage.cards.n; + +import java.util.UUID; +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ColoredManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; +import mage.abilities.effects.common.continuous.BecomesColorTargetEffect; +import mage.abilities.effects.common.continuous.LoseArtifactTypeTargetEffect; +import mage.abilities.effects.common.continuous.LoseCreatureTypeSourceEffect; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.TargetPermanent; +import mage.target.common.TargetArtifactPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author noahg + */ +public final class NeurokTransmuter extends CardImpl { + + final static FilterCreaturePermanent filter = new FilterCreaturePermanent("artifact creature"); + + static { + filter.add(new CardTypePredicate(CardType.ARTIFACT)); + } + + public NeurokTransmuter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // {U}: Target creature becomes an artifact in addition to its other types until end of turn. + Ability becomeArtifactAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCardTypeTargetEffect(Duration.EndOfTurn, CardType.ARTIFACT), new ManaCostsImpl("{U}")); + becomeArtifactAbility.addTarget(new TargetCreaturePermanent()); + this.addAbility(becomeArtifactAbility); + // {U}: Until end of turn, target artifact creature becomes blue and isn't an artifact. + Effect blueEffect = new BecomesColorTargetEffect(ObjectColor.BLUE, Duration.EndOfTurn); + blueEffect.setText("Until end of turn, target artifact creature becomes blue and "); + Ability becomeBlueAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, blueEffect, new ManaCostsImpl("{U}")); + becomeBlueAbility.addTarget(new TargetCreaturePermanent(filter)); + Effect loseArtifactEffect = new LoseArtifactTypeTargetEffect(Duration.EndOfTurn); + loseArtifactEffect.setText("isn't an artifact"); + becomeBlueAbility.addEffect(loseArtifactEffect); + this.addAbility(becomeBlueAbility); + } + + public NeurokTransmuter(final NeurokTransmuter card) { + super(card); + } + + @Override + public NeurokTransmuter copy() { + return new NeurokTransmuter(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Darksteel.java b/Mage.Sets/src/mage/sets/Darksteel.java index 7624752521b..7a443fd8873 100644 --- a/Mage.Sets/src/mage/sets/Darksteel.java +++ b/Mage.Sets/src/mage/sets/Darksteel.java @@ -114,6 +114,7 @@ public final class Darksteel extends ExpansionSet { cards.add(new SetCardInfo("Myr Moonvessel", 133, Rarity.COMMON, mage.cards.m.MyrMoonvessel.class)); cards.add(new SetCardInfo("Nemesis Mask", 134, Rarity.UNCOMMON, mage.cards.n.NemesisMask.class)); cards.add(new SetCardInfo("Neurok Prodigy", 26, Rarity.COMMON, mage.cards.n.NeurokProdigy.class)); + cards.add(new SetCardInfo("Neurok Transmuter", 27, Rarity.UNCOMMON, mage.cards.n.NeurokTransmuter.class)); cards.add(new SetCardInfo("Nim Abomination", 49, Rarity.UNCOMMON, mage.cards.n.NimAbomination.class)); cards.add(new SetCardInfo("Nourish", 78, Rarity.COMMON, mage.cards.n.Nourish.class)); cards.add(new SetCardInfo("Oxidda Golem", 135, Rarity.COMMON, mage.cards.o.OxiddaGolem.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java new file mode 100644 index 00000000000..ac44c271aab --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LoseArtifactTypeTargetEffect.java @@ -0,0 +1,77 @@ + +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * + * @author noahg + */ +public class LoseArtifactTypeTargetEffect extends ContinuousEffectImpl{ + + public LoseArtifactTypeTargetEffect(Duration duration) { + super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral); + dependencyTypes.add(DependencyType.ArtifactAddingRemoving); + setText("isn't an artifact"); + } + + public LoseArtifactTypeTargetEffect(final LoseArtifactTypeTargetEffect effect) { + super(effect); + } + + @Override + public LoseArtifactTypeTargetEffect copy() { + return new LoseArtifactTypeTargetEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); //To change body of generated methods, choose Tools | Templates. + if (duration.isOnlyValidIfNoZoneChange()) { + // If source permanent is no longer onto battlefield discard the effect + if (source.getSourcePermanentIfItStillExists(game) == null) { + discard(); + } + } + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + for (UUID targetId : targetPointer.getTargets(game, source)) { + if (targetId != null) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + switch (layer) { + case TypeChangingEffects_4: + if (sublayer == SubLayer.NA) { + permanent.getCardType().remove(CardType.ARTIFACT); + permanent.getSubtype(game).removeAll(SubType.getArtifactTypes(false)); + } + break; + } + return true; + } + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.TypeChangingEffects_4; + } + +} diff --git a/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java b/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java index 2496f961922..9f75eeaa3c9 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EquipAbility.java @@ -35,14 +35,12 @@ public class EquipAbility extends ActivatedAbilityImpl { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - ActivationStatus activationStatus = super.canActivate(playerId, game); - if (activationStatus.canActivate()) { - Permanent permanent = game.getPermanent(sourceId); - if (permanent != null && permanent.hasSubtype(SubType.EQUIPMENT, game)) { - return activationStatus; - } + Permanent permanent = game.getPermanent(sourceId); + if (permanent != null && permanent.hasSubtype(SubType.EQUIPMENT, game) && !permanent.isCreature()) { + return super.canActivate(playerId, game); + } else { + return ActivationStatus.getFalse(); } - return activationStatus; } public EquipAbility(final EquipAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/FortifyAbility.java b/Mage/src/main/java/mage/abilities/keyword/FortifyAbility.java index 95c2e7be559..43c8ab32546 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FortifyAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FortifyAbility.java @@ -2,13 +2,22 @@ package mage.abilities.keyword; +import mage.abilities.costs.mana.GenericManaCost; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.TimingRule; import mage.constants.Zone; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.Cost; import mage.abilities.effects.common.AttachEffect; import mage.filter.common.FilterControlledLandPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.Target; import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** * @@ -17,10 +26,29 @@ import mage.target.TargetPermanent; //20091005 - 702.64 public class FortifyAbility extends ActivatedAbilityImpl { - public FortifyAbility(Zone zone, AttachEffect effect, Cost cost) { - super(zone, effect, cost); - this.addTarget(new TargetPermanent(new FilterControlledLandPermanent())); - timing = TimingRule.SORCERY; + + public FortifyAbility(int cost) { + this(Outcome.AddAbility, new GenericManaCost(cost)); + } + + public FortifyAbility(Outcome outcome, Cost cost) { + this(outcome, cost, new TargetPermanent(new FilterControlledLandPermanent())); + } + + public FortifyAbility(Outcome outcome, Cost cost, Target target) { + super(Zone.BATTLEFIELD, new AttachEffect(outcome, "Fortify"), cost); + this.addTarget(target); + this.timing = TimingRule.SORCERY; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + Permanent permanent = game.getPermanent(sourceId); + if (permanent != null && permanent.hasSubtype(SubType.FORTIFICATION, game) && !permanent.isCreature() && !permanent.isLand()) { + return super.canActivate(playerId, game); + } else { + return ActivationStatus.getFalse(); + } } public FortifyAbility(final FortifyAbility ability) { @@ -31,4 +59,10 @@ public class FortifyAbility extends ActivatedAbilityImpl { public FortifyAbility copy() { return new FortifyAbility(this); } + + + @Override + public String getRule() { + return "Fortify " + costs.getText() + manaCosts.getText() + " (" + manaCosts.getText() + ": Attach to target land you control. Fortify only as a sorcery.)"; + } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index fb38796b592..cb533251485 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -462,6 +462,16 @@ public enum SubType { return subTypeSet; } + public static Set getArtifactTypes(boolean withCustomSets) { + Set subTypes = EnumSet.noneOf(SubType.class); + for (SubType subType : values()) { + if (subType.getSubTypeSet() == SubTypeSet.ArtifactType && (withCustomSets || !subType.customSet)) { + subTypes.add(subType); + } + } + return subTypes; + } + public static Set getPlaneswalkerTypes(boolean withCustomSets) { Set subTypes = EnumSet.noneOf(SubType.class); for (SubType subType : values()) { From ca8721e4f986742c29c46063c4f4861f256a3124 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Fri, 22 Jun 2018 00:09:32 -0400 Subject: [PATCH 07/74] Implement Giant Albatross --- .../src/mage/cards/g/GiantAlbatross.java | 110 ++++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 2 + .../watchers/common/DealtDamageToWatcher.java | 64 ++++++++++ 3 files changed, 176 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GiantAlbatross.java create mode 100644 Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java diff --git a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java new file mode 100644 index 00000000000..33e3ec355d2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java @@ -0,0 +1,110 @@ +package mage.cards.g; + +import java.util.List; +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.cards.Card; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.Watcher; +import mage.watchers.common.DealtDamageToWatcher; +import mage.watchers.common.PlayerDamagedBySourceWatcher; + +/** + * + * @author noahg + */ +public final class GiantAlbatross extends CardImpl { + + public GiantAlbatross(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.BIRD); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Giant Albatross dies, you may pay {1}{U}. If you do, for each creature that dealt damage to Giant Albatross this turn, destroy that creature unless its controller pays 2 life. A creature destroyed this way can't be regenerated. + Ability ability = new DiesTriggeredAbility(new DoIfCostPaid(new GiantAlbatrossEffect(), new ManaCostsImpl("{1}{U}"))); + DealtDamageToWatcher watcher = new DealtDamageToWatcher(); + watcher.setSourceId(this.objectId); + ability.addWatcher(watcher); + this.addAbility(ability); + } + + public GiantAlbatross(final GiantAlbatross card) { + super(card); + } + + @Override + public GiantAlbatross copy() { + return new GiantAlbatross(this); + } +} + +class GiantAlbatrossEffect extends OneShotEffect { + + public GiantAlbatrossEffect() { + super(Outcome.Detriment); + this.staticText = "for each creature that dealt damage to {this} this turn, destroy that creature unless its controller pays 2 life. A creature destroyed this way can’t be regenerated"; + } + + public GiantAlbatrossEffect(final GiantAlbatrossEffect effect) { + super(effect); + } + + @Override + public GiantAlbatrossEffect copy() { + return new GiantAlbatrossEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + DealtDamageToWatcher watcher = (DealtDamageToWatcher) game.getState().getWatchers().get(DealtDamageToWatcher.class.getSimpleName(), source.getSourceId()); + System.out.println("Dealt damage: "+watcher.dealtDamageToSource); + for (MageObjectReference mageObjectReference : watcher.dealtDamageToSource){ + System.out.println(mageObjectReference.getCard(game).getName()); + System.out.println(mageObjectReference.getCard(game).getLogName()); + } + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + List creatures = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game); + + Cost cost = new PayLifeCost(2); + for (Permanent creature : creatures) { + if (watcher.didDamage(creature, game)) { + final StringBuilder sb = new StringBuilder("Pay 2 life? (Otherwise ").append(creature.getName()).append(" will be destroyed)"); + if (cost.canPay(source, creature.getControllerId(), creature.getControllerId(), game) && player.chooseUse(Outcome.Benefit, sb.toString(), source, game)) { + cost.pay(source, game, creature.getControllerId(), creature.getControllerId(), true, null); + } + if (!cost.isPaid()) { + creature.destroy(source.getSourceId(), game, true); + } + } + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 5de1fb7d4e6..f867f332d38 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -103,6 +103,8 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Forget", 26, Rarity.RARE, mage.cards.f.Forget.class)); cards.add(new SetCardInfo("Funeral March", 48, Rarity.UNCOMMON, mage.cards.f.FuneralMarch.class)); cards.add(new SetCardInfo("Ghost Hounds", 49, Rarity.UNCOMMON, mage.cards.g.GhostHounds.class)); + cards.add(new SetCardInfo("Giant Albatross", "34a", Rarity.COMMON, mage.cards.g.GiantAlbatross.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Giant Albatross", "34b", Rarity.COMMON, mage.cards.g.GiantAlbatross.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grandmother Sengir", 50, Rarity.RARE, mage.cards.g.GrandmotherSengir.class)); cards.add(new SetCardInfo("Greater Werewolf", 51, Rarity.UNCOMMON, mage.cards.g.GreaterWerewolf.class)); cards.add(new SetCardInfo("Hazduhr the Abbot", 8, Rarity.RARE, mage.cards.h.HazduhrTheAbbot.class)); diff --git a/Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java b/Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java new file mode 100644 index 00000000000..3862841bb7f --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java @@ -0,0 +1,64 @@ +package mage.watchers.common; + +import mage.MageObject; +import mage.MageObjectReference; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class DealtDamageToWatcher extends Watcher { + + public final Set dealtDamageToSource = new HashSet<>(); + + public DealtDamageToWatcher() { + super(DealtDamageToWatcher.class.getSimpleName(), WatcherScope.CARD); + } + + public DealtDamageToWatcher(final DealtDamageToWatcher watcher) { + super(watcher); + this.dealtDamageToSource.addAll(watcher.dealtDamageToSource); + } + + @Override + public DealtDamageToWatcher copy() { + return new DealtDamageToWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + boolean eventHasAppropriateType = event.getType() == GameEvent.EventType.DAMAGED_CREATURE || + event.getType() == GameEvent.EventType.DAMAGED_PLANESWALKER; + if (eventHasAppropriateType && sourceId.equals(event.getTargetId())) { + MageObjectReference mor = new MageObjectReference(event.getSourceId(), game); + dealtDamageToSource.add(mor); + } + } + + @Override + public void reset() { + super.reset(); + dealtDamageToSource.clear(); + } + + public boolean didDamage(UUID sourceId, Game game) { + MageObject mageObject = game.getObject(sourceId); + if (mageObject != null) { + return didDamage(new MageObjectReference(mageObject, game)); + } + return false; + } + + private boolean didDamage(MageObjectReference objectReference) { + return dealtDamageToSource.contains(objectReference); + } + + public boolean didDamage(Permanent permanent, Game game) { + return dealtDamageToSource.contains(new MageObjectReference(permanent, game)); + } +} From 4b82d903ff2c07a67074e5550cfa3e998b2d4057 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 22 Jun 2018 06:46:35 +0200 Subject: [PATCH 08/74] * Palladia Mors, the Ruiner - Added missing watcher call. --- Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java b/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java index 2f19168e19b..57e06bac912 100644 --- a/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java +++ b/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java @@ -10,15 +10,15 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HexproofAbility; -import mage.abilities.keyword.VigilanceAbility; import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; @@ -58,7 +58,7 @@ public final class PalladiaMorsTheRuiner extends CardImpl { PalladiaMorsTheRuinerCondition.instance, "{this} has hexproof if it hasn't dealt damage yet" ) - )); + ), new PalladiaMorsTheRuinerWatcher()); } public PalladiaMorsTheRuiner(final PalladiaMorsTheRuiner card) { From b7ae908c6dea7cd8efe8e996599513f30eecfe03 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:02:31 -0400 Subject: [PATCH 09/74] updated M19 spoiler --- Mage.Sets/src/mage/sets/CoreSet2019.java | 2 +- Utils/mtg-cards-data.txt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 4f7ae769be0..d56849aeb48 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -177,7 +177,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Palladia-Mors, the Ruiner", 219, Rarity.MYTHIC, mage.cards.p.PalladiaMorsTheRuiner.class)); cards.add(new SetCardInfo("Patient Rebuilding", 67, Rarity.RARE, mage.cards.p.PatientRebuilding.class)); cards.add(new SetCardInfo("Pegasus Courser", 32, Rarity.COMMON, mage.cards.p.PegasusCourser.class)); - cards.add(new SetCardInfo("Pelakka Wurm", 192, Rarity.UNCOMMON, mage.cards.p.PelakkaWurm.class)); + cards.add(new SetCardInfo("Pelakka Wurm", 192, Rarity.RARE, mage.cards.p.PelakkaWurm.class)); cards.add(new SetCardInfo("Phylactery Lich", 113, Rarity.RARE, mage.cards.p.PhylacteryLich.class)); cards.add(new SetCardInfo("Plague Mare", 114, Rarity.UNCOMMON, mage.cards.p.PlagueMare.class)); cards.add(new SetCardInfo("Plummet", 193, Rarity.COMMON, mage.cards.p.Plummet.class)); diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index d400d5e7986..a1d7a06603e 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -33595,6 +33595,7 @@ Cavalry Drillmaster|Core Set 2019|8|C|{1}{W}|Creature - Human Knight|2|1|When Ca Cleansing Nova|Core Set 2019|9|R|{3}{W}{W}|Sorcery|||Choose one —$• Destroy all creatures.$• Destroy all artifacts and enchantments.| Daybreak Chaplain|Core Set 2019|10|C|{1}{W}|Creature - Human Cleric|1|3|Lifelink| Dwarven Priest|Core Set 2019|11|C|{3}{W}|Creature - Dwarf Cleric|2|4|When Dwarven Priest enters the battlefield, you gain 1 life for each creature you control.| +Gallant Cavalry|Core Set 2019|12|C|{3}{W}|Creature - Human Knight|2|2|Vigilance$When Gallant Cavalry enters the battlefield, create a 2/2 white Knight creature token with vigilance.| Herald of Faith|Core Set 2019|13|U|{3}{W}{W}|Creature - Angel|4|3|Flying$Whenever Herald of Faith attacks, you gain 2 life.| Hieromancer's Cage|Core Set 2019|14|U|{3}{W}|Enchantment|||When Hieromancer's Cage enters the battlefield, exile target nonland permanent an opponent controls until Hieromancer's Cage leaves the battlefield.| Inspired Charge|Core Set 2019|15|C|{2}{W}{W}|Instant|||Creatures you control get +2/+1 until end of turn.| @@ -33656,7 +33657,7 @@ Snapping Drake|Core Set 2019|75|C|{3}{U}|Creature - Drake|3|2|Flying| Supreme Phantom|Core Set 2019|76|R|{1}{U}|Creature - Spirit|1|3|Flying$Other Spirits you control get +1/+1.| Surge Mare|Core Set 2019|77|U|{U}{U}|Creature - Horse Fish|0|5|Surge Mare can't be blocked by green creatures.$Whenever Surge Mare deals damage to an opponent, you may draw a card. If you do, discard a card.${1}{U}: Surge Mare gets +2/-2 until end of turn.| Switcheroo|Core Set 2019|78|U|{4}{U}|Sorcery|||Exchange control of two target creatures.| -Tezzeret, Artifice Master|Core Set 2019|79|M|{3}{U}{U}|Legendary Planeswalker - Tezzeret|5|+1: Create a colorless Thopter artifact creature token with flying.$0: Draw a card. If you control three or more artifacts, draw two cards instead.$−9: You get an emblem with "At the beginning of your end step, search your library for a permanent card, put it into the battlefield, then shuffle your library."| +Tezzeret, Artifice Master|Core Set 2019|79|M|{3}{U}{U}|Legendary Planeswalker - Tezzeret|5|+1: Create a 1/1 colorless Thopter artifact creature token with flying.$0: Draw a card. If you control three or more artifacts, draw two cards instead.$−9: You get an emblem with "At the beginning of your end step, search your library for a permanent card, put it onto the battlefield, then shuffle your library."| Tolarian Scholar|Core Set 2019|80|C|{2}{U}|Creature - Human Wizard|2|3|| Totally Lost|Core Set 2019|81|C|{4}{U}|Instant|||Put target nonland permanent on top of its owner's library.| Uncomfortable Chill|Core Set 2019|82|C|{2}{U}|Instant|||Creatures your opponents control get -2/-0 until end of turn.$Draw a card.| @@ -33752,7 +33753,7 @@ Goreclaw, Terror of Qal Sisma|Core Set 2019|186|R|{3}{G}|Legendary Creature - Be Highland Game|Core Set 2019|188|C|{1}{G}|Creature - Elk|2|1|When Highland Game dies, you gain 2 life.| Hungering Hydra|Core Set 2019|189|R|{X}{G}|Creature - Hydra|0|0|Hungering Hydra enters the battlefield with X +1/+1 counters on it.$Hungering Hydra can't be blocked by more than one creature.$Whenever Hungering Hydra is dealt damage, put that many +1/+1 counters on it.| Oakenform|Core Set 2019|191|C|{2}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +3/+3.| -Pelakka Wurm|Core Set 2019|192|U|{4}{G}{G}{G}|Creature - Wurm|7|7|Trample$When Pelakka Wurm enters the battlefield, you gain 7 life.$When Pelakka Wurm dies, draw a card.| +Pelakka Wurm|Core Set 2019|192|R|{4}{G}{G}{G}|Creature - Wurm|7|7|Trample$When Pelakka Wurm enters the battlefield, you gain 7 life.$When Pelakka Wurm dies, draw a card.| Plummet|Core Set 2019|193|C|{1}{G}|Instant|||Destroy target creature with flying.| Prodigious Growth|Core Set 2019|194|R|{4}{G}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +7/+7 and has trample.| Rabid Bite|Core Set 2019|195|C|{1}{G}|Sorcery|||Target creature you control deals damage equal to its power to target creature you don't control.| @@ -33804,6 +33805,7 @@ Rogue's Gloves|Core Set 2019|243|U|{2}|Artifact - Equipment|||Whenever equipped Sigiled Sword of Valeron|Core Set 2019|244|R|{3}|Artifact - Equipment|||Equipped creature gets +2/+0, has vigilance, and is a Knight in addition to its other types.$Whenever equipped creature attacks, create a 2/2 white Knight creature token with vigilance that's attacking.$Equip {3}| Skyscanner|Core Set 2019|245|C|{3}|Artifact Creature - Thopter|1|1|Flying$When Skyscanner enters the battlefield, draw a card.| Suspicious Bookcase|Core Set 2019|246|U|{2}|Artifact Creature - Wall|0|4|Defender${3}, {T}: Target creature can't be blocked this turn.| +Transmogrifying Wand|Core Set 2019|247|R|{3}|Artifact|||Transmogrifying Wand enters the battlefield with three charge counters on it.${1}, {T}: Remove a charge counter from Transmogrifying Wand: Destroy target creature. Its controller creates a 2/4 white Ox creature token. Activate this ability only any time you could cast a sorcery.| Isolated Tower|Core Set 2019|249|R||Land|||{T}: Add {C}.${1}, {T}: Until end of turn, your opponents and creatures with hexproof they control can be the targets of spells and abilities as though they didn't have hexproof.| Reliquary Tower|Core Set 2019|254|U||Land|||You have no maximum hand size.${T}: Add {C}.| Rupture Spire|Core Set 2019|255|U||Land|||Rupture Spire enters the battlefield tapped.$When Rupture Spire enters the battlefield, sacrifice it unless you pay {1}.${T}: Add one mana of any color.| From a418fcf83283839f60d1fbb9ee5e6848ead4c2e1 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:06:24 -0400 Subject: [PATCH 10/74] fixed Fell Specter not having power or toughness (fixes #5052) --- Mage.Sets/src/mage/cards/f/FellSpecter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage.Sets/src/mage/cards/f/FellSpecter.java b/Mage.Sets/src/mage/cards/f/FellSpecter.java index dd57825f01a..8675bcf28ca 100644 --- a/Mage.Sets/src/mage/cards/f/FellSpecter.java +++ b/Mage.Sets/src/mage/cards/f/FellSpecter.java @@ -1,6 +1,7 @@ package mage.cards.f; import java.util.UUID; +import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiscardsACardOpponentTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -24,6 +25,8 @@ public final class FellSpecter extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.SPECTER); + this.power = new MageInt(1); + this.toughness = new MageInt(3); // Flying this.addAbility(FlyingAbility.getInstance()); From d10553398d861d842c96eff29e0e0c7b333dc204 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:12:29 -0400 Subject: [PATCH 11/74] Implemewnted Gallant Cavalry --- .../src/mage/cards/g/GallantCavalry.java | 43 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 44 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GallantCavalry.java diff --git a/Mage.Sets/src/mage/cards/g/GallantCavalry.java b/Mage.Sets/src/mage/cards/g/GallantCavalry.java new file mode 100644 index 00000000000..8301a61e56e --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GallantCavalry.java @@ -0,0 +1,43 @@ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.constants.SubType; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.KnightToken; + +/** + * + * @author TheElk801 + */ +public final class GallantCavalry extends CardImpl { + + public GallantCavalry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Gallant Cavalry enters the battlefield, create a 2/2 white Knight creature token with vigilance. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new KnightToken()))); + } + + public GallantCavalry(final GallantCavalry card) { + super(card); + } + + @Override + public GallantCavalry copy() { + return new GallantCavalry(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index d56849aeb48..5b94f241b38 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -102,6 +102,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Fire Elemental", 141, Rarity.COMMON, mage.cards.f.FireElemental.class)); cards.add(new SetCardInfo("Fountain of Renewal", 235, Rarity.UNCOMMON, mage.cards.f.FountainOfRenewal.class)); cards.add(new SetCardInfo("Frilled Sea Serpent", 56, Rarity.COMMON, mage.cards.f.FrilledSeaSerpent.class)); + cards.add(new SetCardInfo("Gallant Cavalry", 12, Rarity.COMMON, mage.cards.g.GallantCavalry.class)); cards.add(new SetCardInfo("Gearsmith Guardian", 237, Rarity.COMMON, mage.cards.g.GearsmithGuardian.class)); cards.add(new SetCardInfo("Gearsmith Prodigy", 57, Rarity.COMMON, mage.cards.g.GearsmithProdigy.class)); cards.add(new SetCardInfo("Ghastbark Twins", 181, Rarity.UNCOMMON, mage.cards.g.GhastbarkTwins.class)); From 5c7993af38a84a828325e6d2308b2793fed4bcf3 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:25:52 -0400 Subject: [PATCH 12/74] Implemented Doublecast --- Mage.Sets/src/mage/cards/d/Doublecast.java | 83 ++++++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 84 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/Doublecast.java diff --git a/Mage.Sets/src/mage/cards/d/Doublecast.java b/Mage.Sets/src/mage/cards/d/Doublecast.java new file mode 100644 index 00000000000..eee0d791c4c --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Doublecast.java @@ -0,0 +1,83 @@ +package mage.cards.d; + +import java.util.UUID; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public final class Doublecast extends CardImpl { + + public Doublecast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{R}"); + + // When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy. + this.getSpellAbility().addEffect( + new CreateDelayedTriggeredAbilityEffect(new DoublecastAbility()) + .setText("When you cast your next instant or sorcery spell this turn, " + + "copy that spell. You may choose new targets for the copy") + ); + } + + public Doublecast(final Doublecast card) { + super(card); + } + + @Override + public Doublecast copy() { + return new Doublecast(this); + } +} + +class DoublecastAbility extends DelayedTriggeredAbility { + + public DoublecastAbility() { + super(new CopyTargetSpellEffect(true), Duration.EndOfTurn); + } + + public DoublecastAbility(final DoublecastAbility ability) { + super(ability); + } + + @Override + public DoublecastAbility copy() { + return new DoublecastAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getPlayerId().equals(this.getControllerId())) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null && (spell.isInstant() || spell.isSorcery())) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + } + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "When you cast your next instant or sorcery spell this turn, " + + "copy that spell. You may choose new targets for the copy."; + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 5b94f241b38..a60b2139325 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -83,6 +83,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Disperse", 50, Rarity.COMMON, mage.cards.d.Disperse.class)); cards.add(new SetCardInfo("Divination", 51, Rarity.COMMON, mage.cards.d.Divination.class)); cards.add(new SetCardInfo("Djinn of Wishes", 52, Rarity.RARE, mage.cards.d.DjinnOfWishes.class)); + cards.add(new SetCardInfo("Doublecast", 137, Rarity.UNCOMMON, mage.cards.d.Doublecast.class)); cards.add(new SetCardInfo("Draconic Disciple", 215, Rarity.UNCOMMON, mage.cards.d.DraconicDisciple.class)); cards.add(new SetCardInfo("Dragon's Hoard", 232, Rarity.RARE, mage.cards.d.DragonsHoard.class)); cards.add(new SetCardInfo("Druid of Horns", 176, Rarity.UNCOMMON, mage.cards.d.DruidOfHorns.class)); From c59457b2e5a6d8eb2aff45ecac1ac3a7f3c0b51b Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:26:32 -0400 Subject: [PATCH 13/74] fixed Runic Armasaur triggering off of your abilities --- Mage.Sets/src/mage/cards/r/RunicArmasaur.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/r/RunicArmasaur.java b/Mage.Sets/src/mage/cards/r/RunicArmasaur.java index 0e39352c0f7..4c589622b4a 100644 --- a/Mage.Sets/src/mage/cards/r/RunicArmasaur.java +++ b/Mage.Sets/src/mage/cards/r/RunicArmasaur.java @@ -66,7 +66,9 @@ class RunicArmasaurTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility != null && stackAbility.getAbilityType() == AbilityType.ACTIVATED) { + if (stackAbility != null + && stackAbility.getAbilityType() == AbilityType.ACTIVATED + && game.getOpponents(this.getControllerId()).contains(stackAbility.getControllerId())) { MageObject abilitySourceObject = stackAbility.getSourceObject(game); return abilitySourceObject != null && (abilitySourceObject.isLand() || abilitySourceObject.isCreature()); } From 266a7cdb85c53b6583fff2e2983e10c1c2057a7c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:44:15 -0400 Subject: [PATCH 14/74] Implemented Transmogrifying Wand --- .../src/mage/cards/t/TransmogrifyingWand.java | 91 +++++++++++++++++++ Mage.Sets/src/mage/cards/t/TumbleMagnet.java | 32 +++---- Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + .../mage/game/permanent/token/OxToken.java | 32 +++++++ 4 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/OxToken.java diff --git a/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java b/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java new file mode 100644 index 00000000000..2ba2dfd68e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TransmogrifyingWand.java @@ -0,0 +1,91 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenTargetEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.OxToken; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public final class TransmogrifyingWand extends CardImpl { + + public TransmogrifyingWand(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // Transmogrifying Wand enters the battlefield with three charge counters on it. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance(3)), + "{this} enters the battlefield with three charge counters on it" + )); + + // {1}, {T}, Remove a charge counter from Transmogrifying Wand: Destroy target creature. Its controller creates a 2/4 white Ox creature token. Activate this ability only any time you could cast a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.BATTLEFIELD, + new TransmogrifyingWandEffect(), + new GenericManaCost(1) + ); + ability.addCost(new TapSourceCost()); + ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance())); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public TransmogrifyingWand(final TransmogrifyingWand card) { + super(card); + } + + @Override + public TransmogrifyingWand copy() { + return new TransmogrifyingWand(this); + } +} + +class TransmogrifyingWandEffect extends OneShotEffect { + + public TransmogrifyingWandEffect() { + super(Outcome.Benefit); + this.staticText = "Destroy target creature. Its controller creates a 2/4 white Ox creature token."; + } + + public TransmogrifyingWandEffect(final TransmogrifyingWandEffect effect) { + super(effect); + } + + @Override + public TransmogrifyingWandEffect copy() { + return new TransmogrifyingWandEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(source.getFirstTarget()); + if (creature == null) { + return false; + } + Effect effect = new CreateTokenTargetEffect(new OxToken()); + effect.setTargetPointer(new FixedTarget(creature.getControllerId(), game)); + new DestroyTargetEffect().apply(game, source); + return effect.apply(game, source); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TumbleMagnet.java b/Mage.Sets/src/mage/cards/t/TumbleMagnet.java index 796d70b82bb..c5033485560 100644 --- a/Mage.Sets/src/mage/cards/t/TumbleMagnet.java +++ b/Mage.Sets/src/mage/cards/t/TumbleMagnet.java @@ -1,5 +1,3 @@ - - package mage.cards.t; import java.util.UUID; @@ -15,9 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; import mage.counters.CounterType; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.StaticFilters; import mage.target.TargetPermanent; /** @@ -25,24 +21,26 @@ import mage.target.TargetPermanent; * @author Loki */ public final class TumbleMagnet extends CardImpl { - private static final FilterPermanent filter = new FilterPermanent("artifact or creature"); - static { - filter.add(Predicates.or( - new CardTypePredicate(CardType.ARTIFACT), - new CardTypePredicate(CardType.CREATURE))); - } + public TumbleMagnet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); - public TumbleMagnet (UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.CHARGE.createInstance(3)), "Tumble Magnet enters the battlefield with three charge counters on it")); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TapTargetEffect(), new TapSourceCost()); + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.CHARGE.createInstance(3)), + "{this} enters the battlefield with three charge counters on it" + )); + + Ability ability = new SimpleActivatedAbility( + Zone.BATTLEFIELD, + new TapTargetEffect(), + new TapSourceCost() + ); ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance())); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_CREATURE)); this.addAbility(ability); } - public TumbleMagnet (final TumbleMagnet card) { + public TumbleMagnet(final TumbleMagnet card) { super(card); } diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index a60b2139325..ec51bc069e1 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -259,6 +259,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Tolarian Scholar", 80, Rarity.COMMON, mage.cards.t.TolarianScholar.class)); cards.add(new SetCardInfo("Tormenting Voice", 164, Rarity.COMMON, mage.cards.t.TormentingVoice.class)); cards.add(new SetCardInfo("Totally Lost", 81, Rarity.COMMON, mage.cards.t.TotallyLost.class)); + cards.add(new SetCardInfo("Transmogrifying Wand", 247, Rarity.RARE, mage.cards.t.TransmogrifyingWand.class)); cards.add(new SetCardInfo("Trumpet Blast", 165, Rarity.COMMON, mage.cards.t.TrumpetBlast.class)); cards.add(new SetCardInfo("Trusty Packbeast", 41, Rarity.COMMON, mage.cards.t.TrustyPackbeast.class)); cards.add(new SetCardInfo("Two-Headed Zombie", 123, Rarity.COMMON, mage.cards.t.TwoHeadedZombie.class)); diff --git a/Mage/src/main/java/mage/game/permanent/token/OxToken.java b/Mage/src/main/java/mage/game/permanent/token/OxToken.java new file mode 100644 index 00000000000..082fe757927 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/OxToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * + * @author BetaSteward_at_googlemail.com + */ +public final class OxToken extends TokenImpl { + + public OxToken() { + super("Ox", "2/4 white Ox creature token"); + + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.OX); + power = new MageInt(2); + toughness = new MageInt(4); + + } + + public OxToken(final OxToken token) { + super(token); + } + + @Override + public OxToken copy() { + return new OxToken(this); + } +} From 2f3ef20048bd7d7e02d09657f90f701d324de3bf Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 08:54:18 -0400 Subject: [PATCH 15/74] Implemented Fraying Omnipotence --- .../src/mage/cards/f/FrayingOmnipotence.java | 107 ++++++++++++++++++ Mage.Sets/src/mage/cards/p/Pox.java | 18 +-- Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 3 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java diff --git a/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java b/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java new file mode 100644 index 00000000000..fb3bbc6bb45 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrayingOmnipotence.java @@ -0,0 +1,107 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public final class FrayingOmnipotence extends CardImpl { + + public FrayingOmnipotence(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); + + // Each player loses half their life, then discards half the cards in their hand, then sacrifices half the creatures they control. Round up each time. + this.getSpellAbility().addEffect(new FrayingOmnipotenceEffect()); + } + + public FrayingOmnipotence(final FrayingOmnipotence card) { + super(card); + } + + @Override + public FrayingOmnipotence copy() { + return new FrayingOmnipotence(this); + } +} + +class FrayingOmnipotenceEffect extends OneShotEffect { + + FrayingOmnipotenceEffect() { + super(Outcome.Detriment); + this.staticText = "Each player loses half their life, " + + "then discards half the cards in their hand, " + + "then sacrifices half the creatures they control. " + + "Round up each time."; + } + + FrayingOmnipotenceEffect(final FrayingOmnipotenceEffect effect) { + super(effect); + } + + @Override + public FrayingOmnipotenceEffect copy() { + return new FrayingOmnipotenceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + // Each player loses half of their life, + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + int lifeToLose = (int) Math.ceil(player.getLife() / 2.0); + player.loseLife(lifeToLose, game, false); + } + // then discards half of the cards in their hand, + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + int cardsToDiscard = (int) Math.ceil(player.getHand().size() / 2.0); + if (cardsToDiscard > 0) { + player.discard(cardsToDiscard, false, source, game); + } + } + // then sacrifices half of the creatures they controls, + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + int creaturesToSacrifice = (int) Math.ceil(game.getBattlefield().count(filter, source.getSourceId(), player.getId(), game) / 2.0); + if (creaturesToSacrifice == 0) { + continue; + } + Target target = new TargetControlledCreaturePermanent(creaturesToSacrifice, creaturesToSacrifice, filter, true); + target.chooseTarget(Outcome.Sacrifice, playerId, source, game); + for (UUID permanentId : target.getTargets()) { + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null) { + permanent.sacrifice(source.getSourceId(), game); + } + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/p/Pox.java b/Mage.Sets/src/mage/cards/p/Pox.java index 3437720153f..f1c96ba39f2 100644 --- a/Mage.Sets/src/mage/cards/p/Pox.java +++ b/Mage.Sets/src/mage/cards/p/Pox.java @@ -1,4 +1,3 @@ - package mage.cards.p; import java.util.UUID; @@ -24,8 +23,7 @@ import mage.target.common.TargetControlledPermanent; public final class Pox extends CardImpl { public Pox(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{B}{B}{B}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}{B}{B}"); // Each player loses a third of their life, then discards a third of the cards in their hand, then sacrifices a third of the creatures he or she controls, then sacrifices a third of the lands he or she controls. Round up each time. this.getSpellAbility().addEffect(new PoxEffect()); @@ -42,21 +40,25 @@ public final class Pox extends CardImpl { } class PoxEffect extends OneShotEffect { - + PoxEffect() { super(Outcome.Detriment); - this.staticText = "Each player loses a third of their life, then discards a third of the cards in their hand, then sacrifices a third of the creatures he or she controls, then sacrifices a third of the lands he or she controls. Round up each time."; + this.staticText = "Each player loses a third of their life, " + + "then discards a third of the cards in their hand, " + + "then sacrifices a third of the creatures they control, " + + "then sacrifices a third of the lands they control. " + + "Round up each time."; } - + PoxEffect(final PoxEffect effect) { super(effect); } - + @Override public PoxEffect copy() { return new PoxEffect(this); } - + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index ec51bc069e1..2da53c1c864 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -102,6 +102,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Fiery Finish", 140, Rarity.UNCOMMON, mage.cards.f.FieryFinish.class)); cards.add(new SetCardInfo("Fire Elemental", 141, Rarity.COMMON, mage.cards.f.FireElemental.class)); cards.add(new SetCardInfo("Fountain of Renewal", 235, Rarity.UNCOMMON, mage.cards.f.FountainOfRenewal.class)); + cards.add(new SetCardInfo("Fraying Omnipotence", 97, Rarity.RARE, mage.cards.f.FrayingOmnipotence.class)); cards.add(new SetCardInfo("Frilled Sea Serpent", 56, Rarity.COMMON, mage.cards.f.FrilledSeaSerpent.class)); cards.add(new SetCardInfo("Gallant Cavalry", 12, Rarity.COMMON, mage.cards.g.GallantCavalry.class)); cards.add(new SetCardInfo("Gearsmith Guardian", 237, Rarity.COMMON, mage.cards.g.GearsmithGuardian.class)); From b7069672664cf0f9fa5d8f21d889b8212c818516 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 09:01:45 -0400 Subject: [PATCH 16/74] Implemented Aether Shield Artificer --- .../mage/cards/a/AetherShieldArtificer.java | 63 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 64 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java diff --git a/Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java b/Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java new file mode 100644 index 00000000000..76fd315d472 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java @@ -0,0 +1,63 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public final class AetherShieldArtificer extends CardImpl { + + private static final FilterControlledCreaturePermanent filter + = new FilterControlledCreaturePermanent("artifact creature you control"); + + static { + filter.add(new CardTypePredicate(CardType.ARTIFACT)); + } + + public AetherShieldArtificer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.ARTIFICER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // At the beginning of combat on your turn, target artifact creature you control gets +2/+2 and gains indestructible until end of turn. + Ability ability = new BeginningOfCombatTriggeredAbility( + new BoostTargetEffect(2, 2, Duration.EndOfTurn) + .setText("target artifact creature you control gets +2/+2"), + TargetController.YOU, false + ); + ability.addEffect(new GainAbilityTargetEffect( + IndestructibleAbility.getInstance(), + Duration.EndOfTurn + ).setText("and gains indestructible until end of turn")); + ability.addTarget(new TargetControlledCreaturePermanent(filter)); + this.addAbility(ability); + } + + public AetherShieldArtificer(final AetherShieldArtificer card) { + super(card); + } + + @Override + public AetherShieldArtificer copy() { + return new AetherShieldArtificer(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 2da53c1c864..a8880401546 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -31,6 +31,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Abnormal Endurance", 85, Rarity.COMMON, mage.cards.a.AbnormalEndurance.class)); cards.add(new SetCardInfo("Act of Treason", 127, Rarity.COMMON, mage.cards.a.ActOfTreason.class)); cards.add(new SetCardInfo("Aerial Engineer", 211, Rarity.UNCOMMON, mage.cards.a.AerialEngineer.class)); + cards.add(new SetCardInfo("Aether Shield Artificer", 2, Rarity.UNCOMMON, mage.cards.a.AetherShieldArtificer.class)); cards.add(new SetCardInfo("Aggressive Mammoth", 302, Rarity.RARE, mage.cards.a.AggressiveMammoth.class)); cards.add(new SetCardInfo("Air Elemental", 308, Rarity.UNCOMMON, mage.cards.a.AirElemental.class)); cards.add(new SetCardInfo("Ajani's Influence", 282, Rarity.RARE, mage.cards.a.AjanisInfluence.class)); From d1192661bc7ea3e7bf9f0fb9efdd3ec36241bc3e Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Fri, 22 Jun 2018 12:17:24 -0400 Subject: [PATCH 17/74] Remove debug logging --- Mage.Sets/src/mage/cards/g/GiantAlbatross.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java index 33e3ec355d2..93abfbfc03d 100644 --- a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java +++ b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java @@ -80,11 +80,6 @@ class GiantAlbatrossEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); DealtDamageToWatcher watcher = (DealtDamageToWatcher) game.getState().getWatchers().get(DealtDamageToWatcher.class.getSimpleName(), source.getSourceId()); - System.out.println("Dealt damage: "+watcher.dealtDamageToSource); - for (MageObjectReference mageObjectReference : watcher.dealtDamageToSource){ - System.out.println(mageObjectReference.getCard(game).getName()); - System.out.println(mageObjectReference.getCard(game).getLogName()); - } for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { From 6880923ca4630a1cf74d9e52484c8db9ab1e0944 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:20:08 -0400 Subject: [PATCH 18/74] Updated M19 spoiler, added all reprints --- ...ificer.java => AethershieldArtificer.java} | 10 ++-- .../DetectionTower.java} | 24 ++++----- Mage.Sets/src/mage/sets/CoreSet2019.java | 53 ++++++++++++++++++- Utils/mtg-cards-data.txt | 51 ++++++++++++++++-- 4 files changed, 114 insertions(+), 24 deletions(-) rename Mage.Sets/src/mage/cards/a/{AetherShieldArtificer.java => AethershieldArtificer.java} (88%) rename Mage.Sets/src/mage/cards/{i/IsolatedTower.java => d/DetectionTower.java} (80%) diff --git a/Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java b/Mage.Sets/src/mage/cards/a/AethershieldArtificer.java similarity index 88% rename from Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java rename to Mage.Sets/src/mage/cards/a/AethershieldArtificer.java index 76fd315d472..c2f5378979c 100644 --- a/Mage.Sets/src/mage/cards/a/AetherShieldArtificer.java +++ b/Mage.Sets/src/mage/cards/a/AethershieldArtificer.java @@ -21,7 +21,7 @@ import mage.target.common.TargetControlledCreaturePermanent; * * @author TheElk801 */ -public final class AetherShieldArtificer extends CardImpl { +public final class AethershieldArtificer extends CardImpl { private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("artifact creature you control"); @@ -30,7 +30,7 @@ public final class AetherShieldArtificer extends CardImpl { filter.add(new CardTypePredicate(CardType.ARTIFACT)); } - public AetherShieldArtificer(UUID ownerId, CardSetInfo setInfo) { + public AethershieldArtificer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.DWARF); @@ -52,12 +52,12 @@ public final class AetherShieldArtificer extends CardImpl { this.addAbility(ability); } - public AetherShieldArtificer(final AetherShieldArtificer card) { + public AethershieldArtificer(final AethershieldArtificer card) { super(card); } @Override - public AetherShieldArtificer copy() { - return new AetherShieldArtificer(this); + public AethershieldArtificer copy() { + return new AethershieldArtificer(this); } } diff --git a/Mage.Sets/src/mage/cards/i/IsolatedTower.java b/Mage.Sets/src/mage/cards/d/DetectionTower.java similarity index 80% rename from Mage.Sets/src/mage/cards/i/IsolatedTower.java rename to Mage.Sets/src/mage/cards/d/DetectionTower.java index cec4ab30ea4..3f0ec089949 100644 --- a/Mage.Sets/src/mage/cards/i/IsolatedTower.java +++ b/Mage.Sets/src/mage/cards/d/DetectionTower.java @@ -1,4 +1,4 @@ -package mage.cards.i; +package mage.cards.d; import java.util.UUID; import mage.abilities.Ability; @@ -20,9 +20,9 @@ import mage.game.permanent.Permanent; * * @author TheElk801 */ -public final class IsolatedTower extends CardImpl { +public final class DetectionTower extends CardImpl { - public IsolatedTower(UUID ownerId, CardSetInfo setInfo) { + public DetectionTower(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {T}: Add {C}. @@ -30,26 +30,26 @@ public final class IsolatedTower extends CardImpl { // {1}, {T}: Until end of turn, your opponents and creatures with hexproof they control can be the targets of spells and abilities you control as though they didn't have hexproof. Ability ability = new SimpleActivatedAbility( - new IsolatedTowerEffect(), + new DetectionTowerEffect(), new GenericManaCost(1) ); ability.addCost(new TapSourceCost()); this.addAbility(ability); } - public IsolatedTower(final IsolatedTower card) { + public DetectionTower(final DetectionTower card) { super(card); } @Override - public IsolatedTower copy() { - return new IsolatedTower(this); + public DetectionTower copy() { + return new DetectionTower(this); } } -class IsolatedTowerEffect extends AsThoughEffectImpl { +class DetectionTowerEffect extends AsThoughEffectImpl { - public IsolatedTowerEffect() { + public DetectionTowerEffect() { super(AsThoughEffectType.HEXPROOF, Duration.EndOfTurn, Outcome.Benefit); staticText = "until end of turn, your opponents and " + "creatures with hexproof they control " @@ -57,7 +57,7 @@ class IsolatedTowerEffect extends AsThoughEffectImpl { + "you control as though they didn't have hexproof"; } - public IsolatedTowerEffect(final IsolatedTowerEffect effect) { + public DetectionTowerEffect(final DetectionTowerEffect effect) { super(effect); } @@ -67,8 +67,8 @@ class IsolatedTowerEffect extends AsThoughEffectImpl { } @Override - public IsolatedTowerEffect copy() { - return new IsolatedTowerEffect(this); + public DetectionTowerEffect copy() { + return new DetectionTowerEffect(this); } @Override diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index a8880401546..6b96e2397a3 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -31,7 +31,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Abnormal Endurance", 85, Rarity.COMMON, mage.cards.a.AbnormalEndurance.class)); cards.add(new SetCardInfo("Act of Treason", 127, Rarity.COMMON, mage.cards.a.ActOfTreason.class)); cards.add(new SetCardInfo("Aerial Engineer", 211, Rarity.UNCOMMON, mage.cards.a.AerialEngineer.class)); - cards.add(new SetCardInfo("Aether Shield Artificer", 2, Rarity.UNCOMMON, mage.cards.a.AetherShieldArtificer.class)); + cards.add(new SetCardInfo("Aethershield Artificer", 2, Rarity.UNCOMMON, mage.cards.a.AethershieldArtificer.class)); cards.add(new SetCardInfo("Aggressive Mammoth", 302, Rarity.RARE, mage.cards.a.AggressiveMammoth.class)); cards.add(new SetCardInfo("Air Elemental", 308, Rarity.UNCOMMON, mage.cards.a.AirElemental.class)); cards.add(new SetCardInfo("Ajani's Influence", 282, Rarity.RARE, mage.cards.a.AjanisInfluence.class)); @@ -54,8 +54,10 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Befuddle", 309, Rarity.COMMON, mage.cards.b.Befuddle.class)); cards.add(new SetCardInfo("Blanchwood Armor", 169, Rarity.UNCOMMON, mage.cards.b.BlanchwoodArmor.class)); cards.add(new SetCardInfo("Blood Divination", 86, Rarity.UNCOMMON, mage.cards.b.BloodDivination.class)); + cards.add(new SetCardInfo("Boggart Brute", 131, Rarity.COMMON, mage.cards.b.BoggartBrute.class)); cards.add(new SetCardInfo("Bogstomper", 87, Rarity.COMMON, mage.cards.b.Bogstomper.class)); cards.add(new SetCardInfo("Bone Dragon", 88, Rarity.MYTHIC, mage.cards.b.BoneDragon.class)); + cards.add(new SetCardInfo("Bone to Ash", 47, Rarity.UNCOMMON, mage.cards.b.BoneToAsh.class)); cards.add(new SetCardInfo("Brawl-Bash Ogre", 213, Rarity.UNCOMMON, mage.cards.b.BrawlBashOgre.class)); cards.add(new SetCardInfo("Bristling Boar", 170, Rarity.COMMON, mage.cards.b.BristlingBoar.class)); cards.add(new SetCardInfo("Cancel", 48, Rarity.COMMON, mage.cards.c.Cancel.class)); @@ -65,11 +67,14 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Chaos Wand", 228, Rarity.RARE, mage.cards.c.ChaosWand.class)); cards.add(new SetCardInfo("Child of Night", 89, Rarity.COMMON, mage.cards.c.ChildOfNight.class)); cards.add(new SetCardInfo("Chromium, the Mutable", 214, Rarity.MYTHIC, mage.cards.c.ChromiumTheMutable.class)); + cards.add(new SetCardInfo("Cinder Barrens", 248, Rarity.COMMON, mage.cards.c.CinderBarrens.class)); cards.add(new SetCardInfo("Cleansing Nova", 9, Rarity.RARE, mage.cards.c.CleansingNova.class)); cards.add(new SetCardInfo("Colossal Dreadmaw", 172, Rarity.COMMON, mage.cards.c.ColossalDreadmaw.class)); cards.add(new SetCardInfo("Colossal Majesty", 173, Rarity.UNCOMMON, mage.cards.c.ColossalMajesty.class)); cards.add(new SetCardInfo("Court Cleric", 283, Rarity.UNCOMMON, mage.cards.c.CourtCleric.class)); + cards.add(new SetCardInfo("Crash Through", 133, Rarity.COMMON, mage.cards.c.CrashThrough.class)); cards.add(new SetCardInfo("Crucible of Worlds", 229, Rarity.MYTHIC, mage.cards.c.CrucibleOfWorlds.class)); + cards.add(new SetCardInfo("Daggerback Basilisk", 174, Rarity.COMMON, mage.cards.d.DaggerbackBasilisk.class)); cards.add(new SetCardInfo("Dark-Dweller Oracle", 134, Rarity.RARE, mage.cards.d.DarkDwellerOracle.class)); cards.add(new SetCardInfo("Daybreak Chaplain", 10, Rarity.COMMON, mage.cards.d.DaybreakChaplain.class)); cards.add(new SetCardInfo("Death Baron", 90, Rarity.RARE, mage.cards.d.DeathBaron.class)); @@ -78,18 +83,22 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Demon of Catastrophes", 91, Rarity.RARE, mage.cards.d.DemonOfCatastrophes.class)); cards.add(new SetCardInfo("Departed Deckhand", 49, Rarity.UNCOMMON, mage.cards.d.DepartedDeckhand.class)); cards.add(new SetCardInfo("Desecrated Tomb", 230, Rarity.RARE, mage.cards.d.DesecratedTomb.class)); + cards.add(new SetCardInfo("Detection Tower", 249, Rarity.RARE, mage.cards.d.DetectionTower.class)); cards.add(new SetCardInfo("Diamond Mare", 231, Rarity.UNCOMMON, mage.cards.d.DiamondMare.class)); cards.add(new SetCardInfo("Diregraf Ghoul", 92, Rarity.UNCOMMON, mage.cards.d.DiregrafGhoul.class)); cards.add(new SetCardInfo("Dismissive Pyromancer", 136, Rarity.RARE, mage.cards.d.DismissivePyromancer.class)); cards.add(new SetCardInfo("Disperse", 50, Rarity.COMMON, mage.cards.d.Disperse.class)); cards.add(new SetCardInfo("Divination", 51, Rarity.COMMON, mage.cards.d.Divination.class)); cards.add(new SetCardInfo("Djinn of Wishes", 52, Rarity.RARE, mage.cards.d.DjinnOfWishes.class)); + cards.add(new SetCardInfo("Doomed Dissenter", 93, Rarity.COMMON, mage.cards.d.DoomedDissenter.class)); cards.add(new SetCardInfo("Doublecast", 137, Rarity.UNCOMMON, mage.cards.d.Doublecast.class)); cards.add(new SetCardInfo("Draconic Disciple", 215, Rarity.UNCOMMON, mage.cards.d.DraconicDisciple.class)); + cards.add(new SetCardInfo("Dragon Egg", 138, Rarity.UNCOMMON, mage.cards.d.DragonEgg.class)); cards.add(new SetCardInfo("Dragon's Hoard", 232, Rarity.RARE, mage.cards.d.DragonsHoard.class)); cards.add(new SetCardInfo("Druid of Horns", 176, Rarity.UNCOMMON, mage.cards.d.DruidOfHorns.class)); cards.add(new SetCardInfo("Druid of the Cowl", 177, Rarity.COMMON, mage.cards.d.DruidOfTheCowl.class)); cards.add(new SetCardInfo("Dryad Greenseeker", 178, Rarity.UNCOMMON, mage.cards.d.DryadGreenseeker.class)); + cards.add(new SetCardInfo("Duress", 94, Rarity.COMMON, mage.cards.d.Duress.class)); cards.add(new SetCardInfo("Dwarven Priest", 11, Rarity.COMMON, mage.cards.d.DwarvenPriest.class)); cards.add(new SetCardInfo("Dwindle", 53, Rarity.COMMON, mage.cards.d.Dwindle.class)); cards.add(new SetCardInfo("Electrify", 139, Rarity.COMMON, mage.cards.e.Electrify.class)); @@ -97,20 +106,31 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Elvish Rejuvenator", 180, Rarity.COMMON, mage.cards.e.ElvishRejuvenator.class)); cards.add(new SetCardInfo("Enigma Drake", 216, Rarity.UNCOMMON, mage.cards.e.EnigmaDrake.class)); cards.add(new SetCardInfo("Epicure of Blood", 95, Rarity.COMMON, mage.cards.e.EpicureOfBlood.class)); + cards.add(new SetCardInfo("Essence Scatter", 54, Rarity.COMMON, mage.cards.e.EssenceScatter.class)); cards.add(new SetCardInfo("Exclusion Mage", 55, Rarity.UNCOMMON, mage.cards.e.ExclusionMage.class)); + cards.add(new SetCardInfo("Explosive Apparatus", 233, Rarity.COMMON, mage.cards.e.ExplosiveApparatus.class)); cards.add(new SetCardInfo("Fell Specter", 96, Rarity.UNCOMMON, mage.cards.f.FellSpecter.class)); cards.add(new SetCardInfo("Field Creeper", 234, Rarity.COMMON, mage.cards.f.FieldCreeper.class)); cards.add(new SetCardInfo("Fiery Finish", 140, Rarity.UNCOMMON, mage.cards.f.FieryFinish.class)); cards.add(new SetCardInfo("Fire Elemental", 141, Rarity.COMMON, mage.cards.f.FireElemental.class)); + cards.add(new SetCardInfo("Forest", 277, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 278, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 279, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 280, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forsaken Sanctuary", 250, Rarity.COMMON, mage.cards.f.ForsakenSanctuary.class)); + cards.add(new SetCardInfo("Foul Orchard", 251, Rarity.COMMON, mage.cards.f.FoulOrchard.class)); cards.add(new SetCardInfo("Fountain of Renewal", 235, Rarity.UNCOMMON, mage.cards.f.FountainOfRenewal.class)); cards.add(new SetCardInfo("Fraying Omnipotence", 97, Rarity.RARE, mage.cards.f.FrayingOmnipotence.class)); cards.add(new SetCardInfo("Frilled Sea Serpent", 56, Rarity.COMMON, mage.cards.f.FrilledSeaSerpent.class)); cards.add(new SetCardInfo("Gallant Cavalry", 12, Rarity.COMMON, mage.cards.g.GallantCavalry.class)); + cards.add(new SetCardInfo("Gargoyle Sentinel", 236, Rarity.UNCOMMON, mage.cards.g.GargoyleSentinel.class)); cards.add(new SetCardInfo("Gearsmith Guardian", 237, Rarity.COMMON, mage.cards.g.GearsmithGuardian.class)); cards.add(new SetCardInfo("Gearsmith Prodigy", 57, Rarity.COMMON, mage.cards.g.GearsmithProdigy.class)); cards.add(new SetCardInfo("Ghastbark Twins", 181, Rarity.UNCOMMON, mage.cards.g.GhastbarkTwins.class)); + cards.add(new SetCardInfo("Ghirapur Guide", 182, Rarity.UNCOMMON, mage.cards.g.GhirapurGuide.class)); cards.add(new SetCardInfo("Ghostform", 58, Rarity.COMMON, mage.cards.g.Ghostform.class)); cards.add(new SetCardInfo("Giant Spider", 183, Rarity.COMMON, mage.cards.g.GiantSpider.class)); + cards.add(new SetCardInfo("Gift of Paradise", 184, Rarity.UNCOMMON, mage.cards.g.GiftOfParadise.class)); cards.add(new SetCardInfo("Gigantosaurus", 185, Rarity.RARE, mage.cards.g.Gigantosaurus.class)); cards.add(new SetCardInfo("Goblin Instigator", 142, Rarity.COMMON, mage.cards.g.GoblinInstigator.class)); cards.add(new SetCardInfo("Goblin Motivator", 143, Rarity.COMMON, mage.cards.g.GoblinMotivator.class)); @@ -126,19 +146,25 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Heroic Reinforcements", 217, Rarity.UNCOMMON, mage.cards.h.HeroicReinforcements.class)); cards.add(new SetCardInfo("Hieromancer's Cage", 14, Rarity.UNCOMMON, mage.cards.h.HieromancersCage.class)); cards.add(new SetCardInfo("Highland Game", 188, Rarity.COMMON, mage.cards.h.HighlandGame.class)); + cards.add(new SetCardInfo("Highland Lake", 252, Rarity.COMMON, mage.cards.h.HighlandLake.class)); cards.add(new SetCardInfo("Horizon Scholar", 59, Rarity.UNCOMMON, mage.cards.h.HorizonScholar.class)); cards.add(new SetCardInfo("Hostile Minotaur", 147, Rarity.COMMON, mage.cards.h.HostileMinotaur.class)); cards.add(new SetCardInfo("Hungering Hydra", 189, Rarity.RARE, mage.cards.h.HungeringHydra.class)); + cards.add(new SetCardInfo("Infectious Horror", 101, Rarity.COMMON, mage.cards.i.InfectiousHorror.class)); cards.add(new SetCardInfo("Infernal Reckoning", 102, Rarity.RARE, mage.cards.i.InfernalReckoning.class)); cards.add(new SetCardInfo("Infernal Scarring", 103, Rarity.COMMON, mage.cards.i.InfernalScarring.class)); cards.add(new SetCardInfo("Inspired Charge", 15, Rarity.COMMON, mage.cards.i.InspiredCharge.class)); cards.add(new SetCardInfo("Invoke the Divine", 16, Rarity.COMMON, mage.cards.i.InvokeTheDivine.class)); cards.add(new SetCardInfo("Isareth the Awakener", 104, Rarity.RARE, mage.cards.i.IsarethTheAwakener.class)); + cards.add(new SetCardInfo("Island", 265, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 266, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 267, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 268, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Isolate", 17, Rarity.RARE, mage.cards.i.Isolate.class)); - cards.add(new SetCardInfo("Isolated Tower", 249, Rarity.RARE, mage.cards.i.IsolatedTower.class)); cards.add(new SetCardInfo("Kargan Dragonrider", 297, Rarity.COMMON, mage.cards.k.KarganDragonrider.class)); cards.add(new SetCardInfo("Knight of the Tusk", 18, Rarity.COMMON, mage.cards.k.KnightOfTheTusk.class)); cards.add(new SetCardInfo("Knight's Pledge", 19, Rarity.COMMON, mage.cards.k.KnightsPledge.class)); + cards.add(new SetCardInfo("Knightly Valor", 20, Rarity.UNCOMMON, mage.cards.k.KnightlyValor.class)); cards.add(new SetCardInfo("Lathliss, Dragon Queen", 149, Rarity.RARE, mage.cards.l.LathlissDragonQueen.class)); cards.add(new SetCardInfo("Lava Axe", 150, Rarity.COMMON, mage.cards.l.LavaAxe.class)); cards.add(new SetCardInfo("Lena, Selfless Champion", 21, Rarity.RARE, mage.cards.l.LenaSelflessChampion.class)); @@ -154,18 +180,28 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Llanowar Elves", 314, Rarity.COMMON, mage.cards.l.LlanowarElves.class)); cards.add(new SetCardInfo("Loxodon Line Breaker", 24, Rarity.COMMON, mage.cards.l.LoxodonLineBreaker.class)); cards.add(new SetCardInfo("Luminous Bonds", 25, Rarity.COMMON, mage.cards.l.LuminousBonds.class)); + cards.add(new SetCardInfo("Macabre Waltz", 108, Rarity.COMMON, mage.cards.m.MacabreWaltz.class)); cards.add(new SetCardInfo("Magistrate's Scepter", 238, Rarity.RARE, mage.cards.m.MagistratesScepter.class)); + cards.add(new SetCardInfo("Make a Stand", 26, Rarity.UNCOMMON, mage.cards.m.MakeAStand.class)); cards.add(new SetCardInfo("Manalith", 239, Rarity.COMMON, mage.cards.m.Manalith.class)); cards.add(new SetCardInfo("Marauder's Axe", 240, Rarity.COMMON, mage.cards.m.MaraudersAxe.class)); + cards.add(new SetCardInfo("Meandering River", 253, Rarity.COMMON, mage.cards.m.MeanderingRiver.class)); cards.add(new SetCardInfo("Mentor of the Meek", 27, Rarity.RARE, mage.cards.m.MentorOfTheMeek.class)); cards.add(new SetCardInfo("Meteor Golem", 241, Rarity.UNCOMMON, mage.cards.m.MeteorGolem.class)); cards.add(new SetCardInfo("Mighty Leap", 28, Rarity.COMMON, mage.cards.m.MightyLeap.class)); cards.add(new SetCardInfo("Militia Bugler", 29, Rarity.UNCOMMON, mage.cards.m.MilitiaBugler.class)); + cards.add(new SetCardInfo("Millstone", 242, Rarity.UNCOMMON, mage.cards.m.Millstone.class)); + cards.add(new SetCardInfo("Mind Rot", 109, Rarity.COMMON, mage.cards.m.MindRot.class)); cards.add(new SetCardInfo("Mirror Image", 61, Rarity.UNCOMMON, mage.cards.m.MirrorImage.class)); cards.add(new SetCardInfo("Mist-Cloaked Herald", 310, Rarity.COMMON, mage.cards.m.MistCloakedHerald.class)); cards.add(new SetCardInfo("Mistcaller", 62, Rarity.RARE, mage.cards.m.Mistcaller.class)); + cards.add(new SetCardInfo("Mountain", 273, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 274, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 275, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 276, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Murder", 110, Rarity.UNCOMMON, mage.cards.m.Murder.class)); cards.add(new SetCardInfo("Mystic Archaeologist", 63, Rarity.RARE, mage.cards.m.MysticArchaeologist.class)); + cards.add(new SetCardInfo("Naturalize", 190, Rarity.COMMON, mage.cards.n.Naturalize.class)); cards.add(new SetCardInfo("Nexus of Fate", 306, Rarity.MYTHIC, mage.cards.n.NexusOfFate.class)); cards.add(new SetCardInfo("Nicol Bolas, the Arisen", 218, Rarity.MYTHIC, mage.cards.n.NicolBolasTheArisen.class)); cards.add(new SetCardInfo("Nicol Bolas, the Ravager", 218, Rarity.MYTHIC, mage.cards.n.NicolBolasTheRavager.class)); @@ -184,6 +220,10 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Pelakka Wurm", 192, Rarity.RARE, mage.cards.p.PelakkaWurm.class)); cards.add(new SetCardInfo("Phylactery Lich", 113, Rarity.RARE, mage.cards.p.PhylacteryLich.class)); cards.add(new SetCardInfo("Plague Mare", 114, Rarity.UNCOMMON, mage.cards.p.PlagueMare.class)); + cards.add(new SetCardInfo("Plains", 261, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 262, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 263, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 264, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plummet", 193, Rarity.COMMON, mage.cards.p.Plummet.class)); cards.add(new SetCardInfo("Poison-Tip Archer", 220, Rarity.UNCOMMON, mage.cards.p.PoisonTipArcher.class)); cards.add(new SetCardInfo("Prodigious Growth", 194, Rarity.RARE, mage.cards.p.ProdigiousGrowth.class)); @@ -240,15 +280,22 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Spit Flame", 160, Rarity.RARE, mage.cards.s.SpitFlame.class)); cards.add(new SetCardInfo("Star-Crowned Stag", 38, Rarity.COMMON, mage.cards.s.StarCrownedStag.class)); cards.add(new SetCardInfo("Stitcher's Supplier", 121, Rarity.UNCOMMON, mage.cards.s.StitchersSupplier.class)); + cards.add(new SetCardInfo("Stone Quarry", 256, Rarity.COMMON, mage.cards.s.StoneQuarry.class)); cards.add(new SetCardInfo("Strangling Spores", 122, Rarity.COMMON, mage.cards.s.StranglingSpores.class)); + cards.add(new SetCardInfo("Submerged Boneyard", 257, Rarity.COMMON, mage.cards.s.SubmergedBoneyard.class)); cards.add(new SetCardInfo("Sun Sentinel", 307, Rarity.COMMON, mage.cards.s.SunSentinel.class)); cards.add(new SetCardInfo("Supreme Phantom", 76, Rarity.RARE, mage.cards.s.SupremePhantom.class)); cards.add(new SetCardInfo("Sure Strike", 161, Rarity.COMMON, mage.cards.s.SureStrike.class)); cards.add(new SetCardInfo("Surge Mare", 77, Rarity.UNCOMMON, mage.cards.s.SurgeMare.class)); cards.add(new SetCardInfo("Suspicious Bookcase", 246, Rarity.UNCOMMON, mage.cards.s.SuspiciousBookcase.class)); + cards.add(new SetCardInfo("Swamp", 269, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 270, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 271, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 272, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Switcheroo", 78, Rarity.UNCOMMON, mage.cards.s.Switcheroo.class)); cards.add(new SetCardInfo("Take Vengeance", 40, Rarity.COMMON, mage.cards.t.TakeVengeance.class)); cards.add(new SetCardInfo("Tattered Mummy", 295, Rarity.COMMON, mage.cards.t.TatteredMummy.class)); + cards.add(new SetCardInfo("Tectonic Rift", 162, Rarity.UNCOMMON, mage.cards.t.TectonicRift.class)); cards.add(new SetCardInfo("Tezzeret's Gatebreaker", 289, Rarity.RARE, mage.cards.t.TezzeretsGatebreaker.class)); cards.add(new SetCardInfo("Tezzeret's Strider", 290, Rarity.UNCOMMON, mage.cards.t.TezzeretsStrider.class)); cards.add(new SetCardInfo("Tezzeret, Artifice Master", 79, Rarity.MYTHIC, mage.cards.t.TezzeretArtificeMaster.class)); @@ -261,6 +308,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Tolarian Scholar", 80, Rarity.COMMON, mage.cards.t.TolarianScholar.class)); cards.add(new SetCardInfo("Tormenting Voice", 164, Rarity.COMMON, mage.cards.t.TormentingVoice.class)); cards.add(new SetCardInfo("Totally Lost", 81, Rarity.COMMON, mage.cards.t.TotallyLost.class)); + cards.add(new SetCardInfo("Tranquil Expanse", 259, Rarity.COMMON, mage.cards.t.TranquilExpanse.class)); cards.add(new SetCardInfo("Transmogrifying Wand", 247, Rarity.RARE, mage.cards.t.TransmogrifyingWand.class)); cards.add(new SetCardInfo("Trumpet Blast", 165, Rarity.COMMON, mage.cards.t.TrumpetBlast.class)); cards.add(new SetCardInfo("Trusty Packbeast", 41, Rarity.COMMON, mage.cards.t.TrustyPackbeast.class)); @@ -284,5 +332,6 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Wall of Vines", 210, Rarity.COMMON, mage.cards.w.WallOfVines.class)); cards.add(new SetCardInfo("Waterknot", 311, Rarity.COMMON, mage.cards.w.Waterknot.class)); cards.add(new SetCardInfo("Windreader Sphinx", 84, Rarity.RARE, mage.cards.w.WindreaderSphinx.class)); + cards.add(new SetCardInfo("Woodland Stream", 260, Rarity.COMMON, mage.cards.w.WoodlandStream.class)); } } diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index a1d7a06603e..461becd28b9 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -33585,7 +33585,8 @@ Island|Battlebond|251|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Battlebond|252|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Battlebond|253|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Battlebond|254|C||Basic Land - Forest|||({T}: Add {G}.)| -Aether Shield Artificer|Core Set 2019|2|U|{3}{W}|Creature - Dwarf Artificer|3|3|At the beginning of combat on your turn, target artifact creature you control gets +2/+2 and gains indestructible until end of turn.| +Aegis of the Heavens|Core Set 2019|1|U|{1}{W}|Instant|||Target creature gets +1/+7 until end of turn.| +Aethershield Artificer|Core Set 2019|2|U|{3}{W}|Creature - Dwarf Artificer|3|3|At the beginning of combat on your turn, target artifact creature you control gets +2/+2 and gains indestructible until end of turn.| Ajani, Adversary of Tyrants|Core Set 2019|3|M|{2}{W}{W}|Legendary Planeswalker - Ajani|4|+1: Put a +1/+1 counter on each of up to two target creatures.$−2: Return target creature card with converted mana cost 2 or less from your graveyard to the battlefield.$−7: You get an emblem with "At the beginning of your end step, create three 1/1 white Cat creature tokens with lifelink."| Ajani's Last Stand|Core Set 2019|4|R|{2}{W}{W}|Enchantment|||Whenever a creature or planeswalker you control dies, you may sacrifice Ajani's Last Stand. If you do, create a 4/4 white Avatar creature token with flying.$When a spell or ability an opponent controls causes you to discard this card, if you control a Plains, create a 4/4 white Avatar creature token with flying.| Ajani's Pridemate|Core Set 2019|5|U|{1}{W}|Creature - Cat Soldier|2|2|Whenever you gain life, you may put a +1/+1 counter on Ajani's Pridemate.| @@ -33603,11 +33604,13 @@ Invoke the Divine|Core Set 2019|16|C|{2}{W}|Instant|||Destroy target artifact or Isolate|Core Set 2019|17|R|{W}|Instant|||Exile target permanent with converted mana cost 1.| Knight of the Tusk|Core Set 2019|18|C|{4}{W}{W}|Creature - Human Knight|3|7|Vigilance| Knight's Pledge|Core Set 2019|19|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+2.| +Knightly Valor|Core Set 2019|20|U|{4}{W}|Enchantment - Aura|||Enchant creature$When Knightly Valor enters the battlefield, create a 2/2 white Knight creature token with vigilance.$Enchanted creature gets +2/+2 and has vigilance.| Lena, Selfless Champion|Core Set 2019|21|R|{4}{W}{W}|Legendary Creature - Human Knight|3|3|When Lena, Selfless Champion enters the battlefield, create a 1/1 white Soldier creature token for each nontoken creature you control.$Sacrifice Lena: Creatures you control with power less than Lena's power gain indestructible until end of turn.| Leonin Vanguard|Core Set 2019|22|U|{W}|Creature - Cat Soldier|1|1|At the beginning of combat on your turn, if you control three or more creatures, Leonin Vanguard gets +1/+1 until end of turn and you gain 1 life.| Leonin Warleader|Core Set 2019|23|R|{2}{W}{W}|Creature - Cat Soldier|4|4|Whenever Leonin Warleader attacks, create two 1/1 white Cat creature tokens with lifelink that are tapped and attacking.| Loxodon Line Breaker|Core Set 2019|24|C|{2}{W}|Creature - Elephant Soldier|3|2|| Luminous Bonds|Core Set 2019|25|C|{2}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature can't attack or block.| +Make a Stand|Core Set 2019|26|U|{2}{W}|Instant|||Creatures you control get +1/+0 and gain indestructible until end of turn.| Mentor of the Meek|Core Set 2019|27|R|{2}{W}|Creature - Human Soldier|2|2|Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card.| Mighty Leap|Core Set 2019|28|C|{1}{W}|Instant|||Target creature gets +2/+2 and gains flying until end of turn.| Militia Bugler|Core Set 2019|29|U|{2}{W}|Creature - Human Soldier|2|3|Vigilance$When Militia Bugler enters the battlefield, look at the top four cards of your library. You may reveal a creature card with power 2 or less from among them and put it into your hand. Put the rest on the bottom of your library in a random order.| @@ -33624,15 +33627,18 @@ Suncleanser|Core Set 2019|39|R|{1}{W}|Creature - Human Cleric|1|4|When Suncleans Take Vengeance|Core Set 2019|40|C|{1}{W}|Sorcery|||Destroy target tapped creature.| Trusty Packbeast|Core Set 2019|41|C|{2}{W}|Creature - Beast|2|3|When Trusty Packbeast enters the battlefield, return target artifact card from your graveyard to your hand.| Valiant Knight|Core Set 2019|42|R|{3}{W}|Creature - Human Knight|3|4|Other Knights you control get +1/+1.${3}{W}{W}: Knights you control gain double strike until end of turn.| +Aether Tunnel|Core Set 2019|43|U|{1}{U}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+0 and can't be blocked.| Anticipate|Core Set 2019|44|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.| Aven Wind Mage|Core Set 2019|45|C|{2}{U}|Creature - Bird Wizard|2|2|Flying$Whenever you cast an instant or sorcery spell, Aven Wind Mage gets +1/+1 until end of turn.| Aviation Pioneer|Core Set 2019|46|C|{2}{U}|Creature - Human Artificer|1|2|When Aviation Pioneer enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying.| +Bone to Ash|Core Set 2019|47|U|{2}{U}{U}|Instant|||Counter target creature spell.$Draw a card.| Cancel|Core Set 2019|48|C|{1}{U}{U}|Instant|||Counter target spell.| Departed Deckhand|Core Set 2019|49|U|{1}{U}|Creature - Spirit Pirate|||When Departed Deckhand becomes the target of a spell, sacrifice it.$Departed Deckhand can't be blocked except by Spirits.${3}{U}: Another target creature you control can't be blocked this turn except by Spirits.| Disperse|Core Set 2019|50|C|{1}{U}|Instant|||Return target nonland permanent to its owner's hand.| Divination|Core Set 2019|51|C|{2}{U}|Sorcery|||Draw two cards.| Djinn of Wishes|Core Set 2019|52|R|{3}{U}{U}|Creature - Djinn|4|4|Flying$Djinn of Wishes enters the battlefield with three wish counters on it.${2}{U}{U}, Remove a wish counter from Djinn of Wishes: Reveal the top card of your library. You may play that card without paying its mana cost. If you don't, exile it.| Dwindle|Core Set 2019|53|C|{2}{U}|Enchantment - Aura|||Enchant creature$Enchanted creature gets -6/-0.$When enchanted creature blocks, destroy it.| +Essence Scatter|Core Set 2019|54|C|{1}{U}|Instant|||Counter target creature spell.| Exclusion Mage|Core Set 2019|55|U|{2}{U}|Creature - Human Wizard|2|2|When Exclusion Mage enters the battlefield, return target creature an opponent controls to its owner's hand.| Frilled Sea Serpent|Core Set 2019|56|C|{4}{U}{U}|Creature - Serpent|4|6|{5}{U}{U}: Frilled Sea Serpent can't be blocked this turn.| Gearsmith Prodigy|Core Set 2019|57|C|{U}|Creature - Human Artificer|1|2|Gearsmith Prodigy gets +1/+0 as long as you control an artifact.| @@ -33671,17 +33677,23 @@ Child of Night|Core Set 2019|89|C|{1}{B}|Creature - Vampire|2|1|Lifelink| Death Baron|Core Set 2019|90|R|{1}{B}{B}|Creature - Zombie Wizard|2|2|Skeleton creatures you control and other Zombie creatures you control get +1/+1 and have deathtouch.| Demon of Catastrophes|Core Set 2019|91|R|{2}{B}{B}|Creature - Demon|6|6|As an additional cost to cast this spell, sacrifice a creature.$Flying, trample| Diregraf Ghoul|Core Set 2019|92|U|{B}|Creature - Zombie|2|2|Diregraf Ghoul enters the battlefield tapped.| +Doomed Dissenter|Core Set 2019|93|C|{1}{B}|Creature - Human|1|1|When Doomed Dissenter dies, create a 2/2 black Zombie creature token.| +Duress|Core Set 2019|94|C|{B}|Sorcery|||Target opponent reveals their hand. You choose a noncreature, nonland card from it. That player discards that card.| Epicure of Blood|Core Set 2019|95|C|{4}{B}|Creature - Vampire|4|4|Whenever you gain life, each opponent loses 1 life.| -Fell Specter|Core Set 2019|96|U|{3}{B}|Creature - Specter|||Flying$When Fell Specter enters the battlefield, target opponent discards a card.$Whenever an opponent discards a card, that player loses 2 life.| +Fell Specter|Core Set 2019|96|U|{3}{B}|Creature - Specter|1|3|Flying$When Fell Specter enters the battlefield, target opponent discards a card.$Whenever an opponent discards a card, that player loses 2 life.| Fraying Omnipotence|Core Set 2019|97|R|{3}{B}{B}|Sorcery|||Each player loses half their life, then discards half the cards in their hand, then sacrifices half the creatures they control. Round up each time.| Gravedigger|Core Set 2019|98|U|{3}{B}|Creature - Zombie|2|2|When Gravedigger enters the battlefield, you may return target creature card from your graveyard to your hand.| Graveyard Marshal|Core Set 2019|99|R|{B}{B}|Creature - Zombie Soldier|3|2|{2}{B}, Exile a creature card from your graveyard: Create a tapped 2/2 black Zombie creature token.| +Hired Blade|Core Set 2019|100|C|{2}{B}|Creature - Human Assassin|3|2|Flash| +Infectious Horror|Core Set 2019|101|C|{3}{B}|Creature - Zombie Horror|2|2|Whenever Infectious Horror attacks, each opponent loses 2 life.| Infernal Reckoning|Core Set 2019|102|R|{B}|Instant|||Exile target colorless creature. You gain life equal to its power.| Infernal Scarring|Core Set 2019|103|C|{1}{B}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +2/+0 and has "When this creature dies, draw a card."| Isareth the Awakener|Core Set 2019|104|R|{1}{B}{B}|Legendary Creature - Human Wizard|3|3|Deathtouch$Whenever Isareth the Awakener attacks, you may pay {X}. When you do, return target creature card with converted mana cost X from your graveyard to the battlefield with a corpse counter on it. If that creature would leave the battlefield, exile it instead of putting it anywhere else.| Lich's Caress|Core Set 2019|105|C|{3}{B}{B}|Sorcery|||Destroy target creature. You gain 3 life.| Liliana, Untouched by Death|Core Set 2019|106|M|{2}{B}{B}|Legendary Planeswalker - Liliana|4|+1: Put the top three cards of your library into your graveyard. If at least one of them is a Zombie card, each opponent loses 2 life and you gain 2 life.$−2: Target creature gets -X/-X until end of turn, where X is the number of Zombies you control.$−3: You may cast Zombie cards from your graveyard this turn.| Liliana's Contract|Core Set 2019|107|R|{3}{B}{B}|Enchantment|||When Liliana's Contract enters the battlefield, you draw four cards and you lose 4 life.$At the beginning of your upkeep, if you control four or more Demons with different names, you win the game.| +Macabre Waltz|Core Set 2019|108|C|{1}{B}|Sorcery|||Return up to two target creature cards from your graveyard to your hand, then discard a card.| +Mind Rot|Core Set 2019|109|C|{2}{B}|Sorcery|||Target player discards two cards.| Murder|Core Set 2019|110|U|{1}{B}{B}|Instant|||Destroy target creature.| Nightmare's Thirst|Core Set 2019|111|U|{B}|Instant|||You gain 1 life. Target creature gets -X/-X until end of turn, where X is the amount of life you gained this turn.| Open the Graves|Core Set 2019|112|R|{3}{B}{B}|Enchantment|||Whenever a nontoken creature you control dies, create a 2/2 black Zombie creature token.| @@ -33696,17 +33708,21 @@ Sovereign's Bite|Core Set 2019|120|C|{1}{B}|Sorcery|||Target player loses 3 life Stitcher's Supplier|Core Set 2019|121|U|{B}|Creature - Zombie|1|1|When Stitcher's Supplier enters the battlefield or dies, put the top three cards of your library into your graveyard.| Strangling Spores|Core Set 2019|122|C|{3}{B}|Instant|||Target creature gets -3/-3 until end of turn.| Two-Headed Zombie|Core Set 2019|123|C|{3}{B}|Creature - Zombie|4|2|Menace| +Vampire Neonate|Core Set 2019|124|C|{B}|Creature - Vampire|0|3|{2}, {T}: Each opponent loses 1 life and you gain 1 life.| Vampire Sovereign|Core Set 2019|125|U|{3}{B}{B}|Creature - Vampire|3|4|Flying$When Vampire Sovereign enters the battlefield, target opponent loses 3 life and you gain 3 life.| Walking Corpse|Core Set 2019|126|C|{1}{B}|Creature - Zombie|2|2|| Act of Treason|Core Set 2019|127|C|{2}{R}|Sorcery|||Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn.| Alpine Moon|Core Set 2019|128|R|{R}|Enchantment|||As Alpine Moon enters the battlefield, choose a nonbasic land card name.$Lands your opponents control with the chosen name lose all land types and abilities, and they gain "{T}: Add one mana of any color."| Apex of Power|Core Set 2019|129|M|{7}{R}{R}{R}|Sorcery|||Exile the top seven cards of your library. Until end of turn, you may cast nonland cards exiled this way.$If this spell was cast from your hand, add ten mana of any one color.| Banefire|Core Set 2019|130|R|{X}{R}|Sorcery|||Banefire deals X damage to any target.$If X is 5 or more, this spell can't be countered and the damage can't be prevented.| +Boggart Brute|Core Set 2019|131|C|{2}{R}|Creature - Goblin Warrior|3|2|Menace| Catalyst Elemental|Core Set 2019|132|C|{2}{R}|Creature - Elemental|2|2|Sacrifice Catalyst Elemental: Add {R}{R}.| +Crash Through|Core Set 2019|133|C|{R}|Sorcery|||Creatures you control gain trample until end of turn.$Draw a card.| Dark-Dweller Oracle|Core Set 2019|134|R|{1}{R}|Creature - Goblin Shaman|||{1}, Sacrifice a creature: Exile the top card of your library. You may play that card this turn.| Demanding Dragon|Core Set 2019|135|R|{3}{R}{R}|Creature - Dragon|5|5|Flying$When Demanding Dragon enters the battlefield, it deals 5 damage to target opponent unless that player sacrifices a creature.| -Dismissive Pyromancer|Core Set 2019|136|R|{1}{R}|Creature - Human Wizard|2|2|{R}, {T}, Discard a card: Draw a card.${2}{R}, {T}, Sacrifice Disdainful Pyromancer: It deals 4 damage to target creature.| +Dismissive Pyromancer|Core Set 2019|136|R|{1}{R}|Creature - Human Wizard|2|2|{R}, {T}, Discard a card: Draw a card.${2}{R}, {T}, Sacrifice Dismissive Pyromancer: It deals 4 damage to target creature.| Doublecast|Core Set 2019|137|U|{R}{R}|Sorcery|||When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.| +Dragon Egg|Core Set 2019|138|U|{2}{R}|Creature - Dragon|0|2|Defender$When Dragon Egg dies, create a 2/2 red Dragon creature token with flying. It has "{R}: This creature gets +1/+0 until end of turn."| Electrify|Core Set 2019|139|C|{3}{R}|Instant|||Electrify deals 4 damage to target creature.| Fiery Finish|Core Set 2019|140|U|{4}{R}{R}|Sorcery|||Fiery Finish deals 7 damage to target creature.| Fire Elemental|Core Set 2019|141|C|{3}{R}{R}|Creature - Elemental|5|4|| @@ -33716,6 +33732,7 @@ Goblin Trashmaster|Core Set 2019|144|R|{2}{R}{R}|Creature - Goblin Warrior|3|3|O Guttersnipe|Core Set 2019|145|U|{2}{R}|Creature - Goblin Shaman|2|2|Whenever you cast an instant or sorcery spell, Guttersnipe deals 2 damage to each opponent.| Havoc Devils|Core Set 2019|146|C|{2}{R}{R}|Creature - Devil|4|3|Trample| Hostile Minotaur|Core Set 2019|147|C|{3}{R}|Creature - Minotaur|3|3|Haste| +Inferno Hellion|Core Set 2019|148|U|{3}{R}|Creature - Hellion|7|3|Trample$At the beginning of each end step, if Inferno Hellion attacked or blocked this turn, its owner shuffles it into their library.| Lathliss, Dragon Queen|Core Set 2019|149|R|{4}{R}{R}|Legendary Creature - Dragon|6|6|Flying$Whenever another nontoken Dragon enters the battlefield under your control, create a 5/5 red Dragon creature token with flying.${1}{R}: Dragons you control get +1/+0 until end of turn.| Lava Axe|Core Set 2019|150|C|{4}{R}|Sorcery|||Lava Axe deals 5 damage to target player or planeswalker.| Lightning Mare|Core Set 2019|151|U|{R}{R}|Creature - Elemental Horse|3|1|This spell can't be countered.$Lightning Mare can't be blocked by blue creatures.${1}{R}: Lightning Mare gets +1/+0 until end of turn.| @@ -33729,6 +33746,7 @@ Smelt|Core Set 2019|158|C|{R}|Instant|||Destroy target artifact.| Sparktongue Dragon|Core Set 2019|159|C|{3}{R}{R}|Creature - Dragon|3|3|Flying$When Sparktongue Dragon enters the battlefield, you may pay {2}{R}. When you do, it deals 3 damage to any target.| Spit Flame|Core Set 2019|160|R|{2}{R}|Instant|||Spit Flame deals 4 damage to target creature.$Whenever a Dragon enters the battlefield under your control, you may pay {R}. If you do, return Spit Flame from your graveyard to your hand.| Sure Strike|Core Set 2019|161|C|{1}{R}|Instant|||Target creature gets +3/+0 and gains first strike until end of turn.| +Tectonic Rift|Core Set 2019|162|U|{3}{R}|Sorcery|||Destroy target land. Creatures without flying can't block this turn.| Thud|Core Set 2019|163|U|{R}|Sorcery|||As an additional cost to cast this spell, sacrifice a creature.$Thud deals damage equal to the sacrificed creature's power to any target.| Tormenting Voice|Core Set 2019|164|C|{1}{R}|Sorcery|||As an additional cost to cast this spell, discard a card.$Draw two cards.| Trumpet Blast|Core Set 2019|165|C|{2}{R}|Instant|||Attacking creatures get +2/+0 until end of turn.| @@ -33740,6 +33758,7 @@ Bristling Boar|Core Set 2019|170|C|{3}{G}|Creature - Boar|4|3|Bristling Boar can Centaur Courser|Core Set 2019|171|C|{2}{G}|Creature - Centaur Warrior|3|3|| Colossal Dreadmaw|Core Set 2019|172|C|{4}{G}{G}|Creature - Dinosaur|6|6|Trample| Colossal Majesty|Core Set 2019|173|U|{2}{G}|Enchantment|||At the beginning of your upkeep, if you control a creature with power 4 or greater, draw a card.| +Daggerback Basilisk|Core Set 2019|174|C|{2}{G}|Creature - Basilisk|2|2|Deathtouch| Declare Dominance|Core Set 2019|175|U|{3}{G}{G}|Sorcery|||Target creature gets +3/+3 until end of turn. All creatures able to block it this turn do so.| Druid of Horns|Core Set 2019|176|U|{3}{G}|Creature - Human Druid|2|3|Whenever you cast an Aura spell that targets Druid of Horns, create a 3/3 green Beast creature token.| Druid of the Cowl|Core Set 2019|177|C|{1}{G}|Creature - Elf Druid|1|3|{T}: Add {G}.| @@ -33747,11 +33766,15 @@ Dryad Greenseeker|Core Set 2019|178|U|{1}{G}|Creature - Dryad|1|3|{T}: Look at t Elvish Clancaller|Core Set 2019|179|R|{G}{G}|Creature - Elf Druid|1|1|Other Elves you control get +1/+1.${4}{G}{G}, {T}: Search your library for a card named Elvish Clancaller, put it onto the battlefield, then shuffle your library.| Elvish Rejuvenator|Core Set 2019|180|C|{2}{G}|Creature - Elf Druid|1|1|Whenever Elvish Rejuvenator enters the battlefield, look at the top five cards of your library. You may put a land card from among them onto the battlefield tapped. Put the rest on the bottom of your library in a random order.| Ghastbark Twins|Core Set 2019|181|U|{5}{G}{G}|Creature - Treefolk|7|7|Trample$Ghastbark Twins can block an additional creature each combat.| +Ghirapur Guide|Core Set 2019|182|U|{2}{G}|Creature - Elf Scout|3|2|{2}{G}: Target creature you control can't be blocked by creatures with power 2 or less this turn.| Giant Spider|Core Set 2019|183|C|{3}{G}|Creature - Spider|2|4|Reach| +Gift of Paradise|Core Set 2019|184|U|{2}{G}|Enchantment - Aura|||Enchant land$When Gift of Paradise enters the battlefield, you gain 3 life.$Enchanted land has "{T}: Add two mana of any one color."| Gigantosaurus|Core Set 2019|185|R|{G}{G}{G}{G}{G}|Creature - Dinosaur|10|10|| Goreclaw, Terror of Qal Sisma|Core Set 2019|186|R|{3}{G}|Legendary Creature - Bear|4|3|Creature spells you cast with power 4 or greater cost {2} less to cast.$Whenever Goreclaw, Terror of Qal Sisma attacks, each creature you control with power 4 or greater gets +1/+1 and gains trample until end of turn.| +Greenwood Sentinel|Core Set 2019|187|C|{1}{G}|Creature - Elf Scout|2|2|Vigilance| Highland Game|Core Set 2019|188|C|{1}{G}|Creature - Elk|2|1|When Highland Game dies, you gain 2 life.| Hungering Hydra|Core Set 2019|189|R|{X}{G}|Creature - Hydra|0|0|Hungering Hydra enters the battlefield with X +1/+1 counters on it.$Hungering Hydra can't be blocked by more than one creature.$Whenever Hungering Hydra is dealt damage, put that many +1/+1 counters on it.| +Naturalize|Core Set 2019|190|C|{1}{G}|Instant|||Destroy target artifact or enchantment.| Oakenform|Core Set 2019|191|C|{2}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +3/+3.| Pelakka Wurm|Core Set 2019|192|R|{4}{G}{G}{G}|Creature - Wurm|7|7|Trample$When Pelakka Wurm enters the battlefield, you gain 7 life.$When Pelakka Wurm dies, draw a card.| Plummet|Core Set 2019|193|C|{1}{G}|Instant|||Destroy target creature with flying.| @@ -33763,6 +33786,7 @@ Rhox Oracle|Core Set 2019|198|C|{4}{G}|Creature - Rhino Monk|4|2|When Rhox Oracl Root Snare|Core Set 2019|199|C|{1}{G}|Instant|||Prevent all combat damage that would be dealt this turn.| Runic Armasaur|Core Set 2019|200|R|{1}{G}{G}|Creature - Dinosaur|2|5|Whenever an opponent activates an ability of a creature or land that isn't a mana ability, you may draw a card.| Scapeshift|Core Set 2019|201|M|{2}{G}{G}|Sorcery|||Sacrifice any number of lands. Search your library for up to that many land cards, put them onto the battlefield tapped, then shuffle your library.| +Talons of Wildwood|Core Set 2019|202|C|{1}{G}|Enchantment - Aura|||Enchant creature$Enchanted creature gets +1/+1 and has trample.${2}{G}: Return Talons of Wildwood from your graveyard to your hand.| Thorn Lieutenant|Core Set 2019|203|R|{1}{G}|Creature - Elf Warrior|2|3|Whenever Thorn Lieutenant becomes the target of a spell or ability an opponent controls, create a 1/1 green Elf Warrior creature token.${5}{G}: Thorn Lieutenant gets +4/+4 until end of turn.| Thornhide Wolves|Core Set 2019|204|C|{4}{G}|Creature - Wolf|4|5|| Titanic Growth|Core Set 2019|205|C|{1}{G}|Instant|||Target creature gets +4/+4 until end of turn.| @@ -33794,22 +33818,39 @@ Crucible of Worlds|Core Set 2019|229|M|{3}|Artifact|||You may play land cards fr Desecrated Tomb|Core Set 2019|230|R|{3}|Artifact|||Whenever one or more creature cards leave your graveyard, create a 1/1 black Bat creature token with flying.| Diamond Mare|Core Set 2019|231|U|{2}|Artifact Creature - Horse|1|3|As Diamond Mare enters the battlefield, choose a color.$Whenever you cast a spell of the chosen color, you gain 1 life.| Dragon's Hoard|Core Set 2019|232|R|{3}|Artifact|||Whenever a Dragon enters the battlefield under your control, put a gold counter on Dragon's Hoard.${T}, Remove a gold counter from Dragon's Hoard: Draw a card.${T}: Add one mana of any color.| +Explosive Apparatus|Core Set 2019|233|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: It deals 2 damage to any target.| Field Creeper|Core Set 2019|234|C|{2}|Artifact Creature - Scarecrow|2|1|| Fountain of Renewal|Core Set 2019|235|U|{1}|Artifact|||At the beginning of your upkeep, you gain 1 life.${3}, Sacrifice Fountain of Renewal: Draw a card.| +Gargoyle Sentinel|Core Set 2019|236|U|{3}|Artifact Creature - Gargoyle|3|3|Defender${3}: Until end of turn, Gargoyle Sentinel loses defender and gains flying.| Gearsmith Guardian|Core Set 2019|237|C|{5}|Artifact Creature - Construct|3|5|Gearsmith Guardian gets +2/+0 as long as you control a blue creature.| Magistrate's Scepter|Core Set 2019|238|R|{3}|Artifact|||{4}, {T}: Put a charge counter on Magistrate's Scepter.${T}, Remove three charge counters from Magistrate's Scepter: Take an extra turn after this one.| Manalith|Core Set 2019|239|C|{3}|Artifact|||{T}: Add one mana of any color.| Marauder's Axe|Core Set 2019|240|C|{2}|Artifact - Equipment|||Equipped creature +2/+0.$Equip {2}| Meteor Golem|Core Set 2019|241|U|{7}|Artifact Creature - Golem|3|3|When Meteor Golem enters the battlefield, destroy target nonland permanent an opponent controls.| +Millstone|Core Set 2019|242|U|{2}|Artifact|||{2}, {T}: Target player puts the top two cards of their library into their graveyard.| Rogue's Gloves|Core Set 2019|243|U|{2}|Artifact - Equipment|||Whenever equipped creature deals combat damage to a player, you may draw a card.$Equip {2}| Sigiled Sword of Valeron|Core Set 2019|244|R|{3}|Artifact - Equipment|||Equipped creature gets +2/+0, has vigilance, and is a Knight in addition to its other types.$Whenever equipped creature attacks, create a 2/2 white Knight creature token with vigilance that's attacking.$Equip {3}| Skyscanner|Core Set 2019|245|C|{3}|Artifact Creature - Thopter|1|1|Flying$When Skyscanner enters the battlefield, draw a card.| Suspicious Bookcase|Core Set 2019|246|U|{2}|Artifact Creature - Wall|0|4|Defender${3}, {T}: Target creature can't be blocked this turn.| -Transmogrifying Wand|Core Set 2019|247|R|{3}|Artifact|||Transmogrifying Wand enters the battlefield with three charge counters on it.${1}, {T}: Remove a charge counter from Transmogrifying Wand: Destroy target creature. Its controller creates a 2/4 white Ox creature token. Activate this ability only any time you could cast a sorcery.| -Isolated Tower|Core Set 2019|249|R||Land|||{T}: Add {C}.${1}, {T}: Until end of turn, your opponents and creatures with hexproof they control can be the targets of spells and abilities as though they didn't have hexproof.| +Transmogrifying Wand|Core Set 2019|247|R|{3}|Artifact|||Transmogrifying Wand enters the battlefield with three charge counters on it.${1}, {T}, Remove a charge counter from Transmogrifying Wand: Destroy target creature. Its controller creates a 2/4 white Ox creature token. Activate this ability only any time you could cast a sorcery.| +Cinder Barrens|Core Set 2019|248|C||Land|||Cinder Barrens enters the battlefield tapped.${T}: Add {B} or {R}.| +Detection Tower|Core Set 2019|249|R||Land|||{T}: Add {C}.${1}, {T}: Until end of turn, your opponents and creatures your opponents control with hexproof can be the targets of spells and abilities you control as though they didn't have hexproof.| +Forsaken Sanctuary|Core Set 2019|250|C||Land|||Forsaken Sanctuary enters the battlefield tapped.${T}: Add {W} or {B}.| +Foul Orchard|Core Set 2019|251|C||Land|||Foul Orchard enters the battlefield tapped.${T}: Add {B} or {G}.| +Highland Lake|Core Set 2019|252|C||Land|||Highland Lake enters the battlefield tapped.${T}: Add {U} or {R}.| +Meandering River|Core Set 2019|253|C||Land|||Meandering River enters the battlefield tapped.${T}: Add {W} or {U}.| Reliquary Tower|Core Set 2019|254|U||Land|||You have no maximum hand size.${T}: Add {C}.| Rupture Spire|Core Set 2019|255|U||Land|||Rupture Spire enters the battlefield tapped.$When Rupture Spire enters the battlefield, sacrifice it unless you pay {1}.${T}: Add one mana of any color.| +Stone Quarry|Core Set 2019|256|C||Land|||Stone Quarry enters the battlefield tapped.${T}: Add {R} or {W}.| +Submerged Boneyard|Core Set 2019|257|C||Land|||Submerged Boneyard enters the battlefield tapped.${T}: Add {U} or {B}.| Timber Gorge|Core Set 2019|258|C||Land|||Timber Gorge enters the battlefield tapped.${T}: Add {R} or {G}.| +Tranquil Expanse|Core Set 2019|259|C||Land|||Tranquil Expanse enters the battlefield tapped.${T}: Add {G} or {W}.| +Woodland Stream|Core Set 2019|260|C||Land|||Woodland Stream enters the battlefield tapped.${T}: Add {G} or {U}.| +Plains|Core Set 2019|261|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Core Set 2019|265|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Core Set 2019|269|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Core Set 2019|273|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Core Set 2019|277|C||Basic Land - Forest|||({T}: Add {G}.)| Ajani, Wise Counselor|Core Set 2019|281|M|{3}{W}{W}|Legendary Planeswalker - Ajani|5|+2: You gain 1 life for each creature you control.$−3: Creatures you control get +2/+2 until end of turn.$−9: Put X +1/+1 counters on target creature, where X is your life total.| Ajani's Influence|Core Set 2019|282|R|{2}{W}{W}|Sorcery|||Put two +1/+1 counters on target creature.$Look at the top five cards of your library. You may reveal a white card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.| Court Cleric|Core Set 2019|283|U|{W}|Creature - Human Cleric|1|1|Lifelink$Court Cleric gets +1/+1 as long as you control an Ajani planeswalker.| From 8a9d2978524378e105c3407a5ef65deb10633a46 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:24:20 -0400 Subject: [PATCH 19/74] Implemented Hired Blade --- Mage.Sets/src/mage/cards/h/HiredBlade.java | 38 ++++++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 39 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/h/HiredBlade.java diff --git a/Mage.Sets/src/mage/cards/h/HiredBlade.java b/Mage.Sets/src/mage/cards/h/HiredBlade.java new file mode 100644 index 00000000000..aa83966e647 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HiredBlade.java @@ -0,0 +1,38 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.abilities.keyword.FlashAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author TheElk801 + */ +public final class HiredBlade extends CardImpl { + + public HiredBlade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + } + + public HiredBlade(final HiredBlade card) { + super(card); + } + + @Override + public HiredBlade copy() { + return new HiredBlade(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 6b96e2397a3..16b49e1e7d2 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -147,6 +147,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Hieromancer's Cage", 14, Rarity.UNCOMMON, mage.cards.h.HieromancersCage.class)); cards.add(new SetCardInfo("Highland Game", 188, Rarity.COMMON, mage.cards.h.HighlandGame.class)); cards.add(new SetCardInfo("Highland Lake", 252, Rarity.COMMON, mage.cards.h.HighlandLake.class)); + cards.add(new SetCardInfo("Hired Blade", 100, Rarity.COMMON, mage.cards.h.HiredBlade.class)); cards.add(new SetCardInfo("Horizon Scholar", 59, Rarity.UNCOMMON, mage.cards.h.HorizonScholar.class)); cards.add(new SetCardInfo("Hostile Minotaur", 147, Rarity.COMMON, mage.cards.h.HostileMinotaur.class)); cards.add(new SetCardInfo("Hungering Hydra", 189, Rarity.RARE, mage.cards.h.HungeringHydra.class)); From dff22591a11fb85c095575cc03c67f6670e8a317 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:25:43 -0400 Subject: [PATCH 20/74] Implemented Aegis of the Heavens --- .../src/mage/cards/a/AegisOfTheHeavens.java | 33 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 34 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java diff --git a/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java b/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java new file mode 100644 index 00000000000..b31d109eb75 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java @@ -0,0 +1,33 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author TheElk801 + */ +public final class AegisOfTheHeavens extends CardImpl { + + public AegisOfTheHeavens(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Target creature gets +1/+7 until end of turn. + this.getSpellAbility().addEffect(new BoostTargetEffect(3, 3, Duration.EndOfTurn)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + public AegisOfTheHeavens(final AegisOfTheHeavens card) { + super(card); + } + + @Override + public AegisOfTheHeavens copy() { + return new AegisOfTheHeavens(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 16b49e1e7d2..3f305729557 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -30,6 +30,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Abnormal Endurance", 85, Rarity.COMMON, mage.cards.a.AbnormalEndurance.class)); cards.add(new SetCardInfo("Act of Treason", 127, Rarity.COMMON, mage.cards.a.ActOfTreason.class)); + cards.add(new SetCardInfo("Aegis of the Heavens", 1, Rarity.UNCOMMON, mage.cards.a.AegisOfTheHeavens.class)); cards.add(new SetCardInfo("Aerial Engineer", 211, Rarity.UNCOMMON, mage.cards.a.AerialEngineer.class)); cards.add(new SetCardInfo("Aethershield Artificer", 2, Rarity.UNCOMMON, mage.cards.a.AethershieldArtificer.class)); cards.add(new SetCardInfo("Aggressive Mammoth", 302, Rarity.RARE, mage.cards.a.AggressiveMammoth.class)); From dc9011626859672a20b86cb899f85d3d704ae8be Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:26:27 -0400 Subject: [PATCH 21/74] Implemented Greenwood Sentinel --- .../src/mage/cards/g/GreenwoodSentinel.java | 38 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 39 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GreenwoodSentinel.java diff --git a/Mage.Sets/src/mage/cards/g/GreenwoodSentinel.java b/Mage.Sets/src/mage/cards/g/GreenwoodSentinel.java new file mode 100644 index 00000000000..ff98d1ed355 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GreenwoodSentinel.java @@ -0,0 +1,38 @@ +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author TheElk801 + */ +public final class GreenwoodSentinel extends CardImpl { + + public GreenwoodSentinel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + } + + public GreenwoodSentinel(final GreenwoodSentinel card) { + super(card); + } + + @Override + public GreenwoodSentinel copy() { + return new GreenwoodSentinel(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 3f305729557..25d8153263d 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -141,6 +141,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Gravedigger", 98, Rarity.UNCOMMON, mage.cards.g.Gravedigger.class)); cards.add(new SetCardInfo("Gravewaker", 293, Rarity.RARE, mage.cards.g.Gravewaker.class)); cards.add(new SetCardInfo("Graveyard Marshal", 99, Rarity.RARE, mage.cards.g.GraveyardMarshal.class)); + cards.add(new SetCardInfo("Greenwood Sentinel", 187, Rarity.COMMON, mage.cards.g.GreenwoodSentinel.class)); cards.add(new SetCardInfo("Guttersnipe", 145, Rarity.UNCOMMON, mage.cards.g.Guttersnipe.class)); cards.add(new SetCardInfo("Havoc Devils", 146, Rarity.COMMON, mage.cards.h.HavocDevils.class)); cards.add(new SetCardInfo("Herald of Faith", 13, Rarity.UNCOMMON, mage.cards.h.HeraldOfFaith.class)); From e83e162fdfa35156b0e25de61e1957f527f4137a Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:31:13 -0400 Subject: [PATCH 22/74] Implemented Vampire Neonate --- .../src/mage/cards/v/VampireNeonate.java | 47 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 48 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/v/VampireNeonate.java diff --git a/Mage.Sets/src/mage/cards/v/VampireNeonate.java b/Mage.Sets/src/mage/cards/v/VampireNeonate.java new file mode 100644 index 00000000000..d679819b791 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VampireNeonate.java @@ -0,0 +1,47 @@ +package mage.cards.v; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author TheElk801 + */ +public final class VampireNeonate extends CardImpl { + + public VampireNeonate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}"); + + this.subtype.add(SubType.VAMPIRE); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // {2}, {T}: Each opponent loses 1 life and you gain 1 life. + Ability ability = new SimpleActivatedAbility( + new LoseLifeOpponentsEffect(1), + new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + ability.addEffect(new GainLifeEffect(1).setText("and you gain 1 life")); + this.addAbility(ability); + } + + public VampireNeonate(final VampireNeonate card) { + super(card); + } + + @Override + public VampireNeonate copy() { + return new VampireNeonate(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 25d8153263d..8bee9d1dfda 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -320,6 +320,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Ursine Champion", 304, Rarity.COMMON, mage.cards.u.UrsineChampion.class)); cards.add(new SetCardInfo("Vaevictis Asmadi, the Dire", 225, Rarity.MYTHIC, mage.cards.v.VaevictisAsmadiTheDire.class)); cards.add(new SetCardInfo("Valiant Knight", 42, Rarity.RARE, mage.cards.v.ValiantKnight.class)); + cards.add(new SetCardInfo("Vampire Neonate", 124, Rarity.COMMON, mage.cards.v.VampireNeonate.class)); cards.add(new SetCardInfo("Vampire Sovereign", 125, Rarity.UNCOMMON, mage.cards.v.VampireSovereign.class)); cards.add(new SetCardInfo("Viashino Pyromancer", 166, Rarity.COMMON, mage.cards.v.ViashinoPyromancer.class)); cards.add(new SetCardInfo("Vigilant Baloth", 206, Rarity.UNCOMMON, mage.cards.v.VigilantBaloth.class)); From 0d921d0461073f7faec01aeb974b437eb540a893 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:36:34 -0400 Subject: [PATCH 23/74] Implemented Talons of Wildwood --- .../src/mage/cards/t/TalonsOfWildwood.java | 69 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 70 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java diff --git a/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java b/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java new file mode 100644 index 00000000000..e90adec696b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java @@ -0,0 +1,69 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.constants.Outcome; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; + +/** + * + * @author TheElk801 + */ +public final class TalonsOfWildwood extends CardImpl { + + public TalonsOfWildwood(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature gets +1/+1 and has trample. + ability = new SimpleStaticAbility( + Zone.BATTLEFIELD, new BoostEnchantedEffect(2, 2, Duration.WhileOnBattlefield) + ); + ability.addEffect(new GainAbilityAttachedEffect( + TrampleAbility.getInstance(), + AttachmentType.AURA + ).setText("and has trample")); + this.addAbility(ability); + + // {2}{G}: Return Talons of Wildwood from your graveyard to your hand. + this.addAbility(new SimpleActivatedAbility( + Zone.GRAVEYARD, + new ReturnSourceFromGraveyardToHandEffect(), + new ManaCostsImpl("{2}{G}") + )); + } + + public TalonsOfWildwood(final TalonsOfWildwood card) { + super(card); + } + + @Override + public TalonsOfWildwood copy() { + return new TalonsOfWildwood(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 8bee9d1dfda..441e8b3c26a 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -297,6 +297,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 272, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Switcheroo", 78, Rarity.UNCOMMON, mage.cards.s.Switcheroo.class)); cards.add(new SetCardInfo("Take Vengeance", 40, Rarity.COMMON, mage.cards.t.TakeVengeance.class)); + cards.add(new SetCardInfo("Talons of Wildwood", 202, Rarity.COMMON, mage.cards.t.TalonsOfWildwood.class)); cards.add(new SetCardInfo("Tattered Mummy", 295, Rarity.COMMON, mage.cards.t.TatteredMummy.class)); cards.add(new SetCardInfo("Tectonic Rift", 162, Rarity.UNCOMMON, mage.cards.t.TectonicRift.class)); cards.add(new SetCardInfo("Tezzeret's Gatebreaker", 289, Rarity.RARE, mage.cards.t.TezzeretsGatebreaker.class)); From 1140b7cb37deb29490e25d49570a38b2f3364659 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:39:52 -0400 Subject: [PATCH 24/74] Implemented Aether Tunnel --- Mage.Sets/src/mage/cards/a/AetherTunnel.java | 58 ++++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 59 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AetherTunnel.java diff --git a/Mage.Sets/src/mage/cards/a/AetherTunnel.java b/Mage.Sets/src/mage/cards/a/AetherTunnel.java new file mode 100644 index 00000000000..cef52b890c8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AetherTunnel.java @@ -0,0 +1,58 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.CantBeBlockedAttachedEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.constants.Outcome; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; + +/** + * + * @author TheElk801 + */ +public final class AetherTunnel extends CardImpl { + + public AetherTunnel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature gets +1/+0 and can't be blocked. + ability = new SimpleStaticAbility( + Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 0, Duration.WhileOnBattlefield) + ); + ability.addEffect( + new CantBeBlockedAttachedEffect(AttachmentType.AURA) + .setText("and can't be blocked") + ); + this.addAbility(ability); + } + + public AetherTunnel(final AetherTunnel card) { + super(card); + } + + @Override + public AetherTunnel copy() { + return new AetherTunnel(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 441e8b3c26a..61955db1319 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -32,6 +32,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Act of Treason", 127, Rarity.COMMON, mage.cards.a.ActOfTreason.class)); cards.add(new SetCardInfo("Aegis of the Heavens", 1, Rarity.UNCOMMON, mage.cards.a.AegisOfTheHeavens.class)); cards.add(new SetCardInfo("Aerial Engineer", 211, Rarity.UNCOMMON, mage.cards.a.AerialEngineer.class)); + cards.add(new SetCardInfo("Aether Tunnel", 43, Rarity.UNCOMMON, mage.cards.a.AetherTunnel.class)); cards.add(new SetCardInfo("Aethershield Artificer", 2, Rarity.UNCOMMON, mage.cards.a.AethershieldArtificer.class)); cards.add(new SetCardInfo("Aggressive Mammoth", 302, Rarity.RARE, mage.cards.a.AggressiveMammoth.class)); cards.add(new SetCardInfo("Air Elemental", 308, Rarity.UNCOMMON, mage.cards.a.AirElemental.class)); From cf8e80ef1fc6ff94f75f773b0d0820261e2bc5c1 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 13:57:54 -0400 Subject: [PATCH 25/74] Implemented Inferno Hellion --- .../src/mage/cards/i/InfernoHellion.java | 78 +++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 79 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/i/InfernoHellion.java diff --git a/Mage.Sets/src/mage/cards/i/InfernoHellion.java b/Mage.Sets/src/mage/cards/i/InfernoHellion.java new file mode 100644 index 00000000000..d253009030e --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/InfernoHellion.java @@ -0,0 +1,78 @@ +package mage.cards.i; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.ShuffleIntoLibrarySourceEffect; +import mage.constants.SubType; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.game.Game; +import mage.watchers.common.AttackedThisTurnWatcher; +import mage.watchers.common.BlockedThisTurnWatcher; + +/** + * + * @author TheElk801 + */ +public final class InfernoHellion extends CardImpl { + + public InfernoHellion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.HELLION); + this.power = new MageInt(7); + this.toughness = new MageInt(3); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // At the beginning of each end step, if Inferno Hellion attacked or blocked this turn, its owner shuffles it into their library. + Ability ability = new ConditionalTriggeredAbility( + new BeginningOfEndStepTriggeredAbility( + new ShuffleIntoLibrarySourceEffect(), + TargetController.ANY, false + ), + InfernoHellionCondition.instance, + "At the beginning of each end step, " + + "if {this} attacked or blocked this turn, " + + "its owner shuffles it into their library." + ); + ability.addWatcher(new AttackedThisTurnWatcher()); + ability.addWatcher(new BlockedThisTurnWatcher()); + this.addAbility(ability); + } + + public InfernoHellion(final InfernoHellion card) { + super(card); + } + + @Override + public InfernoHellion copy() { + return new InfernoHellion(this); + } +} + +enum InfernoHellionCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + AttackedThisTurnWatcher watcherAttacked = (AttackedThisTurnWatcher) game.getState().getWatchers().get(AttackedThisTurnWatcher.class.getSimpleName()); + BlockedThisTurnWatcher watcherBlocked = (BlockedThisTurnWatcher) game.getState().getWatchers().get(BlockedThisTurnWatcher.class.getSimpleName()); + MageObjectReference mor = new MageObjectReference(source.getSourceId(), game); + if (watcherAttacked == null || watcherBlocked == null) { + return false; + } + return watcherAttacked.getAttackedThisTurnCreatures().contains(mor) + || watcherBlocked.getBlockedThisTurnCreatures().contains(mor); + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 61955db1319..5ffc862cc6c 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -157,6 +157,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Infectious Horror", 101, Rarity.COMMON, mage.cards.i.InfectiousHorror.class)); cards.add(new SetCardInfo("Infernal Reckoning", 102, Rarity.RARE, mage.cards.i.InfernalReckoning.class)); cards.add(new SetCardInfo("Infernal Scarring", 103, Rarity.COMMON, mage.cards.i.InfernalScarring.class)); + cards.add(new SetCardInfo("Inferno Hellion", 148, Rarity.UNCOMMON, mage.cards.i.InfernoHellion.class)); cards.add(new SetCardInfo("Inspired Charge", 15, Rarity.COMMON, mage.cards.i.InspiredCharge.class)); cards.add(new SetCardInfo("Invoke the Divine", 16, Rarity.COMMON, mage.cards.i.InvokeTheDivine.class)); cards.add(new SetCardInfo("Isareth the Awakener", 104, Rarity.RARE, mage.cards.i.IsarethTheAwakener.class)); From ef705c1ca0ff5f3e40a2f4be9b95aaa992b078d3 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 14:23:42 -0400 Subject: [PATCH 26/74] Implemented Suncleanser --- Mage.Sets/src/mage/cards/s/Suncleanser.java | 143 ++++++++++++++++++++ Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 2 files changed, 144 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/Suncleanser.java diff --git a/Mage.Sets/src/mage/cards/s/Suncleanser.java b/Mage.Sets/src/mage/cards/s/Suncleanser.java new file mode 100644 index 00000000000..8575b71920b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Suncleanser.java @@ -0,0 +1,143 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.counters.Counter; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponent; + +/** + * + * @author TheElk801 + */ +public final class Suncleanser extends CardImpl { + + public Suncleanser(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // When Suncleanser enters the battlefield, choose one — + // • Remove all counters from target creature. It can't have counters put on it for as long as Suncleanser remains on the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility( + new SuncleanserRemoveCountersEffect(false), false + ); + ability.addEffect(new SuncleanserPreventCountersEffect(false)); + ability.addTarget(new TargetCreaturePermanent()); + + // • Target opponent loses all counters. That player can't get counters for as long as Suncleanser remains on the battlefield. + Mode mode = new Mode(); + mode.getEffects().add(new SuncleanserRemoveCountersEffect(true)); + mode.getEffects().add(new SuncleanserPreventCountersEffect(true)); + mode.getTargets().add(new TargetOpponent()); + ability.addMode(mode); + this.addAbility(ability); + } + + public Suncleanser(final Suncleanser card) { + super(card); + } + + @Override + public Suncleanser copy() { + return new Suncleanser(this); + } +} + +class SuncleanserRemoveCountersEffect extends OneShotEffect { + + public SuncleanserRemoveCountersEffect(boolean player) { + super(Outcome.Benefit); + if (player) { + staticText = "Target opponent loses all counters."; + } else { + staticText = "Remove all counters from target creature."; + } + } + + public SuncleanserRemoveCountersEffect(SuncleanserRemoveCountersEffect effect) { + super(effect); + } + + @Override + public SuncleanserRemoveCountersEffect copy() { + return new SuncleanserRemoveCountersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent != null) { + for (Counter counter : permanent.getCounters(game).copy().values()) { // copy to prevent ConcurrentModificationException + permanent.removeCounters(counter, game); + } + return true; + } + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player != null) { + for (Counter counter : player.getCounters().copy().values()) { // copy to prevent ConcurrentModificationException + player.removeCounters(counter.getName(), counter.getCount(), source, game); + } + return true; + } + return false; + } + +} + +class SuncleanserPreventCountersEffect extends ContinuousRuleModifyingEffectImpl { + + public SuncleanserPreventCountersEffect(boolean player) { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + if (player) { + staticText = "That player can't get counters for as long as {this} remains on the battlefield."; + } else { + staticText = "It can't have counters put on it for as long as {this} remains on the battlefield"; + } + } + + public SuncleanserPreventCountersEffect(final SuncleanserPreventCountersEffect effect) { + super(effect); + } + + @Override + public SuncleanserPreventCountersEffect copy() { + return new SuncleanserPreventCountersEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ADD_COUNTERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!source.getFirstTarget().equals(event.getTargetId())) { + return false; + } + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null) { + discard(); + return false; + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 5ffc862cc6c..0a0bfed9787 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -289,6 +289,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Strangling Spores", 122, Rarity.COMMON, mage.cards.s.StranglingSpores.class)); cards.add(new SetCardInfo("Submerged Boneyard", 257, Rarity.COMMON, mage.cards.s.SubmergedBoneyard.class)); cards.add(new SetCardInfo("Sun Sentinel", 307, Rarity.COMMON, mage.cards.s.SunSentinel.class)); + cards.add(new SetCardInfo("Suncleanser", 39, Rarity.RARE, mage.cards.s.Suncleanser.class)); cards.add(new SetCardInfo("Supreme Phantom", 76, Rarity.RARE, mage.cards.s.SupremePhantom.class)); cards.add(new SetCardInfo("Sure Strike", 161, Rarity.COMMON, mage.cards.s.SureStrike.class)); cards.add(new SetCardInfo("Surge Mare", 77, Rarity.UNCOMMON, mage.cards.s.SurgeMare.class)); From ea0b809c2bcb688fd9f4883338fff83cf71107c1 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 14:56:27 -0400 Subject: [PATCH 27/74] fixed Sarkhan's Unsealing second ability --- Mage.Sets/src/mage/cards/s/SarkhansUnsealing.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SarkhansUnsealing.java b/Mage.Sets/src/mage/cards/s/SarkhansUnsealing.java index c5c6965d219..95f462deeaa 100644 --- a/Mage.Sets/src/mage/cards/s/SarkhansUnsealing.java +++ b/Mage.Sets/src/mage/cards/s/SarkhansUnsealing.java @@ -2,7 +2,6 @@ package mage.cards.s; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DamagePlayersEffect; @@ -34,7 +33,7 @@ public final class SarkhansUnsealing extends CardImpl { static { filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 7)); - filter2.add(new PowerPredicate(ComparisonType.MORE_THAN, 7)); + filter2.add(new PowerPredicate(ComparisonType.MORE_THAN, 6)); filter3.add(Predicates.or( new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.PLANESWALKER) @@ -46,14 +45,18 @@ public final class SarkhansUnsealing extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); // Whenever you cast a creature spell with power 4, 5, or 6, Sarkhan's Unsealing deals 4 damage to any target. - Ability ability = new SpellCastControllerTriggeredAbility(new DamageTargetEffect(4), filter, false); + Ability ability = new SpellCastControllerTriggeredAbility( + new DamageTargetEffect(4), + filter, false + ); ability.addTarget(new TargetAnyTarget()); this.addAbility(ability); // Whenever you cast a creature spell with power 7 or greater, Sarkhan's Unsealing deals 4 damage to each opponent and each creature and planeswalker they control. - ability = new EntersBattlefieldTriggeredAbility( + ability = new SpellCastControllerTriggeredAbility( new DamagePlayersEffect(4, TargetController.OPPONENT) - .setText("{this} deals 4 damage to each opponent") + .setText("{this} deals 4 damage to each opponent"), + filter2, false ); ability.addEffect( new DamageAllEffect(4, filter3) From b86294913371def7aa27bad4a51a070860d24433 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 15:36:25 -0400 Subject: [PATCH 28/74] added MTGO Vintage Cube June 2018 to config.xml --- Mage.Server/config/config.xml | 1 + Mage.Server/release/config/config.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index 01a85130846..6ee934ff18f 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -133,6 +133,7 @@ + diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config/config.xml index b3716c92f87..40a2f3ad72c 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config/config.xml @@ -127,6 +127,7 @@ + From bde05efec6a4d9f1e4530d15eca1265d39edf240 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 15:56:52 -0400 Subject: [PATCH 29/74] updated Dragon Egg's subtypes --- Mage.Sets/src/mage/cards/d/DragonEgg.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DragonEgg.java b/Mage.Sets/src/mage/cards/d/DragonEgg.java index f7bf84e88e3..7758179a672 100644 --- a/Mage.Sets/src/mage/cards/d/DragonEgg.java +++ b/Mage.Sets/src/mage/cards/d/DragonEgg.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -20,7 +19,7 @@ public final class DragonEgg extends CardImpl { public DragonEgg(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); - this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.DRAGON, SubType.EGG); this.power = new MageInt(0); this.toughness = new MageInt(2); From caf8ff3cf891d62ac8df60a7a453a4ec0515582c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Alexandre=20Salgueiro=20Galrito?= Date: Fri, 22 Jun 2018 21:20:37 +0100 Subject: [PATCH 30/74] fix Ajani Adversary of Tyrants +1 --- Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java b/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java index ecd035c1610..2197d263dc3 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java +++ b/Mage.Sets/src/mage/cards/a/AjaniAdversaryOfTyrants.java @@ -41,7 +41,7 @@ public final class AjaniAdversaryOfTyrants extends CardImpl { this.addAbility(new PlanswalkerEntersWithLoyalityCountersAbility(4)); // +1: Put a +1/+1 counter on each of up to two target creatures. - Ability ability = new LoyaltyAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); + Ability ability = new LoyaltyAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1); ability.addTarget(new TargetCreaturePermanent(0, 2)); this.addAbility(ability); From 6477334e17b876c7b1c7f480ee3f8aa2d4a67b87 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 16:37:09 -0400 Subject: [PATCH 31/74] added Pendulum of Patterns to M19 --- Mage.Sets/src/mage/sets/CoreSet2019.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/CoreSet2019.java b/Mage.Sets/src/mage/sets/CoreSet2019.java index 0a0bfed9787..34b7cb26f60 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2019.java +++ b/Mage.Sets/src/mage/sets/CoreSet2019.java @@ -223,6 +223,7 @@ public final class CoreSet2019 extends ExpansionSet { cards.add(new SetCardInfo("Patient Rebuilding", 67, Rarity.RARE, mage.cards.p.PatientRebuilding.class)); cards.add(new SetCardInfo("Pegasus Courser", 32, Rarity.COMMON, mage.cards.p.PegasusCourser.class)); cards.add(new SetCardInfo("Pelakka Wurm", 192, Rarity.RARE, mage.cards.p.PelakkaWurm.class)); + cards.add(new SetCardInfo("Pendulum of Patterns", 288, Rarity.COMMON, mage.cards.p.PendulumOfPatterns.class)); cards.add(new SetCardInfo("Phylactery Lich", 113, Rarity.RARE, mage.cards.p.PhylacteryLich.class)); cards.add(new SetCardInfo("Plague Mare", 114, Rarity.UNCOMMON, mage.cards.p.PlagueMare.class)); cards.add(new SetCardInfo("Plains", 261, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); From b473e07d51a0c250da315204263b582632cba284 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 16:59:40 -0400 Subject: [PATCH 32/74] changed Dragon Egg back until MTGJSON is updated with M19 --- Mage.Sets/src/mage/cards/d/DragonEgg.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/d/DragonEgg.java b/Mage.Sets/src/mage/cards/d/DragonEgg.java index 7758179a672..a863de11cde 100644 --- a/Mage.Sets/src/mage/cards/d/DragonEgg.java +++ b/Mage.Sets/src/mage/cards/d/DragonEgg.java @@ -19,7 +19,7 @@ public final class DragonEgg extends CardImpl { public DragonEgg(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); - this.subtype.add(SubType.DRAGON, SubType.EGG); + this.subtype.add(SubType.DRAGON);//, SubType.EGG); uncomment when MTGJSON is updated with M19 this.power = new MageInt(0); this.toughness = new MageInt(2); From dd51968dbcc493a667fefc2c88893c5c044da1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Alexandre=20Salgueiro=20Galrito?= Date: Fri, 22 Jun 2018 22:08:53 +0100 Subject: [PATCH 33/74] fixed Departed Deckand p/t --- Mage.Sets/src/mage/cards/d/DepartedDeckhand.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Sets/src/mage/cards/d/DepartedDeckhand.java b/Mage.Sets/src/mage/cards/d/DepartedDeckhand.java index 23bf78cda76..b8a94e36205 100644 --- a/Mage.Sets/src/mage/cards/d/DepartedDeckhand.java +++ b/Mage.Sets/src/mage/cards/d/DepartedDeckhand.java @@ -20,6 +20,7 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.SubtypePredicate; import mage.target.common.TargetControlledCreaturePermanent; +import mage.MageInt; /** * @@ -39,6 +40,9 @@ public final class DepartedDeckhand extends CardImpl { this.subtype.add(SubType.SPIRIT); this.subtype.add(SubType.PIRATE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + // When Departed Deckhand becomes the target of a spell, sacrifice it. this.addAbility(new BecomesTargetTriggeredAbility( new SacrificeSourceEffect(), From 3f73cf4121e3e1abcf7efd9b0d25dc59d5a7d963 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 17:18:40 -0400 Subject: [PATCH 34/74] add power/toughness to Dark-Dweller Oracle --- Mage.Sets/src/mage/cards/d/DarkDwellerOracle.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage.Sets/src/mage/cards/d/DarkDwellerOracle.java b/Mage.Sets/src/mage/cards/d/DarkDwellerOracle.java index 8ebde210bdb..fe999c3dc9c 100644 --- a/Mage.Sets/src/mage/cards/d/DarkDwellerOracle.java +++ b/Mage.Sets/src/mage/cards/d/DarkDwellerOracle.java @@ -1,6 +1,7 @@ package mage.cards.d; import java.util.UUID; +import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; @@ -35,6 +36,8 @@ public final class DarkDwellerOracle extends CardImpl { this.subtype.add(SubType.GOBLIN); this.subtype.add(SubType.SHAMAN); + this.power = new MageInt(2); + this.toughness = new MageInt(2); // {1}, Sacrifice a creature: Exile the top card of your library. You may play that card this turn. Ability ability = new SimpleActivatedAbility( From fb36f329f97845a170c7b19624d5e830b75fa319 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 23 Jun 2018 01:19:54 +0400 Subject: [PATCH 35/74] Fixed Dragon Egg tests --- Mage.Sets/src/mage/cards/d/DragonEgg.java | 2 +- Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/d/DragonEgg.java b/Mage.Sets/src/mage/cards/d/DragonEgg.java index a863de11cde..7758179a672 100644 --- a/Mage.Sets/src/mage/cards/d/DragonEgg.java +++ b/Mage.Sets/src/mage/cards/d/DragonEgg.java @@ -19,7 +19,7 @@ public final class DragonEgg extends CardImpl { public DragonEgg(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); - this.subtype.add(SubType.DRAGON);//, SubType.EGG); uncomment when MTGJSON is updated with M19 + this.subtype.add(SubType.DRAGON, SubType.EGG); this.power = new MageInt(0); this.toughness = new MageInt(2); diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 344b52e7ea9..30612176f00 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -72,6 +72,7 @@ public class VerifyCardDataTest { // subtype skipListCreate("SUBTYPE"); + skipListAddName("SUBTYPE", "Dragon Egg"); // uncomment when MTGJSON is updated with M19 // number skipListCreate("NUMBER"); From 6ec0cdd34c70dfe9e78182ae191ba0be9b812f4c Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Fri, 22 Jun 2018 17:26:40 -0400 Subject: [PATCH 36/74] Implement Dwarven Sea Clan --- .../src/mage/cards/d/DwarvenSeaClan.java | 86 +++++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 1 + 2 files changed, 87 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DwarvenSeaClan.java diff --git a/Mage.Sets/src/mage/cards/d/DwarvenSeaClan.java b/Mage.Sets/src/mage/cards/d/DwarvenSeaClan.java new file mode 100644 index 00000000000..3443e2074ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DwarvenSeaClan.java @@ -0,0 +1,86 @@ +package mage.cards.d; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingOrBlockingCreature; +import mage.filter.predicate.permanent.ControllerControlsIslandPredicate; +import mage.game.Game; +import mage.target.common.TargetAttackingOrBlockingCreature; + +/** + * + * @author noahg + */ +public final class DwarvenSeaClan extends CardImpl { + + private static final FilterAttackingOrBlockingCreature filter = new FilterAttackingOrBlockingCreature(); + + static { + filter.add(new ControllerControlsIslandPredicate()); + } + + public DwarvenSeaClan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.subtype.add(SubType.DWARF); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {tap}: Choose target attacking or blocking creature whose controller controls an Island. Dwarven Sea Clan deals 2 damage to that creature at end of combat. Activate this ability only before the end of combat step. + Effect effect = new CreateDelayedTriggeredAbilityEffect(new AtTheEndOfCombatDelayedTriggeredAbility(new DamageTargetEffect(2, true, "that creature"))); + effect.setText("Choose target attacking or blocking creature whose controller controls an Island. Dwarven Sea Clan deals 2 damage to that creature at end of combat."); + Ability ability = new ActivateIfConditionActivatedAbility( + Zone.BATTLEFIELD, + effect, + new TapSourceCost(), + BeforeEndCombatCondition.getInstance() + ); + ability.addTarget(new TargetAttackingOrBlockingCreature(1, 1, filter, false)); + addAbility(ability); + } + + public DwarvenSeaClan(final DwarvenSeaClan card) { + super(card); + } + + @Override + public DwarvenSeaClan copy() { + return new DwarvenSeaClan(this); + } +} + +class BeforeEndCombatCondition implements Condition { + private static final BeforeEndCombatCondition instance = new BeforeEndCombatCondition(); + + public static Condition getInstance() { + return instance; + } + + @Override + public boolean apply(Game game, Ability source) { + PhaseStep phaseStep = game.getStep().getType(); + if(phaseStep.getIndex() < PhaseStep.END_COMBAT.getIndex()) { + return true; + } + return false; + } + + @Override + public String toString() { + return "before the end of combat step"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 5de1fb7d4e6..4502fe8dbd1 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -89,6 +89,7 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Dry Spell", "46a", Rarity.COMMON, DrySpell.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dry Spell", "46b", Rarity.COMMON, DrySpell.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dwarven Pony", 70, Rarity.RARE, mage.cards.d.DwarvenPony.class)); + cards.add(new SetCardInfo("Dwarven Sea Clan", 71, Rarity.RARE, mage.cards.d.DwarvenSeaClan.class)); cards.add(new SetCardInfo("Dwarven Trader", "72a", Rarity.COMMON, DwarvenTrader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dwarven Trader", "72b", Rarity.COMMON, DwarvenTrader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ebony Rhino", 106, Rarity.COMMON, mage.cards.e.EbonyRhino.class)); From 2de105ad5e171e7d6f0788378ac0e450f07ddbb1 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 17:50:36 -0400 Subject: [PATCH 37/74] fixed Vaevictis Asmadi, the Dire's triggered ability triggering incorrectly and not properly forcing sacrifice --- .../mage/cards/v/VaevictisAsmadiTheDire.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/VaevictisAsmadiTheDire.java b/Mage.Sets/src/mage/cards/v/VaevictisAsmadiTheDire.java index 16cfdcbb651..70cdd363794 100644 --- a/Mage.Sets/src/mage/cards/v/VaevictisAsmadiTheDire.java +++ b/Mage.Sets/src/mage/cards/v/VaevictisAsmadiTheDire.java @@ -23,6 +23,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.Target; import mage.target.TargetPermanent; /** @@ -79,6 +80,9 @@ class VaevictisAsmadiTheDireTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + if (!game.getCombat().getAttackers().contains(this.getSourceId())) { + return false; + } this.getTargets().clear(); for (UUID playerId : game.getState().getPlayerList(this.getControllerId())) { Player player = game.getPlayer(playerId); @@ -125,15 +129,17 @@ class VaevictisAsmadiTheDireEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { List playersToFlip = new ArrayList(); - for (UUID permId : this.getTargetPointer().getTargets(game, source)) { - Permanent permanent = game.getPermanent(permId); - if (permanent == null - || !permanent.sacrifice(source.getSourceId(), game)) { - continue; - } - Player player = game.getPlayer(permanent.getControllerId()); - if (player != null) { - playersToFlip.add(player); + for (Target target : source.getTargets()) { + for (UUID permId : target.getTargets()) { + Permanent permanent = game.getPermanent(permId); + if (permanent == null + || !permanent.sacrifice(source.getSourceId(), game)) { + continue; + } + Player player = game.getPlayer(permanent.getControllerId()); + if (player != null) { + playersToFlip.add(player); + } } } for (Player player : playersToFlip) { From 9eeebcd3d35ee71a3554b5cfa0db922f64161fb0 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Fri, 22 Jun 2018 18:27:21 -0400 Subject: [PATCH 38/74] Start implementing Joven's Ferrets --- Mage.Sets/src/mage/cards/j/JovensFerrets.java | 108 ++++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 1 + Mage.Sets/src/mage/sets/MastersEditionII.java | 1 + 3 files changed, 110 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/j/JovensFerrets.java diff --git a/Mage.Sets/src/mage/cards/j/JovensFerrets.java b/Mage.Sets/src/mage/cards/j/JovensFerrets.java new file mode 100644 index 00000000000..abf0696185f --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JovensFerrets.java @@ -0,0 +1,108 @@ +package mage.cards.j; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EndOfCombatTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.TapAllEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.common.BlockedAttackerWatcher; + +/** + * + * @author noahg + */ +public final class JovensFerrets extends CardImpl { + + public JovensFerrets(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); + + this.subtype.add(SubType.FERRET); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever Joven's Ferrets attacks, it gets +0/+2 until end of turn. + Effect boostSourceEffect = new BoostSourceEffect(0, 2, Duration.EndOfTurn); + boostSourceEffect.setText("it gets +0/+2 until end of turn"); + this.addAbility(new AttacksTriggeredAbility(boostSourceEffect, false)); + + // At end of combat, tap all creatures that blocked Joven's Ferrets this turn. They don't untap during their controller's next untap step. + Ability eocAbility = new EndOfCombatTriggeredAbility(new JovensFerretsEffect(), false); + eocAbility.addWatcher(new BlockedAttackerWatcher()); + this.addAbility(eocAbility); + } + + public JovensFerrets(final JovensFerrets card) { + super(card); + } + + @Override + public JovensFerrets copy() { + return new JovensFerrets(this); + } +} + +class JovensFerretsEffect extends DontUntapInControllersNextUntapStepTargetEffect { + + public JovensFerretsEffect() { + super(); + } + + public JovensFerretsEffect(final JovensFerretsEffect effect) { + super(effect); + } + + @Override + public JovensFerretsEffect copy() { + return new JovensFerretsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (controller != null && sourcePermanent != null) { + BlockedAttackerWatcher watcher = (BlockedAttackerWatcher) game.getState().getWatchers().get(BlockedAttackerWatcher.class.getSimpleName()); + if (watcher != null) { + List toTap = new ArrayList<>(); + for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source.getSourceId(), game)) { + if (!creature.getId().equals(source.getSourceId())) { + if (watcher.creatureHasBlockedAttacker(sourcePermanent, creature, game)) { + toTap.add(creature); + } + } + } + for (Permanent creature : toTap) { + creature.tap(game); + getTargetPointer().getTargets(game, source).add(creature.getId()); + } + return true; + } + } + return false; + } + + @Override + public String getText(Mode mode) { + return "tap all creatures that blocked {this} this turn. They don't untap during their controller's next untap step."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 5de1fb7d4e6..76daaa08075 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -114,6 +114,7 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Ironclaw Curse", 76, Rarity.RARE, mage.cards.i.IronclawCurse.class)); cards.add(new SetCardInfo("Jinx", 29, Rarity.COMMON, mage.cards.j.Jinx.class)); cards.add(new SetCardInfo("Joven", 77, Rarity.COMMON, mage.cards.j.Joven.class)); + cards.add(new SetCardInfo("Joven's Ferrets", 89, Rarity.COMMON, mage.cards.j.JovensFerrets.class)); cards.add(new SetCardInfo("Joven's Tools", 108, Rarity.UNCOMMON, mage.cards.j.JovensTools.class)); cards.add(new SetCardInfo("Koskun Falls", 55, Rarity.RARE, mage.cards.k.KoskunFalls.class)); cards.add(new SetCardInfo("Koskun Keep", 114, Rarity.UNCOMMON, mage.cards.k.KoskunKeep.class)); diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index d466ec8914b..bee97c5b587 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -144,6 +144,7 @@ public final class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Jester's Mask", 211, Rarity.RARE, mage.cards.j.JestersMask.class)); cards.add(new SetCardInfo("Jeweled Amulet", 212, Rarity.UNCOMMON, mage.cards.j.JeweledAmulet.class)); cards.add(new SetCardInfo("Johtull Wurm", 168, Rarity.UNCOMMON, mage.cards.j.JohtullWurm.class)); + cards.add(new SetCardInfo("Joven's Ferrets", 169, Rarity.UNCOMMON, mage.cards.j.JovensFerrets.class)); cards.add(new SetCardInfo("Juniper Order Advocate", 20, Rarity.UNCOMMON, mage.cards.j.JuniperOrderAdvocate.class)); cards.add(new SetCardInfo("Karplusan Giant", 133, Rarity.UNCOMMON, mage.cards.k.KarplusanGiant.class)); cards.add(new SetCardInfo("Kaysa", 170, Rarity.RARE, mage.cards.k.Kaysa.class)); From 589121d74a0939feecc227d7d1d7e9197fe4133a Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 20:22:51 -0400 Subject: [PATCH 39/74] Fixed Sarkhan Fireblood's loyalty costs --- Mage.Sets/src/mage/cards/s/SarkhanFireblood.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SarkhanFireblood.java b/Mage.Sets/src/mage/cards/s/SarkhanFireblood.java index 50970217812..f997638c233 100644 --- a/Mage.Sets/src/mage/cards/s/SarkhanFireblood.java +++ b/Mage.Sets/src/mage/cards/s/SarkhanFireblood.java @@ -50,11 +50,11 @@ public final class SarkhanFireblood extends CardImpl { new StaticValue(2), new ConditionalSpellManaBuilder(filter), false - ) + ), 1 )); // -7: Create four 5/5 red Dragon creature tokens with flying. - this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new DragonToken2(), 4))); + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new DragonToken2(), 4), -7)); } public SarkhanFireblood(final SarkhanFireblood card) { From 5435deae7b8909bd9d47b178cfa2540692b4cb71 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 21:25:52 -0400 Subject: [PATCH 40/74] Fixed Preeminent Captain putting creatures into play untapped --- Mage.Sets/src/mage/cards/p/PreeminentCaptain.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PreeminentCaptain.java b/Mage.Sets/src/mage/cards/p/PreeminentCaptain.java index 05d5ae902e5..552d957cb2b 100644 --- a/Mage.Sets/src/mage/cards/p/PreeminentCaptain.java +++ b/Mage.Sets/src/mage/cards/p/PreeminentCaptain.java @@ -1,4 +1,3 @@ - package mage.cards.p; import java.util.UUID; @@ -72,12 +71,13 @@ class PreeminentCaptainEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); TargetCardInHand target = new TargetCardInHand(filter); - if (target.canChoose(controller.getId(), game) && target.choose(getOutcome(), controller.getId(), source.getSourceId(), game)) { + if (target.canChoose(controller.getId(), game) + && target.choose(getOutcome(), controller.getId(), source.getSourceId(), game)) { if (!target.getTargets().isEmpty()) { UUID cardId = target.getFirstTarget(); Card card = controller.getHand().get(cardId, game); if (card != null) { - if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { + if (controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, true, null)) { Permanent permanent = game.getPermanent(card.getId()); if (permanent != null) { game.getCombat().addAttackingCreature(permanent.getId(), game); From 39075e000fe557455542920dd49901aad3343978 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 22 Jun 2018 21:26:16 -0400 Subject: [PATCH 41/74] small text fix --- Mage.Sets/src/mage/cards/s/ShieldMare.java | 2 +- Utils/mtg-cards-data.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/ShieldMare.java b/Mage.Sets/src/mage/cards/s/ShieldMare.java index 388a1800953..b75a0b10d09 100644 --- a/Mage.Sets/src/mage/cards/s/ShieldMare.java +++ b/Mage.Sets/src/mage/cards/s/ShieldMare.java @@ -111,6 +111,6 @@ class ShieldMareTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { return "When {this} enters the battlefield or becomes the target" - + " of a spell or ability and opponent controls, you gain 3 life"; + + " of a spell or ability an opponent controls, you gain 3 life"; } } diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 461becd28b9..5b095175494 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -33621,7 +33621,7 @@ Remorseful Cleric|Core Set 2019|33|R|{1}{W}|Creature - Spirit Cleric|2|1|Flying$ Resplendent Angel|Core Set 2019|34|M|{1}{W}{W}|Creature - Angel|3|3|Flying$At the beginning of each end step, if you gained 5 or more life this turn, create a 4/4 white Angel creature token with flying and vigilance.${3}{W}{W}{W}: Until end of turn, Resplendent Angel gets +2/+2 and gains lifelink.| Revitalize|Core Set 2019|35|C|{1}{W}|Instant|||You gain 3 life.$Draw a card.| Rustwing Falcon|Core Set 2019|36|C|{W}|Creature - Bird|1|2|Flying| -Shield Mare|Core Set 2019|37|U|{1}{W}{W}|Creature - Horse|2|3|Shield Mare can't be blocked by red creatures.$When Shield Mare enters the battlefield or becomes the target of a spell or ability and opponent controls, you gain 3 life.| +Shield Mare|Core Set 2019|37|U|{1}{W}{W}|Creature - Horse|2|3|Shield Mare can't be blocked by red creatures.$When Shield Mare enters the battlefield or becomes the target of a spell or ability an opponent controls, you gain 3 life.| Star-Crowned Stag|Core Set 2019|38|C|{3}{W}|Creature - Elk|3|3|Whenever Star-Crowned Stag attacks, tap target creature defending player controls.| Suncleanser|Core Set 2019|39|R|{1}{W}|Creature - Human Cleric|1|4|When Suncleanser enters the battlefield, choose one —$• Remove all counters from target creature. It can't have counters put on it for as long as Suncleanser remains on the battlefield.$• Target opponent loses all counters. That player can't get counters for as long as Suncleanser remains on the battlefield.| Take Vengeance|Core Set 2019|40|C|{1}{W}|Sorcery|||Destroy target tapped creature.| @@ -33633,7 +33633,7 @@ Aven Wind Mage|Core Set 2019|45|C|{2}{U}|Creature - Bird Wizard|2|2|Flying$Whene Aviation Pioneer|Core Set 2019|46|C|{2}{U}|Creature - Human Artificer|1|2|When Aviation Pioneer enters the battlefield, create a 1/1 colorless Thopter artifact creature token with flying.| Bone to Ash|Core Set 2019|47|U|{2}{U}{U}|Instant|||Counter target creature spell.$Draw a card.| Cancel|Core Set 2019|48|C|{1}{U}{U}|Instant|||Counter target spell.| -Departed Deckhand|Core Set 2019|49|U|{1}{U}|Creature - Spirit Pirate|||When Departed Deckhand becomes the target of a spell, sacrifice it.$Departed Deckhand can't be blocked except by Spirits.${3}{U}: Another target creature you control can't be blocked this turn except by Spirits.| +Departed Deckhand|Core Set 2019|49|U|{1}{U}|Creature - Spirit Pirate|2|2|When Departed Deckhand becomes the target of a spell, sacrifice it.$Departed Deckhand can't be blocked except by Spirits.${3}{U}: Another target creature you control can't be blocked this turn except by Spirits.| Disperse|Core Set 2019|50|C|{1}{U}|Instant|||Return target nonland permanent to its owner's hand.| Divination|Core Set 2019|51|C|{2}{U}|Sorcery|||Draw two cards.| Djinn of Wishes|Core Set 2019|52|R|{3}{U}{U}|Creature - Djinn|4|4|Flying$Djinn of Wishes enters the battlefield with three wish counters on it.${2}{U}{U}, Remove a wish counter from Djinn of Wishes: Reveal the top card of your library. You may play that card without paying its mana cost. If you don't, exile it.| @@ -33718,7 +33718,7 @@ Banefire|Core Set 2019|130|R|{X}{R}|Sorcery|||Banefire deals X damage to any tar Boggart Brute|Core Set 2019|131|C|{2}{R}|Creature - Goblin Warrior|3|2|Menace| Catalyst Elemental|Core Set 2019|132|C|{2}{R}|Creature - Elemental|2|2|Sacrifice Catalyst Elemental: Add {R}{R}.| Crash Through|Core Set 2019|133|C|{R}|Sorcery|||Creatures you control gain trample until end of turn.$Draw a card.| -Dark-Dweller Oracle|Core Set 2019|134|R|{1}{R}|Creature - Goblin Shaman|||{1}, Sacrifice a creature: Exile the top card of your library. You may play that card this turn.| +Dark-Dweller Oracle|Core Set 2019|134|R|{1}{R}|Creature - Goblin Shaman|2|2|{1}, Sacrifice a creature: Exile the top card of your library. You may play that card this turn.| Demanding Dragon|Core Set 2019|135|R|{3}{R}{R}|Creature - Dragon|5|5|Flying$When Demanding Dragon enters the battlefield, it deals 5 damage to target opponent unless that player sacrifices a creature.| Dismissive Pyromancer|Core Set 2019|136|R|{1}{R}|Creature - Human Wizard|2|2|{R}, {T}, Discard a card: Draw a card.${2}{R}, {T}, Sacrifice Dismissive Pyromancer: It deals 4 damage to target creature.| Doublecast|Core Set 2019|137|U|{R}{R}|Sorcery|||When you cast your next instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.| @@ -33825,7 +33825,7 @@ Gargoyle Sentinel|Core Set 2019|236|U|{3}|Artifact Creature - Gargoyle|3|3|Defen Gearsmith Guardian|Core Set 2019|237|C|{5}|Artifact Creature - Construct|3|5|Gearsmith Guardian gets +2/+0 as long as you control a blue creature.| Magistrate's Scepter|Core Set 2019|238|R|{3}|Artifact|||{4}, {T}: Put a charge counter on Magistrate's Scepter.${T}, Remove three charge counters from Magistrate's Scepter: Take an extra turn after this one.| Manalith|Core Set 2019|239|C|{3}|Artifact|||{T}: Add one mana of any color.| -Marauder's Axe|Core Set 2019|240|C|{2}|Artifact - Equipment|||Equipped creature +2/+0.$Equip {2}| +Marauder's Axe|Core Set 2019|240|C|{2}|Artifact - Equipment|||Equipped creature gets +2/+0.$Equip {2}| Meteor Golem|Core Set 2019|241|U|{7}|Artifact Creature - Golem|3|3|When Meteor Golem enters the battlefield, destroy target nonland permanent an opponent controls.| Millstone|Core Set 2019|242|U|{2}|Artifact|||{2}, {T}: Target player puts the top two cards of their library into their graveyard.| Rogue's Gloves|Core Set 2019|243|U|{2}|Artifact - Equipment|||Whenever equipped creature deals combat damage to a player, you may draw a card.$Equip {2}| From 6c20ee4a74397da165267b6080ae9d3bf98a31a6 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 23 Jun 2018 16:27:34 +0400 Subject: [PATCH 42/74] * Images: added localized card images download from scryfall source; --- .../sources/AltMtgOnlTokensImageSource.java | 9 +- .../card/dl/sources/CardImageSource.java | 4 +- .../card/dl/sources/CardImageUrls.java | 59 ++++++++++ .../card/dl/sources/GrabbagImageSource.java | 8 +- .../dl/sources/MagicCardsImageSource.java | 11 +- .../card/dl/sources/MagidexImageSource.java | 8 +- .../card/dl/sources/MtgImageSource.java | 10 +- .../dl/sources/MtgOnlTokensImageSource.java | 6 +- .../dl/sources/MythicspoilerComSource.java | 10 +- .../card/dl/sources/ScryfallImageSource.java | 58 +++++++--- .../card/dl/sources/TokensMtgImageSource.java | 10 +- .../dl/sources/WizardCardsImageSource.java | 15 ++- .../plugins/card/images/DownloadPictures.java | 102 ++++++++++++------ .../client/game/TokensMtgImageSourceTest.java | 13 +-- 14 files changed, 230 insertions(+), 93 deletions(-) create mode 100644 Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/AltMtgOnlTokensImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/AltMtgOnlTokensImageSource.java index 6d695696142..df290687c19 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/AltMtgOnlTokensImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/AltMtgOnlTokensImageSource.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.util.HashMap; /** - * * @author spjspj */ public enum AltMtgOnlTokensImageSource implements CardImageSource { @@ -60,7 +59,7 @@ public enum AltMtgOnlTokensImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { return null; } @@ -122,7 +121,7 @@ public enum AltMtgOnlTokensImageSource implements CardImageSource { } @Override - public String generateTokenUrl(CardDownloadData card) throws IOException { + public CardImageUrls generateTokenUrl(CardDownloadData card) throws IOException { if (copyUrlToImage == null) { setupLinks(); } @@ -139,12 +138,12 @@ public enum AltMtgOnlTokensImageSource implements CardImageSource { } return -1; } - + @Override public boolean isTokenSource() { return true; } - + @Override public void doPause(String httpImageUrl) { } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java index e49c334a1c2..f2d0fabf753 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageSource.java @@ -9,9 +9,9 @@ import org.mage.plugins.card.images.CardDownloadData; */ public interface CardImageSource { - String generateURL(CardDownloadData card) throws Exception; + CardImageUrls generateURL(CardDownloadData card) throws Exception; - String generateTokenUrl(CardDownloadData card) throws Exception; + CardImageUrls generateTokenUrl(CardDownloadData card) throws Exception; String getNextHttpImageUrl(); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java new file mode 100644 index 00000000000..1176ad3feda --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CardImageUrls.java @@ -0,0 +1,59 @@ +package org.mage.plugins.card.dl.sources; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author JayDi85 + */ +public class CardImageUrls { + + public String baseUrl; + public List alternativeUrls; + + public CardImageUrls() { + this.baseUrl = null; + this.alternativeUrls = new ArrayList<>(); + } + + public CardImageUrls(String baseUrl) { + this(baseUrl, null); + } + + public CardImageUrls(String baseUrl, String alternativeUrl) { + this(); + + this.baseUrl = baseUrl; + + if (alternativeUrl != null && !alternativeUrl.isEmpty()) { + this.alternativeUrls.add(alternativeUrl); + } + } + + public List getDownloadList() { + List downloadUrls = new ArrayList<>(); + + if (this.baseUrl != null && !this.baseUrl.isEmpty()) { + downloadUrls.add(this.baseUrl); + } + + // no needs in base url duplicate + if (this.alternativeUrls != null) { + for (String url : this.alternativeUrls) { + if (!url.equals(this.baseUrl)) { + downloadUrls.add(url); + } + } + } + + return downloadUrls; + } + + public void addAlternativeUrl(String url) { + if (url != null && !url.isEmpty()) { + this.alternativeUrls.add(url); + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java index 38bb08048a6..2d39d4e9d3f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java @@ -8,11 +8,11 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; + import org.apache.log4j.Logger; import org.mage.plugins.card.images.CardDownloadData; /** - * * @author spjspj */ public enum GrabbagImageSource implements CardImageSource { @@ -48,7 +48,7 @@ public enum GrabbagImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { if (singleLinks == null) { setupLinks(); } @@ -63,7 +63,7 @@ public enum GrabbagImageSource implements CardImageSource { } if (url != null) { - return getSourceName(card, url) + url; + return new CardImageUrls(getSourceName(card, url) + url); } return null; } @@ -375,7 +375,7 @@ public enum GrabbagImageSource implements CardImageSource { } @Override - public String generateTokenUrl(CardDownloadData card) throws IOException { + public CardImageUrls generateTokenUrl(CardDownloadData card) throws IOException { try { return generateURL(card); } catch (Exception ex) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java index 5b72b8aa10e..dfee0c9a469 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java @@ -6,12 +6,12 @@ import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; + import mage.client.dialog.PreferencesDialog; import org.mage.plugins.card.images.CardDownloadData; import org.mage.plugins.card.utils.CardImageUtils; /** - * * @author North */ public enum MagicCardsImageSource implements CardImageSource { @@ -342,6 +342,7 @@ public enum MagicCardsImageSource implements CardImageSource { put("WWK", "worldwake"); put("ZEN", "zendikar"); } + private static final long serialVersionUID = 1L; }; @@ -361,7 +362,7 @@ public enum MagicCardsImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { String collectorId = card.getCollectorId(); String cardSet = card.getSet(); if (collectorId == null || cardSet == null) { @@ -389,11 +390,11 @@ public enum MagicCardsImageSource implements CardImageSource { } url.append(".jpg"); - return url.toString(); + return new CardImageUrls(url.toString()); } @Override - public String generateTokenUrl(CardDownloadData card) { + public CardImageUrls generateTokenUrl(CardDownloadData card) { String name = card.getName(); // add type to name if it's not 0 if (card.getType() > 0) { @@ -406,7 +407,7 @@ public enum MagicCardsImageSource implements CardImageSource { } else { set += '-' + card.getSet(); } - return "http://magiccards.info/extras/token/" + set + '/' + name + ".jpg"; + return new CardImageUrls("http://magiccards.info/extras/token/" + set + '/' + name + ".jpg"); } @Override diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagidexImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagidexImageSource.java index cb03cf64960..f8601c10434 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagidexImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagidexImageSource.java @@ -8,10 +8,10 @@ import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; + import org.mage.plugins.card.images.CardDownloadData; /** - * * @author Pete Rossi */ public enum MagidexImageSource implements CardImageSource { @@ -233,7 +233,7 @@ public enum MagidexImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { String cardDownloadName = card.getDownloadName().toLowerCase(Locale.ENGLISH); String cardSet = card.getSet(); @@ -247,7 +247,7 @@ public enum MagidexImageSource implements CardImageSource { // This will properly escape the url URI uri = new URI("http", "magidex.com", "/extstatic/card/" + formatSetName(cardSet) + '/' + cardDownloadName + ".jpg", null, null); - return uri.toASCIIString(); + return new CardImageUrls(uri.toASCIIString()); } private String formatSetName(String setName) { @@ -264,7 +264,7 @@ public enum MagidexImageSource implements CardImageSource { }; @Override - public String generateTokenUrl(CardDownloadData card) { + public CardImageUrls generateTokenUrl(CardDownloadData card) { return null; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgImageSource.java index d24ba0c4994..f5104a22269 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgImageSource.java @@ -2,14 +2,12 @@ package org.mage.plugins.card.dl.sources; import java.util.Locale; + import org.mage.plugins.card.images.CardDownloadData; /** * Site was shutdown by wizards Feb. 2015 * - * - * - * * @author LevelX2 */ public enum MtgImageSource implements CardImageSource { @@ -32,7 +30,7 @@ public enum MtgImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { String collectorId = card.getCollectorId(); String cardSet = card.getSet(); if (collectorId == null || cardSet == null) { @@ -59,11 +57,11 @@ public enum MtgImageSource implements CardImageSource { } url.append(".jpg"); - return url.toString(); + return new CardImageUrls(url.toString()); } @Override - public String generateTokenUrl(CardDownloadData card) { + public CardImageUrls generateTokenUrl(CardDownloadData card) { return null; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java index 4b2bf849dbf..9d82cbb425c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java @@ -3,11 +3,11 @@ package org.mage.plugins.card.dl.sources; import java.io.IOException; import java.util.HashMap; + import org.apache.log4j.Logger; import org.mage.plugins.card.images.CardDownloadData; /** - * * @author spjspj */ public enum MtgOnlTokensImageSource implements CardImageSource { @@ -59,7 +59,7 @@ public enum MtgOnlTokensImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { return null; } @@ -324,7 +324,7 @@ public enum MtgOnlTokensImageSource implements CardImageSource { } @Override - public String generateTokenUrl(CardDownloadData card) throws IOException { + public CardImageUrls generateTokenUrl(CardDownloadData card) throws IOException { if (copyUrlToImage == null) { setupLinks(); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java index 8e3625dbbb2..9a1f95093d4 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MythicspoilerComSource.java @@ -18,6 +18,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.prefs.Preferences; + import mage.client.MageFrame; import mage.remote.Connection; import mage.remote.Connection.ProxyType; @@ -28,7 +29,6 @@ import org.jsoup.select.Elements; import org.mage.plugins.card.images.CardDownloadData; /** - * * @author LevelX2 */ public enum MythicspoilerComSource implements CardImageSource { @@ -329,7 +329,7 @@ public enum MythicspoilerComSource implements CardImageSource { } private Map getSetLinksFromPage(String cardSet, Set aliasesStart, Preferences prefs, - ProxyType proxyType, String baseUrl, String pageUrl) throws IOException { + ProxyType proxyType, String baseUrl, String pageUrl) throws IOException { Map pageLinks = new HashMap<>(); String urlDocument; @@ -391,7 +391,7 @@ public enum MythicspoilerComSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { String collectorId = card.getCollectorId(); String cardSet = card.getSet(); if (collectorId == null || cardSet == null) { @@ -410,11 +410,11 @@ public enum MythicspoilerComSource implements CardImageSource { .replaceAll(",", "") .replaceAll("/", ""); String link = setLinks.get(searchName); - return link; + return new CardImageUrls(link); } @Override - public String generateTokenUrl(CardDownloadData card + public CardImageUrls generateTokenUrl(CardDownloadData card ) { return null; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index 2cb30a5a2e8..a99cf3f2876 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -6,19 +6,34 @@ import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; + +import mage.client.dialog.PreferencesDialog; import org.mage.plugins.card.images.CardDownloadData; /** * @author Quercitron, JayDi85 - * */ public enum ScryfallImageSource implements CardImageSource { instance; private final Set supportedSets; + private final Map languageAliases; ScryfallImageSource() { + // https://scryfall.com/docs/api/languages + languageAliases = new HashMap<>(); + languageAliases.put("en", "en"); + languageAliases.put("es", "es"); + languageAliases.put("jp", "ja"); + languageAliases.put("it", "it"); + languageAliases.put("fr", "fr"); + languageAliases.put("cn", "zhs"); // Simplified Chinese + languageAliases.put("de", "de"); + languageAliases.put("ko", "ko"); + languageAliases.put("pt", "pt"); + languageAliases.put("ru", "ru"); + supportedSets = new LinkedHashSet<>(); // supportedSets.add("PTC"); // supportedSets.add("LEA"); @@ -214,33 +229,52 @@ public enum ScryfallImageSource implements CardImageSource { supportedSets.add("BBD"); // supportedSets.add("CM2"); supportedSets.add("M19"); - } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { + + String preferredLanguage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PREF_LANGUAGE, "en"); + String defaultCode = "en"; + String localizedCode = languageAliases.getOrDefault(preferredLanguage, defaultCode); + // loc example: https://api.scryfall.com/cards/xln/121/ru?format=image + + // TODO: do not use API at all? It's can help with scryfall request limits (1 request instead 2) + + String baseUrl = null; + String alternativeUrl = null; // special card number like "103a" already compatible - if (card.isCollectorIdWithStr()) { - return "https://img.scryfall.com/cards/large/en/" + formatSetName(card.getSet()) + "/" + if (baseUrl == null && card.isCollectorIdWithStr()) { + baseUrl = "https://img.scryfall.com/cards/large/" + localizedCode + "/" + formatSetName(card.getSet()) + "/" + + card.getCollectorId() + ".jpg"; + alternativeUrl = "https://img.scryfall.com/cards/large/" + defaultCode + "/" + formatSetName(card.getSet()) + "/" + card.getCollectorId() + ".jpg"; } - // double faced cards do not supporte by API (need direct link for img) + // double faced cards do not supports by API (need direct link for img) // example: https://img.scryfall.com/cards/large/en/xln/173b.jpg - if (card.isTwoFacedCard()) { - return "https://img.scryfall.com/cards/large/en/" + formatSetName(card.getSet()) + "/" + if (baseUrl == null && card.isTwoFacedCard()) { + baseUrl = "https://img.scryfall.com/cards/large/" + localizedCode + "/" + formatSetName(card.getSet()) + "/" + + card.getCollectorId() + (card.isSecondSide() ? "b" : "a") + ".jpg"; + alternativeUrl = "https://img.scryfall.com/cards/large/" + defaultCode + "/" + formatSetName(card.getSet()) + "/" + card.getCollectorId() + (card.isSecondSide() ? "b" : "a") + ".jpg"; } // basic cards by api call (redirect to img link) - // example: https://api.scryfall.com/cards/xln/121?format=image - return "https://api.scryfall.com/cards/" + formatSetName(card.getSet()) + "/" - + card.getCollectorId() + "?format=image"; + // example: https://api.scryfall.com/cards/xln/121/en?format=image + if (baseUrl == null) { + baseUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet()) + "/" + + card.getCollectorId() + "/" + localizedCode + "?format=image"; + alternativeUrl = "https://api.scryfall.com/cards/" + formatSetName(card.getSet()) + "/" + + card.getCollectorId() + "/" + defaultCode + "?format=image"; + } + + return new CardImageUrls(baseUrl, alternativeUrl); } @Override - public String generateTokenUrl(CardDownloadData card) throws Exception { + public CardImageUrls generateTokenUrl(CardDownloadData card) throws Exception { return null; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TokensMtgImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TokensMtgImageSource.java index 589cc4640d9..da76bfc8b6f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TokensMtgImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TokensMtgImageSource.java @@ -16,6 +16,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; + import mage.constants.SubType; import org.apache.log4j.Logger; import org.mage.plugins.card.images.CardDownloadData; @@ -23,7 +24,6 @@ import org.mage.plugins.card.images.DownloadPictures; import org.mage.plugins.card.utils.CardImageUtils; /** - * * @author Quercitron */ public enum TokensMtgImageSource implements CardImageSource { @@ -58,7 +58,7 @@ public enum TokensMtgImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { return null; } @@ -80,7 +80,7 @@ public enum TokensMtgImageSource implements CardImageSource { } @Override - public String generateTokenUrl(CardDownloadData card) throws IOException { + public CardImageUrls generateTokenUrl(CardDownloadData card) throws IOException { String name = card.getName(); String set = card.getSet(); int type = card.getType(); @@ -125,7 +125,7 @@ public enum TokensMtgImageSource implements CardImageSource { String url = "http://tokens.mtg.onl/tokens/" + tokenData.getExpansionSetCode().trim() + '_' + tokenData.getNumber().trim() + '-' + tokenData.getName().trim() + ".jpg"; url = url.replace(' ', '-'); - return url; + return new CardImageUrls(url); } @Override @@ -248,7 +248,7 @@ public enum TokensMtgImageSource implements CardImageSource { List newTokensData = new ArrayList<>(); try (InputStreamReader inputReader = new InputStreamReader(inputStream, "Cp1252"); - BufferedReader reader = new BufferedReader(inputReader)) { + BufferedReader reader = new BufferedReader(inputReader)) { // we have to specify encoding to read special comma reader.readLine(); // skip header diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index 2c200b624b9..dff6ed8304c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -20,6 +20,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; + import mage.cards.Sets; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; @@ -462,7 +463,7 @@ public enum WizardCardsImageSource implements CardImageSource { } @Override - public String generateURL(CardDownloadData card) throws Exception { + public CardImageUrls generateURL(CardDownloadData card) throws Exception { String collectorId = card.getCollectorId(); String cardSet = card.getSet(); if (collectorId == null || cardSet == null) { @@ -495,7 +496,7 @@ public enum WizardCardsImageSource implements CardImageSource { List l = new ArrayList<>(setLinks.values()); if (l.size() >= number) { link = l.get(number - 1); - } else {; + } else { link = l.get(number - 21); if (link != null) { link = link.replace(Integer.toString(number - 20), (Integer.toString(number - 20) + 'a')); @@ -508,8 +509,12 @@ public enum WizardCardsImageSource implements CardImageSource { if (link != null && !link.startsWith("http://")) { link = "http://gatherer.wizards.com" + link; } - return link; + if (link != null) { + return new CardImageUrls(link); + } else { + return null; + } } private Map getSetLinks(String cardSet) { @@ -703,7 +708,7 @@ public enum WizardCardsImageSource implements CardImageSource { } @Override - public String generateTokenUrl(CardDownloadData card) { + public CardImageUrls generateTokenUrl(CardDownloadData card) { return null; } @@ -712,7 +717,7 @@ public enum WizardCardsImageSource implements CardImageSource { return 60.0f; } -// private final class GetImageLinkTask implements Runnable { + // private final class GetImageLinkTask implements Runnable { // // private int multiverseId; // private String cardName; diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 389c0be5a7d..0153ff3367b 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -15,6 +15,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.swing.*; + import mage.cards.ExpansionSet; import mage.cards.Sets; import mage.cards.repository.CardCriteria; @@ -33,6 +34,7 @@ import org.apache.log4j.Logger; import org.mage.plugins.card.dl.sources.*; import org.mage.plugins.card.properties.SettingsManager; import org.mage.plugins.card.utils.CardImageUtils; + import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable { @@ -485,7 +487,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } try (InputStreamReader input = new InputStreamReader(in); - BufferedReader reader = new BufferedReader(input)) { + BufferedReader reader = new BufferedReader(input)) { String line = reader.readLine(); while (line != null) { @@ -597,25 +599,25 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab logger.debug("Downloading image: " + card.getName() + " (" + card.getSet() + ')'); - String url; + CardImageUrls urls; + if (ignoreUrls.contains(card.getSet()) || card.isToken()) { if (!"0".equals(card.getCollectorId())) { continue; } - url = cardImageSource.generateTokenUrl(card); + urls = cardImageSource.generateTokenUrl(card); } else { - url = cardImageSource.generateURL(card); + urls = cardImageSource.generateURL(card); } - if (url == null) { + if (urls == null) { String imageRef = cardImageSource.getNextHttpImageUrl(); String fileName = cardImageSource.getFileForHttpImage(imageRef); if (imageRef != null && fileName != null) { imageRef = cardImageSource.getSourceName() + imageRef; try { - URL imageUrl = new URL(imageRef); card.setToken(cardImageSource.isTokenSource()); - Runnable task = new DownloadTask(card, imageUrl, fileName, cardImageSource.getTotalImages()); + Runnable task = new DownloadTask(card, imageRef, fileName, cardImageSource.getTotalImages()); executor.execute(task); } catch (Exception ex) { } @@ -626,7 +628,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } } } else { - Runnable task = new DownloadTask(card, new URL(url), cardsToDownload.size()); + Runnable task = new DownloadTask(card, urls, cardsToDownload.size()); executor.execute(task); } @@ -662,22 +664,26 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab private final class DownloadTask implements Runnable { private final CardDownloadData card; - private final URL url; + private final CardImageUrls urls; private final int count; private final String actualFilename; private final boolean useSpecifiedPaths; - DownloadTask(CardDownloadData card, URL url, int count) { + DownloadTask(CardDownloadData card, String baseUrl, int count) { + this(card, new CardImageUrls(baseUrl, null), count); + } + + DownloadTask(CardDownloadData card, CardImageUrls urls, int count) { this.card = card; - this.url = url; + this.urls = urls; this.count = count; this.actualFilename = ""; useSpecifiedPaths = false; } - DownloadTask(CardDownloadData card, URL url, String actualFilename, int count) { + DownloadTask(CardDownloadData card, String baseUrl, String actualFilename, int count) { this.card = card; - this.url = url; + this.urls = new CardImageUrls(baseUrl, null); this.count = count; this.actualFilename = actualFilename; useSpecifiedPaths = true; @@ -751,14 +757,55 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } } - cardImageSource.doPause(url.getPath()); - URLConnection httpConn = url.openConnection(p); - httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); - httpConn.connect(); - int responseCode = ((HttpURLConnection) httpConn).getResponseCode(); + // can download images from many alternative urls + List downloadUrls; + if (this.urls != null) { + downloadUrls = this.urls.getDownloadList(); + } else { + downloadUrls = new ArrayList<>(); + } - if (responseCode == 200) { - // download OK + boolean isDownloadOK = false; + URLConnection httpConn = null; + List errorsList = new ArrayList<>(); + for (String currentUrl : downloadUrls) { + URL url = new URL(currentUrl); + + // on download cancel need to stop + if (cancel) { + return; + } + + // download + cardImageSource.doPause(url.getPath()); + httpConn = url.openConnection(p); + httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); + httpConn.connect(); + int responseCode = ((HttpURLConnection) httpConn).getResponseCode(); + + // check result + if (responseCode != 200) { + // show errors only on full fail (all urls were not work) + errorsList.add("Image download for " + card.getName() + + (!card.getDownloadName().equals(card.getName()) ? " downloadname: " + card.getDownloadName() : "") + + " (" + card.getSet() + ") failed - responseCode: " + responseCode + " url: " + url.toString()); + + if (logger.isDebugEnabled()) { + // Shows the returned html from the request to the web server + logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream())); + } + + // go to next try + continue; + } else { + // all fine + isDownloadOK = true; + break; + } + } + + // can save result + if (isDownloadOK & httpConn != null) { // save data to temp OutputStream out = null; OutputStream tfileout = null; @@ -792,8 +839,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } out.write(buf, 0, len); } - } - finally { + } finally { StreamUtils.closeQuietly(in); StreamUtils.closeQuietly(out); StreamUtils.closeQuietly(tfileout); @@ -815,15 +861,9 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab } } else { - // download ERROR - logger.warn("Image download for " + card.getName() - + (!card.getDownloadName().equals(card.getName()) ? " downloadname: " + card.getDownloadName() : "") - + " (" + card.getSet() + ") failed - responseCode: " + responseCode + " url: " + url.toString() - ); - - if (logger.isDebugEnabled()) { - // Shows the returned html from the request to the web server - logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream())); + // download errors + for (String err : errorsList) { + logger.warn(err); } } diff --git a/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java b/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java index 8e485c801e6..16f4e618f14 100644 --- a/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java +++ b/Mage.Client/src/test/java/mage/client/game/TokensMtgImageSourceTest.java @@ -4,11 +4,11 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.mage.plugins.card.dl.sources.CardImageSource; +import org.mage.plugins.card.dl.sources.CardImageUrls; import org.mage.plugins.card.dl.sources.TokensMtgImageSource; import org.mage.plugins.card.images.CardDownloadData; /** - * * @author Quercitron */ @Ignore @@ -18,15 +18,16 @@ public class TokensMtgImageSourceTest { public void generateTokenUrlTest() throws Exception { CardImageSource imageSource = TokensMtgImageSource.instance; - String url = imageSource.generateTokenUrl(new CardDownloadData("Thopter", "ORI", "0", false, 1, "ORI", "")); - Assert.assertEquals("http://tokens.mtg.onl/tokens/ORI_010-Thopter.jpg", url); + CardImageUrls url = imageSource.generateTokenUrl(new CardDownloadData("Thopter", "ORI", "0", false, 1, "ORI", "")); + Assert.assertEquals("http://tokens.mtg.onl/tokens/ORI_010-Thopter.jpg", url.baseUrl); + url = imageSource.generateTokenUrl(new CardDownloadData("Thopter", "ORI", "0", false, 2, "ORI", "")); - Assert.assertEquals("http://tokens.mtg.onl/tokens/ORI_011-Thopter.jpg", url); + Assert.assertEquals("http://tokens.mtg.onl/tokens/ORI_011-Thopter.jpg", url.baseUrl); url = imageSource.generateTokenUrl(new CardDownloadData("Ashaya, the Awoken World", "ORI", "0", false, 0, "ORI", "")); - Assert.assertEquals("http://tokens.mtg.onl/tokens/ORI_007-Ashaya,-the-Awoken-World.jpg", url); + Assert.assertEquals("http://tokens.mtg.onl/tokens/ORI_007-Ashaya,-the-Awoken-World.jpg", url.baseUrl); url = imageSource.generateTokenUrl(new CardDownloadData("Emblem Gideon, Ally of Zendikar", "BFZ", "0", false, 0, null, "")); - Assert.assertEquals("http://tokens.mtg.onl/tokens/BFZ_012-Gideon-Emblem.jpg", url); + Assert.assertEquals("http://tokens.mtg.onl/tokens/BFZ_012-Gideon-Emblem.jpg", url.baseUrl); } } From 90b2a287e12a46c36c444971a47a052719af5951 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 23 Jun 2018 19:03:14 +0400 Subject: [PATCH 43/74] UI: added info about image download sources and modes; --- .../plugins/card/images/DownloadPictures.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 0153ff3367b..0e9556dd902 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -44,9 +44,9 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab private static final Logger logger = Logger.getLogger(DownloadPictures.class); - public static final String ALL_IMAGES = "- All images from that source"; - public static final String ALL_STANDARD_IMAGES = "- All images from standard from that source"; - public static final String ALL_TOKENS = "- Only all token images from that source"; + public static final String ALL_IMAGES = "- ALL images from selected source (CAN BE VERY SLOW)"; + public static final String ALL_STANDARD_IMAGES = "- Only images from STANDARD sets"; + public static final String ALL_TOKENS = "- Only token images from selected source"; private JDialog dialog; private final JProgressBar bar; @@ -78,14 +78,14 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab private Proxy p = Proxy.NO_PROXY; enum DownloadSources { - WIZARDS("wizards.com", WizardCardsImageSource.instance), - MYTHICSPOILER("mythicspoiler.com", MythicspoilerComSource.instance), - TOKENS("tokens.mtg.onl", TokensMtgImageSource.instance), + WIZARDS("1. wizards.com - low quality CARDS, multi-language, can be SLOW", WizardCardsImageSource.instance), + TOKENS("2. tokens.mtg.onl - high quality TOKENS", TokensMtgImageSource.instance), + SCRYFALL("3. scryfall.com - high quality CARDS, multi-language", ScryfallImageSource.instance), + MAGIDEX("4. magidex.com - high quality CARDS", MagidexImageSource.instance), + GRAB_BAG("5. GrabBag - STAR WARS cards and tokens", GrabbagImageSource.instance), + MYTHICSPOILER("6. mythicspoiler.com", MythicspoilerComSource.instance), + ALTERNATIVE("7. alternative.mtg.onl", AltMtgOnlTokensImageSource.instance); // MTG_ONL("mtg.onl", MtgOnlTokensImageSource.instance), Not working correctly yet - ALTERNATIVE("alternative.mtg.onl", AltMtgOnlTokensImageSource.instance), - GRAB_BAG("GrabBag", GrabbagImageSource.instance), - MAGIDEX("magidex.com", MagidexImageSource.instance), - SCRYFALL("scryfall.com", ScryfallImageSource.instance); // MAGICCARDS("magiccards.info", MagicCardsImageSource.instance) private final String text; From c78cbc40eb2644acc07067a374cdbb12229082af Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 11:11:35 -0400 Subject: [PATCH 44/74] Fix Heat Stroke --- Mage.Sets/src/mage/cards/h/HeatStroke.java | 68 ++++++++++++++++--- Mage.Sets/src/mage/cards/j/JovensFerrets.java | 9 ++- .../common/WasBlockedThisTurnWatcher.java | 53 +++++++++++++++ 3 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 Mage/src/main/java/mage/watchers/common/WasBlockedThisTurnWatcher.java diff --git a/Mage.Sets/src/mage/cards/h/HeatStroke.java b/Mage.Sets/src/mage/cards/h/HeatStroke.java index e2b04db1869..60ed8c12d15 100644 --- a/Mage.Sets/src/mage/cards/h/HeatStroke.java +++ b/Mage.Sets/src/mage/cards/h/HeatStroke.java @@ -1,35 +1,46 @@ package mage.cards.h; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.UUID; + +import mage.MageObjectReference; +import mage.abilities.Ability; import mage.abilities.common.EndOfCombatTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.BlockedPredicate; import mage.filter.predicate.permanent.BlockingPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.common.BlockedThisTurnWatcher; +import mage.watchers.common.WasBlockedThisTurnWatcher; /** * @author dustinroepsch */ public final class HeatStroke extends CardImpl { - private static final FilterPermanent filter = new FilterCreaturePermanent(); - - static { - filter.add(Predicates.or(new BlockedPredicate(), new BlockingPredicate())); - } - public HeatStroke(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}"); // At end of combat, destroy each creature that blocked or was blocked this turn. - this.addAbility(new EndOfCombatTriggeredAbility(new DestroyAllEffect(filter) - .setText("destroy each creature that blocked or was blocked this turn"), false)); + Ability ability = new EndOfCombatTriggeredAbility(new HeatStrokeEffect(), false); + ability.addWatcher(new BlockedThisTurnWatcher()); + ability.addWatcher(new WasBlockedThisTurnWatcher()); + this.addAbility(ability); } public HeatStroke(final HeatStroke card) { @@ -41,3 +52,44 @@ public final class HeatStroke extends CardImpl { return new HeatStroke(this); } } + +class HeatStrokeEffect extends OneShotEffect { + + public HeatStrokeEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "destroy each creature that blocked or was blocked this turn"; + } + + public HeatStrokeEffect(HeatStrokeEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + BlockedThisTurnWatcher blockedWatcher = (BlockedThisTurnWatcher) game.getState().getWatchers().get(BlockedThisTurnWatcher.class.getSimpleName()); + WasBlockedThisTurnWatcher wasBlockedThisTurnWatcher = (WasBlockedThisTurnWatcher) game.getState().getWatchers().get(WasBlockedThisTurnWatcher.class.getSimpleName()); + boolean toRet = false; + Set toDestroy = new HashSet<>(); + + if (blockedWatcher != null){ + toDestroy.addAll(blockedWatcher.getBlockedThisTurnCreatures()); + } + if (wasBlockedThisTurnWatcher != null){ + toDestroy.addAll(wasBlockedThisTurnWatcher.getWasBlockedThisTurnCreatures()); + } + + for (MageObjectReference mor : toDestroy) { + Permanent permanent = mor.getPermanent(game); + if (permanent != null && permanent.isCreature()){ + permanent.destroy(source.getSourceId(), game, false); + toRet = true; + } + } + return toRet; + } + + @Override + public HeatStrokeEffect copy() { + return new HeatStrokeEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/j/JovensFerrets.java b/Mage.Sets/src/mage/cards/j/JovensFerrets.java index abf0696185f..a4df82c443c 100644 --- a/Mage.Sets/src/mage/cards/j/JovensFerrets.java +++ b/Mage.Sets/src/mage/cards/j/JovensFerrets.java @@ -25,6 +25,7 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.targetpointer.FixedTarget; import mage.watchers.common.BlockedAttackerWatcher; /** @@ -61,10 +62,10 @@ public final class JovensFerrets extends CardImpl { } } -class JovensFerretsEffect extends DontUntapInControllersNextUntapStepTargetEffect { +class JovensFerretsEffect extends OneShotEffect { public JovensFerretsEffect() { - super(); + super(Outcome.Benefit); } public JovensFerretsEffect(final JovensFerretsEffect effect) { @@ -93,7 +94,9 @@ class JovensFerretsEffect extends DontUntapInControllersNextUntapStepTargetEffec } for (Permanent creature : toTap) { creature.tap(game); - getTargetPointer().getTargets(game, source).add(creature.getId()); + DontUntapInControllersNextUntapStepTargetEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); + effect.setTargetPointer(new FixedTarget(creature.getId())); + game.addEffect(effect, source); } return true; } diff --git a/Mage/src/main/java/mage/watchers/common/WasBlockedThisTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/WasBlockedThisTurnWatcher.java new file mode 100644 index 00000000000..65e44a07afc --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/WasBlockedThisTurnWatcher.java @@ -0,0 +1,53 @@ + +package mage.watchers.common; + +import mage.MageObjectReference; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; + +/** + * + * @author noahg + */ +public class WasBlockedThisTurnWatcher extends Watcher { + + private final Set wasBlockedThisTurnCreatures; + + public WasBlockedThisTurnWatcher() { + super(WasBlockedThisTurnWatcher.class.getSimpleName(), WatcherScope.GAME); + wasBlockedThisTurnCreatures = new HashSet<>(); + } + + public WasBlockedThisTurnWatcher(final WasBlockedThisTurnWatcher watcher) { + super(watcher); + wasBlockedThisTurnCreatures = new HashSet<>(watcher.wasBlockedThisTurnCreatures); + } + + @Override + public Watcher copy() { + return new WasBlockedThisTurnWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.BLOCKER_DECLARED) { + this.wasBlockedThisTurnCreatures.add(new MageObjectReference(event.getTargetId(), game)); + } + } + + public Set getWasBlockedThisTurnCreatures() { + return this.wasBlockedThisTurnCreatures; + } + + @Override + public void reset() { + super.reset(); + wasBlockedThisTurnCreatures.clear(); + } + +} From 4fc42adb32fc0c29d61b996c22158859ec5a3a62 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 11:31:37 -0400 Subject: [PATCH 45/74] Fix Heat Stroke area of influence --- Mage.Sets/src/mage/cards/h/HeatStroke.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/h/HeatStroke.java b/Mage.Sets/src/mage/cards/h/HeatStroke.java index 60ed8c12d15..130b465e313 100644 --- a/Mage.Sets/src/mage/cards/h/HeatStroke.java +++ b/Mage.Sets/src/mage/cards/h/HeatStroke.java @@ -68,6 +68,7 @@ class HeatStrokeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { BlockedThisTurnWatcher blockedWatcher = (BlockedThisTurnWatcher) game.getState().getWatchers().get(BlockedThisTurnWatcher.class.getSimpleName()); WasBlockedThisTurnWatcher wasBlockedThisTurnWatcher = (WasBlockedThisTurnWatcher) game.getState().getWatchers().get(WasBlockedThisTurnWatcher.class.getSimpleName()); + Set inROI = new HashSet<>(game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source.getSourceId(), game)); boolean toRet = false; Set toDestroy = new HashSet<>(); @@ -80,7 +81,7 @@ class HeatStrokeEffect extends OneShotEffect { for (MageObjectReference mor : toDestroy) { Permanent permanent = mor.getPermanent(game); - if (permanent != null && permanent.isCreature()){ + if (permanent != null && permanent.isCreature() && inROI.contains(permanent)){ permanent.destroy(source.getSourceId(), game, false); toRet = true; } From b8d72b96190903718119da0ed7a9a14380ff9aba Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 23 Jun 2018 13:30:39 -0400 Subject: [PATCH 46/74] fixed One with the Machine counting all permanents and not just artifacts --- Mage.Sets/src/mage/cards/o/OneWithTheMachine.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/o/OneWithTheMachine.java b/Mage.Sets/src/mage/cards/o/OneWithTheMachine.java index 3827e58217a..953963221ee 100644 --- a/Mage.Sets/src/mage/cards/o/OneWithTheMachine.java +++ b/Mage.Sets/src/mage/cards/o/OneWithTheMachine.java @@ -6,6 +6,7 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; /** * @@ -18,7 +19,7 @@ public final class OneWithTheMachine extends CardImpl { // Draw cards equal to the highest converted mana cost among artifacts you control. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect( - new HighestConvertedManaCostValue() + new HighestConvertedManaCostValue(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT) ).setText("Draw cards equal to the highest converted mana cost among artifacts you control")); } From a1bfe071c80109394bb1f2bb9d042aca17a35e75 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 14:41:14 -0400 Subject: [PATCH 47/74] Switch from Watcher to getDealtDamageByThisTurn() --- .../src/mage/cards/g/GiantAlbatross.java | 33 +++++++++---------- Mage.Sets/src/mage/sets/Homelands.java | 4 +-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java index 93abfbfc03d..b9ec60ce36a 100644 --- a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java +++ b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java @@ -44,9 +44,6 @@ public final class GiantAlbatross extends CardImpl { // When Giant Albatross dies, you may pay {1}{U}. If you do, for each creature that dealt damage to Giant Albatross this turn, destroy that creature unless its controller pays 2 life. A creature destroyed this way can't be regenerated. Ability ability = new DiesTriggeredAbility(new DoIfCostPaid(new GiantAlbatrossEffect(), new ManaCostsImpl("{1}{U}"))); - DealtDamageToWatcher watcher = new DealtDamageToWatcher(); - watcher.setSourceId(this.objectId); - ability.addWatcher(watcher); this.addAbility(ability); } @@ -79,21 +76,23 @@ class GiantAlbatrossEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - DealtDamageToWatcher watcher = (DealtDamageToWatcher) game.getState().getWatchers().get(DealtDamageToWatcher.class.getSimpleName(), source.getSourceId()); - for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { - Player player = game.getPlayer(playerId); - if (player != null) { - List creatures = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (sourcePermanent != null) { + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + List creatures = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game); - Cost cost = new PayLifeCost(2); - for (Permanent creature : creatures) { - if (watcher.didDamage(creature, game)) { - final StringBuilder sb = new StringBuilder("Pay 2 life? (Otherwise ").append(creature.getName()).append(" will be destroyed)"); - if (cost.canPay(source, creature.getControllerId(), creature.getControllerId(), game) && player.chooseUse(Outcome.Benefit, sb.toString(), source, game)) { - cost.pay(source, game, creature.getControllerId(), creature.getControllerId(), true, null); - } - if (!cost.isPaid()) { - creature.destroy(source.getSourceId(), game, true); + Cost cost = new PayLifeCost(2); + for (Permanent creature : creatures) { + if (sourcePermanent.getDealtDamageByThisTurn().contains(new MageObjectReference(creature.getId(), game))) { + final StringBuilder sb = new StringBuilder("Pay 2 life? (Otherwise ").append(creature.getName()).append(" will be destroyed)"); + if (cost.canPay(source, creature.getControllerId(), creature.getControllerId(), game) && player.chooseUse(Outcome.Benefit, sb.toString(), source, game)) { + cost.pay(source, game, creature.getControllerId(), creature.getControllerId(), true, null); + } + if (!cost.isPaid()) { + creature.destroy(source.getSourceId(), game, true); + } } } } diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index f867f332d38..2b22d743b6d 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -103,8 +103,8 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Forget", 26, Rarity.RARE, mage.cards.f.Forget.class)); cards.add(new SetCardInfo("Funeral March", 48, Rarity.UNCOMMON, mage.cards.f.FuneralMarch.class)); cards.add(new SetCardInfo("Ghost Hounds", 49, Rarity.UNCOMMON, mage.cards.g.GhostHounds.class)); - cards.add(new SetCardInfo("Giant Albatross", "34a", Rarity.COMMON, mage.cards.g.GiantAlbatross.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Giant Albatross", "34b", Rarity.COMMON, mage.cards.g.GiantAlbatross.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Giant Albatross", "27a", Rarity.COMMON, mage.cards.g.GiantAlbatross.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Giant Albatross", "27b", Rarity.COMMON, mage.cards.g.GiantAlbatross.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Grandmother Sengir", 50, Rarity.RARE, mage.cards.g.GrandmotherSengir.class)); cards.add(new SetCardInfo("Greater Werewolf", 51, Rarity.UNCOMMON, mage.cards.g.GreaterWerewolf.class)); cards.add(new SetCardInfo("Hazduhr the Abbot", 8, Rarity.RARE, mage.cards.h.HazduhrTheAbbot.class)); From b97719fbb64dc6f7f0e3129c57057ce5c10e6821 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 17:30:57 -0400 Subject: [PATCH 48/74] Add Delirium, not recognized as a card --- Mage.Sets/src/mage/cards/d/DeliriumCard.java | 89 +++++++++++++++++++ Mage.Sets/src/mage/sets/Mirage.java | 1 + .../ControllerIsActivePlayerPredicate.java | 27 ++++++ 3 files changed, 117 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DeliriumCard.java create mode 100644 Mage/src/main/java/mage/filter/predicate/permanent/ControllerIsActivePlayerPredicate.java diff --git a/Mage.Sets/src/mage/cards/d/DeliriumCard.java b/Mage.Sets/src/mage/cards/d/DeliriumCard.java new file mode 100644 index 00000000000..2cc0631b0b0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeliriumCard.java @@ -0,0 +1,89 @@ +package mage.cards.d; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.CastOnlyIfConditionIsTrueAbility; +import mage.abilities.condition.common.OnOpponentsTurnCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PreventCombatDamageBySourceEffect; +import mage.abilities.effects.common.PreventCombatDamageToSourceEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIsActivePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author noahg + */ +public final class DeliriumCard extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new ControllerIsActivePlayerPredicate()); + } + + public DeliriumCard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}{R}"); + + + // Cast this spell only during an opponent’s turn. + this.addAbility(new CastOnlyIfConditionIsTrueAbility(OnOpponentsTurnCondition.instance, "Cast this spell only during an opponent’s turn.")); + // Tap target creature that player controls. That creature deals damage equal to its power to the player. Prevent all combat damage that would be dealt to and dealt by the creature this turn. + this.spellAbility.addEffect(new TapTargetEffect().setText("tap target creature that player controls")); + this.spellAbility.addTarget(new TargetCreaturePermanent(filter)); + this.spellAbility.addEffect(new DeliriumCardEffect()); + this.spellAbility.addEffect(new PreventCombatDamageBySourceEffect(Duration.EndOfTurn).setText("Prevent all combat damage that would be dealt to")); + this.spellAbility.addEffect(new PreventCombatDamageToSourceEffect(Duration.EndOfTurn).setText("and dealt by the creature this turn.")); + } + + public DeliriumCard(final DeliriumCard card) { + super(card); + } + + @Override + public DeliriumCard copy() { + return new DeliriumCard(this); + } +} + +class DeliriumCardEffect extends OneShotEffect { + + public DeliriumCardEffect() { + super(Outcome.Damage); + this.staticText = "that creature deals damage equal to its power to the player"; + } + + public DeliriumCardEffect(DeliriumCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(source.getFirstTarget()); + if (creature != null) { + int amount = creature.getPower().getValue(); + Player controller = game.getPlayer(creature.getControllerId()); + if (controller != null) { + controller.damage(amount, creature.getId(), game, false, true); + return true; + } + } + return false; + } + + @Override + public DeliriumCardEffect copy() { + return new DeliriumCardEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index d67f5b34103..3098c4e0b27 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -83,6 +83,7 @@ public final class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Dark Banishing", 115, Rarity.COMMON, mage.cards.d.DarkBanishing.class)); cards.add(new SetCardInfo("Dark Ritual", 116, Rarity.COMMON, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Dazzling Beauty", 8, Rarity.COMMON, mage.cards.d.DazzlingBeauty.class)); + cards.add(new SetCardInfo("Delirium", 260, Rarity.UNCOMMON, mage.cards.d.DeliriumCard.class)); cards.add(new SetCardInfo("Dirtwater Wraith", 117, Rarity.COMMON, mage.cards.d.DirtwaterWraith.class)); cards.add(new SetCardInfo("Disempower", 9, Rarity.COMMON, mage.cards.d.Disempower.class)); cards.add(new SetCardInfo("Disenchant", 10, Rarity.COMMON, mage.cards.d.Disenchant.class)); diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIsActivePlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIsActivePlayerPredicate.java new file mode 100644 index 00000000000..e67b02e0862 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIsActivePlayerPredicate.java @@ -0,0 +1,27 @@ + +package mage.filter.predicate.permanent; + +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * + * @author North + */ +public class ControllerIsActivePlayerPredicate implements Predicate { + @Override + public boolean apply(Permanent input, Game game) { + if(input.getControllerId() == null){ + return false; + } + return game.getActivePlayerId().equals(input.getControllerId()); + } + + @Override + public String toString() { + return "controlled by the active player"; + } +} From 1b33f99cecfde7461b50e653e5244ec4c224141e Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 19:08:06 -0400 Subject: [PATCH 49/74] Implement Heart Wolf --- Mage.Sets/src/mage/cards/b/BurnAway.java | 2 +- Mage.Sets/src/mage/cards/h/HeartWolf.java | 113 ++++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 1 + .../effects/common/SacrificeSourceEffect.java | 2 + 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/h/HeartWolf.java diff --git a/Mage.Sets/src/mage/cards/b/BurnAway.java b/Mage.Sets/src/mage/cards/b/BurnAway.java index 69db42a5ddf..54e6e9344d0 100644 --- a/Mage.Sets/src/mage/cards/b/BurnAway.java +++ b/Mage.Sets/src/mage/cards/b/BurnAway.java @@ -62,7 +62,7 @@ class BurnAwayDelayedTriggeredAbility extends DelayedTriggeredAbility { public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (zEvent.isDiesEvent() && zEvent.getTarget() != null && zEvent.getTargetId().equals(getTargets().getFirstTarget())) { - this.getTargets().clear(); // else spell fizzels because target creature died + this.getTargets().clear(); // else spell fizzles because target creature died Target target = new TargetPlayer(); target.add(zEvent.getTarget().getControllerId(), game); this.addTarget(target); diff --git a/Mage.Sets/src/mage/cards/h/HeartWolf.java b/Mage.Sets/src/mage/cards/h/HeartWolf.java new file mode 100644 index 00000000000..6692e3fd000 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HeartWolf.java @@ -0,0 +1,113 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.condition.common.IsPhaseCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileGraveyardAllTargetPlayerEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.constants.*; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.turn.Phase; +import mage.target.Target; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author noahg + */ +public final class HeartWolf extends CardImpl { + + private final static FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new SubtypePredicate(SubType.DWARF)); + } + + public HeartWolf(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.WOLF); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // {tap}: Target Dwarf creature gets +2/+0 and gains first strike until end of turn. When that creature leaves the battlefield this turn, sacrifice Heart Wolf. Activate this ability only during combat. + Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new BoostTargetEffect(2, 0, Duration.EndOfTurn) + .setText("Target Dwarf creature gets +2/+0"), new TapSourceCost(), new IsPhaseCondition(TurnPhase.COMBAT)); + ability.addEffect(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), + Duration.EndOfTurn).setText("and gains first strike until end of turn")); + ability.addTarget(new TargetCreaturePermanent(filter)); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new HeartWolfDelayedTriggeredAbility(), true)); + this.addAbility(ability); + } + + public HeartWolf(final HeartWolf card) { + super(card); + } + + @Override + public HeartWolf copy() { + return new HeartWolf(this); + } +} + +class HeartWolfDelayedTriggeredAbility extends DelayedTriggeredAbility { + + public HeartWolfDelayedTriggeredAbility() { + super(new SacrificeSourceEffect(), Duration.EndOfTurn, false); + } + + public HeartWolfDelayedTriggeredAbility(HeartWolfDelayedTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + System.out.println("Source: "+game.getCard(sourceId).getLogName()); + System.out.println("Source ID: "+sourceId); + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if(zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getTarget() != null && zEvent.getTargetId().equals(getTargets().getFirstTarget())){ + this.getTargets().clear(); // else spell fizzles because target creature died + return true; + } + return false; + } + + @Override + public HeartWolfDelayedTriggeredAbility copy() { + return new HeartWolfDelayedTriggeredAbility(this); + } + + @Override + public String getRule() { + return "When that creature leaves the battlefield this turn, sacrifice {this}."; + } + + @Override + public String toString() { + return "HeartWolfDelayedTriggeredAbility as a string!"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 4502fe8dbd1..bd16aacd6c0 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -108,6 +108,7 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Greater Werewolf", 51, Rarity.UNCOMMON, mage.cards.g.GreaterWerewolf.class)); cards.add(new SetCardInfo("Hazduhr the Abbot", 8, Rarity.RARE, mage.cards.h.HazduhrTheAbbot.class)); cards.add(new SetCardInfo("Headstone", 52, Rarity.COMMON, mage.cards.h.Headstone.class)); + cards.add(new SetCardInfo("Heart Wolf", 75, Rarity.RARE, mage.cards.h.HeartWolf.class)); cards.add(new SetCardInfo("Hungry Mist", "88a", Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hungry Mist", "88b", Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ihsan's Shade", 53, Rarity.UNCOMMON, mage.cards.i.IhsansShade.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java index 26e828dc580..8918989958b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java @@ -31,7 +31,9 @@ public class SacrificeSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { + System.out.println("Source class: "+ source.getClass().getName()); MageObject sourceObject = source.getSourceObjectIfItStillExists(game); + System.out.println("Source object: "+sourceObject); if (sourceObject == null) { // Check if the effect was installed by the spell the source was cast by (e.g. Necromancy), if not don't sacrifice the permanent if (source.getSourceObject(game) instanceof Spell) { From 95aa52d9ac69d94444525d53e1b88044f911ec35 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 19:09:51 -0400 Subject: [PATCH 50/74] Remove debug prints --- Mage.Sets/src/mage/cards/h/HeartWolf.java | 9 +-------- .../abilities/effects/common/SacrificeSourceEffect.java | 2 -- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/cards/h/HeartWolf.java b/Mage.Sets/src/mage/cards/h/HeartWolf.java index 6692e3fd000..7409660149e 100644 --- a/Mage.Sets/src/mage/cards/h/HeartWolf.java +++ b/Mage.Sets/src/mage/cards/h/HeartWolf.java @@ -86,10 +86,8 @@ class HeartWolfDelayedTriggeredAbility extends DelayedTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { - System.out.println("Source: "+game.getCard(sourceId).getLogName()); - System.out.println("Source ID: "+sourceId); ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if(zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getTarget() != null && zEvent.getTargetId().equals(getTargets().getFirstTarget())){ + if (zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getTarget() != null && zEvent.getTargetId().equals(getTargets().getFirstTarget())) { this.getTargets().clear(); // else spell fizzles because target creature died return true; } @@ -105,9 +103,4 @@ class HeartWolfDelayedTriggeredAbility extends DelayedTriggeredAbility { public String getRule() { return "When that creature leaves the battlefield this turn, sacrifice {this}."; } - - @Override - public String toString() { - return "HeartWolfDelayedTriggeredAbility as a string!"; - } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java index 8918989958b..26e828dc580 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeSourceEffect.java @@ -31,9 +31,7 @@ public class SacrificeSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - System.out.println("Source class: "+ source.getClass().getName()); MageObject sourceObject = source.getSourceObjectIfItStillExists(game); - System.out.println("Source object: "+sourceObject); if (sourceObject == null) { // Check if the effect was installed by the spell the source was cast by (e.g. Necromancy), if not don't sacrifice the permanent if (source.getSourceObject(game) instanceof Spell) { From 3bd716fb123165476fa9d69e1ab55f114d74eff1 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 01:22:18 +0200 Subject: [PATCH 51/74] * Sinbirds Invocation - use standard method card card moving. --- Mage.Sets/src/mage/cards/s/SunbirdsInvocation.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SunbirdsInvocation.java b/Mage.Sets/src/mage/cards/s/SunbirdsInvocation.java index 4bba3ef9154..e90d03fbb7a 100644 --- a/Mage.Sets/src/mage/cards/s/SunbirdsInvocation.java +++ b/Mage.Sets/src/mage/cards/s/SunbirdsInvocation.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -136,14 +135,7 @@ class SunbirdsInvocationEffect extends OneShotEffect { } } } - } - - while (!cards.isEmpty()) { - Card card = cards.getRandom(game); - if (card != null) { - cards.remove(card); - card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, false); - } + controller.putCardsOnBottomOfLibrary(cards, game, source, false); } return true; } From e71c54593e675fa295811e373f70912787e979c4 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 01:22:54 +0200 Subject: [PATCH 52/74] * The Ur-Dragon - Reworked triggered ability. --- Mage.Sets/src/mage/cards/t/TheUrDragon.java | 97 +++------------------ 1 file changed, 12 insertions(+), 85 deletions(-) diff --git a/Mage.Sets/src/mage/cards/t/TheUrDragon.java b/Mage.Sets/src/mage/cards/t/TheUrDragon.java index ca68b29fa0a..0c425272e5f 100644 --- a/Mage.Sets/src/mage/cards/t/TheUrDragon.java +++ b/Mage.Sets/src/mage/cards/t/TheUrDragon.java @@ -1,16 +1,12 @@ - package mage.cards.t; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; import mage.abilities.keyword.FlyingAbility; @@ -22,8 +18,6 @@ import mage.filter.predicate.mageobject.SubtypePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.watchers.Watcher; /** * @author TheElk801 @@ -60,7 +54,7 @@ public final class TheUrDragon extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield - this.addAbility(new TheUrDragonTriggeredAbility(), new DragonsAttackedWatcher()); + this.addAbility(new TheUrDragonTriggeredAbility()); } public TheUrDragon(final TheUrDragon card) { @@ -73,46 +67,10 @@ public final class TheUrDragon extends CardImpl { } } -class DragonsAttackedWatcher extends Watcher { - - public final Set attackedThisTurnCreatures = new HashSet<>(); - - public DragonsAttackedWatcher() { - super(DragonsAttackedWatcher.class.getSimpleName(), WatcherScope.GAME); - } - - public DragonsAttackedWatcher(final DragonsAttackedWatcher watcher) { - super(watcher); - this.attackedThisTurnCreatures.addAll(watcher.attackedThisTurnCreatures); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.BEGIN_COMBAT_STEP_PRE) { - this.attackedThisTurnCreatures.clear(); - } - if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) { - if (game.getPermanent(event.getSourceId()).hasSubtype(SubType.DRAGON, game)) { - this.attackedThisTurnCreatures.add(new MageObjectReference(event.getSourceId(), game)); - } - } - } - - public Set getAttackedThisTurnCreatures() { - return this.attackedThisTurnCreatures; - } - - @Override - public DragonsAttackedWatcher copy() { - return new DragonsAttackedWatcher(this); - } - -} - class TheUrDragonTriggeredAbility extends TriggeredAbilityImpl { public TheUrDragonTriggeredAbility() { - super(Zone.BATTLEFIELD, new TheUrDragonEffect(), false); + super(Zone.BATTLEFIELD, null, false); } public TheUrDragonTriggeredAbility(final TheUrDragonTriggeredAbility ability) { @@ -131,58 +89,27 @@ class TheUrDragonTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + int attackingDragons = 0; for (UUID attacker : game.getCombat().getAttackers()) { Permanent creature = game.getPermanent(attacker); if (creature != null && creature.getControllerId() != null && creature.getControllerId().equals(this.getControllerId()) && creature.hasSubtype(SubType.DRAGON, game)) { - return true; + attackingDragons++; } } + if (attackingDragons > 0) { + this.getEffects().clear(); + addEffect(new DrawCardSourceControllerEffect(attackingDragons)); + addEffect(new PutCardFromHandOntoBattlefieldEffect()); + return true; + } return false; } @Override public String getRule() { - return "Whenever one or more Dragons you control attack, " + super.getRule(); - } -} - -class TheUrDragonEffect extends OneShotEffect { - - public TheUrDragonEffect() { - super(Outcome.Benefit); - this.staticText = "draw that many cards, then you may put a permanent card from your hand onto the battlefield"; - } - - public TheUrDragonEffect(final TheUrDragonEffect effect) { - super(effect); - } - - @Override - public TheUrDragonEffect copy() { - return new TheUrDragonEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - DragonsAttackedWatcher watcher = (DragonsAttackedWatcher) game.getState().getWatchers().get(DragonsAttackedWatcher.class.getSimpleName()); - if (watcher != null) { - int attackingDragons = 0; - for (MageObjectReference attacker : watcher.getAttackedThisTurnCreatures()) { - if (attacker.getPermanentOrLKIBattlefield(game).getControllerId().equals(controller.getId())) { - attackingDragons++; - } - } - if (attackingDragons > 0) { - controller.drawCards(attackingDragons, game); - } - return new PutCardFromHandOntoBattlefieldEffect().apply(game, source); - } - } - return false; + return "Whenever one or more Dragons you control attack, draw that many cards, then you may put a permanent card from your hand onto the battlefield."; } } From fdddbbbbe693aa85eb68058f6f3c0e7c15e72b21 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 01:23:49 +0200 Subject: [PATCH 53/74] * Some rework/fixes/optimizations of calculation of available mana. --- Mage.Sets/src/mage/cards/c/CabalCoffers.java | 5 +- .../org/mage/test/utils/ManaOptionsTest.java | 99 +++++++++++++++++++ Mage/src/main/java/mage/Mana.java | 48 +++++++-- .../java/mage/abilities/mana/ManaOptions.java | 66 +++++++++---- .../main/java/mage/players/PlayerImpl.java | 1 + 5 files changed, 190 insertions(+), 29 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CabalCoffers.java b/Mage.Sets/src/mage/cards/c/CabalCoffers.java index 72b167b5e4b..15afb42b599 100644 --- a/Mage.Sets/src/mage/cards/c/CabalCoffers.java +++ b/Mage.Sets/src/mage/cards/c/CabalCoffers.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -28,9 +27,9 @@ public final class CabalCoffers extends CardImpl { } public CabalCoffers(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); - // {2}, {tap}: Add {B} for each Swamp you control. + // {2}, {T}: Add {B} for each Swamp you control. Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new PermanentsOnBattlefieldCount(filter), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); this.addAbility(ability); diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java index b72003e40f6..e7d2c5e9461 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java @@ -454,4 +454,103 @@ public class ManaOptionsTest extends CardTestPlayerBase { Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); assertManaOptions("{C}{C}", manaOptions); } + + @Test + public void testManaSourcesWithCosts() { + // {T}: Add {C} to your mana pool. + // {5}, {T}: Add {W}{U}{B}{R}{G} to your mana pool. + addCard(Zone.BATTLEFIELD, playerA, "Crystal Quarry", 1); + + // {T}: Add {C} to your mana pool. + // {W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B} to your mana pool. + addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 3); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + + setStopAt(1, PhaseStep.UPKEEP); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 16, manaOptions.size()); + assertManaOptions("{C}{C}{C}{C}{W}{W}{W}", manaOptions); + assertManaOptions("{C}{C}{C}{W}{W}{W}{W}", manaOptions); + assertManaOptions("{C}{C}{C}{W}{W}{W}{B}", manaOptions); + assertManaOptions("{C}{C}{C}{W}{W}{B}{B}", manaOptions); + assertManaOptions("{C}{C}{W}{W}{W}{W}{W}", manaOptions); + assertManaOptions("{C}{C}{W}{W}{W}{W}{B}", manaOptions); + assertManaOptions("{C}{C}{W}{W}{W}{B}{B}", manaOptions); + assertManaOptions("{C}{C}{W}{W}{B}{B}{B}", manaOptions); + assertManaOptions("{C}{C}{W}{B}{B}{B}{B}", manaOptions); + assertManaOptions("{C}{W}{W}{W}{W}{W}{W}", manaOptions); + assertManaOptions("{C}{W}{W}{W}{W}{W}{B}", manaOptions); + assertManaOptions("{C}{W}{W}{W}{W}{B}{B}", manaOptions); + assertManaOptions("{C}{W}{W}{W}{B}{B}{B}", manaOptions); + assertManaOptions("{C}{W}{W}{B}{B}{B}{B}", manaOptions); + assertManaOptions("{C}{W}{B}{B}{B}{B}{B}", manaOptions); + assertManaOptions("{C}{B}{B}{B}{B}{B}{B}", manaOptions); + } + + @Test + public void testSungrassPrairie() { + // {1}, {T}: Add {G}{W}. + addCard(Zone.BATTLEFIELD, playerA, "Sungrass Prairie", 1); + // {T}: Add one mana of any color to your mana pool. + addCard(Zone.BATTLEFIELD, playerA, "Alloy Myr", 2); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); + + assertManaOptions("{W}{G}{Any}", manaOptions); + assertManaOptions("{Any}{Any}", manaOptions); + } + + @Test + public void testSungrassPrairie2() { + // {1}, {T}: Add {G}{W}. + addCard(Zone.BATTLEFIELD, playerA, "Sungrass Prairie", 5); + // ({T}: Add {U} or {W} to your mana pool.) + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 9); + // ({T}: Add {G} or {U} to your mana pool.) + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 3); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 88, manaOptions.size()); + + assertManaOptions("{W}{W}{W}{W}{W}{W}{W}{W}{W}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions); + assertManaOptions("{W}{W}{W}{W}{W}{W}{W}{W}{U}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions); + } + + @Test + public void testSungrassPrairie3() { + // {1}, {T}: Add {G}{W}. + addCard(Zone.BATTLEFIELD, playerA, "Sungrass Prairie", 1); + // ({T}: Add {U} or {W} to your mana pool.) + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 1); + // ({T}: Add {G} or {U} to your mana pool.) + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 1); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + + Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); + assertManaOptions("{U}{U}", manaOptions); + assertManaOptions("{W}{G}{G}", manaOptions); + assertManaOptions("{W}{U}{G}", manaOptions); + assertManaOptions("{W}{W}{G}", manaOptions); + } + } diff --git a/Mage/src/main/java/mage/Mana.java b/Mage/src/main/java/mage/Mana.java index 1788fd264e4..f6a594292fb 100644 --- a/Mage/src/main/java/mage/Mana.java +++ b/Mage/src/main/java/mage/Mana.java @@ -1051,7 +1051,8 @@ public class Mana implements Comparable, Serializable, Copyable { /** * Returns if this {@link Mana} object has more than or equal values of mana - * as the passed in {@link Mana} object. + * as the passed in {@link Mana} object. Ignores {Any} mana to prevent + * endless iterations. * * @param mana the mana to compare with * @return if this object has more than or equal mana to the passed in @@ -1090,13 +1091,44 @@ public class Mana implements Comparable, Serializable, Copyable { moreMana = mana1; lessMana = mana2; } - if (lessMana.getWhite() > moreMana.getWhite() - || lessMana.getRed() > moreMana.getRed() - || lessMana.getGreen() > moreMana.getGreen() - || lessMana.getBlue() > moreMana.getBlue() - || lessMana.getBlack() > moreMana.getBlack() - || lessMana.getColorless() > moreMana.getColorless() - || lessMana.getAny() > moreMana.getAny()) { + int anyDiff = mana2.getAny() - mana1.getAny(); + if (lessMana.getWhite() > moreMana.getWhite()) { + anyDiff -= lessMana.getWhite() - moreMana.getWhite(); + if (anyDiff < 0) { + return null; + } + } + if (lessMana.getRed() > moreMana.getRed()) { + anyDiff -= lessMana.getRed() - moreMana.getRed(); + if (anyDiff < 0) { + return null; + } + } + if (lessMana.getGreen() > moreMana.getGreen()) { + anyDiff -= lessMana.getGreen() - moreMana.getGreen(); + if (anyDiff < 0) { + return null; + } + } + if (lessMana.getBlue() > moreMana.getBlue()) { + anyDiff -= lessMana.getBlue() - moreMana.getBlue(); + if (anyDiff < 0) { + return null; + } + } + if (lessMana.getBlack() > moreMana.getBlack()) { + anyDiff -= lessMana.getBlack() - moreMana.getBlack(); + if (anyDiff < 0) { + return null; + } + } + if (lessMana.getColorless() > moreMana.getColorless()) { + anyDiff -= lessMana.getColorless() - moreMana.getColorless(); + if (anyDiff < 0) { + return null; + } + } + if (lessMana.getAny() > moreMana.getAny()) { return null; } return moreMana; diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index 077b77fad43..cd649cc976b 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -114,6 +114,7 @@ public class ManaOptions extends ArrayList { } public void addManaWithCost(List abilities, Game game) { + int replaces = 0; if (isEmpty()) { this.add(new Mana()); } @@ -187,7 +188,8 @@ public class ManaOptions extends ArrayList { Mana moreValuable = Mana.getMoreValuableMana(newMana, existingMana); if (moreValuable != null) { existingMana.setToMana(moreValuable); - logger.trace("mana replaced " + newMana.toString() + " <=> " + existingMana.toString() + " from " + ability.getRule()); + replaces++; + // logger.info("mana replaced " + newMana.toString() + " <=> " + existingMana.toString() + " from " + ability.getRule()); continue CombineWithExisting; } } @@ -203,6 +205,10 @@ public class ManaOptions extends ArrayList { } } } + if (this.size() > 5 || replaces > 5) { + logger.info("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString()); + logger.info("Abilities: " + abilities.toString()); + } } public void addMana(Mana addMana) { @@ -255,20 +261,28 @@ public class ManaOptions extends ArrayList { this.clear(); for (Mana mana : copy) { Mana oldMan = mana.copy(); - if (mana.includesMana(cost)) { - // colorless costs can be paid with different colored mana, can lead to different color combinations + if (mana.includesMana(cost)) { // it can be paid + // generic mana costs can be paid with different colored mana, can lead to different color combinations if (cost.getGeneric() > 0 && cost.getGeneric() > (mana.getGeneric() + mana.getColorless())) { Mana coloredCost = cost.copy(); coloredCost.setGeneric(0); mana.subtract(coloredCost); + boolean oldManaWasReplaced = false; for (Mana payCombination : getPossiblePayCombinations(cost.getGeneric(), mana)) { Mana newMana = mana.copy(); newMana.subtract(payCombination); newMana.add(addMana); - if (oldMan.contains(newMana) && oldMan.count() > newMana.count()) { - newMana.setToMana(oldMan); + Mana moreValuable = Mana.getMoreValuableMana(oldMan, newMana); + if (!oldMan.equals(moreValuable)) { + this.add(newMana); + if (moreValuable != null) { + oldManaWasReplaced = true; // the new mana includes all possibilities of the old one + } } - this.add(newMana); + + } + if (!oldManaWasReplaced) { + this.add(oldMan); } } else { while (mana.includesMana(cost)) { @@ -303,28 +317,33 @@ public class ManaOptions extends ArrayList { existingManas.add(new Mana()); } for (Mana existingMana : existingManas) { - Mana manaToPay = manaAvailable.copy(); - manaToPay.subtract(existingMana); - if (manaToPay.getBlack() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlackMana(1).toString())) { - manaToPay.subtract(Mana.BlackMana(1)); + Mana manaToPayFrom = manaAvailable.copy(); + manaToPayFrom.subtract(existingMana); + if (manaToPayFrom.getBlack() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlackMana(1).toString())) { + manaToPayFrom.subtract(Mana.BlackMana(1)); addManaCombination(Mana.BlackMana(1), existingMana, payCombinations, payCombinationsStrings); } - if (manaToPay.getBlue() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlueMana(1).toString())) { - manaToPay.subtract(Mana.BlueMana(1)); + if (manaToPayFrom.getBlue() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlueMana(1).toString())) { + manaToPayFrom.subtract(Mana.BlueMana(1)); addManaCombination(Mana.BlueMana(1), existingMana, payCombinations, payCombinationsStrings); } - if (manaToPay.getGreen() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.GreenMana(1).toString())) { - manaToPay.subtract(Mana.GreenMana(1)); + if (manaToPayFrom.getGreen() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.GreenMana(1).toString())) { + manaToPayFrom.subtract(Mana.GreenMana(1)); addManaCombination(Mana.GreenMana(1), existingMana, payCombinations, payCombinationsStrings); } - if (manaToPay.getRed() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.RedMana(1).toString())) { - manaToPay.subtract(Mana.RedMana(1)); + if (manaToPayFrom.getRed() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.RedMana(1).toString())) { + manaToPayFrom.subtract(Mana.RedMana(1)); addManaCombination(Mana.RedMana(1), existingMana, payCombinations, payCombinationsStrings); } - if (manaToPay.getWhite() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.WhiteMana(1).toString())) { - manaToPay.subtract(Mana.WhiteMana(1)); + if (manaToPayFrom.getWhite() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.WhiteMana(1).toString())) { + manaToPayFrom.subtract(Mana.WhiteMana(1)); addManaCombination(Mana.WhiteMana(1), existingMana, payCombinations, payCombinationsStrings); } + // Pay with any only needed if colored payment was not possible + if (payCombinations.isEmpty() && manaToPayFrom.getAny() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.AnyMana(1).toString())) { + manaToPayFrom.subtract(Mana.AnyMana(1)); + addManaCombination(Mana.AnyMana(1), existingMana, payCombinations, payCombinationsStrings); + } } } } else { @@ -352,5 +371,16 @@ public class ManaOptions extends ArrayList { list.add(s); } } + // Remove fully included variations + for (int i = this.size() - 1; i >= 0; i--) { + for (int ii = 0; ii < i; ii++) { + Mana moreValuable = Mana.getMoreValuableMana(this.get(i), this.get(ii)); + if (moreValuable != null) { + this.get(ii).setToMana(moreValuable); + this.remove(i); + break; + } + } + } } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 39d60b01ffd..1266ae494e8 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2647,6 +2647,7 @@ public abstract class PlayerImpl implements Player, Serializable { available.addMana(manaAbilities, game); } for (Abilities manaAbilities : sourceWithCosts) { + available.removeDuplicated(); available.addManaWithCost(manaAbilities, game); } From da92cd745ea8ac6e52f2fce6432ea18d6047d19a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 01:27:42 +0200 Subject: [PATCH 54/74] * Some rework/fixes/optimizations of calculation of available mana. --- Mage/src/main/java/mage/abilities/mana/ManaOptions.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index cd649cc976b..6633be31e8a 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -189,7 +189,6 @@ public class ManaOptions extends ArrayList { if (moreValuable != null) { existingMana.setToMana(moreValuable); replaces++; - // logger.info("mana replaced " + newMana.toString() + " <=> " + existingMana.toString() + " from " + ability.getRule()); continue CombineWithExisting; } } @@ -205,9 +204,9 @@ public class ManaOptions extends ArrayList { } } } - if (this.size() > 5 || replaces > 5) { - logger.info("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString()); - logger.info("Abilities: " + abilities.toString()); + if (this.size() > 30 || replaces > 30) { + logger.trace("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString()); + logger.trace("Abilities: " + abilities.toString()); } } From f3d4194adda940a5fc9bfef9ed68a1ff30c7c487 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 02:01:20 +0200 Subject: [PATCH 55/74] * Palladia-Mors, the Ruiner - Fixed possible exception. --- Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java b/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java index 57e06bac912..66db41dd0d7 100644 --- a/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java +++ b/Mage.Sets/src/mage/cards/p/PalladiaMorsTheRuiner.java @@ -79,7 +79,7 @@ enum PalladiaMorsTheRuinerCondition implements Condition { public boolean apply(Game game, Ability source) { Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); PalladiaMorsTheRuinerWatcher watcher = (PalladiaMorsTheRuinerWatcher) game.getState().getWatchers().get(PalladiaMorsTheRuinerWatcher.class.getSimpleName()); - return !watcher.getDamagers().contains(new MageObjectReference(permanent, game)); + return permanent != null && !watcher.getDamagers().contains(new MageObjectReference(permanent, game)); } @Override @@ -91,7 +91,7 @@ enum PalladiaMorsTheRuinerCondition implements Condition { class PalladiaMorsTheRuinerWatcher extends Watcher { - private Set damagers = new HashSet(); + private final Set damagers = new HashSet(); public PalladiaMorsTheRuinerWatcher() { super(PalladiaMorsTheRuinerWatcher.class.getSimpleName(), WatcherScope.GAME); From 9a1ce53c9eac0811193abe103d7164b587795f25 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 20:06:47 -0400 Subject: [PATCH 56/74] Implement Prophecy, not showing up --- Mage.Sets/src/mage/cards/p/Prophecy.java | 86 ++++++++++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 1 + 2 files changed, 87 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/Prophecy.java diff --git a/Mage.Sets/src/mage/cards/p/Prophecy.java b/Mage.Sets/src/mage/cards/p/Prophecy.java new file mode 100644 index 00000000000..d58d65ab3c4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/Prophecy.java @@ -0,0 +1,86 @@ +package mage.cards.p; + +import java.util.UUID; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author noahg + */ +public final class Prophecy extends CardImpl { + + public Prophecy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}"); + + + // Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles his or her library. + this.spellAbility.addEffect(new ProphecyEffect()); + + // Draw a card at the beginning of the next turn's upkeep. + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); + } + + public Prophecy(final Prophecy card) { + super(card); + } + + @Override + public Prophecy copy() { + return new Prophecy(this); + } +} + +class ProphecyEffect extends OneShotEffect { + + public ProphecyEffect() { + super(Outcome.GainLife); + this.staticText = "Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles his or her library"; + } + + public ProphecyEffect(final ProphecyEffect effect) { + super(effect); + } + + @Override + public ProphecyEffect copy() { + return new ProphecyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + MageObject sourceObject = game.getObject(source.getSourceId()); + if (sourceObject == null || targetPlayer == null) { + return false; + } + if (targetPlayer.getLibrary().hasCards()) { + CardsImpl cards = new CardsImpl(); + Card card = targetPlayer.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + cards.add(card); + targetPlayer.revealCards(sourceObject.getName(), cards, game); + if (card.isLand()) { + targetPlayer.gainLife(1, game, source.getSourceId()); + } + targetPlayer.shuffleLibrary(source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 4502fe8dbd1..7d519d6df0f 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -131,6 +131,7 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Mystic Decree", 34, Rarity.RARE, mage.cards.m.MysticDecree.class)); cards.add(new SetCardInfo("Narwhal", 35, Rarity.RARE, mage.cards.n.Narwhal.class)); cards.add(new SetCardInfo("Primal Order", 92, Rarity.RARE, mage.cards.p.PrimalOrder.class)); + cards.add(new SetCardInfo("Prophecy", 11, Rarity.COMMON, mage.cards.p.Prophecy.class)); cards.add(new SetCardInfo("Rashka the Slayer", 12, Rarity.RARE, mage.cards.r.RashkaTheSlayer.class)); cards.add(new SetCardInfo("Reef Pirates", "36a", Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Reef Pirates", "36b", Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); From d9c7daaf5280d317cd1f9f79059911a8d413edc3 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 20:45:21 -0400 Subject: [PATCH 57/74] Fix this.spellAbility NPE --- Mage.Sets/src/mage/cards/p/Prophecy.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/Prophecy.java b/Mage.Sets/src/mage/cards/p/Prophecy.java index d58d65ab3c4..dd58eccb418 100644 --- a/Mage.Sets/src/mage/cards/p/Prophecy.java +++ b/Mage.Sets/src/mage/cards/p/Prophecy.java @@ -17,6 +17,7 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; +import mage.target.common.TargetOpponent; /** * @@ -29,7 +30,8 @@ public final class Prophecy extends CardImpl { // Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles his or her library. - this.spellAbility.addEffect(new ProphecyEffect()); + this.getSpellAbility().addEffect(new ProphecyEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); // Draw a card at the beginning of the next turn's upkeep. this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); @@ -64,8 +66,9 @@ class ProphecyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player targetPlayer = game.getPlayer(source.getFirstTarget()); + Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); - if (sourceObject == null || targetPlayer == null) { + if (sourceObject == null || targetPlayer == null || controller == null) { return false; } if (targetPlayer.getLibrary().hasCards()) { @@ -75,9 +78,9 @@ class ProphecyEffect extends OneShotEffect { return false; } cards.add(card); - targetPlayer.revealCards(sourceObject.getName(), cards, game); + targetPlayer.revealCards(sourceObject.getIdName(), cards, game); if (card.isLand()) { - targetPlayer.gainLife(1, game, source.getSourceId()); + controller.gainLife(1, game, source.getSourceId()); } targetPlayer.shuffleLibrary(source, game); } From d1d38974428c6a6df95219b208d48db5641ba369 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 21:22:44 -0400 Subject: [PATCH 58/74] Fix this.spellAbility NPE --- .../d/{DeliriumCard.java => Delirium.java} | 34 +++++++++---------- Mage.Sets/src/mage/sets/Mirage.java | 3 +- 2 files changed, 18 insertions(+), 19 deletions(-) rename Mage.Sets/src/mage/cards/d/{DeliriumCard.java => Delirium.java} (63%) diff --git a/Mage.Sets/src/mage/cards/d/DeliriumCard.java b/Mage.Sets/src/mage/cards/d/Delirium.java similarity index 63% rename from Mage.Sets/src/mage/cards/d/DeliriumCard.java rename to Mage.Sets/src/mage/cards/d/Delirium.java index 2cc0631b0b0..3a04e3523ae 100644 --- a/Mage.Sets/src/mage/cards/d/DeliriumCard.java +++ b/Mage.Sets/src/mage/cards/d/Delirium.java @@ -6,9 +6,7 @@ import mage.abilities.Ability; import mage.abilities.common.CastOnlyIfConditionIsTrueAbility; import mage.abilities.condition.common.OnOpponentsTurnCondition; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PreventCombatDamageBySourceEffect; -import mage.abilities.effects.common.PreventCombatDamageToSourceEffect; -import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -25,7 +23,7 @@ import mage.target.common.TargetCreaturePermanent; * * @author noahg */ -public final class DeliriumCard extends CardImpl { +public final class Delirium extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); @@ -33,38 +31,38 @@ public final class DeliriumCard extends CardImpl { filter.add(new ControllerIsActivePlayerPredicate()); } - public DeliriumCard(UUID ownerId, CardSetInfo setInfo) { + public Delirium(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}{R}"); // Cast this spell only during an opponent’s turn. this.addAbility(new CastOnlyIfConditionIsTrueAbility(OnOpponentsTurnCondition.instance, "Cast this spell only during an opponent’s turn.")); // Tap target creature that player controls. That creature deals damage equal to its power to the player. Prevent all combat damage that would be dealt to and dealt by the creature this turn. - this.spellAbility.addEffect(new TapTargetEffect().setText("tap target creature that player controls")); - this.spellAbility.addTarget(new TargetCreaturePermanent(filter)); - this.spellAbility.addEffect(new DeliriumCardEffect()); - this.spellAbility.addEffect(new PreventCombatDamageBySourceEffect(Duration.EndOfTurn).setText("Prevent all combat damage that would be dealt to")); - this.spellAbility.addEffect(new PreventCombatDamageToSourceEffect(Duration.EndOfTurn).setText("and dealt by the creature this turn.")); + this.getSpellAbility().addEffect(new TapTargetEffect().setText("target creature that player controls")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + this.getSpellAbility().addEffect(new DeliriumEffect()); + this.getSpellAbility().addEffect(new PreventDamageToTargetEffect(Duration.EndOfTurn, true).setText("Prevent all combat damage that would be dealt to")); + this.getSpellAbility().addEffect(new PreventDamageByTargetEffect(Duration.EndOfTurn, true).setText("and dealt by the creature this turn.")); } - public DeliriumCard(final DeliriumCard card) { + public Delirium(final Delirium card) { super(card); } @Override - public DeliriumCard copy() { - return new DeliriumCard(this); + public Delirium copy() { + return new Delirium(this); } } -class DeliriumCardEffect extends OneShotEffect { +class DeliriumEffect extends OneShotEffect { - public DeliriumCardEffect() { + public DeliriumEffect() { super(Outcome.Damage); this.staticText = "that creature deals damage equal to its power to the player"; } - public DeliriumCardEffect(DeliriumCardEffect effect) { + public DeliriumEffect(DeliriumEffect effect) { super(effect); } @@ -83,7 +81,7 @@ class DeliriumCardEffect extends OneShotEffect { } @Override - public DeliriumCardEffect copy() { - return new DeliriumCardEffect(this); + public DeliriumEffect copy() { + return new DeliriumEffect(this); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index 3098c4e0b27..845414da3a6 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -1,6 +1,7 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.cards.d.Delirium; import mage.constants.Rarity; import mage.constants.SetType; @@ -83,7 +84,7 @@ public final class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Dark Banishing", 115, Rarity.COMMON, mage.cards.d.DarkBanishing.class)); cards.add(new SetCardInfo("Dark Ritual", 116, Rarity.COMMON, mage.cards.d.DarkRitual.class)); cards.add(new SetCardInfo("Dazzling Beauty", 8, Rarity.COMMON, mage.cards.d.DazzlingBeauty.class)); - cards.add(new SetCardInfo("Delirium", 260, Rarity.UNCOMMON, mage.cards.d.DeliriumCard.class)); + cards.add(new SetCardInfo("Delirium", 260, Rarity.UNCOMMON, Delirium.class)); cards.add(new SetCardInfo("Dirtwater Wraith", 117, Rarity.COMMON, mage.cards.d.DirtwaterWraith.class)); cards.add(new SetCardInfo("Disempower", 9, Rarity.COMMON, mage.cards.d.Disempower.class)); cards.add(new SetCardInfo("Disenchant", 10, Rarity.COMMON, mage.cards.d.Disenchant.class)); From d7a38a65c875d216db932ff6f52dfdb71cb6c93e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 23 Jun 2018 21:32:22 -0400 Subject: [PATCH 59/74] fixed Skyrider Patrol confirmation text --- Mage.Sets/src/mage/cards/s/SkyriderPatrol.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/s/SkyriderPatrol.java b/Mage.Sets/src/mage/cards/s/SkyriderPatrol.java index 135fefc5301..ac0bb3b8b19 100644 --- a/Mage.Sets/src/mage/cards/s/SkyriderPatrol.java +++ b/Mage.Sets/src/mage/cards/s/SkyriderPatrol.java @@ -47,7 +47,9 @@ public final class SkyriderPatrol extends CardImpl { this.addAbility(new BeginningOfCombatTriggeredAbility( new DoIfCostPaid( new SkyriderPatrolCreateReflexiveTriggerEffect(), - new ManaCostsImpl("{G}{U}") + new ManaCostsImpl("{G}{U}"), + "Pay {G}{U} to put a +1/+1 counter on another" + + " creature you control and give it flying?" ).setText("you may pay {G}{U}. When you do, " + "put a +1/+1 counter on another target creature you control, " + "and that creature gains flying until end of turn."), From c63785748089471a030e400437d2e8d53226fe5e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 23 Jun 2018 21:39:36 -0400 Subject: [PATCH 60/74] Added null detection to MageObjectReference --- Mage/src/main/java/mage/MageObjectReference.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/MageObjectReference.java b/Mage/src/main/java/mage/MageObjectReference.java index c9e67326439..ff72668e405 100644 --- a/Mage/src/main/java/mage/MageObjectReference.java +++ b/Mage/src/main/java/mage/MageObjectReference.java @@ -1,4 +1,3 @@ - package mage; import java.io.Serializable; @@ -24,6 +23,11 @@ public class MageObjectReference implements Comparable, Ser private final int zoneChangeCounter; public MageObjectReference(MageObject mageObject, Game game) { + if (mageObject == null) { + this.sourceId = null; + this.zoneChangeCounter = -1; + return; + } this.sourceId = mageObject.getId(); this.zoneChangeCounter = mageObject.getZoneChangeCounter(game); } From 1a9ee7b0b928836a9bd33a7de44c637fe84772f2 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sat, 23 Jun 2018 21:52:36 -0400 Subject: [PATCH 61/74] Update to ungendered wording --- Mage.Sets/src/mage/cards/p/Prophecy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/Prophecy.java b/Mage.Sets/src/mage/cards/p/Prophecy.java index dd58eccb418..e8c1e6260f5 100644 --- a/Mage.Sets/src/mage/cards/p/Prophecy.java +++ b/Mage.Sets/src/mage/cards/p/Prophecy.java @@ -29,7 +29,7 @@ public final class Prophecy extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}"); - // Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles his or her library. + // Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles their library. this.getSpellAbility().addEffect(new ProphecyEffect()); this.getSpellAbility().addTarget(new TargetOpponent()); @@ -51,7 +51,7 @@ class ProphecyEffect extends OneShotEffect { public ProphecyEffect() { super(Outcome.GainLife); - this.staticText = "Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles his or her library"; + this.staticText = "Reveal the top card of target opponent's library. If it's a land, you gain 1 life. Then that player shuffles their library"; } public ProphecyEffect(final ProphecyEffect effect) { From 595ead66a7413f81599d094317918bffc6b4edd5 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 23 Jun 2018 22:38:04 -0400 Subject: [PATCH 62/74] Implemented Rysorian Badger --- .../src/mage/cards/r/RysorianBadger.java | 105 ++++++++++++++++++ Mage.Sets/src/mage/sets/Homelands.java | 1 + 2 files changed, 106 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RysorianBadger.java diff --git a/Mage.Sets/src/mage/cards/r/RysorianBadger.java b/Mage.Sets/src/mage/cards/r/RysorianBadger.java new file mode 100644 index 00000000000..0b188d906f4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RysorianBadger.java @@ -0,0 +1,105 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAndIsNotBlockedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.AssignNoCombatDamageSourceEffect; +import mage.cards.Card; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.permanent.DefendingPlayerOwnsCardPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +/** + * + * @author TheElk801 + */ +public final class RysorianBadger extends CardImpl { + + private static final FilterCreatureCard filter = new FilterCreatureCard("creature cards from defending player's graveyard"); + + static { + filter.add(new DefendingPlayerOwnsCardPredicate()); + } + + public RysorianBadger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.BADGER); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Rysorian Badger attacks and isn't blocked, you may exile up to two target creature cards from defending player's graveyard. If you do, you gain 1 life for each card exiled this way and Rysorian Badger assigns no combat damage this turn. + Ability ability = new AttacksAndIsNotBlockedTriggeredAbility( + new RysorianBadgerEffect(), true + ); + ability.addTarget(new TargetCardInGraveyard(0, 2, filter)); + this.addAbility(ability); + } + + public RysorianBadger(final RysorianBadger card) { + super(card); + } + + @Override + public RysorianBadger copy() { + return new RysorianBadger(this); + } +} + +class RysorianBadgerEffect extends OneShotEffect { + + public RysorianBadgerEffect() { + super(Outcome.Benefit); + this.staticText = "you may exile up to two target creature cards " + + "from defending player's graveyard. If you do, " + + "you gain 1 life for each card exiled this way " + + "and {this} assigns no combat damage this turn."; + } + + public RysorianBadgerEffect(final RysorianBadgerEffect effect) { + super(effect); + } + + @Override + public RysorianBadgerEffect copy() { + return new RysorianBadgerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cardsToExile = new CardsImpl(); + for (UUID cardId : this.getTargetPointer().getTargets(game, source)) { + Card card = game.getCard(cardId); + if (card != null) { + cardsToExile.add(card); + } + } + int cardsExiled = 0; + player.moveCardsToExile(cardsToExile.getCards(game), source, game, false, null, null); + for (Card card : cardsToExile.getCards(game)) { + if (game.getState().getZone(card.getId()) == Zone.EXILED) { + cardsExiled++; + } + } + player.gainLife(cardsExiled, game, source); + game.addEffect(new AssignNoCombatDamageSourceEffect(Duration.EndOfTurn), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 16621bcbed1..a9c872ebbc8 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -142,6 +142,7 @@ public final class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Root Spider", 94, Rarity.UNCOMMON, mage.cards.r.RootSpider.class)); cards.add(new SetCardInfo("Roots", 95, Rarity.UNCOMMON, mage.cards.r.Roots.class)); cards.add(new SetCardInfo("Roterothopter", 109, Rarity.COMMON, mage.cards.r.Roterothopter.class)); + cards.add(new SetCardInfo("Rysorian Badger", 96, Rarity.RARE, mage.cards.r.RysorianBadger.class)); cards.add(new SetCardInfo("Samite Alchemist", 117, Rarity.COMMON, mage.cards.s.SamiteAlchemist.class)); cards.add(new SetCardInfo("Sea Sprite", 38, Rarity.UNCOMMON, mage.cards.s.SeaSprite.class)); cards.add(new SetCardInfo("Sengir Autocrat", 56, Rarity.UNCOMMON, mage.cards.s.SengirAutocrat.class)); From 3d31e12eaed078022d5740a00a5fb0ac3e7ff880 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 10:34:43 +0200 Subject: [PATCH 63/74] * Fixed wrong P/T values of Aegis of the Heavens. --- Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java b/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java index b31d109eb75..c66d70d625e 100644 --- a/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java +++ b/Mage.Sets/src/mage/cards/a/AegisOfTheHeavens.java @@ -18,7 +18,7 @@ public final class AegisOfTheHeavens extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); // Target creature gets +1/+7 until end of turn. - this.getSpellAbility().addEffect(new BoostTargetEffect(3, 3, Duration.EndOfTurn)); + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 7, Duration.EndOfTurn)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } From d1cc207b42490ed4389b8f624471dc6c6d3b2fb6 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 10:39:24 +0200 Subject: [PATCH 64/74] * Herald of Faith - Fixed life gain from 1 to 2 life. --- Mage.Sets/src/mage/cards/h/HeraldOfFaith.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/h/HeraldOfFaith.java b/Mage.Sets/src/mage/cards/h/HeraldOfFaith.java index a8912a006f8..02b4255b59e 100644 --- a/Mage.Sets/src/mage/cards/h/HeraldOfFaith.java +++ b/Mage.Sets/src/mage/cards/h/HeraldOfFaith.java @@ -4,11 +4,11 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; /** * @@ -27,7 +27,7 @@ public final class HeraldOfFaith extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Herald of Faith attacks, you gain 2 life. - this.addAbility(new AttacksTriggeredAbility(new GainLifeEffect(1), false)); + this.addAbility(new AttacksTriggeredAbility(new GainLifeEffect(2), false)); } public HeraldOfFaith(final HeraldOfFaith card) { From 8f27adce46527020a461248dbe180103ada66f39 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 10:43:10 +0200 Subject: [PATCH 65/74] * Revitalize - Fixed life gaining, 3 instead of 4. --- Mage.Sets/src/mage/cards/r/Revitalize.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/r/Revitalize.java b/Mage.Sets/src/mage/cards/r/Revitalize.java index 241448f180b..f4464efc4af 100644 --- a/Mage.Sets/src/mage/cards/r/Revitalize.java +++ b/Mage.Sets/src/mage/cards/r/Revitalize.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -18,7 +17,7 @@ public final class Revitalize extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); // You gain 3 life. - this.getSpellAbility().addEffect(new GainLifeEffect(4)); + this.getSpellAbility().addEffect(new GainLifeEffect(3)); // Draw a card. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); From 56526d5413587d14c58cb236a2853762b7d2f347 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 24 Jun 2018 21:13:35 +1000 Subject: [PATCH 66/74] Copy Paste image source --- .../card/dl/sources/CopyPasteImageSource.java | 255 ++++++++++++++++++ .../sources/CopyPasteImageSourceDialog.form | 79 ++++++ .../sources/CopyPasteImageSourceDialog.java | 182 +++++++++++++ .../plugins/card/images/DownloadPictures.java | 3 +- 4 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java create mode 100644 Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.form create mode 100644 Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java new file mode 100644 index 00000000000..0fc6561b978 --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSource.java @@ -0,0 +1,255 @@ +package org.mage.plugins.card.dl.sources; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JOptionPane; +import mage.cards.Sets; +import org.mage.plugins.card.images.CardDownloadData; + +/** + * + * @author spjspj + */ +public enum CopyPasteImageSource implements CardImageSource { + + instance; + + private Set supportedSets = new LinkedHashSet(); + private Set missingCards = new LinkedHashSet(); + HashMap singleLinks = null; + boolean loadedFromDialog = false; + boolean viewMissingCards = true; + HashMap singleLinksDone = null; + private static int maxTimes = 2; + + @Override + public String getSourceName() { + return ""; + } + + @Override + public float getAverageSize() { + return 260.7f; + } + + @Override + public String getNextHttpImageUrl() { + if (singleLinks == null) { + setupLinks(); + } + + for (String key : singleLinksDone.keySet()) { + if (singleLinksDone.get(key) < maxTimes) { + singleLinksDone.put(key, maxTimes); + return key; + } + } + if (maxTimes < 2) { + maxTimes++; + } + for (String key : singleLinksDone.keySet()) { + if (singleLinksDone.get(key) < maxTimes) { + singleLinksDone.put(key, maxTimes); + return key; + } + } + return null; + } + + @Override + public String getFileForHttpImage(String httpImageUrl) { + String copy = httpImageUrl; + if (copy != null) { + return singleLinks.get(copy); + } + return null; + } + + @Override + public CardImageUrls generateURL(CardDownloadData card) throws Exception { + if (singleLinks == null) { + setupLinks(); + } + String url = singleLinks.get(card.getSet() + "/" + card.getName()); + if (url != null && url.length() > 0) { + return new CardImageUrls(url); + } + url = singleLinks.get(card.getSet() + "/" + card.getName() + "." + card.getCollectorId()); + if (url != null && url.length() > 0) { + return new CardImageUrls(url); + } + return null; + } + + int ls_size_mc = 0; + int ls_size_ss = 0; + int ls_size_sl = 0; + int num_nos = 0; + + private boolean isDifferent() { + boolean isdiff = false; + if (ls_size_mc != missingCards.size()) { + ls_size_mc = missingCards.size(); + isdiff = true; + } + if (ls_size_ss != supportedSets.size()) { + ls_size_ss = supportedSets.size(); + isdiff = true; + } + if (ls_size_sl != singleLinks.size()) { + ls_size_sl = singleLinks.size(); + isdiff = true; + } + num_nos++; + if (num_nos > 2) { + num_nos = 0; + isdiff = true; + } + return isdiff; + } + + private void setupLinks() { + if (singleLinks != null && loadedFromDialog) { + if (!viewMissingCards) { + if (isDifferent() && JOptionPane.showConfirmDialog(null, + "View your missing cards and reset the list of card images to download again?", + "View missing cards (found " + missingCards.size() + ") / Reset URLs to download ", JOptionPane.YES_NO_OPTION) + == JOptionPane.YES_OPTION) { + viewMissingCards = true; + singleLinks.clear(); + loadedFromDialog = false; + supportedSets.clear(); + } else { + return; + } + } + if (!(viewMissingCards && missingCards.size() > 0)) { + return; + } + } + singleLinks = new HashMap<>(); + loadedFromDialog = false; + + final CopyPasteImageSourceDialog dialog = new CopyPasteImageSourceDialog(); + dialog.pack(); + int count = 0; + if (viewMissingCards && missingCards.size() > 0 && singleLinks.size() == 0) { + viewMissingCards = false; + String displayMissingCardsStr = "Up to the first 20 cards are:\n"; + String missingCardsStr = ""; + if (this.missingCards != null) { + for (String card : this.missingCards) { + if (count < 20) { + displayMissingCardsStr = displayMissingCardsStr + card + "\n"; + } + missingCardsStr = missingCardsStr + card + "\n"; + + count++; + } + } + StringSelection stringSelection = new StringSelection(missingCardsStr); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(stringSelection, null); + + if (isDifferent() && JOptionPane.showConfirmDialog(null, + displayMissingCardsStr + "\n\nReset the list again?\n(NB: The full list has been copied to the clipboard)", + "Your missing cards (found " + missingCards.size() + "): ", JOptionPane.YES_NO_OPTION) + == JOptionPane.YES_OPTION) { + viewMissingCards = true; + singleLinks.clear(); + loadedFromDialog = false; + supportedSets.clear(); + } else { + return; + } + } + dialog.setVisible(true); + String[] lines = dialog.getPastedData().split(System.getProperty("line.separator")); + + for (String line : lines) { + // Break into >> "\1", "\2" + Pattern regex = Pattern.compile("\\s*\"(.*?)/(.*?)\"\\s*,\\s*\"(.*?)\""); + Matcher regexMatcher = regex.matcher(line); + while (regexMatcher.find()) { + String setCode = regexMatcher.group(1); + String cardName = regexMatcher.group(2); + String imageURL = regexMatcher.group(3); + supportedSets.add(setCode); + singleLinks.put(setCode + "/" + cardName, imageURL); + isDifferent(); + } + } + + loadedFromDialog = true; + if (lines.length == 0) { + loadedFromDialog = false; + viewMissingCards = true; + } + } + + @Override + public CardImageUrls generateTokenUrl(CardDownloadData card) throws IOException { + try { + return generateURL(card); + } catch (Exception ex) { + } + return null; + } + + @Override + public int getTotalImages() { + if (singleLinks == null) { + setupLinks(); + } + if (singleLinks != null) { + return singleLinks.size(); + } + return -1; + } + + @Override + public boolean isTokenSource() { + return false; + } + + @Override + public ArrayList getSupportedSets() { + setupLinks(); + ArrayList supportedSetsCopy = new ArrayList<>(); + if (supportedSets.size() == 0) { + for (String setCode : Sets.getInstance().keySet()) { + supportedSets.add(setCode); + } + } + + supportedSetsCopy.addAll(supportedSets); + return supportedSetsCopy; + } + + @Override + public void doPause(String httpImageUrl) { + } + + @Override + public boolean isImageProvided(String setCode, String cardName) { + missingCards.add(setCode + "/" + cardName); + + if (singleLinks != null) { + return singleLinks.containsKey(setCode + "/" + cardName) || singleLinks.containsKey(setCode + "/" + cardName + "-a"); + } + return false; + } + + @Override + public boolean isSetSupportedComplete(String setCode) { + return false; + } +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.form b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.form new file mode 100644 index 00000000000..fe9fa3cf9bd --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.form @@ -0,0 +1,79 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.java new file mode 100644 index 00000000000..b64d7cc8105 --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/CopyPasteImageSourceDialog.java @@ -0,0 +1,182 @@ +package org.mage.plugins.card.dl.sources; + +import mage.util.StreamUtils; + +import java.awt.*; +import java.awt.event.*; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Set; +import javax.swing.*; + +public class CopyPasteImageSourceDialog extends JDialog { + + private JPanel contentPane; + private JButton buttonOK; + private JButton buttonCancel; + private JEditorPane txtDeckList; + + private String tmpPath; + + public CopyPasteImageSourceDialog() { + initComponents(); + setContentPane(contentPane); + setModal(true); + getRootPane().setDefaultButton(buttonOK); + + buttonOK.addActionListener(e -> onOK()); + buttonCancel.addActionListener(e -> onCancel()); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + onCancel(); + } + }); + + // Close on "ESC" + contentPane.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + } + + private void onOK() { + BufferedWriter bw = null; + try { + File temp = File.createTempFile("import_images_from_url", ".txt"); + bw = new BufferedWriter(new FileWriter(temp)); + bw.write(txtDeckList.getText()); + tmpPath = temp.getPath(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + StreamUtils.closeQuietly(bw); + } + + dispose(); + } + + private void onCancel() { + dispose(); + } + + public String getTmpPath() { + return tmpPath; + } + + private void initComponents() { + contentPane = new JPanel(); + JPanel panel1 = new JPanel(); + JPanel panel2 = new JPanel(); + buttonOK = new JButton(); + buttonCancel = new JButton(); + JPanel panel3 = new JPanel(); + txtDeckList = new JEditorPane(); + + { + contentPane.setMinimumSize(new Dimension(540, 450)); + + contentPane.setBorder(new javax.swing.border.CompoundBorder( + new javax.swing.border.TitledBorder(new javax.swing.border.EmptyBorder(0, 0, 0, 0), + "Download Images from Copy/Pasted Text", javax.swing.border.TitledBorder.CENTER, + javax.swing.border.TitledBorder.TOP, new java.awt.Font("Dialog", java.awt.Font.PLAIN, 12), + java.awt.Color.BLACK), contentPane.getBorder())); + + contentPane.addPropertyChangeListener(e -> { + if ("border".equals(e.getPropertyName())) { + throw new RuntimeException(); + } + }); + + contentPane.addPropertyChangeListener(e -> { + if ("border".equals(e.getPropertyName())) { + throw new RuntimeException(); + } + }); + + contentPane.setLayout(new GridBagLayout()); + ((GridBagLayout) contentPane.getLayout()).columnWidths = new int[]{0, 0}; + ((GridBagLayout) contentPane.getLayout()).rowHeights = new int[]{0, 0, 0}; + ((GridBagLayout) contentPane.getLayout()).columnWeights = new double[]{0.01, 1.0E-4}; + ((GridBagLayout) contentPane.getLayout()).rowWeights = new double[]{0.01, 0.0, 1.0E-4}; + + { + panel1.setLayout(new GridBagLayout()); + ((GridBagLayout) panel1.getLayout()).columnWidths = new int[]{0, 0, 0}; + ((GridBagLayout) panel1.getLayout()).rowHeights = new int[]{0, 0}; + ((GridBagLayout) panel1.getLayout()).columnWeights = new double[]{0.0, 0.01, 1.0E-4}; + ((GridBagLayout) panel1.getLayout()).rowWeights = new double[]{0.01, 1.0E-4}; + + { + panel2.setLayout(new GridBagLayout()); + ((GridBagLayout) panel2.getLayout()).columnWidths = new int[]{0, 4, 0, 0}; + ((GridBagLayout) panel2.getLayout()).rowHeights = new int[]{0, 0}; + ((GridBagLayout) panel2.getLayout()).columnWeights = new double[]{0.01, 0.0, 0.01, 1.0E-4}; + ((GridBagLayout) panel2.getLayout()).rowWeights = new double[]{0.0, 1.0E-4}; + + //---- buttonOK ---- + buttonOK.setText("Download from URLs"); + panel2.add(buttonOK, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0)); + + //---- buttonCancel ---- + buttonCancel.setText("Cancel"); + panel2.add(buttonCancel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0)); + } + panel1.add(panel2, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + } + contentPane.add(panel1, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + + { + panel3.setLayout(new GridBagLayout()); + ((GridBagLayout) panel3.getLayout()).columnWidths = new int[]{0, 0}; + ((GridBagLayout) panel3.getLayout()).rowHeights = new int[]{0, 0}; + ((GridBagLayout) panel3.getLayout()).columnWeights = new double[]{0.0, 1.0E-4}; + ((GridBagLayout) panel3.getLayout()).rowWeights = new double[]{1.0, 1.0E-4}; + + txtDeckList.setMinimumSize(new Dimension(250, 400)); + txtDeckList.setPreferredSize(new Dimension(550, 400)); + + txtDeckList.setText("// Example follows. \nNB: **DELETE ALL TEXT AND GO SELECT THIS SOURCE AGAIN TO SEE THE NAMES CARDS YOU'RE MISSING IMAGES FOR!!!***\n\"SWS/Might of the Wild\", \"http://i.imgur.com/eNXOdxp.jpg\"\n\"PTC/Wolf of Devil's Breach\", \"https://img.scryfall.com/cards/large/en/psoi/192s.jpg\"\n\nExpected columns: Name of Card (Set Trigraph\\Name), URL of image\n\n\n"); + + JScrollPane txtScrollableDeckList = new JScrollPane(txtDeckList); + panel3.add(txtScrollableDeckList, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + } + contentPane.add(panel3, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 5, 0), 0, 0)); + } + } + + Set missingCards = null; + + public void addMissingCards(Set missingCards) { + this.missingCards = missingCards; + String missingCardsStr = ""; + boolean found = false; + if (this.missingCards != null) { + for (String card : this.missingCards) { + found = true; + missingCardsStr = missingCardsStr + card + "\n"; + } + } + if (found == false) { + missingCardsStr = "\n\nNote: Leave blank to see your missing card names!\n"; + } + txtDeckList.setText(txtDeckList.getText() + "\n\nYour missing card images are:\n" + missingCardsStr); + } + + public String getPastedData() { + return txtDeckList.getText(); + } +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 0e9556dd902..7be47db2d1f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -84,7 +84,8 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab MAGIDEX("4. magidex.com - high quality CARDS", MagidexImageSource.instance), GRAB_BAG("5. GrabBag - STAR WARS cards and tokens", GrabbagImageSource.instance), MYTHICSPOILER("6. mythicspoiler.com", MythicspoilerComSource.instance), - ALTERNATIVE("7. alternative.mtg.onl", AltMtgOnlTokensImageSource.instance); + ALTERNATIVE("7. alternative.mtg.onl", AltMtgOnlTokensImageSource.instance), + COPYPASTE("8. Copy and Paste Image URLs", CopyPasteImageSource.instance); // MTG_ONL("mtg.onl", MtgOnlTokensImageSource.instance), Not working correctly yet // MAGICCARDS("magiccards.info", MagicCardsImageSource.instance) From 99cfd8653310990e86d38d2265c37ff7ed925761 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 18:11:17 +0200 Subject: [PATCH 67/74] * Some more fixes for ManaOptions #5023 --- Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java | 6 +++--- .../abilities/mana/CommanderColorIdentityManaAbility.java | 6 ++++-- .../java/mage/abilities/mana/ConditionalManaAbility.java | 4 +--- Mage/src/main/java/mage/abilities/mana/ManaOptions.java | 2 +- .../main/java/mage/abilities/mana/SimpleManaAbility.java | 4 ++-- .../main/java/mage/abilities/mana/TriggeredManaAbility.java | 3 +-- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java b/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java index 67453e05318..6b08d876413 100644 --- a/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java +++ b/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java @@ -66,11 +66,11 @@ class NykthosShrineToNyxManaAbility extends ActivatedManaAbilityImpl { @Override public List getNetMana(Game game) { - netMana.clear(); + ArrayList netManaCopy = new ArrayList<>(); if (game != null) { - netMana.addAll(((ManaEffect) this.getEffects().get(0)).getNetMana(game, this)); + netManaCopy.addAll(((ManaEffect) this.getEffects().get(0)).getNetMana(game, this)); } - return netMana; + return netManaCopy; } } diff --git a/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java b/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java index 054f3159369..44ad62ff232 100644 --- a/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java @@ -1,6 +1,6 @@ - package mage.abilities.mana; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import mage.Mana; @@ -42,6 +42,7 @@ public class CommanderColorIdentityManaAbility extends ActivatedManaAbilityImpl @Override public List getNetMana(Game game) { + List netManas = new ArrayList<>(); if (netMana.isEmpty() && game != null) { Player controller = game.getPlayer(getControllerId()); if (controller != null) { @@ -68,7 +69,8 @@ public class CommanderColorIdentityManaAbility extends ActivatedManaAbilityImpl } } } - return netMana; + netManas.addAll(netMana); + return netManas; } @Override diff --git a/Mage/src/main/java/mage/abilities/mana/ConditionalManaAbility.java b/Mage/src/main/java/mage/abilities/mana/ConditionalManaAbility.java index 06c2919d175..c2d0a97511f 100644 --- a/Mage/src/main/java/mage/abilities/mana/ConditionalManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/ConditionalManaAbility.java @@ -33,8 +33,6 @@ public class ConditionalManaAbility extends ActivatedManaAbilityImpl { @Override public List getNetMana(Game game) { - List newNetMana = new ArrayList<>(); - newNetMana.addAll(conditionalManaEffect.getNetMana(game, this)); - return newNetMana; + return new ArrayList<>(conditionalManaEffect.getNetMana(game, this)); } } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index 6633be31e8a..81792fc8c9a 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -53,7 +53,7 @@ public class ManaOptions extends ArrayList { boolean hasTapCost = hasTapCost(abilities.get(0)); for (Mana netMana : netManas) { for (Mana mana : copy) { - if (!hasTapCost || checkTappedForManaReplacement(abilities.get(0), game, netMana)) { + if (!hasTapCost /* || checkTappedForManaReplacement(abilities.get(0), game, netMana) */) { // Seems to produce endless iterations so deactivated for now: https://github.com/magefree/mage/issues/5023 Mana newMana = new Mana(); newMana.add(mana); newMana.add(netMana); diff --git a/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java b/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java index 4998d58b5c5..0877a667c25 100644 --- a/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java @@ -1,6 +1,6 @@ - package mage.abilities.mana; +import java.util.ArrayList; import java.util.List; import mage.Mana; import mage.abilities.costs.Cost; @@ -55,7 +55,7 @@ public class SimpleManaAbility extends ActivatedManaAbilityImpl { if (predictable) { return super.getNetMana(game); } - return netMana; + return new ArrayList(netMana); } } diff --git a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java index 92054553ac6..41173683bd1 100644 --- a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java @@ -1,4 +1,3 @@ - package mage.abilities.mana; import java.util.ArrayList; @@ -54,7 +53,7 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen } return newNetMana; } - return netMana; + return new ArrayList(netMana); } /** From ca21f3200998fe079dcce14c2ab60dc2faef798f Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 24 Jun 2018 21:26:52 +0200 Subject: [PATCH 68/74] Xmage 1.4.30V6 --- Mage.Common/src/main/java/mage/utils/MageVersion.java | 2 +- .../mage/abilities/effects/common/CopyTokenEffect.java | 10 +++++----- .../java/mage/cards/repository/CardRepository.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 83c66f8e409..0cc812c6857 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -14,7 +14,7 @@ public class MageVersion implements Serializable, Comparable { public final static int MAGE_VERSION_MAJOR = 1; public final static int MAGE_VERSION_MINOR = 4; public final static int MAGE_VERSION_PATCH = 30; - public final static String MAGE_VERSION_MINOR_PATCH = "V5"; + public final static String MAGE_VERSION_MINOR_PATCH = "V6"; public final static String MAGE_VERSION_INFO = ""; private final int major; diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyTokenEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyTokenEffect.java index 56463f3927f..63b3018d234 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyTokenEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyTokenEffect.java @@ -5,10 +5,10 @@ import mage.abilities.effects.ContinuousEffectImpl; import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.Token; public class CopyTokenEffect extends ContinuousEffectImpl { + protected Token token; public CopyTokenEffect(Token token) { @@ -28,19 +28,19 @@ public class CopyTokenEffect extends ContinuousEffectImpl { permanent.setName(token.getName()); permanent.getColor(game).setColor(token.getColor(game)); permanent.getCardType().clear(); - for (CardType type: token.getCardType()) { + for (CardType type : token.getCardType()) { permanent.addCardType(type); } permanent.getSubtype(game).clear(); - for (SubType type: token.getSubtype(game)) { + for (SubType type : token.getSubtype(game)) { permanent.getSubtype(game).add(type); } permanent.getSuperType().clear(); - for (SuperType type: token.getSuperType()) { + for (SuperType type : token.getSuperType()) { permanent.addSuperType(type); } permanent.getAbilities().clear(); - for (Ability ability: token.getAbilities()) { + for (Ability ability : token.getAbilities()) { permanent.addAbility(ability, game); } permanent.getPower().setValue(token.getPower().getValue()); diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 902185cc5ca..3fb3d62175d 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -32,7 +32,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 51; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 115; + private static final long CARD_CONTENT_VERSION = 116; private Dao cardDao; private Set classNames; From c4b274d666daaeebdf4345c83de8e8ff37b84185 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 24 Jun 2018 18:07:48 -0400 Subject: [PATCH 69/74] fixed Vivien of the Arkbow's second ability --- Mage.Sets/src/mage/cards/v/VivienOfTheArkbow.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/v/VivienOfTheArkbow.java b/Mage.Sets/src/mage/cards/v/VivienOfTheArkbow.java index af43a37d6d0..f40809e0bb2 100644 --- a/Mage.Sets/src/mage/cards/v/VivienOfTheArkbow.java +++ b/Mage.Sets/src/mage/cards/v/VivienOfTheArkbow.java @@ -51,6 +51,7 @@ public final class VivienOfTheArkbow extends CardImpl { ability = new LoyaltyAbility(new DamageWithPowerTargetEffect(), -3); ability.addTarget(new TargetControlledCreaturePermanent()); ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); // −9: Creatures you control get +4/+4 and gain trample until end of turn. ability = new LoyaltyAbility( From 44559371587d8c15e696e23c8f6fa519e8dcf3e2 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 24 Jun 2018 18:21:16 -0400 Subject: [PATCH 70/74] fixed Novice Knight not working correctly --- Mage.Sets/src/mage/cards/n/NoviceKnight.java | 66 +++++++++++++++----- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/Mage.Sets/src/mage/cards/n/NoviceKnight.java b/Mage.Sets/src/mage/cards/n/NoviceKnight.java index c7fb1e98855..1a40f02fc3f 100644 --- a/Mage.Sets/src/mage/cards/n/NoviceKnight.java +++ b/Mage.Sets/src/mage/cards/n/NoviceKnight.java @@ -2,19 +2,20 @@ package mage.cards.n; import java.util.UUID; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.OrCondition; -import mage.abilities.condition.common.EnchantedSourceCondition; -import mage.abilities.condition.common.EquippedSourceCondition; -import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect; +import mage.abilities.effects.AsThoughEffectImpl; import mage.constants.SubType; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.AsThoughEffectType; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Outcome; import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; /** * @@ -35,18 +36,7 @@ public final class NoviceKnight extends CardImpl { // As long as Novice Knight is enchanted or equipped, it can attack as though it didn't have defender. this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, - new ConditionalContinuousEffect( - new CanAttackAsThoughItDidntHaveDefenderSourceEffect( - Duration.WhileOnBattlefield - ), - new OrCondition( - EquippedSourceCondition.instance, - new EnchantedSourceCondition() - ), - "As long as {this} is enchanted or equipped, " - + "it can attack as though it didn't have defender." - ) + Zone.BATTLEFIELD, new NoviceKnightEffect() )); } @@ -59,3 +49,45 @@ public final class NoviceKnight extends CardImpl { return new NoviceKnight(this); } } + +class NoviceKnightEffect extends AsThoughEffectImpl { + + public NoviceKnightEffect() { + super(AsThoughEffectType.ATTACK, Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "As long as {this} is enchanted or equipped, " + + "it can attack as though it didn't have defender."; + } + + public NoviceKnightEffect(final NoviceKnightEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public NoviceKnightEffect copy() { + return new NoviceKnightEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!objectId.equals(source.getSourceId())) { + return false; + } + Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); + if (permanent != null) { + for (UUID uuid : permanent.getAttachments()) { + Permanent attached = game.getBattlefield().getPermanent(uuid); + if (attached != null + && (attached.hasSubtype(SubType.EQUIPMENT, game) + || attached.hasSubtype(SubType.AURA, game))) { + return true; + } + } + } + return false; + } +} From 567507384046d1aa8b89ec39bdf70c7a4d8c3583 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 24 Jun 2018 18:57:54 -0400 Subject: [PATCH 71/74] fixed Talons of Wildwood giving +2/+2 instead of +1/+1 --- Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java b/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java index e90adec696b..a843c1a9464 100644 --- a/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java +++ b/Mage.Sets/src/mage/cards/t/TalonsOfWildwood.java @@ -42,7 +42,7 @@ public final class TalonsOfWildwood extends CardImpl { // Enchanted creature gets +1/+1 and has trample. ability = new SimpleStaticAbility( - Zone.BATTLEFIELD, new BoostEnchantedEffect(2, 2, Duration.WhileOnBattlefield) + Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 1, Duration.WhileOnBattlefield) ); ability.addEffect(new GainAbilityAttachedEffect( TrampleAbility.getInstance(), From 88eeaa3f1e81b0f56d0d5ff13da35563d6bef016 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 24 Jun 2018 19:29:12 -0400 Subject: [PATCH 72/74] temporarily removed future requirement from standard to allow M19 --- .../Mage.Deck.Constructed/src/mage/deck/Standard.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java index 0e2c21598ea..bc1c2a5a640 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Standard.java @@ -48,8 +48,8 @@ public class Standard extends Constructed { if ((set.getSetType() == SetType.CORE || set.getSetType() == SetType.EXPANSION || set.getSetType() == SetType.SUPPLEMENTAL_STANDARD_LEGAL) - && (!set.getReleaseDate().before(earliestDate) - && !set.getReleaseDate().after(current.getTime()))) { + && !set.getReleaseDate().before(earliestDate)) { +// && !set.getReleaseDate().after(current.getTime()))) { setCodes.add(set.getCode()); } } From 087e58383aff9e6ecb22a608f8ea3576c0abd19e Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 24 Jun 2018 21:27:10 -0400 Subject: [PATCH 73/74] fixed Alpine Moon causing a rollback --- .../mage/cards/repository/CardRepository.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 3fb3d62175d..6b5fa163f18 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -159,7 +159,10 @@ public enum CardRepository { QueryBuilder qb = cardDao.queryBuilder(); qb.distinct().selectColumns("name"); Where where = qb.where(); - where.and(where.not().not().like("supertypes", '%' + SuperType.BASIC.name() + '%'), where.like("types", '%' + CardType.LAND.name() + '%')); + where.and( + where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'), + where.like("types", '%' + CardType.LAND.name() + '%') + ); List results = cardDao.query(qb.prepare()); for (CardInfo card : results) { int result = card.getName().indexOf(" // "); @@ -252,7 +255,10 @@ public enum CardRepository { QueryBuilder qb = cardDao.queryBuilder(); qb.distinct().selectColumns("name"); Where where = qb.where(); - where.and(where.not().like("types", '%' + CardType.CREATURE.name() + '%'), where.not().like("types", '%' + CardType.LAND.name() + '%')); + where.and( + where.not().like("types", '%' + CardType.CREATURE.name() + '%'), + where.not().like("types", '%' + CardType.LAND.name() + '%') + ); List results = cardDao.query(qb.prepare()); for (CardInfo card : results) { int result = card.getName().indexOf(" // "); @@ -275,7 +281,10 @@ public enum CardRepository { QueryBuilder qb = cardDao.queryBuilder(); qb.distinct().selectColumns("name"); Where where = qb.where(); - where.and(where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'), where.not().like("types", '%' + CardType.LAND.name() + '%')); + where.and( + where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'), + where.not().like("types", '%' + CardType.LAND.name() + '%') + ); List results = cardDao.query(qb.prepare()); for (CardInfo card : results) { int result = card.getName().indexOf(" // "); From 2f936c7c7896e126573b3d4abe68c8a008e1a452 Mon Sep 17 00:00:00 2001 From: Noah Gleason Date: Sun, 24 Jun 2018 21:55:15 -0400 Subject: [PATCH 74/74] Remove useless watcher --- .../src/mage/cards/g/GiantAlbatross.java | 13 ++-- .../watchers/common/DealtDamageToWatcher.java | 64 ------------------- 2 files changed, 5 insertions(+), 72 deletions(-) delete mode 100644 Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java diff --git a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java index b9ec60ce36a..d2c05f3e8cd 100644 --- a/Mage.Sets/src/mage/cards/g/GiantAlbatross.java +++ b/Mage.Sets/src/mage/cards/g/GiantAlbatross.java @@ -1,7 +1,5 @@ package mage.cards.g; -import java.util.List; -import java.util.UUID; import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; @@ -11,20 +9,19 @@ import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoIfCostPaid; -import mage.cards.Card; -import mage.constants.Outcome; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.watchers.Watcher; -import mage.watchers.common.DealtDamageToWatcher; -import mage.watchers.common.PlayerDamagedBySourceWatcher; + +import java.util.List; +import java.util.UUID; /** * diff --git a/Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java b/Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java deleted file mode 100644 index 3862841bb7f..00000000000 --- a/Mage/src/main/java/mage/watchers/common/DealtDamageToWatcher.java +++ /dev/null @@ -1,64 +0,0 @@ -package mage.watchers.common; - -import mage.MageObject; -import mage.MageObjectReference; -import mage.constants.WatcherScope; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.watchers.Watcher; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -public class DealtDamageToWatcher extends Watcher { - - public final Set dealtDamageToSource = new HashSet<>(); - - public DealtDamageToWatcher() { - super(DealtDamageToWatcher.class.getSimpleName(), WatcherScope.CARD); - } - - public DealtDamageToWatcher(final DealtDamageToWatcher watcher) { - super(watcher); - this.dealtDamageToSource.addAll(watcher.dealtDamageToSource); - } - - @Override - public DealtDamageToWatcher copy() { - return new DealtDamageToWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - boolean eventHasAppropriateType = event.getType() == GameEvent.EventType.DAMAGED_CREATURE || - event.getType() == GameEvent.EventType.DAMAGED_PLANESWALKER; - if (eventHasAppropriateType && sourceId.equals(event.getTargetId())) { - MageObjectReference mor = new MageObjectReference(event.getSourceId(), game); - dealtDamageToSource.add(mor); - } - } - - @Override - public void reset() { - super.reset(); - dealtDamageToSource.clear(); - } - - public boolean didDamage(UUID sourceId, Game game) { - MageObject mageObject = game.getObject(sourceId); - if (mageObject != null) { - return didDamage(new MageObjectReference(mageObject, game)); - } - return false; - } - - private boolean didDamage(MageObjectReference objectReference) { - return dealtDamageToSource.contains(objectReference); - } - - public boolean didDamage(Permanent permanent, Game game) { - return dealtDamageToSource.contains(new MageObjectReference(permanent, game)); - } -}