From 5cc3b4c4f20a22e24e7ff02f6abb7d0c2f238c71 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 17 Feb 2018 16:55:46 +0100 Subject: [PATCH 01/21] Implemented Power Leak --- Mage.Sets/src/mage/cards/p/PowerLeak.java | 133 ++++++++++++++++++ Mage.Sets/src/mage/sets/FourthEdition.java | 1 + .../src/mage/sets/LimitedEditionAlpha.java | 1 + .../src/mage/sets/LimitedEditionBeta.java | 1 + Mage.Sets/src/mage/sets/RevisedEdition.java | 1 + Mage.Sets/src/mage/sets/UnlimitedEdition.java | 1 + 6 files changed, 138 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PowerLeak.java diff --git a/Mage.Sets/src/mage/cards/p/PowerLeak.java b/Mage.Sets/src/mage/cards/p/PowerLeak.java new file mode 100644 index 0000000000..4e4d912088 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PowerLeak.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.p; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.PreventDamageByTargetEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetEnchantmentPermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class PowerLeak extends CardImpl { + + public PowerLeak(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{U}"); + this.subtype.add(SubType.AURA); + + // Enchant enchantment + TargetPermanent auraTarget = new TargetEnchantmentPermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment)); + this.addAbility(new EnchantAbility(auraTarget.getTargetName())); + + // At the beginning of the upkeep of enchanted enchantment's controller, that player may pay any amount of mana. Power Leak deals 2 damage to that player. Prevent X of that damage, where X is the amount of mana that player paid this way. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new PowerLeakEffect(), TargetController.CONTROLLER_ATTACHED_TO, false, true, "At the beginning of the upkeep of enchanted enchantment's controller, ")); + } + + public PowerLeak(final PowerLeak card) { + super(card); + } + + @Override + public PowerLeak copy() { + return new PowerLeak(this); + } +} + +class PowerLeakEffect extends OneShotEffect { + + public PowerLeakEffect() { + super(Outcome.Detriment); + this.staticText = "that player may pay any amount of mana. {this} deals 2 damage to that player. Prevent X of that damage, where X is the amount of mana that player paid this way"; + } + + public PowerLeakEffect(final PowerLeakEffect effect) { + super(effect); + } + + @Override + public PowerLeakEffect copy() { + return new PowerLeakEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getActivePlayerId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (player != null && permanent != null) { + ManaCosts cost = new ManaCostsImpl<>("{X}"); + String message = "Pay {X} to prevent X damage from " + permanent.getLogName() + "?"; + int xValue = 0; + if (player != null && player.chooseUse(Outcome.Neutral, message, source, game)) { + xValue = player.announceXMana(0, Integer.MAX_VALUE, "Choose the amount of mana to pay", game, source); + cost.add(new GenericManaCost(xValue)); + if (cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) { + game.informPlayers(player.getLogName() + " paid {" + xValue + "} for " + permanent.getLogName()); + } else { + game.informPlayers(player.getLogName() + " didn't pay {X} for " + permanent.getLogName()); + } + } else { + game.informPlayers(player.getLogName() + " didn't pay {X} for " + permanent.getLogName()); + } + + PreventDamageByTargetEffect effect = new PreventDamageByTargetEffect(Duration.OneUse, xValue, false); + if (xValue != 0 && cost.isPaid()) { + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + } + player.damage(2, source.getSourceId(), game, false, true); + effect.discard(); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/FourthEdition.java b/Mage.Sets/src/mage/sets/FourthEdition.java index 3b5f261657..17ca81da47 100644 --- a/Mage.Sets/src/mage/sets/FourthEdition.java +++ b/Mage.Sets/src/mage/sets/FourthEdition.java @@ -142,6 +142,7 @@ public class FourthEdition extends ExpansionSet { cards.add(new SetCardInfo("Phantasmal Terrain", 89, Rarity.COMMON, mage.cards.p.PhantasmalTerrain.class)); cards.add(new SetCardInfo("Phantom Monster", 90, Rarity.UNCOMMON, mage.cards.p.PhantomMonster.class)); cards.add(new SetCardInfo("Pirate Ship", 91, Rarity.RARE, mage.cards.p.PirateShip.class)); + cards.add(new SetCardInfo("Power Leak", 92, Rarity.COMMON, mage.cards.p.PowerLeak.class)); cards.add(new SetCardInfo("Power Sink", 93, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Prodigal Sorcerer", 94, Rarity.COMMON, mage.cards.p.ProdigalSorcerer.class)); cards.add(new SetCardInfo("Psionic Entity", 95, Rarity.RARE, mage.cards.p.PsionicEntity.class)); diff --git a/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java b/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java index f5c1f06606..4a87e3baac 100644 --- a/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java +++ b/Mage.Sets/src/mage/sets/LimitedEditionAlpha.java @@ -209,6 +209,7 @@ public class LimitedEditionAlpha extends ExpansionSet { cards.add(new SetCardInfo("Plains", 285, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 286, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plateau", 287, Rarity.RARE, mage.cards.p.Plateau.class)); + cards.add(new SetCardInfo("Power Leak", 72, Rarity.COMMON, mage.cards.p.PowerLeak.class)); cards.add(new SetCardInfo("Power Sink", 73, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Power Surge", 168, Rarity.RARE, mage.cards.p.PowerSurge.class)); cards.add(new SetCardInfo("Prodigal Sorcerer", 74, Rarity.COMMON, mage.cards.p.ProdigalSorcerer.class)); diff --git a/Mage.Sets/src/mage/sets/LimitedEditionBeta.java b/Mage.Sets/src/mage/sets/LimitedEditionBeta.java index ff30f36357..aed946ed8a 100644 --- a/Mage.Sets/src/mage/sets/LimitedEditionBeta.java +++ b/Mage.Sets/src/mage/sets/LimitedEditionBeta.java @@ -214,6 +214,7 @@ public class LimitedEditionBeta extends ExpansionSet { cards.add(new SetCardInfo("Plains", 289, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 290, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plateau", 280, Rarity.RARE, mage.cards.p.Plateau.class)); + cards.add(new SetCardInfo("Power Leak", 72, Rarity.COMMON, mage.cards.p.PowerLeak.class)); cards.add(new SetCardInfo("Power Sink", 73, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Power Surge", 168, Rarity.RARE, mage.cards.p.PowerSurge.class)); cards.add(new SetCardInfo("Prodigal Sorcerer", 74, Rarity.COMMON, mage.cards.p.ProdigalSorcerer.class)); diff --git a/Mage.Sets/src/mage/sets/RevisedEdition.java b/Mage.Sets/src/mage/sets/RevisedEdition.java index a798e9d1fc..5a07e32a60 100644 --- a/Mage.Sets/src/mage/sets/RevisedEdition.java +++ b/Mage.Sets/src/mage/sets/RevisedEdition.java @@ -220,6 +220,7 @@ public class RevisedEdition extends ExpansionSet { cards.add(new SetCardInfo("Plains", 294, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 295, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plateau", 296, Rarity.RARE, mage.cards.p.Plateau.class)); + cards.add(new SetCardInfo("Power Leak", 73, Rarity.COMMON, mage.cards.p.PowerLeak.class)); cards.add(new SetCardInfo("Power Sink", 74, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Power Surge", 169, Rarity.RARE, mage.cards.p.PowerSurge.class)); cards.add(new SetCardInfo("Primal Clay", 271, Rarity.RARE, mage.cards.p.PrimalClay.class)); diff --git a/Mage.Sets/src/mage/sets/UnlimitedEdition.java b/Mage.Sets/src/mage/sets/UnlimitedEdition.java index e12d2c0d43..c1213ef331 100644 --- a/Mage.Sets/src/mage/sets/UnlimitedEdition.java +++ b/Mage.Sets/src/mage/sets/UnlimitedEdition.java @@ -214,6 +214,7 @@ public class UnlimitedEdition extends ExpansionSet { cards.add(new SetCardInfo("Plains", 290, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 291, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plateau", 292, Rarity.RARE, mage.cards.p.Plateau.class)); + cards.add(new SetCardInfo("Power Leak", 72, Rarity.COMMON, mage.cards.p.PowerLeak.class)); cards.add(new SetCardInfo("Power Sink", 73, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Power Surge", 168, Rarity.RARE, mage.cards.p.PowerSurge.class)); cards.add(new SetCardInfo("Prodigal Sorcerer", 74, Rarity.COMMON, mage.cards.p.ProdigalSorcerer.class)); From 005fcd1040ba2b0cc7d7bad4f354397a5e90eb0a Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 17 Feb 2018 19:00:35 +0100 Subject: [PATCH 02/21] Implemented Reverse Polarity --- .../src/mage/cards/r/ReversePolarity.java | 135 ++++++++++++++++++ Mage.Sets/src/mage/sets/Antiquities.java | 1 + Mage.Sets/src/mage/sets/RevisedEdition.java | 1 + 3 files changed, 137 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/ReversePolarity.java diff --git a/Mage.Sets/src/mage/cards/r/ReversePolarity.java b/Mage.Sets/src/mage/cards/r/ReversePolarity.java new file mode 100644 index 0000000000..8e6766f700 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReversePolarity.java @@ -0,0 +1,135 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.r; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +/** + * + * @author jeffwadsworth, MTGFan & L_J + */ +public class ReversePolarity extends CardImpl { + + public ReversePolarity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}{W}"); + + // You gain X life, where X is twice the damage dealt to you so far this turn by artifacts. + this.getSpellAbility().addEffect(new GainLifeEffect(new ReversePolarityAmount(), "You gain X life, where X is twice the damage dealt to you so far this turn by artifacts")); + this.getSpellAbility().addWatcher(new ReversePolarityWatcher()); + } + + public ReversePolarity(final ReversePolarity card) { + super(card); + } + + @Override + public ReversePolarity copy() { + return new ReversePolarity(this); + } +} + +class ReversePolarityAmount implements DynamicValue { + + @Override + public int calculate(Game game, Ability source, Effect effect) { + ReversePolarityWatcher watcher = (ReversePolarityWatcher) game.getState().getWatchers().get(ReversePolarityWatcher.class.getSimpleName()); + if(watcher != null) { + return watcher.getArtifactDamageReceivedThisTurn(source.getControllerId()) * 2; + } + return 0; + } + + @Override + public ReversePolarityAmount copy() { + return new ReversePolarityAmount(); + } + + @Override + public String getMessage() { + return ""; + } +} + +class ReversePolarityWatcher extends Watcher { + + private final Map artifactDamageReceivedThisTurn = new HashMap<>(); + + public ReversePolarityWatcher() { + super(ReversePolarityWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public ReversePolarityWatcher(final ReversePolarityWatcher watcher) { + super(watcher); + for (Entry entry : watcher.artifactDamageReceivedThisTurn.entrySet()) { + artifactDamageReceivedThisTurn.put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { + UUID playerId = event.getTargetId(); + if (playerId != null) { + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null && permanent.isArtifact()) { + artifactDamageReceivedThisTurn.putIfAbsent(playerId, 0); + artifactDamageReceivedThisTurn.compute(playerId, (k, v) -> v + event.getAmount()); + } + } + } + } + + public int getArtifactDamageReceivedThisTurn(UUID playerId) { + return artifactDamageReceivedThisTurn.getOrDefault(playerId, 0); + } + + @Override + public void reset() { + artifactDamageReceivedThisTurn.clear(); + } + + @Override + public ReversePolarityWatcher copy() { + return new ReversePolarityWatcher(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Antiquities.java b/Mage.Sets/src/mage/sets/Antiquities.java index e64f3108ea..80b2acc03f 100644 --- a/Mage.Sets/src/mage/sets/Antiquities.java +++ b/Mage.Sets/src/mage/sets/Antiquities.java @@ -111,6 +111,7 @@ public class Antiquities extends ExpansionSet { cards.add(new SetCardInfo("Primal Clay", 26, Rarity.UNCOMMON, mage.cards.p.PrimalClay.class)); cards.add(new SetCardInfo("Rakalite", 27, Rarity.UNCOMMON, mage.cards.r.Rakalite.class)); cards.add(new SetCardInfo("Reconstruction", 56, Rarity.COMMON, mage.cards.r.Reconstruction.class)); + cards.add(new SetCardInfo("Reverse Polarity", 100, Rarity.COMMON, mage.cards.r.ReversePolarity.class)); cards.add(new SetCardInfo("Rocket Launcher", 28, Rarity.UNCOMMON, mage.cards.r.RocketLauncher.class)); cards.add(new SetCardInfo("Sage of Lat-Nam", 57, Rarity.COMMON, mage.cards.s.SageOfLatNam.class)); cards.add(new SetCardInfo("Shapeshifter", 29, Rarity.RARE, mage.cards.s.Shapeshifter.class)); diff --git a/Mage.Sets/src/mage/sets/RevisedEdition.java b/Mage.Sets/src/mage/sets/RevisedEdition.java index 5a07e32a60..74924028ac 100644 --- a/Mage.Sets/src/mage/sets/RevisedEdition.java +++ b/Mage.Sets/src/mage/sets/RevisedEdition.java @@ -235,6 +235,7 @@ public class RevisedEdition extends ExpansionSet { cards.add(new SetCardInfo("Regrowth", 121, Rarity.UNCOMMON, mage.cards.r.Regrowth.class)); cards.add(new SetCardInfo("Resurrection", 218, Rarity.UNCOMMON, mage.cards.r.Resurrection.class)); cards.add(new SetCardInfo("Reverse Damage", 219, Rarity.RARE, mage.cards.r.ReverseDamage.class)); + cards.add(new SetCardInfo("Reverse Polarity", 220, Rarity.UNCOMMON, mage.cards.r.ReversePolarity.class)); cards.add(new SetCardInfo("Righteousness", 221, Rarity.RARE, mage.cards.r.Righteousness.class)); cards.add(new SetCardInfo("Roc of Kher Ridges", 171, Rarity.RARE, mage.cards.r.RocOfKherRidges.class)); cards.add(new SetCardInfo("Rock Hydra", 172, Rarity.RARE, mage.cards.r.RockHydra.class)); From 3a800dea8e77c37c9665906d5f24fb760559c785 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 17 Feb 2018 18:41:23 +0000 Subject: [PATCH 03/21] Added missing Goblin Rock Sled --- Mage.Sets/src/mage/sets/FourthEdition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/FourthEdition.java b/Mage.Sets/src/mage/sets/FourthEdition.java index 17ca81da47..0b5d5d5f42 100644 --- a/Mage.Sets/src/mage/sets/FourthEdition.java +++ b/Mage.Sets/src/mage/sets/FourthEdition.java @@ -264,6 +264,7 @@ public class FourthEdition extends ExpansionSet { cards.add(new SetCardInfo("Giant Strength", 196, Rarity.COMMON, mage.cards.g.GiantStrength.class)); cards.add(new SetCardInfo("Goblin Balloon Brigade", 197, Rarity.UNCOMMON, mage.cards.g.GoblinBalloonBrigade.class)); cards.add(new SetCardInfo("Goblin King", 198, Rarity.RARE, mage.cards.g.GoblinKing.class)); + cards.add(new SetCardInfo("Goblin Rock Sled", 199, Rarity.COMMON, mage.cards.g.GoblinRockSled.class)); cards.add(new SetCardInfo("Gray Ogre", 200, Rarity.COMMON, mage.cards.g.GrayOgre.class)); cards.add(new SetCardInfo("Hill Giant", 201, Rarity.COMMON, mage.cards.h.HillGiant.class)); cards.add(new SetCardInfo("Hurloon Minotaur", 202, Rarity.COMMON, mage.cards.h.HurloonMinotaur.class)); From 17800b6df158a9e0b0b8e585ad47954770987b60 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 17 Feb 2018 22:25:27 +0000 Subject: [PATCH 04/21] Menace fix --- .../java/mage/game/combat/CombatGroup.java | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 57b9877d49..350dfd4e6f 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -302,7 +302,7 @@ public class CombatGroup implements Serializable, Copyable { Map assigned = new HashMap<>(); if (blocked) { boolean excessDamageToDefender = true; - for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException + for (UUID blockerId : blockerOrder) { Permanent blocker = game.getPermanent(blockerId); if (blocker != null) { int lethalDamage; @@ -732,42 +732,35 @@ public class CombatGroup implements Serializable, Copyable { for (UUID uuid : attackers) { Permanent attacker = game.getPermanent(uuid); - // Check if there are enough blockers to have a legal block - if (attacker != null && this.blocked && attacker.getMinBlockedBy() > 1 && !blockers.isEmpty() && blockers.size() < attacker.getMinBlockedBy()) { - for (UUID blockerId : blockers) { - Permanent blocker = game.getPermanent(blockerId); - if (blocker != null) { - blocker.setBlocking(blocker.getBlocking() - 1); + if (attacker != null && this.blocked) { + // Check if there are enough blockers to have a legal block + if (attacker.getMinBlockedBy() > 1 && !blockers.isEmpty() && blockers.size() < attacker.getMinBlockedBy()) { + for (UUID blockerId : new ArrayList<>(blockers)) { + game.getCombat().removeBlocker(blockerId, game); } - } - blockers.clear(); - blockerOrder.clear(); - this.blocked = false; - if (!game.isSimulation()) { - game.informPlayers(attacker.getLogName() + " can't be blocked except by " + attacker.getMinBlockedBy() + " or more creatures. Blockers discarded."); - } - blockWasLegal = false; - } - // Check if there are too many blockers (maxBlockedBy = 0 means no restrictions) - if (attacker != null && this.blocked && attacker.getMaxBlockedBy() > 0 && attacker.getMaxBlockedBy() < blockers.size()) { - for (UUID blockerId : blockers) { - Permanent blocker = game.getPermanent(blockerId); - if (blocker != null) { - blocker.setBlocking(blocker.getBlocking() - 1); + blockers.clear(); + blockerOrder.clear(); + if (!game.isSimulation()) { + game.informPlayers(attacker.getLogName() + " can't be blocked except by " + attacker.getMinBlockedBy() + " or more creatures. Blockers discarded."); } + blockWasLegal = false; } - blockers.clear(); - blockerOrder.clear(); - this.blocked = false; - if (!game.isSimulation()) { - game.informPlayers(new StringBuilder(attacker.getLogName()) - .append(" can't be blocked by more than ").append(attacker.getMaxBlockedBy()) - .append(attacker.getMaxBlockedBy() == 1 ? " creature." : " creatures.") - .append(" Blockers discarded.").toString()); + // Check if there are too many blockers (maxBlockedBy = 0 means no restrictions) + if (attacker.getMaxBlockedBy() > 0 && attacker.getMaxBlockedBy() < blockers.size()) { + for (UUID blockerId : new ArrayList<>(blockers)) { + game.getCombat().removeBlocker(blockerId, game); + } + blockers.clear(); + blockerOrder.clear(); + if (!game.isSimulation()) { + game.informPlayers(new StringBuilder(attacker.getLogName()) + .append(" can't be blocked by more than ").append(attacker.getMaxBlockedBy()) + .append(attacker.getMaxBlockedBy() == 1 ? " creature." : " creatures.") + .append(" Blockers discarded.").toString()); + } + blockWasLegal = false; } - blockWasLegal = false; } - } return blockWasLegal; } From 1f5e2f1bede7996e3abfdf4c7d940ba0899aae02 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 17 Feb 2018 22:27:07 +0000 Subject: [PATCH 05/21] Minor revert --- Mage/src/main/java/mage/game/combat/CombatGroup.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 350dfd4e6f..c66dcd0c06 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -302,7 +302,7 @@ public class CombatGroup implements Serializable, Copyable { Map assigned = new HashMap<>(); if (blocked) { boolean excessDamageToDefender = true; - for (UUID blockerId : blockerOrder) { + for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException Permanent blocker = game.getPermanent(blockerId); if (blocker != null) { int lethalDamage; From 2e827a50ecfabac0524f719feaab13e9483a4a18 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 17 Feb 2018 23:13:19 +0000 Subject: [PATCH 06/21] Hellcarver Demon fix Was able to cast previously exiled cards --- .../src/mage/cards/h/HellcarverDemon.java | 118 +++++++----------- 1 file changed, 43 insertions(+), 75 deletions(-) diff --git a/Mage.Sets/src/mage/cards/h/HellcarverDemon.java b/Mage.Sets/src/mage/cards/h/HellcarverDemon.java index 1032ae1f69..af8462e4b7 100644 --- a/Mage.Sets/src/mage/cards/h/HellcarverDemon.java +++ b/Mage.Sets/src/mage/cards/h/HellcarverDemon.java @@ -27,35 +27,34 @@ */ package mage.cards.h; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.Card; import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.CardsImpl; import mage.cards.CardSetInfo; -import mage.constants.AsThoughEffectType; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; +import mage.filter.FilterCard; import mage.filter.common.FilterControlledPermanent; -import mage.filter.common.FilterNonlandCard; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCardInExile; -import mage.target.targetpointer.FixedTarget; +import mage.target.TargetCard; /** * - * @author jeffwadsworth + * @author jeffwadsworth & L_J */ public class HellcarverDemon extends CardImpl { @@ -84,9 +83,6 @@ public class HellcarverDemon extends CardImpl { class HellcarverDemonEffect extends OneShotEffect { - private static final FilterControlledPermanent filterPermanents = new FilterControlledPermanent("Permanent"); - private static FilterNonlandCard filter = new FilterNonlandCard("nonland card exiled with Hellcarver Demon"); - public HellcarverDemonEffect() { super(Outcome.PlayForFree); staticText = "sacrifice all other permanents you control and discard your hand. Exile the top six cards of your library. You may cast any number of nonland cards exiled this way without paying their mana costs."; @@ -99,41 +95,43 @@ class HellcarverDemonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent hellcarverDemon = game.getPermanent(source.getSourceId()); - - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanents, source.getControllerId(), game)) { - if (!Objects.equals(permanent, hellcarverDemon)) { - permanent.sacrifice(source.getSourceId(), game); - } - } - - if (controller != null && !controller.getHand().isEmpty()) { - int cardsInHand = controller.getHand().size(); - controller.discard(cardsInHand, false, source, game); - } - - for (int i = 0; i < 6; i++) { - if (controller != null - && controller.getLibrary().hasCards()) { - Card topCard = controller.getLibrary().getFromTop(game); - topCard.moveToExile(source.getSourceId(), "Cards exiled by Hellcarver Demon", source.getSourceId(), game); - } - } - - while (controller != null - && controller.canRespond() - && controller.chooseUse(Outcome.PlayForFree, controller.getLogName() + " can cast another nonland card exiled with Hellcarver Demon without paying that card's mana cost.", source, game)) { - TargetCardInExile target = new TargetCardInExile(filter, source.getSourceId()); - while (controller.chooseUse(Outcome.PlayForFree, "Cast another spell exiled by Hellcarver Demon?", source, game)) { - controller.choose(Outcome.PlayForFree, game.getExile().getExileZone(source.getSourceId()), target, game); - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - ContinuousEffect effect = new HellcarverDemonCastFromExileEffect(); - effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); - controller.cast(card.getSpellAbility(), game, true); + Permanent sourceObject = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (controller != null && sourceObject != null) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) { + if (!Objects.equals(permanent, sourceObject)) { + permanent.sacrifice(source.getSourceId(), game); + } + } + if (!controller.getHand().isEmpty()) { + int cardsInHand = controller.getHand().size(); + controller.discard(cardsInHand, false, source, game); + } + // move cards from library to exile + Set currentExiledCards = new HashSet<>(); + currentExiledCards.addAll(controller.getLibrary().getTopCards(game, 6)); + controller.moveCardsToExile(currentExiledCards, source, game, true, source.getSourceId(), sourceObject.getIdName()); + + // cast the possible cards without paying the mana + Cards cardsToCast = new CardsImpl(); + cardsToCast.addAll(currentExiledCards); + boolean alreadyCast = false; + while (!cardsToCast.isEmpty() + && controller.canRespond()) { + if (!controller.chooseUse(outcome, "Cast a" + (alreadyCast ? "nother" : "" ) + " card exiled with " + sourceObject.getLogName() + " without paying its mana cost?", source, game)) { + break; + } + TargetCard targetCard = new TargetCard(1, Zone.EXILED, new FilterCard("nonland card to cast for free")); + if (controller.choose(Outcome.PlayForFree, cardsToCast, targetCard, game)) { + alreadyCast = true; + Card card = game.getCard(targetCard.getFirstTarget()); + if (card != null) { + if (controller.cast(card.getSpellAbility(), game, true)) { + cardsToCast.remove(card); + } else { + game.informPlayer(controller, "You're not able to cast " + card.getIdName() + " or you canceled the casting."); + } + } } - target.clearChosen(); } return true; } @@ -145,33 +143,3 @@ class HellcarverDemonEffect extends OneShotEffect { return new HellcarverDemonEffect(this); } } - -class HellcarverDemonCastFromExileEffect extends AsThoughEffectImpl { - - public HellcarverDemonCastFromExileEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); - staticText = "You may play the card from exile without paying its mana cost"; - } - - public HellcarverDemonCastFromExileEffect(final HellcarverDemonCastFromExileEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public HellcarverDemonCastFromExileEffect copy() { - return new HellcarverDemonCastFromExileEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (targetPointer.getTargets(game, source).contains(sourceId)) { - return game.getState().getZone(sourceId) == Zone.EXILED; - } - return false; - } -} From d80d58896379dc6eea8cd4c7f83a3aa5854cec1f Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 18 Feb 2018 02:52:16 +0100 Subject: [PATCH 07/21] * Reworked flashback ability (fixes #4482 #3324 #215 #3435 #3883 #3337 #2183 #2447). --- Mage.Sets/src/mage/cards/c/Conflagrate.java | 5 +- .../abilities/keywords/FlashbackTest.java | 36 ++++- .../main/java/mage/abilities/AbilityImpl.java | 14 +- .../java/mage/abilities/SpellAbility.java | 9 +- .../abilities/costs/VariableCostImpl.java | 2 - .../abilities/keyword/FlashbackAbility.java | 150 ++++++------------ Mage/src/main/java/mage/cards/CardImpl.java | 2 +- .../mage/constants/SpellAbilityCastMode.java | 3 +- .../main/java/mage/players/PlayerImpl.java | 8 +- 9 files changed, 107 insertions(+), 122 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/Conflagrate.java b/Mage.Sets/src/mage/cards/c/Conflagrate.java index 11e5cf6b27..7028972735 100644 --- a/Mage.Sets/src/mage/cards/c/Conflagrate.java +++ b/Mage.Sets/src/mage/cards/c/Conflagrate.java @@ -30,7 +30,6 @@ package mage.cards.c; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.costs.Cost; -import mage.abilities.costs.common.DiscardTargetCost; import mage.abilities.costs.common.DiscardXTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.DynamicValue; @@ -82,8 +81,8 @@ class ConflagrateVariableValue implements DynamicValue { public int calculate(Game game, Ability sourceAbility, Effect effect) { int xValue = sourceAbility.getManaCostsToPay().getX(); for (Cost cost : sourceAbility.getCosts()) { - if (cost instanceof DiscardTargetCost) { - xValue = ((DiscardTargetCost) cost).getCards().size(); + if (cost instanceof DiscardXTargetCost) { + xValue = ((DiscardXTargetCost) cost).getAmount(); } } return xValue; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java index 30f0d24fb9..02bf64326b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java @@ -27,9 +27,9 @@ */ package org.mage.test.cards.abilities.keywords; +import mage.abilities.keyword.TrampleAbility; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -39,6 +39,27 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class FlashbackTest extends CardTestPlayerBase { + @Test + public void testNormalWildHunger() { + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + // Target creature gets +3/+1 and gains trample until end of turn. + // Flashback {3}{R} + addCard(Zone.GRAVEYARD, playerA, "Wild Hunger"); + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 5, 3); + assertAbility(playerA, "Silvercoat Lion", TrampleAbility.getInstance(), true); + assertExileCount("Wild Hunger", 1); + } + /** * Fracturing Gust is bugged. In a match against Affinity, it worked * properly when cast from hand. When I cast it from graveyard c/o @@ -219,7 +240,7 @@ public class FlashbackTest extends CardTestPlayerBase { // Conflagrate deals X damage divided as you choose among any number of target creatures and/or players. // Flashback-{R}{R}, Discard X cards. - addCard(Zone.HAND, playerA, "Conflagrate", 1); + addCard(Zone.HAND, playerA, "Conflagrate", 1); // Sorcery {X}{X}{R} addCard(Zone.HAND, playerA, "Forest", 4); @@ -307,20 +328,26 @@ public class FlashbackTest extends CardTestPlayerBase { public void testAltarsReap() { addCard(Zone.LIBRARY, playerA, "Island", 2); - addCard(Zone.GRAVEYARD, playerA, "Altar's Reap", 1); + // As an additional cost to cast Altar's Reap, sacrifice a creature. + // Draw two cards. + addCard(Zone.GRAVEYARD, playerA, "Altar's Reap", 1); // Instant {1}{B} addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 4); addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); + // Flash + // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. + // The flashback cost is equal to its mana cost. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); setChoice(playerA, "Altar's Reap"); - activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback {1}{B}"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback"); setChoice(playerA, "Snapcaster Mage"); setStopAt(1, PhaseStep.END_TURN); execute(); assertGraveyardCount(playerA, "Snapcaster Mage", 1); + assertExileCount(playerA, "Altar's Reap", 1); } /** @@ -520,7 +547,6 @@ public class FlashbackTest extends CardTestPlayerBase { * to a spell, and the flashback cost is already an alternative cost. */ @Test - @Ignore public void testSnapcasterMageSpellWithAlternateCost() { addCard(Zone.BATTLEFIELD, playerA, "Island", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 8caf2db48c..23f8176212 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -44,7 +44,6 @@ import mage.abilities.effects.Effects; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DynamicManaEffect; import mage.abilities.effects.common.ManaEffect; -import mage.abilities.keyword.FlashbackAbility; import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.cards.Card; import mage.cards.SplitCard; @@ -337,9 +336,7 @@ public abstract class AbilityImpl implements Ability { if (sourceObject != null && this.getAbilityType() != AbilityType.TRIGGERED) { // triggered abilities check this already in playerImpl.triggerAbility sourceObject.adjustTargets(this, game); } - // Flashback abilities haven't made the choices the underlying spell might need for targeting. - if (!(this instanceof FlashbackAbility) - && !getTargets().isEmpty()) { + if (!getTargets().isEmpty()) { Outcome outcome = getEffects().isEmpty() ? Outcome.Detriment : getEffects().get(0).getOutcome(); if (getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game) == false) { if ((variableManaCost != null || announceString != null)) { @@ -445,8 +442,15 @@ public abstract class AbilityImpl implements Ability { @Override public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) { + if (this instanceof SpellAbility) { + if (((SpellAbility) this).getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) { + // A player can't apply two alternative methods of casting or two alternative costs to a single spell. + // So can only use alternate costs if the spell is cast in normal mode + return false; + } + } boolean alternativeCostisUsed = false; - if (sourceObject != null && !(sourceObject instanceof Permanent) && !(this instanceof FlashbackAbility)) { + if (sourceObject != null && !(sourceObject instanceof Permanent)) { Abilities abilities = null; if (sourceObject instanceof Card) { abilities = ((Card) sourceObject).getAbilities(game); diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 2cd291a957..289fa05592 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -217,7 +217,7 @@ public class SpellAbility extends ActivatedAbilityImpl { this.name = "Cast fused " + cardName; break; default: - this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " by " + spellAbilityCastMode.toString() : ""); + this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " using " + spellAbilityCastMode.toString() : ""); } } @@ -230,4 +230,11 @@ public class SpellAbility extends ActivatedAbilityImpl { setSpellName(); } + public SpellAbility getSpellAbilityToResolve(Game game) { + return this; + } + + public void setId(UUID idToUse) { + this.id = idToUse; + } } diff --git a/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java b/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java index aefaf6f526..6b96444bd2 100644 --- a/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/VariableCostImpl.java @@ -29,7 +29,6 @@ package mage.abilities.costs; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.keyword.FlashbackAbility; import mage.abilities.mana.ManaAbility; import mage.game.Game; import mage.game.stack.StackObject; @@ -173,7 +172,6 @@ public abstract class VariableCostImpl implements Cost, VariableCost { StackObject stackObject = game.getStack().getStackObject(source.getId()); if (controller != null && (source instanceof ManaAbility - || source instanceof FlashbackAbility || stackObject != null)) { xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game), "Announce the number of " + actionText, game, source, this); diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index c05f20118f..aa457aea70 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -32,14 +32,13 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; -import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.Card; import mage.cards.SplitCard; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SpellAbilityCastMode; import mage.constants.SpellAbilityType; import mage.constants.TimingRule; import mage.constants.Zone; @@ -66,23 +65,21 @@ import mage.target.targetpointer.FixedTarget; public class FlashbackAbility extends SpellAbility { private String abilityName; + private SpellAbility spellAbilityToResolve; public FlashbackAbility(Cost cost, TimingRule timingRule) { - super(null, "", Zone.GRAVEYARD); + super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.FLASHBACK); this.setAdditionalCostsRuleVisible(false); this.name = "Flashback " + cost.getText(); - this.addEffect(new FlashbackEffect()); this.addCost(cost); this.timing = timingRule; - this.usesStack = false; - this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE; - setCostModificationActive(false); } public FlashbackAbility(final FlashbackAbility ability) { super(ability); this.spellAbilityType = ability.spellAbilityType; this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; } @Override @@ -108,6 +105,47 @@ public class FlashbackAbility extends SpellAbility { return false; } + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card != null) { + if (spellAbilityToResolve == null) { + SpellAbility spellAbilityCopy = null; + if (card.isSplitCard()) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.getManaCosts().clear(); + spellAbilityCopy.getManaCostsToPay().clear(); + spellAbilityCopy.getCosts().addAll(this.getCosts()); + spellAbilityCopy.addCost(this.getManaCosts()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + ContinuousEffect effect = new FlashbackReplacementEffect(); + effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); + game.addEffect(effect, this); + } + } + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + @Override public FlashbackAbility copy() { return new FlashbackAbility(this); @@ -144,102 +182,18 @@ public class FlashbackAbility extends SpellAbility { return sbRule.toString(); } - @Override - public void setSpellAbilityType(SpellAbilityType spellAbilityType) { - this.spellAbilityType = spellAbilityType; - } - - @Override - public SpellAbilityType getSpellAbilityType() { - return this.spellAbilityType; - } - + /** + * Used for split card sin PlayerImpl method: + * getOtherUseableActivatedAbilities + * + * @param abilityName + */ public void setAbilityName(String abilityName) { this.abilityName = abilityName; } } -class FlashbackEffect extends OneShotEffect { - - public FlashbackEffect() { - super(Outcome.Benefit); - staticText = ""; - } - - public FlashbackEffect(final FlashbackEffect effect) { - super(effect); - } - - @Override - public FlashbackEffect copy() { - return new FlashbackEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Card card = (Card) game.getObject(source.getSourceId()); - if (card != null) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - SpellAbility spellAbility; - switch (((FlashbackAbility) source).getSpellAbilityType()) { - case SPLIT_LEFT: - spellAbility = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); - break; - case SPLIT_RIGHT: - spellAbility = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); - break; - default: - spellAbility = card.getSpellAbility().copy(); - } - - spellAbility.clear(); - // set the payed flashback costs to the spell ability so abilities like Converge or calculation of {X} values work - spellAbility.getManaCostsToPay().clear(); - spellAbility.getManaCostsToPay().addAll(source.getManaCosts()); - spellAbility.getManaCosts().clear(); - spellAbility.getManaCosts().addAll(source.getManaCosts()); - // needed to get e.g. paid costs from Conflagrate - - for (Cost cost : source.getCosts()) { - if (cost instanceof Costs) { - Costs listOfCosts = (Costs) cost; - for (Cost singleCost : listOfCosts) { - if (singleCost instanceof ManaCost) { - singleCost.clearPaid(); - spellAbility.getManaCosts().add((ManaCost) singleCost); - spellAbility.getManaCostsToPay().add((ManaCost) singleCost); - } else { - spellAbility.getCosts().add(singleCost); - } - } - - } else { - if (cost instanceof ManaCost) { - spellAbility.getManaCosts().add((ManaCost) cost); - spellAbility.getManaCostsToPay().add((ManaCost) cost); - } else { - spellAbility.getCosts().add(cost); - } - } - } - if (!game.isSimulation()) { - game.informPlayers(controller.getLogName() + " flashbacks " + card.getLogName()); - } - if (controller.cast(spellAbility, game, false)) { - ContinuousEffect effect = new FlashbackReplacementEffect(); - effect.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()))); - game.addEffect(effect, source); - } - return true; - } - } - return false; - } - -} - class FlashbackReplacementEffect extends ReplacementEffectImpl { public FlashbackReplacementEffect() { @@ -287,7 +241,7 @@ class FlashbackReplacementEffect extends ReplacementEffectImpl { && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { int zcc = game.getState().getZoneChangeCounter(source.getSourceId()); - if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() == zcc) { + if (((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc) { return true; } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index f82868e628..4fc6a220e9 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -516,7 +516,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { Card mainCard = getMainCard(); ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); ZoneChangeInfo.Stack info - = new ZoneChangeInfo.Stack(event, new Spell(this, ability.copy(), controllerId, event.getFromZone())); + = new ZoneChangeInfo.Stack(event, new Spell(this, ability.getSpellAbilityToResolve(game), controllerId, event.getFromZone())); return ZonesHandler.cast(info, game); } diff --git a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java index 663c8c8f1b..71aecf37ed 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityCastMode.java @@ -33,7 +33,8 @@ package mage.constants; */ public enum SpellAbilityCastMode { NORMAL("Normal"), - MADNESS("Madness"); + MADNESS("Madness"), + FLASHBACK("Flashback"); private final String text; diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index ceb43d787f..a61e8023f3 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1158,7 +1158,7 @@ public abstract class PlayerImpl implements Player, Serializable { } } else { int bookmark = game.bookmarkState(); - if (ability.activate(game, ability instanceof FlashbackAbility)) { + if (ability.activate(game, false)) { ability.resolve(game); game.removeBookmark(bookmark); resetStoredBookmark(game); @@ -1219,11 +1219,7 @@ public abstract class PlayerImpl implements Player, Serializable { result = playManaAbility((ActivatedManaAbilityImpl) ability.copy(), game); break; case SPELL: - if (ability instanceof FlashbackAbility) { - result = playAbility(ability.copy(), game); - } else { - result = cast((SpellAbility) ability, game, false); - } + result = cast((SpellAbility) ability.copy(), game, false); break; default: result = playAbility(ability.copy(), game); From 71ed488c1e06d9739f37b52142bfd9ff719c3ef0 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 18 Feb 2018 18:31:10 +0100 Subject: [PATCH 08/21] * Some deck format tests changed/added. --- .../mage/sets/MasterpieceSeriesAmonkhet.java | 3 - .../serverside/deck/DeckValidatorTest.java | 179 ++++++++++++++++-- 2 files changed, 165 insertions(+), 17 deletions(-) diff --git a/Mage.Sets/src/mage/sets/MasterpieceSeriesAmonkhet.java b/Mage.Sets/src/mage/sets/MasterpieceSeriesAmonkhet.java index 291c6782fe..d526453921 100644 --- a/Mage.Sets/src/mage/sets/MasterpieceSeriesAmonkhet.java +++ b/Mage.Sets/src/mage/sets/MasterpieceSeriesAmonkhet.java @@ -27,9 +27,7 @@ */ package mage.sets; -import mage.cards.CardGraphicInfo; import mage.cards.ExpansionSet; -import mage.cards.FrameStyle; import mage.constants.Rarity; import mage.constants.SetType; @@ -50,7 +48,6 @@ public class MasterpieceSeriesAmonkhet extends ExpansionSet { this.blockName = "Masterpiece Series"; this.hasBoosters = false; this.hasBasicLands = false; - CardGraphicInfo cardGraphicInfo = new CardGraphicInfo(FrameStyle.KLD_INVENTION, false); cards.add(new SetCardInfo("Aggravated Assault", 25, Rarity.SPECIAL, mage.cards.a.AggravatedAssault.class)); cards.add(new SetCardInfo("Armageddon", 31, Rarity.SPECIAL, mage.cards.a.Armageddon.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java index f7bf2fe462..38d002d524 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java @@ -27,24 +27,24 @@ */ package org.mage.test.serverside.deck; +import java.util.ArrayList; +import java.util.List; import mage.cards.decks.Deck; import mage.cards.decks.DeckValidator; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.deck.Limited; import mage.deck.Modern; +import mage.deck.Standard; import org.junit.Assert; import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -import java.util.ArrayList; -import java.util.List; +import org.mage.test.serverside.base.MageTestBase; /** * * @author LevelX2 */ -public class DeckValidatorTest extends CardTestPlayerBase { +public class DeckValidatorTest extends MageTestBase { static class CardNameAmount { @@ -84,6 +84,38 @@ public class DeckValidatorTest extends CardTestPlayerBase { } + @Test + public void testStandardValid() { + ArrayList deck = new ArrayList<>(); + + deck.add(new CardNameAmount("MPS-AKH", 28, 4)); // Rhonas the Indomitable + deck.add(new CardNameAmount("Built to Smash", 4)); + deck.add(new CardNameAmount("Heroic Intervention", 4)); + deck.add(new CardNameAmount("Mountain", 48)); + + DeckValidator validator = new Standard(); + boolean validationSuccessful = testDeckValid(validator, deck); + Assert.assertTrue(validator.getInvalid().toString(), validationSuccessful); + } + + @Test + public void testStandardNotValid() { + ArrayList deck = new ArrayList<>(); + + deck.add(new CardNameAmount("MPS-AKH", 28, 4)); // Rhonas the Indomitable + deck.add(new CardNameAmount("Built to Smash", 4)); + deck.add(new CardNameAmount("Heroic Intervention", 4)); + deck.add(new CardNameAmount("Mountain", 47)); + + ArrayList sideboard = new ArrayList<>(); + sideboard.add(new CardNameAmount("Mountain", 16)); + + DeckValidator validator = new Standard(); + testDeckValid(validator, deck, sideboard); + Assert.assertEquals("invalid message not correct", + "{Sideboard=Must contain no more than 15 cards : has 16 cards, Deck=Must contain at least 60 cards: has only 59 cards}", validator.getInvalid().toString()); + } + @Test public void testLimitedValid() { ArrayList deck = new ArrayList<>(); @@ -226,37 +258,156 @@ public class DeckValidatorTest extends CardTestPlayerBase { @Test public void testModernBanned() { ArrayList deckList = new ArrayList<>(); + DeckValidator validator = new Modern(); + deckList.add(new CardNameAmount("Ancestral Vision", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + boolean validationSuccessful = testDeckValid(validator, deckList); + Assert.assertTrue(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Ancient Den", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + deckList.add(new CardNameAmount("Birthing Pod", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Blazing Shoal", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Bloodbraid Elf", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertTrue(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Chrome Mox", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Cloudpost", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Dark Depths", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Deathrite Shaman", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Dig Through Time", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Dread Return", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Glimpse of Nature", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Great Furnace", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Green Sun's Zenith", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Hypergenesis", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Jace, the Mind Sculptor", 4)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertTrue(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); + + deckList.clear(); deckList.add(new CardNameAmount("Mental Misstep", 4)); - Assert.assertFalse("banned cards are not allowed", testDeckValid(new Modern(), deckList)); + deckList.add(new CardNameAmount("Mountain", 56)); + validationSuccessful = testDeckValid(validator, deckList); + Assert.assertFalse(validator.getInvalid().toString(), validationSuccessful); + validator.getInvalid().clear(); } private boolean testDeckValid(DeckValidator validator, List cards) { + return testDeckValid(validator, cards, null); + } + + private boolean testDeckValid(DeckValidator validator, List cards, List cardsSideboard) { Deck deckToTest = new Deck(); - for (CardNameAmount cardNameAmount : cards) { - CardInfo cardinfo; - if (cardNameAmount.getName().isEmpty()) { - cardinfo = CardRepository.instance.findCard(cardNameAmount.getSetCode(), cardNameAmount.getCardNumber()); - } else { - cardinfo = CardRepository.instance.findCard(cardNameAmount.getName()); + if (cards != null) { + for (CardNameAmount cardNameAmount : cards) { + CardInfo cardinfo; + if (cardNameAmount.getName().isEmpty()) { + cardinfo = CardRepository.instance.findCard(cardNameAmount.getSetCode(), cardNameAmount.getCardNumber()); + } else { + cardinfo = CardRepository.instance.findCard(cardNameAmount.getName()); + } + for (int i = 0; i < cardNameAmount.getNumber(); i++) { + deckToTest.getCards().add(cardinfo.getCard()); + } } - for (int i = 0; i < cardNameAmount.getNumber(); i++) { - deckToTest.getCards().add(cardinfo.getCard()); + } + if (cardsSideboard != null) { + for (CardNameAmount cardNameAmount : cardsSideboard) { + CardInfo cardinfo; + if (cardNameAmount.getName().isEmpty()) { + cardinfo = CardRepository.instance.findCard(cardNameAmount.getSetCode(), cardNameAmount.getCardNumber()); + } else { + cardinfo = CardRepository.instance.findCard(cardNameAmount.getName()); + } + for (int i = 0; i < cardNameAmount.getNumber(); i++) { + deckToTest.getSideboard().add(cardinfo.getCard()); + } } } return validator.validate(deckToTest); From f0ee60eedb3bd027492e3112cbccca93323e04cb Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 18 Feb 2018 18:31:48 +0100 Subject: [PATCH 09/21] * Fixed some possible null pointer exceptions seen in xmage.de server log. --- Mage.Sets/src/mage/cards/d/DreamThief.java | 12 ++++++--- Mage.Sets/src/mage/cards/p/Panharmonicon.java | 8 +++--- .../mage/cards/t/TreacherousPitDweller.java | 25 +++++++++++-------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DreamThief.java b/Mage.Sets/src/mage/cards/d/DreamThief.java index 79338e861c..d8d5364b3f 100644 --- a/Mage.Sets/src/mage/cards/d/DreamThief.java +++ b/Mage.Sets/src/mage/cards/d/DreamThief.java @@ -27,6 +27,7 @@ */ package mage.cards.d; +import java.util.List; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; @@ -52,7 +53,7 @@ public class DreamThief extends CardImpl { private static final String rule = "draw a card if you've cast another blue spell this turn"; public DreamThief(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); this.subtype.add(SubType.FAERIE); this.subtype.add(SubType.ROGUE); @@ -84,9 +85,12 @@ class CastBlueSpellThisTurnCondition implements Condition { public boolean apply(Game game, Ability source) { SpellsCastWatcher watcher = (SpellsCastWatcher) game.getState().getWatchers().get(SpellsCastWatcher.class.getSimpleName()); if (watcher != null) { - for (Spell spell : watcher.getSpellsCastThisTurn(source.getControllerId())) { - if (!spell.getSourceId().equals(source.getSourceId()) && spell.getColor(game).isBlue()) { - return true; + List spells = watcher.getSpellsCastThisTurn(source.getControllerId()); + if (spells != null) { + for (Spell spell : spells) { + if (!spell.getSourceId().equals(source.getSourceId()) && spell.getColor(game).isBlue()) { + return true; + } } } } diff --git a/Mage.Sets/src/mage/cards/p/Panharmonicon.java b/Mage.Sets/src/mage/cards/p/Panharmonicon.java index 4bade4472d..26dfcafdf5 100644 --- a/Mage.Sets/src/mage/cards/p/Panharmonicon.java +++ b/Mage.Sets/src/mage/cards/p/Panharmonicon.java @@ -50,7 +50,7 @@ import mage.game.events.NumberOfTriggersEvent; public class Panharmonicon extends CardImpl { public Panharmonicon(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); // If an artifact or creature entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PanharmoniconEffect())); @@ -95,7 +95,9 @@ class PanharmoniconEffect extends ReplacementEffectImpl { if (source.getControllerId().equals(event.getPlayerId())) { GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); // Only EtB triggers - if (sourceEvent.getType() == EventType.ENTERS_THE_BATTLEFIELD && sourceEvent instanceof EntersTheBattlefieldEvent) { + if (sourceEvent != null + && sourceEvent.getType() == EventType.ENTERS_THE_BATTLEFIELD + && sourceEvent instanceof EntersTheBattlefieldEvent) { EntersTheBattlefieldEvent entersTheBattlefieldEvent = (EntersTheBattlefieldEvent) sourceEvent; // Only for entering artifacts or creatures if (entersTheBattlefieldEvent.getTarget().isArtifact() @@ -116,4 +118,4 @@ class PanharmoniconEffect extends ReplacementEffectImpl { event.setAmount(event.getAmount() + 1); return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/t/TreacherousPitDweller.java b/Mage.Sets/src/mage/cards/t/TreacherousPitDweller.java index aeb1ccac8f..16429b4734 100644 --- a/Mage.Sets/src/mage/cards/t/TreacherousPitDweller.java +++ b/Mage.Sets/src/mage/cards/t/TreacherousPitDweller.java @@ -29,6 +29,7 @@ package mage.cards.t; import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.ContinuousEffectImpl; @@ -49,7 +50,7 @@ import mage.target.common.TargetOpponent; public class TreacherousPitDweller extends CardImpl { public TreacherousPitDweller(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{B}"); this.subtype.add(SubType.DEMON); this.power = new MageInt(4); @@ -77,24 +78,24 @@ class TreacherousPitDwellerTriggeredAbility extends TriggeredAbilityImpl { private static final String ruleText = "When {this} enters the battlefield from a graveyard, "; public TreacherousPitDwellerTriggeredAbility() { - super(Zone.BATTLEFIELD, new TreacherousPitDwellerEffect(),false); + super(Zone.BATTLEFIELD, new TreacherousPitDwellerEffect(), false); addTarget(new TargetOpponent()); } public TreacherousPitDwellerTriggeredAbility(final TreacherousPitDwellerTriggeredAbility ability) { super(ability); } - + @Override public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; } - + @Override public boolean checkTrigger(GameEvent event, Game game) { - return event.getTargetId().equals(getSourceId()) && ((EntersTheBattlefieldEvent) event).getFromZone() == Zone.GRAVEYARD; - } - + return event.getTargetId().equals(getSourceId()) && ((EntersTheBattlefieldEvent) event).getFromZone() == Zone.GRAVEYARD; + } + @Override public TreacherousPitDwellerTriggeredAbility copy() { return new TreacherousPitDwellerTriggeredAbility(this); @@ -104,7 +105,7 @@ class TreacherousPitDwellerTriggeredAbility extends TriggeredAbilityImpl { public String getRule() { return ruleText + super.getRule(); } - + } class TreacherousPitDwellerEffect extends ContinuousEffectImpl { @@ -125,10 +126,12 @@ class TreacherousPitDwellerEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = (Permanent) source.getSourceObjectIfItStillExists(game); + MageObject permanent = source.getSourceObjectIfItStillExists(game); // it can also return Card object Player targetOpponent = game.getPlayer(source.getFirstTarget()); - if (permanent != null && targetOpponent != null) { - return permanent.changeControllerId(targetOpponent.getId(), game); + if (permanent != null + && (permanent instanceof Permanent) + && targetOpponent != null) { + return ((Permanent) permanent).changeControllerId(targetOpponent.getId(), game); } else { discard(); } From a6644b0eb214c6beea694c74866580457d9b6556 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 18 Feb 2018 22:37:08 +0100 Subject: [PATCH 10/21] * Added Steamflogger Boss, Thunderblade Charge and Putrid Cyclops. --- Mage.Sets/src/mage/cards/p/PutridCyclops.java | 112 ++++++++++++++++++ .../src/mage/cards/s/SteamfloggerBoss.java | 83 +++++++++++++ .../src/mage/cards/t/ThunderbladeCharge.java | 104 ++++++++++++++++ Mage.Sets/src/mage/sets/FutureSight.java | 3 + Mage.Sets/src/mage/sets/Unstable.java | 1 + 5 files changed, 303 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PutridCyclops.java create mode 100644 Mage.Sets/src/mage/cards/s/SteamfloggerBoss.java create mode 100644 Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java diff --git a/Mage.Sets/src/mage/cards/p/PutridCyclops.java b/Mage.Sets/src/mage/cards/p/PutridCyclops.java new file mode 100644 index 0000000000..1dfe474b52 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PutridCyclops.java @@ -0,0 +1,112 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.p; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class PutridCyclops extends CardImpl { + + public PutridCyclops(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.CYCLOPS); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Putrid Cyclops enters the battlefield, scry 1, then reveal the top card of your library. Putrid Cyclops gets -X/-X until end of turn, where X is that card's converted mana cost. + this.addAbility(new EntersBattlefieldTriggeredAbility(new PutridCyclopEffect())); + } + + public PutridCyclops(final PutridCyclops card) { + super(card); + } + + @Override + public PutridCyclops copy() { + return new PutridCyclops(this); + } +} + +class PutridCyclopEffect extends OneShotEffect { + + public PutridCyclopEffect() { + super(Outcome.Detriment); + this.staticText = "scry 1, then reveal the top card of your library. {this} gets -X/-X until end of turn, where X is that card's converted mana cost" + + " (To scry 1, look at the top card of your library, then you may put that card on the bottom of your library.)"; + } + + public PutridCyclopEffect(final PutridCyclopEffect effect) { + super(effect); + } + + @Override + public PutridCyclopEffect copy() { + return new PutridCyclopEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + new ScryEffect(1).apply(game, source); + Card card = controller.getLibrary().getFromTop(game); + if (card != null) { + controller.revealCards(sourceObject.getIdName(), new CardsImpl(card), game); + int unboost = card.getConvertedManaCost() * -1; + ContinuousEffect effect = new BoostSourceEffect(unboost, unboost, Duration.EndOfTurn); + game.addEffect(effect, source); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SteamfloggerBoss.java b/Mage.Sets/src/mage/cards/s/SteamfloggerBoss.java new file mode 100644 index 0000000000..d613079e9a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SteamfloggerBoss.java @@ -0,0 +1,83 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import static mage.filter.predicate.permanent.ControllerControlsIslandPredicate.filter; + +/** + * + * @author LevelX2 + */ +public class SteamfloggerBoss extends CardImpl { + + public SteamfloggerBoss(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.RIGGER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Other Rigger creatures you control get +1/+0 and have haste. + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, + new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield, new FilterCreaturePermanent(SubType.RIGGER, "Rigger creatures"), true)); + Effect effect = new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter); + effect.setText("and have haste"); + ability.addEffect(effect); + this.addAbility(ability); + + // If a Rigger you control would assemble a Contraption, it assembles two Contraptions instead. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect("If a Rigger you control would assemble a Contraption, it assembles two Contraptions instead. (NOT IMPLEMENTED)"))); + + } + + public SteamfloggerBoss(final SteamfloggerBoss card) { + super(card); + } + + @Override + public SteamfloggerBoss copy() { + return new SteamfloggerBoss(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java b/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java new file mode 100644 index 0000000000..658c9920ec --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderbladeCharge.java @@ -0,0 +1,104 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCreatureOrPlayer; + +/** + * + * @author LevelX2 + */ +public class ThunderbladeCharge extends CardImpl { + + public ThunderbladeCharge(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}{R}"); + + // Thunderblade Charge deals 3 damage to target creature or player. + this.getSpellAbility().addEffect(new DamageTargetEffect(3)); + this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); + + // Whenever one or more creatures you control deal combat damage to a player, if Thunderblade Charge is in your graveyard, you may pay {2}{R}{R}{R}. If you do, you may cast it without paying its mana cost. + this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(Zone.GRAVEYARD, + new DoIfCostPaid(new ThunderbladeChargeCastEffect(), new ManaCostsImpl("{2}{R}{R}{R}")) + .setText("if {this} is in your graveyard, you may pay {2}{R}{R}{R}. If you do, you may cast it without paying its mana cost"))); + } + + public ThunderbladeCharge(final ThunderbladeCharge card) { + super(card); + } + + @Override + public ThunderbladeCharge copy() { + return new ThunderbladeCharge(this); + } +} + +class ThunderbladeChargeCastEffect extends OneShotEffect { + + public ThunderbladeChargeCastEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "you may cast {this} without paying its mana cost"; + } + + public ThunderbladeChargeCastEffect(final ThunderbladeChargeCastEffect effect) { + super(effect); + } + + @Override + public ThunderbladeChargeCastEffect copy() { + return new ThunderbladeChargeCastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Card sourceCard = game.getCard(source.getSourceId()); + if (controller != null + && sourceCard != null + && Zone.GRAVEYARD == game.getState().getZone(sourceCard.getId())) { + controller.cast(sourceCard.getSpellAbility(), game, true); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/FutureSight.java b/Mage.Sets/src/mage/sets/FutureSight.java index 9f578ac5ff..a8669801cc 100644 --- a/Mage.Sets/src/mage/sets/FutureSight.java +++ b/Mage.Sets/src/mage/sets/FutureSight.java @@ -171,6 +171,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Petrified Plating", 133, Rarity.COMMON, mage.cards.p.PetrifiedPlating.class)); cards.add(new SetCardInfo("Phosphorescent Feast", 149, Rarity.UNCOMMON, mage.cards.p.PhosphorescentFeast.class)); cards.add(new SetCardInfo("Pooling Venom", 74, Rarity.UNCOMMON, mage.cards.p.PoolingVenom.class)); + cards.add(new SetCardInfo("Putrid Cyclops", 75, Rarity.COMMON, mage.cards.p.PutridCyclops.class)); cards.add(new SetCardInfo("Pyromancer's Swath", 104, Rarity.RARE, mage.cards.p.PyromancersSwath.class)); cards.add(new SetCardInfo("Quagnoth", 150, Rarity.RARE, mage.cards.q.Quagnoth.class)); cards.add(new SetCardInfo("Quiet Disrepair", 134, Rarity.COMMON, mage.cards.q.QuietDisrepair.class)); @@ -206,6 +207,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Spirit en-Dal", 17, Rarity.UNCOMMON, mage.cards.s.SpiritEnDal.class)); cards.add(new SetCardInfo("Sporoloth Ancient", 152, Rarity.COMMON, mage.cards.s.SporolothAncient.class)); cards.add(new SetCardInfo("Sprout Swarm", 138, Rarity.COMMON, mage.cards.s.SproutSwarm.class)); + cards.add(new SetCardInfo("Steamflogger Boss", 121, Rarity.RARE, mage.cards.s.SteamfloggerBoss.class)); cards.add(new SetCardInfo("Storm Entity", 122, Rarity.UNCOMMON, mage.cards.s.StormEntity.class)); cards.add(new SetCardInfo("Street Wraith", 90, Rarity.UNCOMMON, mage.cards.s.StreetWraith.class)); cards.add(new SetCardInfo("Stronghold Rats", 79, Rarity.UNCOMMON, mage.cards.s.StrongholdRats.class)); @@ -215,6 +217,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Tarmogoyf", 153, Rarity.RARE, mage.cards.t.Tarmogoyf.class)); cards.add(new SetCardInfo("Tarox Bladewing", 123, Rarity.RARE, mage.cards.t.TaroxBladewing.class)); cards.add(new SetCardInfo("Thornweald Archer", 154, Rarity.COMMON, mage.cards.t.ThornwealdArcher.class)); + cards.add(new SetCardInfo("Thunderblade Charge", 124, Rarity.RARE, mage.cards.t.ThunderbladeCharge.class)); cards.add(new SetCardInfo("Tolaria West", 173, Rarity.UNCOMMON, mage.cards.t.TolariaWest.class)); cards.add(new SetCardInfo("Tombstalker", 91, Rarity.RARE, mage.cards.t.Tombstalker.class)); cards.add(new SetCardInfo("Unblinking Bleb", 45, Rarity.COMMON, mage.cards.u.UnblinkingBleb.class)); diff --git a/Mage.Sets/src/mage/sets/Unstable.java b/Mage.Sets/src/mage/sets/Unstable.java index f675d4cbeb..7f1e655814 100644 --- a/Mage.Sets/src/mage/sets/Unstable.java +++ b/Mage.Sets/src/mage/sets/Unstable.java @@ -75,6 +75,7 @@ public class Unstable extends ExpansionSet { cards.add(new SetCardInfo("Plains", 212, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Snickering Squirrel", 68, Rarity.COMMON, mage.cards.s.SnickeringSquirrel.class)); cards.add(new SetCardInfo("Squirrel-Powered Scheme", 70, Rarity.UNCOMMON, mage.cards.s.SquirrelPoweredScheme.class)); + cards.add(new SetCardInfo("Steamflogger Boss", 93, Rarity.RARE, mage.cards.s.SteamfloggerBoss.class)); cards.add(new SetCardInfo("Steel Squirrel", 162, Rarity.UNCOMMON, mage.cards.s.SteelSquirrel.class)); cards.add(new SetCardInfo("Summon the Pack", 74, Rarity.MYTHIC, mage.cards.s.SummonThePack.class)); cards.add(new SetCardInfo("Swamp", 214, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(FrameStyle.UNH_FULL_ART_BASIC, false))); From 4d4b0d145e3dcd32e0b43f56b00c0f29834bfbe0 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 18 Feb 2018 23:44:14 +0100 Subject: [PATCH 11/21] * Sacrificed a bug of SacrificeAllCost (fixing a problem with Soulblast looping forever). --- Mage.Sets/src/mage/cards/k/KaerveksSpite.java | 13 +++++----- Mage.Sets/src/mage/cards/s/Soulblast.java | 12 ++++------ .../costs/common/SacrificeAllCost.java | 24 +++++++++---------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KaerveksSpite.java b/Mage.Sets/src/mage/cards/k/KaerveksSpite.java index bc0c37007d..349fa1ccf8 100644 --- a/Mage.Sets/src/mage/cards/k/KaerveksSpite.java +++ b/Mage.Sets/src/mage/cards/k/KaerveksSpite.java @@ -12,25 +12,24 @@ import mage.filter.common.FilterControlledPermanent; public class KaerveksSpite extends CardImpl { - private FilterControlledPermanent permanentsYouControl = new FilterControlledPermanent("all permanents you control"); - public KaerveksSpite(UUID ownerId, CardSetInfo cardSetInfo) { super(ownerId, cardSetInfo, new CardType[]{CardType.INSTANT}, "{B}{B}{B}"); - //As an additional cost to cast Kaervek's Spite, sacrifice all permanents you control and discard your hand. - this.getSpellAbility().addCost(new SacrificeAllCost(permanentsYouControl)); + // As an additional cost to cast Kaervek's Spite, sacrifice all permanents you control and discard your hand. + this.getSpellAbility().addCost(new SacrificeAllCost(new FilterControlledPermanent("all permanents you control"))); this.getSpellAbility().addCost(new DiscardHandCost()); - //Target player loses 5 life. + // Target player loses 5 life. Effect effect = new LoseLifeTargetEffect(5); this.getSpellAbility().addEffect(effect); } - public KaerveksSpite(final KaerveksSpite other){ + public KaerveksSpite(final KaerveksSpite other) { super(other); } - public KaerveksSpite copy(){ + @Override + public KaerveksSpite copy() { return new KaerveksSpite(this); } } diff --git a/Mage.Sets/src/mage/cards/s/Soulblast.java b/Mage.Sets/src/mage/cards/s/Soulblast.java index 1c9d495fca..c6391096b6 100644 --- a/Mage.Sets/src/mage/cards/s/Soulblast.java +++ b/Mage.Sets/src/mage/cards/s/Soulblast.java @@ -36,7 +36,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -48,14 +48,12 @@ import mage.target.common.TargetCreatureOrPlayer; */ public class Soulblast extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures you control"); - public Soulblast(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{3}{R}{R}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R}{R}{R}"); // As an additional cost to cast Soulblast, sacrifice all creatures you control. - this.getSpellAbility().addCost(new SacrificeAllCost(filter)); + this.getSpellAbility().addCost(new SacrificeAllCost(StaticFilters.FILTER_PERMANENT_CREATURES_CONTROLLED)); + // Soulblast deals damage to target creature or player equal to the total power of the sacrificed creatures. this.getSpellAbility().addEffect(new SoulblastEffect()); this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); @@ -90,7 +88,7 @@ class SoulblastEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int power = 0; - for (Cost cost :source.getCosts()) { + for (Cost cost : source.getCosts()) { if (cost instanceof SacrificeAllCost) { for (Permanent permanent : ((SacrificeAllCost) cost).getPermanents()) { power += permanent.getPower().getValue(); diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java index 88f5d8af97..a784838b8c 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java @@ -55,19 +55,19 @@ public class SacrificeAllCost extends CostImpl { public SacrificeAllCost(final SacrificeAllCost cost) { super(cost); - for (Permanent permanent: cost.permanents) { - this.permanents.add(permanent.copy()); - } + this.permanents.addAll(cost.permanents); // because this are already copied permanents, they can't change, so no copy again is needed this.filter = cost.filter.copy(); } @Override public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { - permanents.add(permanent.copy()); - permanent.sacrifice(sourceId, game); + if (permanent.sacrifice(sourceId, game)) { + permanents.add(permanent.copy()); + } } - return true; + paid = true; + return paid; } @Override @@ -81,13 +81,13 @@ public class SacrificeAllCost extends CostImpl { activator = controllerId; } } - - for (Permanent permanent :game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { - if(!game.getPlayer(activator).canPaySacrificeCost(permanent, sourceId, controllerId, game)) { - return false; - } + + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { + if (!game.getPlayer(activator).canPaySacrificeCost(permanent, sourceId, controllerId, game)) { + return false; + } } - + return true; } From 210770669c5e78fe23aae9b7e5c8e0f3a0d8d159 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 18 Feb 2018 23:44:52 +0100 Subject: [PATCH 12/21] * Added Shah of Naar Isle (Future Sight complete now). --- .../src/mage/cards/s/ShahOfNaarIsle.java | 142 ++++++++++++++++++ Mage.Sets/src/mage/sets/FutureSight.java | 1 + .../mage/abilities/keyword/EchoAbility.java | 1 + .../main/java/mage/game/events/GameEvent.java | 1 + 4 files changed, 145 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java diff --git a/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java b/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java new file mode 100644 index 0000000000..0dc5a2fcc2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShahOfNaarIsle.java @@ -0,0 +1,142 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.EchoAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class ShahOfNaarIsle extends CardImpl { + + public ShahOfNaarIsle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.EFREET); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Echo {0} + this.addAbility(new EchoAbility("{0}")); + + // When Shah of Naar Isle's echo cost is paid, each opponent may draw up to three cards. + this.addAbility(new ShahOfNaarIsleTriggeredAbility()); + } + + public ShahOfNaarIsle(final ShahOfNaarIsle card) { + super(card); + } + + @Override + public ShahOfNaarIsle copy() { + return new ShahOfNaarIsle(this); + } +} + +class ShahOfNaarIsleTriggeredAbility extends TriggeredAbilityImpl { + + public ShahOfNaarIsleTriggeredAbility() { + super(Zone.BATTLEFIELD, new ShahOfNaarIsleEffect(), false); + } + + public ShahOfNaarIsleTriggeredAbility(final ShahOfNaarIsleTriggeredAbility effect) { + super(effect); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ECHO_PAID; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return getSourceId().equals(event.getSourceId()); + } + + @Override + public ShahOfNaarIsleTriggeredAbility copy() { + return new ShahOfNaarIsleTriggeredAbility(this); + } + + @Override + public String getRule() { + return "When {this}'s echo cost is paid, " + super.getRule(); + } +} + +class ShahOfNaarIsleEffect extends OneShotEffect { + + public ShahOfNaarIsleEffect() { + super(Outcome.DrawCard); + this.staticText = "each opponent may draw up to three cards"; + } + + public ShahOfNaarIsleEffect(final ShahOfNaarIsleEffect effect) { + super(effect); + } + + @Override + public ShahOfNaarIsleEffect copy() { + return new ShahOfNaarIsleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + for (UUID playerId : game.getOpponents(controller.getId())) { + Player opponent = game.getPlayer(playerId); + if (opponent != null) { + int number = opponent.getAmount(0, 3, "Draw how many cards?", game); + opponent.drawCards(number, game); + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/FutureSight.java b/Mage.Sets/src/mage/sets/FutureSight.java index a8669801cc..f1ff59de3c 100644 --- a/Mage.Sets/src/mage/sets/FutureSight.java +++ b/Mage.Sets/src/mage/sets/FutureSight.java @@ -190,6 +190,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Scout's Warning", 16, Rarity.RARE, mage.cards.s.ScoutsWarning.class)); cards.add(new SetCardInfo("Second Wind", 57, Rarity.UNCOMMON, mage.cards.s.SecondWind.class)); cards.add(new SetCardInfo("Seht's Tiger", 31, Rarity.RARE, mage.cards.s.SehtsTiger.class)); + cards.add(new SetCardInfo("Shah of Naar Isle", 119, Rarity.RARE, mage.cards.s.ShahOfNaarIsle.class)); cards.add(new SetCardInfo("Shapeshifter's Marrow", 58, Rarity.RARE, mage.cards.s.ShapeshiftersMarrow.class)); cards.add(new SetCardInfo("Shimian Specter", 76, Rarity.RARE, mage.cards.s.ShimianSpecter.class)); cards.add(new SetCardInfo("Shivan Sand-Mage", 108, Rarity.UNCOMMON, mage.cards.s.ShivanSandMage.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/EchoAbility.java b/Mage/src/main/java/mage/abilities/keyword/EchoAbility.java index afe35d923f..e978cb9dc0 100644 --- a/Mage/src/main/java/mage/abilities/keyword/EchoAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/EchoAbility.java @@ -187,6 +187,7 @@ class EchoEffect extends OneShotEffect { if (controller.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + '?', source, game)) { cost.clearPaid(); if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ECHO_PAID, source.getSourceId(), source.getSourceId(), source.getControllerId())); return true; } } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 06816e0f4a..ef7ec2b5b3 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -92,6 +92,7 @@ public class GameEvent implements Serializable { DRAW_CARDS, // applies to an instruction to draw more than one card before any replacement effects apply to individual cards drawn DRAW_CARD, DREW_CARD, EXPLORED, + ECHO_PAID, MIRACLE_CARD_REVEALED, MADNESS_CARD_EXILED, INVESTIGATED, From bb512faa9878eb77a98d9d3b38e84b8698c7be49 Mon Sep 17 00:00:00 2001 From: spjspj Date: Mon, 19 Feb 2018 12:12:18 +1100 Subject: [PATCH 13/21] Add option to get debug information from game state in chat --- .../main/java/mage/server/ChatManager.java | 36 +++++-- .../java/mage/server/game/GameController.java | 94 +++++++++++++++++++ 2 files changed, 124 insertions(+), 6 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index 371ead70e5..d479f1b569 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -28,6 +28,7 @@ package mage.server; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -37,6 +38,8 @@ import java.util.regex.Pattern; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.server.exceptions.UserNotFoundException; +import mage.server.game.GameController; +import mage.server.game.GameManager; import mage.server.util.SystemUtil; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; @@ -220,6 +223,27 @@ public enum ChatManager { chatSessions.get(chatId).broadcastInfoToUser(user, message); return true; } + if (command.startsWith("GAME")) { + message += "
" + GameManager.instance.getChatId(chatId); + ChatSession session = chatSessions.get(chatId); + if (session != null && session.getInfo() != null) { + String gameId = session.getInfo(); + if (gameId.startsWith("Game ")) { + UUID id = java.util.UUID.fromString(gameId.substring(5, gameId.length())); + for (Entry entry : GameManager.instance.getGameController().entrySet()) { + if (entry.getKey().equals(id)) { + GameController controller = entry.getValue(); + if (controller != null) { + message += controller.getGameStateDebugMessage(); + chatSessions.get(chatId).broadcastInfoToUser(user, message); + } + } + } + + } + } + return true; + } if (command.startsWith("CARD ")) { Matcher matchPattern = getCardTextPattern.matcher(message.toLowerCase()); if (matchPattern.find()) { @@ -289,18 +313,18 @@ public enum ChatManager { public void sendReconnectMessage(UUID userId) { UserManager.instance.getUser(userId).ifPresent(user -> getChatSessions() - .stream() - .filter(chat -> chat.hasUser(userId)) - .forEach(chatSession -> chatSession.broadcast(null, user.getName() + " has reconnected", MessageColor.BLUE, true, MessageType.STATUS, null))); + .stream() + .filter(chat -> chat.hasUser(userId)) + .forEach(chatSession -> chatSession.broadcast(null, user.getName() + " has reconnected", MessageColor.BLUE, true, MessageType.STATUS, null))); } public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) { UserManager.instance.getUser(userId).ifPresent(user -> getChatSessions() - .stream() - .filter(chat -> chat.hasUser(userId)) - .forEach(chatSession -> chatSession.broadcast(null, user.getName() + reason.getMessage(), MessageColor.BLUE, true, MessageType.STATUS, null))); + .stream() + .filter(chat -> chat.hasUser(userId)) + .forEach(chatSession -> chatSession.broadcast(null, user.getName() + reason.getMessage(), MessageColor.BLUE, true, MessageType.STATUS, null))); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index d039beff8d..1af7921751 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -50,6 +50,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.GameException; import mage.game.GameOptions; +import mage.game.GameState; import mage.game.Table; import mage.game.events.Listener; import mage.game.events.PlayerQueryEvent; @@ -1088,4 +1089,97 @@ public class GameController implements GameCallback { return false; } + public String getGameStateDebugMessage() { + if (game == null) { + return ""; + } + GameState state = game.getState(); + if (state == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append("
Game State:
"); + sb.append(state); + + sb.append("
Active player is: "); + sb.append(game.getPlayer(state.getActivePlayerId()).getName()); + sb.append("
isGameOver: "); + sb.append(state.isGameOver()); + sb.append("
Current phase is: "); + sb.append(state.getTurn().getPhase()); + sb.append("
getBattlefield: "); + sb.append(state.getBattlefield()); + sb.append("
getChoosingPlayerId: "); + if (state.getChoosingPlayerId() != null) { + sb.append(game.getPlayer(state.getChoosingPlayerId()).getName()); + } else { + sb.append("noone!"); + } + sb.append("
getCombat: "); + sb.append(state.getCombat()); + sb.append("
getCommand: "); + sb.append(state.getCommand()); + sb.append("
getContinuousEffects: "); + sb.append(state.getContinuousEffects()); + sb.append("
getCopiedCards: "); + sb.append(state.getCopiedCards()); + sb.append("
getDelayed: "); + sb.append(state.getDelayed()); + sb.append("
getDesignations: "); + sb.append(state.getDesignations()); + sb.append("
getExile: "); + sb.append(state.getExile()); + sb.append("
getMonarchId: "); + sb.append(state.getMonarchId()); + sb.append("
getNextPermanentOrderNumber: "); + sb.append(state.getNextPermanentOrderNumber()); + sb.append("
getPlayerByOrderId: "); + if (state.getPlayerByOrderId() != null) { + sb.append(game.getPlayer(state.getPlayerByOrderId()).getName()); + } else { + sb.append("noone!"); + } + sb.append("
getPlayerList: "); + sb.append(state.getPlayerList()); + sb.append("
getPlayers: "); + sb.append(state.getPlayers()); + sb.append("
Player with Priority is: "); + if (state.getPriorityPlayerId() != null) { + sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); + } else { + sb.append("noone!"); + } + sb.append("
getRevealed: "); + sb.append(state.getRevealed()); + sb.append("
getSpecialActions: "); + sb.append(state.getSpecialActions()); + sb.append("
getStack: "); + sb.append(state.getStack()); + sb.append("
getStepNum: "); + sb.append(state.getStepNum()); + sb.append("
getTriggers: "); + sb.append(state.getTriggers()); + sb.append("
getTurn: "); + sb.append(state.getTurn()); + sb.append("
getTurnId: "); + sb.append(state.getTurnId()); + sb.append("
getTurnMods: "); + sb.append(state.getTurnMods()); + sb.append("
getTurnNum: "); + sb.append(state.getTurnNum()); + sb.append("
Future Timeout:"); + if (futureTimeout != null) { + sb.append("Cancelled?="); + sb.append(futureTimeout.isCancelled()); + sb.append(",,,Done?="); + sb.append(futureTimeout.isDone()); + sb.append(",,,GetDelay?="); + sb.append((int) futureTimeout.getDelay(TimeUnit.SECONDS)); + } else { + sb.append("Not using future Timeout!"); + } + sb.append("
"); + return sb.toString(); + } + } From 51f52dcc8baa91a3ce0786083537b77e9ce59c4b Mon Sep 17 00:00:00 2001 From: Plopman <> Date: Mon, 19 Feb 2018 23:13:41 +0100 Subject: [PATCH 14/21] Fix MaximumHandSizeControllerEffect setText function --- .../MaximumHandSizeControllerEffect.java | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/MaximumHandSizeControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/MaximumHandSizeControllerEffect.java index b3193d9669..cedfb9fcec 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/MaximumHandSizeControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/MaximumHandSizeControllerEffect.java @@ -142,42 +142,50 @@ public class MaximumHandSizeControllerEffect extends ContinuousEffectImpl { private void setText() { StringBuilder sb = new StringBuilder(); - switch (targetController) { - case ANY: - if (handSize instanceof StaticValue && ((StaticValue) handSize).getValue() == Integer.MAX_VALUE) { - sb.append("All players have no "); - } else { - sb.append("All players "); - } - break; - case OPPONENT: - if (handSize instanceof StaticValue && ((StaticValue) handSize).getValue() == Integer.MAX_VALUE) { - sb.append("Each opponent has no "); - } else { - sb.append("Each opponent's "); - } - break; - case YOU: - if (handSize instanceof StaticValue && ((StaticValue) handSize).getValue() == Integer.MAX_VALUE) { - sb.append("You have no "); - } else { - sb.append("Your "); - } - break; - } - sb.append("maximum hand size"); - if (handSizeModification == HandSizeModification.INCREASE) { - sb.append(" is increased by "); - } else if (handSizeModification == HandSizeModification.REDUCE) { - sb.append(" is reduced by "); - } else if (!((handSize instanceof StaticValue) && ((StaticValue) handSize).getValue() != Integer.MAX_VALUE) || !(handSize instanceof StaticValue)) { - sb.append(" is "); - } - if ((handSize instanceof StaticValue && ((StaticValue) handSize).getValue() != Integer.MAX_VALUE)) { - sb.append(CardUtil.numberToText(((StaticValue) handSize).getValue())); - } else if (!(handSize instanceof StaticValue)) { - sb.append(handSize.getMessage()); + if(handSize instanceof StaticValue && ((StaticValue) handSize).getValue() == Integer.MAX_VALUE) { + switch (targetController) { + case ANY: + sb.append("Players have no maximum hand size"); + break; + case OPPONENT: + sb.append("Each opponent has no maximum hand size"); + break; + case YOU: + sb.append("You have no maximum hand size"); + break; + } + } else { + switch (targetController) { + case ANY: + sb.append("All players maximum hand size"); + break; + case OPPONENT: + sb.append("Each opponent's maximum hand size"); + break; + case YOU: + sb.append("Your maximum hand size"); + break; + } + + switch (handSizeModification) { + case SET: + sb.append(" is "); + break; + case INCREASE: + sb.append(" is increased by "); + break; + case REDUCE: + sb.append(" is reduced by "); + break; + } + + if (handSize instanceof StaticValue) { + sb.append(CardUtil.numberToText(((StaticValue) handSize).getValue())); + } else if (!(handSize instanceof StaticValue)) { + sb.append(handSize.getMessage()); + } } + if (duration == Duration.EndOfGame) { sb.append(" for the rest of the game"); } From d09aacdbfb36564611a6ab85d57876324cfc71fc Mon Sep 17 00:00:00 2001 From: Plopman <> Date: Mon, 19 Feb 2018 23:27:33 +0100 Subject: [PATCH 15/21] Fix Diamond Kaleidoscope second ability --- Mage.Sets/src/mage/cards/d/DiamondKaleidoscope.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DiamondKaleidoscope.java b/Mage.Sets/src/mage/cards/d/DiamondKaleidoscope.java index 1676e4475b..53d804be96 100644 --- a/Mage.Sets/src/mage/cards/d/DiamondKaleidoscope.java +++ b/Mage.Sets/src/mage/cards/d/DiamondKaleidoscope.java @@ -68,8 +68,7 @@ public class DiamondKaleidoscope extends CardImpl { this.addAbility(ability); // Sacrifice a Prism token: Add one mana of any color to your mana pool. - ability = new AnyColorManaAbility(); - ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); + ability = new AnyColorManaAbility(new SacrificeTargetCost(new TargetControlledPermanent(filter))); this.addAbility(ability); } From 89b6aeacd6ef5c5827ad9033746c00e26197f943 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Feb 2018 16:16:26 +0100 Subject: [PATCH 16/21] * Added a test related to #4539. --- .../java/mage/server/TableController.java | 2 +- .../src/mage/cards/s/SultaiAscendancy.java | 3 +- .../cards/enchantments/SpreadingSeasTest.java | 43 +++++++++++++++++-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index 35df50ba08..c45f07adc4 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -990,7 +990,7 @@ public class TableController { || !match.isDoneSideboarding() || (!matchPlayer.hasQuit() && match.getGame() != null && matchPlayer.getPlayer().isInGame())) { Optional user = UserManager.instance.getUser(userPlayerEntry.getKey()); - if (!user.isPresent()) { + if (!user.isPresent() || user.get().isActive()) { logger.warn("- Active user of match is missing: " + matchPlayer.getName()); logger.warn("-- matchId:" + match.getId()); logger.warn("-- userId:" + userPlayerEntry.getKey()); diff --git a/Mage.Sets/src/mage/cards/s/SultaiAscendancy.java b/Mage.Sets/src/mage/cards/s/SultaiAscendancy.java index d516d565ad..053e802bbd 100644 --- a/Mage.Sets/src/mage/cards/s/SultaiAscendancy.java +++ b/Mage.Sets/src/mage/cards/s/SultaiAscendancy.java @@ -46,8 +46,7 @@ import mage.filter.FilterCard; public class SultaiAscendancy extends CardImpl { public SultaiAscendancy(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{B}{G}{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}{G}{U}"); // At the beginning of your upkeep, look at the top two cards of your library. Put any number of them into your graveyard and the rest on top of your library in any order. Effect effect = new LookLibraryAndPickControllerEffect( diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SpreadingSeasTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SpreadingSeasTest.java index c44ce2e37d..f5528bbe33 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SpreadingSeasTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/SpreadingSeasTest.java @@ -86,12 +86,12 @@ public class SpreadingSeasTest extends CardTestPlayerBase { } @Test - public void testUtopiaSprawlWithSpreadingSeas(){ + public void testUtopiaSprawlWithSpreadingSeas() { addCard(Zone.HAND, playerA, "Spreading Seas", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.BATTLEFIELD, playerA, "Island", 10); addCard(Zone.HAND, playerA, "Utopia Sprawl"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Utopia Sprawl","Forest"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Utopia Sprawl", "Forest"); setChoice(playerA, "Green"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spreading Seas", "Forest"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); @@ -99,9 +99,8 @@ public class SpreadingSeasTest extends CardTestPlayerBase { assertNotSubtype("Forest", SubType.FOREST); } - @Test - public void testSpreadingSeasWithUrzaLand(){ + public void testSpreadingSeasWithUrzaLand() { addCard(Zone.HAND, playerA, "Spreading Seas", 1); addCard(Zone.BATTLEFIELD, playerA, "Urza's Tower", 1); addCard(Zone.BATTLEFIELD, playerA, "Island", 10); @@ -111,4 +110,40 @@ public class SpreadingSeasTest extends CardTestPlayerBase { assertNotSubtype("Urza's Tower", SubType.URZAS); assertNotSubtype("Urza's Tower", SubType.TOWER); } + + /** + * https://github.com/magefree/mage/issues/4529 Some spell effects that + * effect the use of mana abilities on lands are inoperative. Example + * Spreading Seas transforms enchanted land into an island and it loses all + * other abilities. The AI does not recognize this and is able to use all + * abilities of the enchanted land including all previous mana abilities and + * activated abilities, in addition to now also being an island due to + * Spreading Sea's effect. + */ + @Test + public void testSpreadingRemovesOtherAbilities() { + + // Enchant land + // When Spreading Seas enters the battlefield, draw a card. + // Enchanted land is an Island. + addCard(Zone.HAND, playerA, "Spreading Seas", 1); // ENCHANTMENT {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // {T}: Add {C} to your mana pool. + // {1}{R}, {T}: Create a 0/1 red Kobold creature token named Kobolds of Kher Keep. + addCard(Zone.BATTLEFIELD, playerB, "Kher Keep", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spreading Seas", "Kher Keep"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "{1}{R}"); // Ability should not be available + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Spreading Seas", 1); + + assertPermanentCount(playerB, "Kobolds of Kher Keep", 0); + assertTapped("Kher Keep", false); + } + } From c5cbdcf68fcc52c6b9068d52b9ee33d71b57cc0d Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Feb 2018 17:57:23 +0100 Subject: [PATCH 17/21] * Fixed some target pointer handling (fixes #4540). --- .../src/mage/cards/a/ActOfAuthority.java | 6 +-- .../src/mage/cards/b/BackFromTheBrink.java | 2 +- .../src/mage/cards/b/BarrinsUnmaking.java | 6 +-- Mage.Sets/src/mage/cards/m/MimicVat.java | 34 +++++++-------- .../mage/test/cards/copy/MimicVatTest.java | 43 +++++++++++++++++++ 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/ActOfAuthority.java b/Mage.Sets/src/mage/cards/a/ActOfAuthority.java index 3a0f108245..913dc43fbb 100644 --- a/Mage.Sets/src/mage/cards/a/ActOfAuthority.java +++ b/Mage.Sets/src/mage/cards/a/ActOfAuthority.java @@ -27,6 +27,7 @@ */ package mage.cards.a; +import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -44,8 +45,6 @@ import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; -import java.util.UUID; - /** * * @author LevelX2 @@ -53,8 +52,7 @@ import java.util.UUID; public class ActOfAuthority extends CardImpl { public ActOfAuthority(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{W}{W}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); // When Act of Authority enters the battlefield, you may exile target artifact or enchantment. Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetEffect(), true); diff --git a/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java b/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java index 6e92e76df4..b887c7b5a2 100644 --- a/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java +++ b/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java @@ -103,7 +103,7 @@ class BackFromTheBrinkCost extends CostImpl { if (controller != null) { Card card = controller.getGraveyard().get(targets.getFirstTarget(), game); if (card != null && controller.moveCards(card, Zone.EXILED, ability, game)) { - ability.getEffects().get(0).setTargetPointer(new FixedTarget(card.getId())); + ability.getEffects().get(0).setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId()))); paid = card.getManaCost().pay(ability, game, sourceId, controllerId, noMana); } } diff --git a/Mage.Sets/src/mage/cards/b/BarrinsUnmaking.java b/Mage.Sets/src/mage/cards/b/BarrinsUnmaking.java index 2ef729a53c..ec6af647ee 100644 --- a/Mage.Sets/src/mage/cards/b/BarrinsUnmaking.java +++ b/Mage.Sets/src/mage/cards/b/BarrinsUnmaking.java @@ -52,7 +52,7 @@ public class BarrinsUnmaking extends CardImpl { public BarrinsUnmaking(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); - // Return target permanent to its owner's hand if that permanent shares a color with the most common color among all permanents or a color tied for most common. + // Return target permanent to its owner's hand if that permanent shares a color with the most common color among all permanents or a color tied for most common. this.getSpellAbility().addEffect(new BarrinsUnmakingEffect()); this.getSpellAbility().addTarget(new TargetPermanent()); } @@ -85,12 +85,12 @@ class BarrinsUnmakingEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent != null) { Condition condition = new MostCommonColorCondition(permanent.getColor(game)); if (condition.apply(game, source)) { Effect effect = new ReturnToHandTargetEffect(); - effect.setTargetPointer(new FixedTarget(permanent.getId())); + effect.setTargetPointer(new FixedTarget(permanent, game)); return effect.apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 8fd4c37ba9..9f5b563611 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -38,8 +38,8 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -122,7 +122,7 @@ class MimicVatTriggeredAbility extends TriggeredAbilityImpl { && !(permanent instanceof PermanentToken) && permanent.isCreature()) { - getEffects().get(0).setTargetPointer(new FixedTarget(permanent.getId())); + getEffects().get(0).setTargetPointer(new FixedTarget(permanent.getId(), game)); return true; } return false; @@ -152,22 +152,22 @@ class MimicVatEffect extends OneShotEffect { if (controller == null || permanent == null) { return false; } - // return older cards to graveyard - Set toGraveyard = new HashSet<>(); - for (UUID imprintedId : permanent.getImprinted()) { - Card card = game.getCard(imprintedId); - if (card != null) { - toGraveyard.add(card); - } - } - controller.moveCards(toGraveyard, Zone.GRAVEYARD, source, game); - permanent.clearImprinted(game); - // Imprint a new one - Card card = game.getCard(getTargetPointer().getFirst(game, source)); - if (card != null) { - controller.moveCardsToExile(card, source, game, true, source.getSourceId(), permanent.getName() + " (Imprint)"); - permanent.imprint(card.getId(), game); + Card newCard = game.getCard(getTargetPointer().getFirst(game, source)); + if (newCard != null) { + // return older cards to graveyard + Set toGraveyard = new HashSet<>(); + for (UUID imprintedId : permanent.getImprinted()) { + Card card = game.getCard(imprintedId); + if (card != null) { + toGraveyard.add(card); + } + } + controller.moveCards(toGraveyard, Zone.GRAVEYARD, source, game); + permanent.clearImprinted(game); + + controller.moveCardsToExile(newCard, source, game, true, source.getSourceId(), permanent.getName() + " (Imprint)"); + permanent.imprint(newCard.getId(), game); } return true; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java index b708a3d6c9..73f7b9eacb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/MimicVatTest.java @@ -111,4 +111,47 @@ public class MimicVatTest extends CardTestPlayerBase { } + /** + * Player A has Mimic Vat and plays Sidisi, Undead Vizier and exploits. + * Player N responds to Mimic Vat Trigger with Shred Memory, exiling Sidisi. + * Sidisi gets exiled but then xmage allows player A to imprint the + * creature, which shouldn't be possible. + */ + @Test + public void TestExileFails() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // Imprint - Whenever a nontoken creature dies, you may exile that card. If you do, return each other card exiled with Mimic Vat to its owner's graveyard. + // {3}, {T}: Create a token that's a copy of a card exiled with Mimic Vat. It gains haste. Exile it at the beginning of the next end step. + addCard(Zone.BATTLEFIELD, playerA, "Mimic Vat", 1); // Artifact {3} + + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + // Exile up to four target cards from a single graveyard. + // Transmute {1}{B}{B} + addCard(Zone.HAND, playerB, "Shred Memory", 1); // Instant {1}{B} + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion"); + setChoice(playerA, "Yes"); + setChoice(playerA, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Shred Memory", "Silvercoat Lion", "Whenever a nontoken creature dies"); + setChoice(playerA, "Yes"); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}: Create a token that's a copy of a card exiled with "); + setChoice(playerA, "Yes"); + setChoice(playerA, "Silvercoat Lion"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertGraveyardCount(playerB, "Shred Memory", 1); + + assertExileCount(playerB, "Silvercoat Lion", 1); + assertPermanentCount(playerA, "Silvercoat Lion", 0); + + } } From 866c1c3973cc6b1f448a08dec485724bf502b4f0 Mon Sep 17 00:00:00 2001 From: Plopman <> Date: Tue, 20 Feb 2018 18:29:10 +0100 Subject: [PATCH 18/21] Fix Helm of Awakening and Sphere of Resistance text --- .../effects/common/cost/SpellsCostReductionAllEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java index 54bc9bbd81..0b7af33808 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java @@ -55,7 +55,7 @@ public class SpellsCostReductionAllEffect extends CostModificationEffectImpl { private final boolean upTo; public SpellsCostReductionAllEffect(int amount) { - this(new FilterCard("All Spells "), amount); + this(new FilterCard("Spells"), amount); } public SpellsCostReductionAllEffect(FilterCard filter, int amount) { From c8d142492f07f68ae4fddba1b8f7121e3e3ca1ed Mon Sep 17 00:00:00 2001 From: L_J Date: Tue, 20 Feb 2018 17:38:14 +0000 Subject: [PATCH 19/21] Updated watcher for Gaze of the Gorgon --- .../java/mage/watchers/common/BlockedAttackerWatcher.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mage/src/main/java/mage/watchers/common/BlockedAttackerWatcher.java b/Mage/src/main/java/mage/watchers/common/BlockedAttackerWatcher.java index 23a15d10cd..e43ed433fe 100644 --- a/Mage/src/main/java/mage/watchers/common/BlockedAttackerWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/BlockedAttackerWatcher.java @@ -89,4 +89,9 @@ public class BlockedAttackerWatcher extends Watcher { Set blockedAttackers = blockData.get(new MageObjectReference(blocker, game)); return blockedAttackers != null && blockedAttackers.contains(new MageObjectReference(attacker, game)); } + + public boolean creatureHasBlockedAttacker(MageObjectReference attacker, MageObjectReference blocker, Game game) { + Set blockedAttackers = blockData.get(blocker); + return blockedAttackers != null && blockedAttackers.contains(attacker); + } } From 6d01e8e16f9738dfce0d0f6d547389178de9191b Mon Sep 17 00:00:00 2001 From: L_J Date: Tue, 20 Feb 2018 17:39:42 +0000 Subject: [PATCH 20/21] Gaze of the Gorgon rewrite (fixes #4199) --- .../src/mage/cards/g/GazeOfTheGorgon.java | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GazeOfTheGorgon.java b/Mage.Sets/src/mage/cards/g/GazeOfTheGorgon.java index ca7d2159fd..33c2702a8b 100644 --- a/Mage.Sets/src/mage/cards/g/GazeOfTheGorgon.java +++ b/Mage.Sets/src/mage/cards/g/GazeOfTheGorgon.java @@ -27,10 +27,13 @@ */ package mage.cards.g; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.RegenerateTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -43,10 +46,6 @@ import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.watchers.common.BlockedAttackerWatcher; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - /** * * @author LevelX2 @@ -60,8 +59,7 @@ public class GazeOfTheGorgon extends CardImpl { // Regenerate target creature. At end of combat, destroy all creatures that blocked or were blocked by that creature this turn. this.getSpellAbility().addEffect(new RegenerateTargetEffect()); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect( - new AtTheEndOfCombatDelayedTriggeredAbility(new GazeOfTheGorgonEffect()))); + this.getSpellAbility().addEffect(new GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect()); this.getSpellAbility().addWatcher(new BlockedAttackerWatcher()); } @@ -75,15 +73,46 @@ public class GazeOfTheGorgon extends CardImpl { } } -class GazeOfTheGorgonEffect extends OneShotEffect { +class GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect extends OneShotEffect { - public GazeOfTheGorgonEffect() { + public GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect() { + super(Outcome.Benefit); + this.staticText = "At this turn's next end of combat, destroy all creatures that blocked or were blocked by it this turn"; + } + + public GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect(final GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect effect) { + super(effect); + } + + @Override + public GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect copy() { + return new GazeOfTheGorgonCreateDelayedTriggeredAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (!source.getTargets().isEmpty() && source.getFirstTarget() != null) { + MageObjectReference mor = new MageObjectReference(source.getFirstTarget(), game); + AtTheEndOfCombatDelayedTriggeredAbility delayedAbility = new AtTheEndOfCombatDelayedTriggeredAbility(new GazeOfTheGorgonEffect(mor)); + game.addDelayedTriggeredAbility(delayedAbility, source); + return true; + } + return false; + } +} + +class GazeOfTheGorgonEffect extends OneShotEffect { + + MageObjectReference targetCreature; + + public GazeOfTheGorgonEffect(MageObjectReference targetCreature) { super(Outcome.DestroyPermanent); - this.staticText = "destroy all creatures that blocked or were blocked by that creature this turn"; + this.targetCreature = targetCreature; } public GazeOfTheGorgonEffect(final GazeOfTheGorgonEffect effect) { super(effect); + targetCreature = effect.targetCreature; } @Override @@ -94,14 +123,13 @@ class GazeOfTheGorgonEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent targetCreature = game.getPermanentOrLKIBattlefield(source.getTargets().getFirstTarget()); if (controller != null && targetCreature != null) { BlockedAttackerWatcher watcher = (BlockedAttackerWatcher) game.getState().getWatchers().get(BlockedAttackerWatcher.class.getSimpleName()); if (watcher != null) { List toDestroy = new ArrayList<>(); for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source.getSourceId(), game)) { - if (!creature.getId().equals(targetCreature.getId())) { - if (watcher.creatureHasBlockedAttacker(creature, targetCreature, game) || watcher.creatureHasBlockedAttacker(targetCreature, creature, game)) { + if (!creature.getId().equals(targetCreature.getSourceId())) { + if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), targetCreature, game) || watcher.creatureHasBlockedAttacker(targetCreature, new MageObjectReference(creature, game), game)) { toDestroy.add(creature); } } From 42a9959a5dcd139a96caae31d90610fbf751e92a Mon Sep 17 00:00:00 2001 From: Plopman <> Date: Wed, 21 Feb 2018 00:06:38 +0100 Subject: [PATCH 21/21] WickedReward add missing target --- Mage.Sets/src/mage/cards/w/WickedReward.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Sets/src/mage/cards/w/WickedReward.java b/Mage.Sets/src/mage/cards/w/WickedReward.java index 90499a3f4a..9261954c8d 100644 --- a/Mage.Sets/src/mage/cards/w/WickedReward.java +++ b/Mage.Sets/src/mage/cards/w/WickedReward.java @@ -8,6 +8,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; public class WickedReward extends CardImpl { @@ -19,6 +20,7 @@ public class WickedReward extends CardImpl { //Target creature gets +4/+2 until end of turn. this.getSpellAbility().addEffect(new BoostTargetEffect(4, 2, Duration.EndOfTurn)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } public WickedReward(WickedReward other){