From a63e024ea45d3856ce5581d3d05de4e2d46f6c03 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 6 Oct 2017 12:29:14 -0400 Subject: [PATCH 01/77] initial setup for phasing fix --- .../src/main/java/mage/game/permanent/Permanent.java | 4 ++++ .../main/java/mage/game/permanent/PermanentImpl.java | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index fedf9aad487..aaf0b42d132 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -80,8 +80,12 @@ public interface Permanent extends Card, Controllable { boolean phaseIn(Game game); + boolean phaseIn(Game game, boolean indirectPhase); + boolean phaseOut(Game game); + boolean phaseOut(Game game, boolean indirectPhase); + boolean isMonstrous(); void setMonstrous(boolean value); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index f4bd39bb1a8..464cbabb422 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -89,6 +89,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected boolean controlledFromStartOfControllerTurn; protected int turnsOnBattlefield; protected boolean phasedIn = true; + protected boolean indirectPhase = false; protected boolean faceDown; protected boolean attacking; protected int blocking; @@ -138,6 +139,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.controlledFromStartOfControllerTurn = permanent.controlledFromStartOfControllerTurn; this.turnsOnBattlefield = permanent.turnsOnBattlefield; this.phasedIn = permanent.phasedIn; + this.indirectPhase = permanent.indirectPhase; this.faceDown = permanent.faceDown; this.attacking = permanent.attacking; this.blocking = permanent.blocking; @@ -463,6 +465,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public boolean phaseIn(Game game) { + return phaseIn(game, false); + } + + @Override + public boolean phaseIn(Game game, boolean indirectPhase) { if (!phasedIn) { if (!replaceEvent(EventType.PHASE_IN, game)) { this.phasedIn = true; @@ -478,6 +485,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { @Override public boolean phaseOut(Game game) { + return phaseOut(game, false); + } + + @Override + public boolean phaseOut(Game game, boolean indirectPhase) { if (phasedIn) { if (!replaceEvent(EventType.PHASE_OUT, game)) { this.phasedIn = false; From 3d20e4dbefd0791c07a170a4c4ec164b29ce493c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Tue, 10 Oct 2017 13:35:41 -0400 Subject: [PATCH 02/77] changed how phasing is handled --- Mage.Sets/src/mage/cards/e/Equipoise.java | 7 ++--- .../src/mage/cards/t/TeferisProtection.java | 6 +++- .../java/mage/game/permanent/Permanent.java | 4 ++- .../mage/game/permanent/PermanentImpl.java | 28 ++++++++++++++++--- .../main/java/mage/players/PlayerImpl.java | 13 ++++----- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/Mage.Sets/src/mage/cards/e/Equipoise.java b/Mage.Sets/src/mage/cards/e/Equipoise.java index 7f3f5268041..a1606980a8d 100644 --- a/Mage.Sets/src/mage/cards/e/Equipoise.java +++ b/Mage.Sets/src/mage/cards/e/Equipoise.java @@ -55,8 +55,7 @@ import mage.target.TargetPlayer; public class Equipoise extends CardImpl { public Equipoise(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); // At the beginning of your upkeep, for each land target player controls in excess of the number you control, choose a land he or she controls, then the chosen permanents phase out. Repeat this process for artifacts and creatures. Ability ability = new BeginningOfUpkeepTriggeredAbility(new EquipoiseEffect(), TargetController.YOU, false); @@ -112,12 +111,12 @@ class EquipoiseEffect extends OneShotEffect { int numberTargetPlayer = game.getBattlefield().count(filter, source.getSourceId(), targetPlayer.getId(), game); int excess = numberTargetPlayer - numberController; if (excess > 0) { - FilterPermanent filterChoose = new FilterPermanent(cardType.toString().toLowerCase() + (excess > 1 ? "s":"") +" of target player"); + FilterPermanent filterChoose = new FilterPermanent(cardType.toString().toLowerCase() + (excess > 1 ? "s" : "") + " of target player"); filterChoose.add(new ControllerIdPredicate(targetPlayer.getId())); filterChoose.add(new CardTypePredicate(cardType)); Target target = new TargetPermanent(excess, excess, filterChoose, true); controller.chooseTarget(outcome, target, source, game); - for (UUID permanentId:target.getTargets()) { + for (UUID permanentId : target.getTargets()) { Permanent permanent = game.getPermanent(permanentId); if (permanent != null) { permanent.phaseOut(game); diff --git a/Mage.Sets/src/mage/cards/t/TeferisProtection.java b/Mage.Sets/src/mage/cards/t/TeferisProtection.java index a08ad3ba122..b9ae7df6090 100644 --- a/Mage.Sets/src/mage/cards/t/TeferisProtection.java +++ b/Mage.Sets/src/mage/cards/t/TeferisProtection.java @@ -171,7 +171,11 @@ class TeferisProtectionPhaseOutEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_PERMANENT, controller.getId(), game)) { - permanent.phaseOut(game); + Permanent attachedTo = game.getPermanent(permanent.getAttachedTo()); + // don't phase out auras directly if they're attached to your stuff + if (!(attachedTo != null && attachedTo.getControllerId().equals(controller.getId()))) { + permanent.phaseOut(game); + } } return true; } diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index aaf0b42d132..45e8226439f 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -78,9 +78,11 @@ public interface Permanent extends Card, Controllable { boolean isPhasedIn(); + boolean isPhasedOutIndirectly(); + boolean phaseIn(Game game); - boolean phaseIn(Game game, boolean indirectPhase); + boolean phaseIn(Game game, boolean onlyDirect); boolean phaseOut(Game game); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 464cbabb422..c30051d825d 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -464,18 +464,31 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } @Override - public boolean phaseIn(Game game) { - return phaseIn(game, false); + public boolean isPhasedOutIndirectly() { + return !phasedIn && indirectPhase; } @Override - public boolean phaseIn(Game game, boolean indirectPhase) { + public boolean phaseIn(Game game) { + return phaseIn(game, true); + } + + @Override + public boolean phaseIn(Game game, boolean onlyDirect) { if (!phasedIn) { - if (!replaceEvent(EventType.PHASE_IN, game)) { + if (!replaceEvent(EventType.PHASE_IN, game) + && ((onlyDirect && !indirectPhase) || (!onlyDirect))) { this.phasedIn = true; + this.indirectPhase = false; if (!game.isSimulation()) { game.informPlayers(getLogName() + " phased in"); } + for (UUID attachedId : this.getAttachments()) { + Permanent attachedPerm = game.getPermanent(attachedId); + if (attachedPerm != null) { + attachedPerm.phaseIn(game, false); + } + } fireEvent(EventType.PHASED_IN, game); return true; } @@ -492,7 +505,14 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { public boolean phaseOut(Game game, boolean indirectPhase) { if (phasedIn) { if (!replaceEvent(EventType.PHASE_OUT, game)) { + for (UUID attachedId : this.getAttachments()) { + Permanent attachedPerm = game.getPermanent(attachedId); + if (attachedPerm != null) { + attachedPerm.phaseOut(game, true); + } + } this.phasedIn = false; + this.indirectPhase = indirectPhase; if (!game.isSimulation()) { game.informPlayers(getLogName() + " phased out"); } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 1567246fdae..cb5e474cf28 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1484,16 +1484,15 @@ public abstract class PlayerImpl implements Player, Serializable { // phasing out is known as phasing out "indirectly." An enchantment or Equipment // that phased out indirectly won't phase in by itself, but instead phases in // along with the card it's attached to. - for (UUID attachmentId : permanent.getAttachments()) { - Permanent attachment = game.getPermanent(attachmentId); - if (attachment != null) { - attachment.phaseOut(game); - } + Permanent attachedTo = game.getPermanent(permanent.getAttachedTo()); + if (!(attachedTo != null && attachedTo.getControllerId().equals(this.getId()))) { + permanent.phaseOut(game, false); } - permanent.phaseOut(game); } for (Permanent permanent : phasedOut) { - permanent.phaseIn(game); + if (!permanent.isPhasedOutIndirectly()) { + permanent.phaseIn(game); + } } } From fdf3f831ca487bf0df8946f5d600f495d0691385 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Tue, 10 Oct 2017 14:31:00 -0400 Subject: [PATCH 03/77] updated cards which phase things out to properly handle indirect phasing (#4071) --- Mage.Sets/src/mage/cards/e/Equipoise.java | 9 +- Mage.Sets/src/mage/cards/t/Taniwha.java | 15 ++-- .../src/mage/cards/t/TeferisProtection.java | 12 +-- Mage.Sets/src/mage/cards/t/TeferisRealm.java | 11 ++- .../effects/common/PhaseOutAllEffect.java | 85 +++++++++++++++++++ 5 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/effects/common/PhaseOutAllEffect.java diff --git a/Mage.Sets/src/mage/cards/e/Equipoise.java b/Mage.Sets/src/mage/cards/e/Equipoise.java index a1606980a8d..46aafab810d 100644 --- a/Mage.Sets/src/mage/cards/e/Equipoise.java +++ b/Mage.Sets/src/mage/cards/e/Equipoise.java @@ -27,11 +27,13 @@ */ package mage.cards.e; +import java.util.List; import java.util.Objects; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PhaseOutAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -116,12 +118,7 @@ class EquipoiseEffect extends OneShotEffect { filterChoose.add(new CardTypePredicate(cardType)); Target target = new TargetPermanent(excess, excess, filterChoose, true); controller.chooseTarget(outcome, target, source, game); - for (UUID permanentId : target.getTargets()) { - Permanent permanent = game.getPermanent(permanentId); - if (permanent != null) { - permanent.phaseOut(game); - } - } + new PhaseOutAllEffect(target.getTargets()).apply(game, source); } } } diff --git a/Mage.Sets/src/mage/cards/t/Taniwha.java b/Mage.Sets/src/mage/cards/t/Taniwha.java index ac9524bd133..6e7897ec3c8 100644 --- a/Mage.Sets/src/mage/cards/t/Taniwha.java +++ b/Mage.Sets/src/mage/cards/t/Taniwha.java @@ -27,11 +27,14 @@ */ package mage.cards.t; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PhaseOutAllEffect; import mage.abilities.keyword.PhasingAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; @@ -41,6 +44,7 @@ import mage.constants.SubType; import mage.constants.Outcome; import mage.constants.SuperType; import mage.constants.TargetController; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledLandPermanent; import mage.game.Game; import mage.game.permanent.Permanent; @@ -53,7 +57,7 @@ import mage.players.Player; public class Taniwha extends CardImpl { public Taniwha(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.SERPENT); this.power = new MageInt(7); @@ -61,10 +65,10 @@ public class Taniwha extends CardImpl { // Trample this.addAbility(TrampleAbility.getInstance()); - + // Phasing this.addAbility(PhasingAbility.getInstance()); - + // At the beginning of your upkeep, all lands you control phase out. this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TaniwhaEffect(), TargetController.YOU, false)); } @@ -99,10 +103,11 @@ class TaniwhaEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { + List permIds = new ArrayList<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterControlledLandPermanent(), controller.getId(), game)) { - permanent.phaseOut(game); + permIds.add(permanent.getId()); } - return true; + return new PhaseOutAllEffect(permIds).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/t/TeferisProtection.java b/Mage.Sets/src/mage/cards/t/TeferisProtection.java index b9ae7df6090..98701104c96 100644 --- a/Mage.Sets/src/mage/cards/t/TeferisProtection.java +++ b/Mage.Sets/src/mage/cards/t/TeferisProtection.java @@ -27,11 +27,14 @@ */ package mage.cards.t; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.effects.common.PhaseOutAllEffect; import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; import mage.abilities.effects.common.continuous.LifeTotalCantChangeControllerEffect; import mage.abilities.keyword.ProtectionAbility; @@ -170,14 +173,11 @@ class TeferisProtectionPhaseOutEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { + List permIds = new ArrayList<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_PERMANENT, controller.getId(), game)) { - Permanent attachedTo = game.getPermanent(permanent.getAttachedTo()); - // don't phase out auras directly if they're attached to your stuff - if (!(attachedTo != null && attachedTo.getControllerId().equals(controller.getId()))) { - permanent.phaseOut(game); - } + permIds.add(permanent.getId()); } - return true; + return new PhaseOutAllEffect(permIds).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/t/TeferisRealm.java b/Mage.Sets/src/mage/cards/t/TeferisRealm.java index d74b69fcb16..da7adaa853d 100644 --- a/Mage.Sets/src/mage/cards/t/TeferisRealm.java +++ b/Mage.Sets/src/mage/cards/t/TeferisRealm.java @@ -27,6 +27,7 @@ */ package mage.cards.t; +import java.util.ArrayList; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -45,8 +46,11 @@ import mage.game.permanent.Permanent; import mage.players.Player; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; +import mage.abilities.effects.common.PhaseOutAllEffect; +import mage.filter.common.FilterControlledLandPermanent; /** * @@ -55,7 +59,7 @@ import java.util.UUID; public class TeferisRealm extends CardImpl { public TeferisRealm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{U}"); addSuperType(SuperType.WORLD); // At the beginning of each player's upkeep, that player chooses artifact, creature, land, or non-Aura enchantment. All nontoken permanents of that type phase out. @@ -135,10 +139,11 @@ class TeferisRealmEffect extends OneShotEffect { return false; } game.informPlayers(player.getLogName() + " chooses " + choosenType + "s to phase out"); + List permIds = new ArrayList<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, controller.getId(), game)) { - permanent.phaseOut(game); + permIds.add(permanent.getId()); } - return true; + return new PhaseOutAllEffect(permIds).apply(game, source); } return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PhaseOutAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PhaseOutAllEffect.java new file mode 100644 index 00000000000..0ac346eda27 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/PhaseOutAllEffect.java @@ -0,0 +1,85 @@ +/* + * 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.abilities.effects.common; + +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * This class should only be used within the application of another effect + * + * @author TheElk801 + */ +public class PhaseOutAllEffect extends OneShotEffect { + + private final List idList; + + public PhaseOutAllEffect(List idList) { + super(Outcome.Neutral); + this.idList = idList; + } + + public PhaseOutAllEffect(final PhaseOutAllEffect effect) { + super(effect); + this.idList = effect.idList; + } + + @Override + public PhaseOutAllEffect copy() { + return new PhaseOutAllEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + // First we phase out everything that isn't attached to anything + // Anything attached to these permanents will phase out indirectly + for (UUID permanentId : idList) { + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null) { + Permanent attachedTo = game.getPermanent(permanent.getAttachedTo()); + if (attachedTo == null) { + permanent.phaseOut(game); + } + } + } + // Once this is done, we'll have permanents which are attached to something but haven't phased out + // These will be phased out directly + for (UUID permanentId : idList) { + Permanent permanent = game.getPermanent(permanentId); + if (permanent != null && permanent.isPhasedIn()) { + permanent.phaseOut(game); + } + } + return true; + } +} From 58d3fc2328ae753845cc8dfcbabe429d78fbdd7f Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 21 Oct 2017 16:13:45 +0200 Subject: [PATCH 04/77] Fixed player leaving/conceding handling. --- .../src/mage/player/ai/ComputerPlayer6.java | 8 +- .../src/mage/player/ai/ComputerPlayer7.java | 10 +- .../mage/player/ai/GameStateEvaluator2.java | 2 +- .../player/ai/simulators/ActionSimulator.java | 2 +- .../src/mage/player/ai/MCTSNode.java | 6 +- .../src/mage/player/ai/ComputerPlayer2.java | 8 +- .../src/mage/player/ai/ComputerPlayer3.java | 12 +- .../mage/player/ai/GameStateEvaluator.java | 2 +- .../src/mage/player/human/HumanPlayer.java | 36 +- .../src/mage/player/human/PlayerResponse.java | 17 +- .../src/mage/cards/w/WatertrapWeaver.java | 2 +- .../multiplayer/PlayerLeftGameRange1Test.java | 746 +++++++++--------- .../java/org/mage/test/player/TestPlayer.java | 7 + .../java/org/mage/test/stub/PlayerStub.java | 10 +- Mage/src/main/java/mage/game/Game.java | 4 +- Mage/src/main/java/mage/game/GameImpl.java | 99 ++- .../main/java/mage/game/combat/Combat.java | 4 +- Mage/src/main/java/mage/game/turn/Phase.java | 20 +- Mage/src/main/java/mage/game/turn/Turn.java | 6 +- Mage/src/main/java/mage/players/Player.java | 2 + .../main/java/mage/players/PlayerImpl.java | 13 +- 21 files changed, 553 insertions(+), 463 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index db6da80b747..f281d360e21 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -519,7 +519,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { logger.trace("interrupted - " + val); return val; } - if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.gameOver(null)) { + if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) { logger.trace("Add actions -- reached end state, node count=" + SimulationNode2.nodeCount + ", depth=" + depth); val = GameStateEvaluator2.evaluate(playerId, game); UUID currentPlayerId = node.getGame().getPlayerList().get(); @@ -540,7 +540,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { } } - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { val = GameStateEvaluator2.evaluate(playerId, game); } else if (!node.getChildren().isEmpty()) { //declared attackers or blockers or triggered abilities @@ -588,7 +588,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { logger.debug("Sim Prio [" + depth + "] -- repeated action: " + action.toString()); continue; } - if (!sim.gameOver(null) && action.isUsesStack()) { + if (!sim.checkIfGameIsOver() && action.isUsesStack()) { // only pass if the last action uses the stack UUID nextPlayerId = sim.getPlayerList().get(); do { @@ -864,7 +864,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { break; case CLEANUP: game.getPhase().getStep().beginStep(game, activePlayerId); - if (!game.checkStateAndTriggered() && !game.gameOver(null)) { + if (!game.checkStateAndTriggered() && !game.checkIfGameIsOver()) { game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); game.getTurn().setPhase(new BeginningPhase()); game.getPhase().setStep(new UntapStep()); diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java index 99bce93ce88..48f46b53f54 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java @@ -233,7 +233,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { return GameStateEvaluator2.evaluate(playerId, game); } // Condition to stop deeper simulation - if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.gameOver(null)) { + if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) { val = GameStateEvaluator2.evaluate(playerId, game); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>'); @@ -267,7 +267,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { } } - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { val = GameStateEvaluator2.evaluate(playerId, game); } else if (stepFinished) { logger.debug("Step finished"); @@ -481,7 +481,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); finishCombat(sim); - if (sim.gameOver(null)) { + if (sim.checkIfGameIsOver()) { val = GameStateEvaluator2.evaluate(playerId, sim); } else if (!counter) { val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta); @@ -549,7 +549,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { logger.debug("interrupted"); return; } - if (!game.gameOver(null)) { + if (!game.checkIfGameIsOver()) { game.getPhase().setStep(step); if (!step.skipStep(game, game.getActivePlayerId())) { step.beginStep(game, game.getActivePlayerId()); @@ -598,7 +598,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { logger.debug("interrupted"); return; } - if (!game.gameOver(null)) { + if (!game.checkIfGameIsOver()) { game.getTurn().getPhase().endPhase(game, game.getActivePlayerId()); game.getTurn().setPhase(new EndPhase()); if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) { diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java index 09f8b291231..580f47ba324 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/GameStateEvaluator2.java @@ -33,7 +33,7 @@ public final class GameStateEvaluator2 { public static int evaluate(UUID playerId, Game game) { Player player = game.getPlayer(playerId); Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { if (player.hasLost() || opponent.hasWon()) { return LOSE_GAME_SCORE; } diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/ActionSimulator.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/ActionSimulator.java index b18c34dec53..e466891d3ef 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/ActionSimulator.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/simulators/ActionSimulator.java @@ -61,7 +61,7 @@ public class ActionSimulator { public int evaluateState() { Player opponent = game.getPlayer(game.getOpponents(player.getId()).iterator().next()); - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { if (player.hasLost() || opponent.hasWon()) { return Integer.MIN_VALUE; } diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java index 7eef75f10e4..86a60967989 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSNode.java @@ -79,7 +79,7 @@ public class MCTSNode { this.game = game; this.stateValue = game.getState().getValue(game, targetPlayer); this.fullStateValue = game.getState().getValue(true, game); - this.terminal = game.gameOver(null); + this.terminal = game.checkIfGameIsOver(); setPlayer(); nodeCount = 1; // logger.info(this.stateValue); @@ -90,7 +90,7 @@ public class MCTSNode { this.game = game; this.stateValue = game.getState().getValue(game, targetPlayer); this.fullStateValue = game.getState().getValue(true, game); - this.terminal = game.gameOver(null); + this.terminal = game.checkIfGameIsOver(); this.parent = parent; this.action = action; setPlayer(); @@ -104,7 +104,7 @@ public class MCTSNode { this.combat = combat; this.stateValue = game.getState().getValue(game, targetPlayer); this.fullStateValue = game.getState().getValue(true, game); - this.terminal = game.gameOver(null); + this.terminal = game.checkIfGameIsOver(); this.parent = parent; setPlayer(); nodeCount++; diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java index 681fcac0fbc..fa86b2205a1 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer2.java @@ -330,7 +330,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player { return GameStateEvaluator.evaluate(playerId, game); } int val; - if (node.depth > maxDepth || game.gameOver(null)) { + if (node.depth > maxDepth || game.checkIfGameIsOver()) { logger.debug(indent(node.depth) + "simulating -- reached end state"); val = GameStateEvaluator.evaluate(playerId, game); } @@ -357,7 +357,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player { } } - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { val = GameStateEvaluator.evaluate(playerId, game); } else if (!node.getChildren().isEmpty()) { @@ -403,7 +403,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player { logger.debug(indent(node.depth) + "found useless action: " + action); continue; } - if (!sim.gameOver(null) && action.isUsesStack()) { + if (!sim.checkIfGameIsOver() && action.isUsesStack()) { // only pass if the last action uses the stack sim.getPlayer(currentPlayer.getId()).pass(game); sim.getPlayerList().getNext(); @@ -588,7 +588,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player { break; case CLEANUP: game.getPhase().getStep().beginStep(game, activePlayerId); - if (!game.checkStateAndTriggered() && !game.gameOver(null)) { + if (!game.checkStateAndTriggered() && !game.checkIfGameIsOver()) { game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); game.getTurn().setPhase(new BeginningPhase()); game.getPhase().setStep(new UntapStep()); diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java index b5233608078..ec7c6f409d4 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java @@ -184,7 +184,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { logger.debug(indent(node.depth) + "interrupted"); return GameStateEvaluator.evaluate(playerId, game); } - if (node.depth > maxDepth || game.gameOver(null)) { + if (node.depth > maxDepth || game.checkIfGameIsOver()) { logger.debug(indent(node.depth) + "simulating -- reached end state"); val = GameStateEvaluator.evaluate(playerId, game); } @@ -204,7 +204,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } } - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { val = GameStateEvaluator.evaluate(playerId, game); } else if (stepFinished) { @@ -408,7 +408,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); Combat simCombat = sim.getCombat().copy(); finishCombat(sim); - if (sim.gameOver(null)) { + if (sim.checkIfGameIsOver()) { val = GameStateEvaluator.evaluate(playerId, sim); } else if (!counter) { @@ -450,7 +450,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { return GameStateEvaluator.evaluate(playerId, game); } Integer val = null; - if (!game.gameOver(null)) { + if (!game.checkIfGameIsOver()) { logger.debug(indent(node.depth) + "simulating -- ending turn"); simulateToEnd(game); game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext()); @@ -478,7 +478,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { logger.debug("interrupted"); return; } - if (!game.gameOver(null)) { + if (!game.checkIfGameIsOver()) { game.getPhase().setStep(step); if (!step.skipStep(game, game.getActivePlayerId())) { step.beginStep(game, game.getActivePlayerId()); @@ -526,7 +526,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { logger.debug("interrupted"); return; } - if (!game.gameOver(null)) { + if (!game.checkIfGameIsOver()) { game.getTurn().getPhase().endPhase(game, game.getActivePlayerId()); game.getTurn().setPhase(new EndPhase()); if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) { diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java index e45943dad8e..df9e389d031 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/GameStateEvaluator.java @@ -70,7 +70,7 @@ public final class GameStateEvaluator { public static int evaluate(UUID playerId, Game game, boolean ignoreTapped) { Player player = game.getPlayer(playerId); Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next()); - if (game.gameOver(null)) { + if (game.checkIfGameIsOver()) { if (player.hasLost() || opponent.hasWon()) return LOSE_SCORE; if (opponent.hasLost() || player.hasWon()) diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 1e934e19156..06a47786461 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -53,6 +53,7 @@ import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; +import mage.game.GameImpl; import mage.game.combat.CombatGroup; import mage.game.draft.Draft; import mage.game.events.GameEvent; @@ -186,13 +187,25 @@ public class HumanPlayer extends PlayerImpl { response.clear(); logger.debug("Waiting response from player: " + getId()); game.resumeTimer(getTurnControlledBy()); - synchronized (response) { - try { - response.wait(); - } catch (InterruptedException ex) { - logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex); - } finally { - game.pauseTimer(getTurnControlledBy()); + boolean loop = true; + while (loop) { + loop = false; + synchronized (response) { + try { + response.wait(); + } catch (InterruptedException ex) { + logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex); + } finally { + game.pauseTimer(getTurnControlledBy()); + } + } + if (response.getResponseConcedeCheck()) { + ((GameImpl) game).checkConcede(); + if (game.hasEnded()) { + return; + } + response.clear(); + loop = true; } } if (recordingMacro && !macroTriggeredSelectionFlag) { @@ -1706,6 +1719,15 @@ public class HumanPlayer extends PlayerImpl { } } + @Override + public void signalPlayerConcede() { + synchronized (response) { + response.setResponseConcedeCheck(); + response.notifyAll(); + logger.debug("Set check concede for waiting player: " + getId()); + } + } + @Override public void skip() { synchronized (response) { diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/PlayerResponse.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/PlayerResponse.java index 2f1e5871c29..b4e7b063bd1 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/PlayerResponse.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/PlayerResponse.java @@ -43,6 +43,7 @@ public class PlayerResponse implements Serializable { private Integer responseInteger; private ManaType responseManaType; private UUID responseManaTypePlayerId; + private Boolean responseConcedeCheck; public PlayerResponse() { clear(); @@ -55,7 +56,8 @@ public class PlayerResponse implements Serializable { + ',' + responseBoolean + ',' + responseInteger + ',' + responseManaType - + ',' + responseManaTypePlayerId; + + ',' + responseManaTypePlayerId + + ',' + responseConcedeCheck; } public PlayerResponse(PlayerResponse other) { @@ -69,6 +71,7 @@ public class PlayerResponse implements Serializable { responseInteger = other.responseInteger; responseManaType = other.responseManaType; responseManaTypePlayerId = other.responseManaTypePlayerId; + responseConcedeCheck = other.responseConcedeCheck; } public void clear() { @@ -78,6 +81,7 @@ public class PlayerResponse implements Serializable { responseInteger = null; responseManaType = null; responseManaTypePlayerId = null; + responseConcedeCheck = null; } public String getString() { @@ -104,6 +108,17 @@ public class PlayerResponse implements Serializable { this.responseBoolean = responseBoolean; } + public Boolean getResponseConcedeCheck() { + if (responseConcedeCheck == null) { + return false; + } + return responseConcedeCheck; + } + + public void setResponseConcedeCheck() { + this.responseConcedeCheck = true; + } + public Integer getInteger() { return responseInteger; } diff --git a/Mage.Sets/src/mage/cards/w/WatertrapWeaver.java b/Mage.Sets/src/mage/cards/w/WatertrapWeaver.java index 2820a85eeed..703eb4ae9a5 100644 --- a/Mage.Sets/src/mage/cards/w/WatertrapWeaver.java +++ b/Mage.Sets/src/mage/cards/w/WatertrapWeaver.java @@ -61,7 +61,7 @@ public class WatertrapWeaver extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(2); - // When Watertrap Weaver enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step. + // When Watertrap Weaver enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step. EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); ability.addEffect(new DontUntapInControllersNextUntapStepTargetEffect("that creature")); ability.addTarget(new TargetCreaturePermanent(filter)); diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java index 69424f14255..1ac18334e58 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRange1Test.java @@ -1,373 +1,373 @@ -/* - * 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 org.mage.test.multiplayer; - -import java.io.FileNotFoundException; -import mage.constants.MultiplayerAttackOption; -import mage.constants.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; -import mage.counters.CounterType; -import mage.game.FreeForAll; -import mage.game.Game; -import mage.game.GameException; -import mage.game.permanent.Permanent; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestMultiPlayerBase; - -/** - * - * @author LevelX2 - */ -public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase { - - @Override - protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { - // Start Life = 2 - Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2); - // Player order: A -> D -> C -> B - playerA = createPlayer(game, playerA, "PlayerA"); - playerB = createPlayer(game, playerB, "PlayerB"); - playerC = createPlayer(game, playerC, "PlayerC"); - playerD = createPlayer(game, playerD, "PlayerD"); - return game; - } - - /** - * Tests Enchantment to control other permanent - */ - @Test - public void TestControlledByEnchantment() { - addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 4); - // Enchant creature - // You control enchanted creature. - addCard(Zone.HAND, playerA, "Control Magic"); - - addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Control Magic", "Rootwater Commando"); - - attack(3, playerC, "Silvercoat Lion", playerB); - - setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertLife(playerB, 0); - assertPermanentCount(playerB, 0); - assertPermanentCount(playerA, "Rootwater Commando", 0); - assertGraveyardCount(playerA, "Control Magic", 1); - - } - - /** - * Tests Sorcery to control other players permanent - */ - @Test - public void TestControlledBySorcery() { - addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 4); - // Exchange control of target artifact or creature and another target permanent that shares one of those types with it. - // (This effect lasts indefinitely.) - addCard(Zone.HAND, playerA, "Legerdemain"); // Sorcery - addCard(Zone.BATTLEFIELD, playerA, "Wall of Air"); - - addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Legerdemain", "Rootwater Commando^Wall of Air"); - - attack(3, playerC, "Silvercoat Lion", playerB); - - setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertLife(playerB, 0); - assertGraveyardCount(playerA, "Legerdemain", 1); - assertPermanentCount(playerB, 0); - assertPermanentCount(playerA, "Rootwater Commando", 0); // removed from game because player B left - assertPermanentCount(playerB, "Wall of Air", 0); - assertGraveyardCount(playerA, "Wall of Air", 0); - assertPermanentCount(playerA, "Wall of Air", 1); // Returned back to player A - - } - - /** - * Tests Instant to control other permanent - */ - @Test - public void TestOtherPlayerControllsCreature() { - addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); - - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); - // Untap target nonlegendary creature and gain control of it until end of turn. That creature gains haste until end of turn. - addCard(Zone.HAND, playerA, "Blind with Anger"); // Instant - - addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando"); - - attack(3, playerC, "Silvercoat Lion", playerB); - - setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertLife(playerB, 0); - assertGraveyardCount(playerA, "Blind with Anger", 1); - assertPermanentCount(playerB, 0); - assertPermanentCount(playerA, "Rootwater Commando", 0); // Removed from game because player C left - assertPermanentCount(playerA, "Rootwater Commando", 0); // Returned back to player A - } - - /** - * Xmage throws an error involving an emblem unable to find the initial - * source if it has a proc. To reproduce, a Planeswalker was taken from an - * original player's control, such as using Scrambleverse to shuffle Jace, - * Unraveler of Secrets, to a second player and then the second player uses - * Jace's ability to create an emblem ("Whenever an opponent casts his or - * her first spell each turn, counter that spell."). Then the original - * player concedes the game and removes the Planeswalker. Once it becomes an - * opponent of the original player's turn and that opponent plays a spell, - * Xmage throws an error and rollsback the turn. - * - * I don't have the actual error report on my due to negligence, but what I - * can recollect is that the error message was along the lines of "The - * emblem cannot find the original source. This turn will be rolled back". - * This error message will always appear when an opponent tries to play a - * spell. Player order: A -> D -> C -> B - */ - @Test - public void TestOtherPlayerPlaneswalkerCreatedEmblem() { - // +1: Scry 1, then draw a card. - // -2: Return target creature to its owner's hand. - // -8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell." - addCard(Zone.BATTLEFIELD, playerB, "Jace, Unraveler of Secrets"); - addCounters(1, PhaseStep.DRAW, playerB, "Jace, Unraveler of Secrets", CounterType.LOYALTY, 8); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 6); - // Enchant permanent (Target a permanent as you cast this. This card enters the battlefield attached to that permanent.) - // You control enchanted permanent. - addCard(Zone.HAND, playerA, "Confiscate"); // Enchantment Aura - - addCard(Zone.BATTLEFIELD, playerC, "Plains", 2); - addCard(Zone.HAND, playerC, "Silvercoat Lion"); - - addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); - addCard(Zone.HAND, playerD, "Silvercoat Lion"); - - addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Confiscate", "Jace, Unraveler of Secrets"); - activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-8: You get an emblem with"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando"); - - attack(3, playerC, "Silvercoat Lion", playerB); - castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Silvercoat Lion"); - - castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); - - setStopAt(5, PhaseStep.END_TURN); - execute(); - - assertLife(playerB, 0); - assertPermanentCount(playerB, 0); - assertGraveyardCount(playerA, "Confiscate", 1); - assertPermanentCount(playerA, "Jace, Unraveler of Secrets", 0); // Removed from game because player C left the game - assertEmblemCount(playerA, 1); - assertPermanentCount(playerC, "Silvercoat Lion", 2); // Emblem does not work yet on player C, because range 1 - assertGraveyardCount(playerD, "Silvercoat Lion", 1); // Emblem should counter the spell - } - - /** - * Situation: I attacked an opponent with some creatures with True - * Conviction in play. There were multiple "deals combat damage to a - * player"-triggers (Edric, Spymaster of Trest, Daxos of Meletis et al), - * then the opponent lost the game during the first strike combat - * damage-step . In the second combat damage step the triggers went on the - * stack again, although there was no player being dealt damage (multiplayer - * game, so the game wasn't over yet). I don't think these abilities should - * trigger again here. - */ - @Test - public void TestPlayerDiesDuringFirstStrikeDamageStep() { - // Creatures you control have double strike and lifelink. - addCard(Zone.BATTLEFIELD, playerD, "True Conviction"); - // Whenever a creature deals combat damage to one of your opponents, its controller may draw a card. - addCard(Zone.BATTLEFIELD, playerD, "Edric, Spymaster of Trest"); - addCard(Zone.BATTLEFIELD, playerD, "Dross Crocodile", 8); // Creature 5/1 - - attack(2, playerD, "Dross Crocodile", playerC); - - setStopAt(3, PhaseStep.END_TURN); - execute(); - - assertLife(playerC, -3); - assertLife(playerD, 7); - - assertHandCount(playerD, 2); // 1 (normal draw) + 1 from True Convition - assertPermanentCount(playerC, 0); - - } - - /** - * I've encountered a case today where someone conceded on their turn. The - * remaining phases were went through as normal, but my Luminarch Ascension - * did not trigger during the end step. - */ - // Player order: A -> D -> C -> B - @Test - public void TestTurnEndTrigger() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension. - // {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it.. - addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W} - - addCard(Zone.HAND, playerC, "Lightning Bolt"); - addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1); - - addCard(Zone.HAND, playerD, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); - castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerD); - - setStopAt(3, PhaseStep.PRECOMBAT_MAIN); - execute(); - - assertPermanentCount(playerA, "Luminarch Ascension", 1); - assertGraveyardCount(playerC, "Lightning Bolt", 1); - - assertLife(playerD, -1); - Assert.assertFalse("Player D is no longer in the game", playerD.isInGame()); - - assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2 - } - - @Test - public void TestTurnEndTriggerAfterConcede() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - // At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension. - // {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it.. - addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W} - - addCard(Zone.HAND, playerD, "Silvercoat Lion"); - addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); - - concede(2, PhaseStep.BEGIN_COMBAT, playerD); - - setStopAt(3, PhaseStep.PRECOMBAT_MAIN); - execute(); - - assertPermanentCount(playerA, "Luminarch Ascension", 1); - - assertLife(playerD, 2); - Assert.assertFalse("Player D is no longer in the game", playerD.isInGame()); - - assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2 - } - - /** - * Pithing Needle keeps the named card's abilities disabled even after the - * player controlling the Needle loses the game. - * - * I saw it happen during a Commander game. A player cast Pithing Needle - * targeting my Proteus Staff. After I killed him, I still couldn't activate - * the Staff. - */ - @Test - public void TestPithingNeedle() { - addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); - // As Pithing Needle enters the battlefield, name a card. - // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. - addCard(Zone.HAND, playerA, "Pithing Needle"); // Artifact {1} - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); - addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1); - - addCard(Zone.BATTLEFIELD, playerD, "Island", 3); - // {2}{U}, {T}: Put target creature on the bottom of its owner's library. That creature's controller reveals cards from the - // top of his or her library until he or she reveals a creature card. The player puts that card onto the battlefield and the - // rest on the bottom of his or her library in any order. Activate this ability only any time you could cast a sorcery. - addCard(Zone.BATTLEFIELD, playerD, "Proteus Staff", 1); - - addCard(Zone.BATTLEFIELD, playerD, "Eager Cadet", 1); - addCard(Zone.LIBRARY, playerD, "Storm Crow", 2); - - addCard(Zone.BATTLEFIELD, playerC, "Island", 3); - addCard(Zone.BATTLEFIELD, playerC, "Proteus Staff", 1); - addCard(Zone.BATTLEFIELD, playerC, "Wall of Air", 1); - addCard(Zone.LIBRARY, playerC, "Wind Drake", 2); - - addCard(Zone.BATTLEFIELD, playerB, "Island", 3); - addCard(Zone.BATTLEFIELD, playerB, "Proteus Staff", 1); - - skipInitShuffling(); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); - setChoice(playerA, "Proteus Staff"); - - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerD, "{2}{U}", "Silvercoat Lion"); // not allowed - - activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerC, "{2}{U}", "Eager Cadet"); // allowed because Needle out of range - concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA); - - activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{U}", "Wall of Air"); // allowed because Needle lost game - - setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertPermanentCount(playerA, 0); - - assertLife(playerA, 2); - Assert.assertFalse("Player A is no longer in the game", playerA.isInGame()); - - Permanent staffPlayerD = getPermanent("Proteus Staff", playerD); - Assert.assertFalse("Staff of player D could not be used", staffPlayerD.isTapped()); - - assertPermanentCount(playerD, "Eager Cadet", 0); - assertPermanentCount(playerD, "Storm Crow", 1); - - Permanent staffPlayerC = getPermanent("Proteus Staff", playerC); - Assert.assertTrue("Staff of player C could be used", staffPlayerC.isTapped()); - - assertPermanentCount(playerC, "Wall of Air", 0); - assertPermanentCount(playerC, "Wind Drake", 1); - - Permanent staffPlayerB = getPermanent("Proteus Staff", playerB); - Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped()); - - } -} +/* + * 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 org.mage.test.multiplayer; + +import java.io.FileNotFoundException; +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBase; + +/** + * + * @author LevelX2 + */ +public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + // Start Life = 2 + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } + + /** + * Tests Enchantment to control other permanent + */ + @Test + public void TestControlledByEnchantment() { + addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + // Enchant creature + // You control enchanted creature. + addCard(Zone.HAND, playerA, "Control Magic"); + + addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Control Magic", "Rootwater Commando"); + + attack(3, playerC, "Silvercoat Lion", playerB); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 0); + assertPermanentCount(playerB, 0); + assertPermanentCount(playerA, "Rootwater Commando", 0); + assertGraveyardCount(playerA, "Control Magic", 1); + + } + + /** + * Tests Sorcery to control other players permanent + */ + @Test + public void TestControlledBySorcery() { + addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + // Exchange control of target artifact or creature and another target permanent that shares one of those types with it. + // (This effect lasts indefinitely.) + addCard(Zone.HAND, playerA, "Legerdemain"); // Sorcery + addCard(Zone.BATTLEFIELD, playerA, "Wall of Air"); + + addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Legerdemain", "Rootwater Commando^Wall of Air"); + + attack(3, playerC, "Silvercoat Lion", playerB); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 0); + assertGraveyardCount(playerA, "Legerdemain", 1); + assertPermanentCount(playerB, 0); + assertPermanentCount(playerA, "Rootwater Commando", 0); // removed from game because player B left + assertPermanentCount(playerB, "Wall of Air", 0); + assertGraveyardCount(playerA, "Wall of Air", 0); + assertPermanentCount(playerA, "Wall of Air", 1); // Returned back to player A + + } + + /** + * Tests Instant to control other permanent + */ + @Test + public void TestOtherPlayerControllsCreature() { + addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando"); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + // Untap target nonlegendary creature and gain control of it until end of turn. That creature gains haste until end of turn. + addCard(Zone.HAND, playerA, "Blind with Anger"); // Instant + + addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando"); + + attack(3, playerC, "Silvercoat Lion", playerB); + + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 0); + assertGraveyardCount(playerA, "Blind with Anger", 1); + assertPermanentCount(playerB, 0); + assertPermanentCount(playerA, "Rootwater Commando", 0); // Removed from game because player C left + assertPermanentCount(playerA, "Rootwater Commando", 0); // Returned back to player A + } + + /** + * Xmage throws an error involving an emblem unable to find the initial + * source if it has a proc. To reproduce, a Planeswalker was taken from an + * original player's control, such as using Scrambleverse to shuffle Jace, + * Unraveler of Secrets, to a second player and then the second player uses + * Jace's ability to create an emblem ("Whenever an opponent casts his or + * her first spell each turn, counter that spell."). Then the original + * player concedes the game and removes the Planeswalker. Once it becomes an + * opponent of the original player's turn and that opponent plays a spell, + * Xmage throws an error and rollsback the turn. + * + * I don't have the actual error report on my due to negligence, but what I + * can recollect is that the error message was along the lines of "The + * emblem cannot find the original source. This turn will be rolled back". + * This error message will always appear when an opponent tries to play a + * spell. Player order: A -> D -> C -> B + */ + @Test + public void TestOtherPlayerPlaneswalkerCreatedEmblem() { + // +1: Scry 1, then draw a card. + // -2: Return target creature to its owner's hand. + // -8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell." + addCard(Zone.BATTLEFIELD, playerB, "Jace, Unraveler of Secrets"); + addCounters(1, PhaseStep.DRAW, playerB, "Jace, Unraveler of Secrets", CounterType.LOYALTY, 8); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // Enchant permanent (Target a permanent as you cast this. This card enters the battlefield attached to that permanent.) + // You control enchanted permanent. + addCard(Zone.HAND, playerA, "Confiscate"); // Enchantment Aura + + addCard(Zone.BATTLEFIELD, playerC, "Plains", 2); + addCard(Zone.HAND, playerC, "Silvercoat Lion"); + + addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); + addCard(Zone.HAND, playerD, "Silvercoat Lion"); + + addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Confiscate", "Jace, Unraveler of Secrets"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-8: You get an emblem with"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando"); + + attack(3, playerC, "Silvercoat Lion", playerB); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Silvercoat Lion"); + + castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); + + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 0); + assertPermanentCount(playerB, 0); + assertGraveyardCount(playerA, "Confiscate", 1); + assertPermanentCount(playerA, "Jace, Unraveler of Secrets", 0); // Removed from game because player C left the game + assertEmblemCount(playerA, 1); + assertPermanentCount(playerC, "Silvercoat Lion", 2); // Emblem does not work yet on player C, because range 1 + assertGraveyardCount(playerD, "Silvercoat Lion", 1); // Emblem should counter the spell + } + + /** + * Situation: I attacked an opponent with some creatures with True + * Conviction in play. There were multiple "deals combat damage to a + * player"-triggers (Edric, Spymaster of Trest, Daxos of Meletis et al), + * then the opponent lost the game during the first strike combat + * damage-step . In the second combat damage step the triggers went on the + * stack again, although there was no player being dealt damage (multiplayer + * game, so the game wasn't over yet). I don't think these abilities should + * trigger again here. + */ + @Test + public void TestPlayerDiesDuringFirstStrikeDamageStep() { + // Creatures you control have double strike and lifelink. + addCard(Zone.BATTLEFIELD, playerD, "True Conviction"); + // Whenever a creature deals combat damage to one of your opponents, its controller may draw a card. + addCard(Zone.BATTLEFIELD, playerD, "Edric, Spymaster of Trest"); + addCard(Zone.BATTLEFIELD, playerD, "Dross Crocodile", 8); // Creature 5/1 + + attack(2, playerD, "Dross Crocodile", playerC); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerC, -3); + assertLife(playerD, 7); + + assertHandCount(playerD, 2); // 1 (normal draw) + 1 from True Convition + assertPermanentCount(playerC, 0); + + } + + /** + * I've encountered a case today where someone conceded on their turn. The + * remaining phases were went through as normal, but my Luminarch Ascension + * did not trigger during the end step. + */ + // Player order: A -> D -> C -> B + @Test + public void TestTurnEndTrigger() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension. + // {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it.. + addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W} + + addCard(Zone.HAND, playerC, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1); + + addCard(Zone.HAND, playerD, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); + castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerD); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Luminarch Ascension", 1); + assertGraveyardCount(playerC, "Lightning Bolt", 1); + + assertLife(playerD, -1); + Assert.assertFalse("Player D is no longer in the game", playerD.isInGame()); + + assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2 + } + + @Test + public void TestTurnEndTriggerAfterConcede() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension. + // {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it.. + addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W} + + addCard(Zone.HAND, playerD, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerD, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion"); + + concede(2, PhaseStep.BEGIN_COMBAT, playerD); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Luminarch Ascension", 1); + + assertLife(playerD, 2); + Assert.assertFalse("Player D is no longer in the game", playerD.isInGame()); + + assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2 + } + + /** + * Pithing Needle keeps the named card's abilities disabled even after the + * player controlling the Needle loses the game. + * + * I saw it happen during a Commander game. A player cast Pithing Needle + * targeting my Proteus Staff. After I killed him, I still couldn't activate + * the Staff. + */ + @Test + public void TestPithingNeedle() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + // As Pithing Needle enters the battlefield, name a card. + // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. + addCard(Zone.HAND, playerA, "Pithing Needle"); // Artifact {1} + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1); + + addCard(Zone.BATTLEFIELD, playerD, "Island", 3); + // {2}{U}, {T}: Put target creature on the bottom of its owner's library. That creature's controller reveals cards from the + // top of his or her library until he or she reveals a creature card. The player puts that card onto the battlefield and the + // rest on the bottom of his or her library in any order. Activate this ability only any time you could cast a sorcery. + addCard(Zone.BATTLEFIELD, playerD, "Proteus Staff", 1); + + addCard(Zone.BATTLEFIELD, playerD, "Eager Cadet", 1); + addCard(Zone.LIBRARY, playerD, "Storm Crow", 2); + + addCard(Zone.BATTLEFIELD, playerC, "Island", 3); + addCard(Zone.BATTLEFIELD, playerC, "Proteus Staff", 1); + addCard(Zone.BATTLEFIELD, playerC, "Wall of Air", 1); + addCard(Zone.LIBRARY, playerC, "Wind Drake", 2); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + addCard(Zone.BATTLEFIELD, playerB, "Proteus Staff", 1); + + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); + setChoice(playerA, "Proteus Staff"); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerD, "{2}{U}", "Silvercoat Lion"); // not allowed + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerC, "{2}{U}", "Eager Cadet"); // allowed because Needle out of range + concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA); + + activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{U}", "Wall of Air"); // allowed because Needle lost game + + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 2); + Assert.assertFalse("Player A is no longer in the game", playerA.isInGame()); + + assertPermanentCount(playerA, 0); + + Permanent staffPlayerD = getPermanent("Proteus Staff", playerD); + Assert.assertFalse("Staff of player D could not be used", staffPlayerD.isTapped()); + + assertPermanentCount(playerD, "Eager Cadet", 0); + assertPermanentCount(playerD, "Storm Crow", 1); + + Permanent staffPlayerC = getPermanent("Proteus Staff", playerC); + Assert.assertTrue("Staff of player C could be used", staffPlayerC.isTapped()); + + assertPermanentCount(playerC, "Wall of Air", 0); + assertPermanentCount(playerC, "Wind Drake", 1); + + Permanent staffPlayerB = getPermanent("Proteus Staff", playerB); + Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped()); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 74a9d873994..595a54d6e60 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -57,6 +57,7 @@ import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.SummoningSicknessPredicate; import mage.game.Game; +import mage.game.GameImpl; import mage.game.Graveyard; import mage.game.Table; import mage.game.combat.CombatGroup; @@ -519,6 +520,7 @@ public class TestPlayer implements Player { } if (groups[0].equals("Concede")) { game.concede(getId()); + ((GameImpl) game).checkConcede(); actions.remove(action); } } @@ -1182,6 +1184,11 @@ public class TestPlayer implements Player { computerPlayer.abort(); } + @Override + public void signalPlayerConcede() { + computerPlayer.signalPlayerConcede(); + } + @Override public void abortReset() { computerPlayer.abortReset(); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 54fdb7ac45d..e082f0a0b90 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -27,6 +27,8 @@ */ package org.mage.test.stub; +import java.io.Serializable; +import java.util.*; import mage.MageObject; import mage.abilities.*; import mage.abilities.costs.AlternativeSourceCosts; @@ -62,9 +64,6 @@ import mage.target.TargetAmount; import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; -import java.io.Serializable; -import java.util.*; - /** * * @author Quercitron @@ -702,6 +701,11 @@ public class PlayerStub implements Player { } + @Override + public void signalPlayerConcede() { + + } + @Override public void abortReset() { diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 044e6a3ae79..3956dd24b7b 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -173,7 +173,7 @@ public interface Game extends MageItem, Serializable { UUID getPriorityPlayerId(); - boolean gameOver(UUID playerId); + boolean checkIfGameIsOver(); boolean hasEnded(); @@ -347,6 +347,8 @@ public interface Game extends MageItem, Serializable { void concede(UUID playerId); + void setConcedingPlayer(UUID playerId); + void setManaPaymentMode(UUID playerId, boolean autoPayment); void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 768703d8044..d371a119edf 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -161,6 +161,8 @@ public abstract class GameImpl implements Game, Serializable { private final LinkedList stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the stack // used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist) protected Map enterWithCounters = new HashMap<>(); + // used to proceed player conceding requests + private final LinkedList concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) { this.id = UUID.randomUUID(); @@ -535,26 +537,58 @@ public abstract class GameImpl implements Game, Serializable { } } - /** - * Starts check if game is over or if playerId is given let the player - * concede. - * - * @param playerId - * @return - */ +// /** +// * Starts check if game is over or if playerId is given let the player +// * concede. +// * +// * @param playerId +// * @return +// */ +// @Override +// public synchronized boolean gameOver(UUID playerId) { +// if (playerId == null) { +// boolean result = checkIfGameIsOver(); +// return result; +// } else { +// logger.debug("Game over for player Id: " + playerId + " gameId " + getId()); +// concedingPlayers.add(playerId); +// Player player = getPlayer(state.getPriorityPlayerId()); +// if (player != null && player.isHuman()) { +// player.signalPlayerConcede(); +// } else { +// checkConcede(); +// } +// return true; +// } +// } @Override - public synchronized boolean gameOver(UUID playerId) { - if (playerId == null) { - boolean result = checkIfGameIsOver(); - return result; + public void setConcedingPlayer(UUID playerId) { + Player player = getPlayer(state.getPriorityPlayerId()); + if (player != null) { + if (!player.hasLeft() && player.isHuman()) { + if (!concedingPlayers.contains(playerId)) { + logger.debug("Game over for player Id: " + playerId + " gameId " + getId()); + concedingPlayers.add(playerId); + player.signalPlayerConcede(); + } + } else { + // no asynchronous action so check directly + checkConcede(); + } } else { - logger.debug("Game over for player Id: " + playerId + " gameId " + getId()); - leave(playerId); - return true; + checkConcede(); + checkIfGameIsOver(); } } - private boolean checkIfGameIsOver() { + public void checkConcede() { + while (!concedingPlayers.isEmpty()) { + leave(concedingPlayers.removeFirst()); + } + } + + @Override + public boolean checkIfGameIsOver() { if (state.isGameOver()) { return true; } @@ -578,7 +612,7 @@ public abstract class GameImpl implements Game, Serializable { } for (Player player : state.getPlayers().values()) { if (!player.hasLeft() && !player.hasLost()) { - logger.debug(new StringBuilder("Player ").append(player.getName()).append(" has won gameId: ").append(this.getId())); + logger.debug("Player " + player.getName() + " has won gameId: " + this.getId()); player.won(this); } } @@ -696,13 +730,13 @@ public abstract class GameImpl implements Game, Serializable { Player player = getPlayer(playerList.get()); boolean wasPaused = state.isPaused(); state.resume(); - if (!gameOver(null)) { + if (!checkIfGameIsOver()) { fireInformEvent("Turn " + state.getTurnNum()); if (checkStopOnTurnOption()) { return; } state.getTurn().resumePlay(this, wasPaused); - if (!isPaused() && !gameOver(null)) { + if (!isPaused() && !checkIfGameIsOver()) { endOfTurn(); player = playerList.getNext(this); state.setTurnNum(state.getTurnNum() + 1); @@ -712,11 +746,11 @@ public abstract class GameImpl implements Game, Serializable { } protected void play(UUID nextPlayerId) { - if (!isPaused() && !gameOver(null)) { + if (!isPaused() && !checkIfGameIsOver()) { playerList = state.getPlayerList(nextPlayerId); Player playerByOrder = getPlayer(playerList.get()); state.setPlayerByOrderId(playerByOrder.getId()); - while (!isPaused() && !gameOver(null)) { + while (!isPaused() && !checkIfGameIsOver()) { if (!playExtraTurns()) { break; } @@ -733,7 +767,7 @@ public abstract class GameImpl implements Game, Serializable { state.setPlayerByOrderId(playerByOrder.getId()); } } - if (gameOver(null) && !isSimulation()) { + if (checkIfGameIsOver() && !isSimulation()) { winnerId = findWinnersAndLosers(); StringBuilder sb = new StringBuilder("GAME END gameId: ").append(this.getId()).append(' '); int count = 0; @@ -816,7 +850,7 @@ public abstract class GameImpl implements Game, Serializable { skipTurn = state.getTurn().play(this, player); } while (executingRollback); - if (isPaused() || gameOver(null)) { + if (isPaused() || checkIfGameIsOver()) { return false; } if (!skipTurn) { @@ -854,7 +888,7 @@ public abstract class GameImpl implements Game, Serializable { saveState(false); - if (gameOver(null)) { + if (checkIfGameIsOver()) { return; } @@ -1245,7 +1279,7 @@ public abstract class GameImpl implements Game, Serializable { clearAllBookmarks(); try { applyEffects(); - while (!isPaused() && !gameOver(null) && !this.getTurn().isEndTurnRequested()) { + while (!isPaused() && !checkIfGameIsOver() && !this.getTurn().isEndTurnRequested()) { if (!resuming) { state.getPlayers().resetPassed(); state.getPlayerList().setCurrent(activePlayerId); @@ -1254,14 +1288,14 @@ public abstract class GameImpl implements Game, Serializable { } fireUpdatePlayersEvent(); Player player; - while (!isPaused() && !gameOver(null)) { + while (!isPaused() && !checkIfGameIsOver()) { try { if (bookmark == 0) { bookmark = bookmarkState(); } player = getPlayer(state.getPlayerList().get()); state.setPriorityPlayerId(player.getId()); - while (!player.isPassed() && player.canRespond() && !isPaused() && !gameOver(null)) { + while (!player.isPassed() && player.canRespond() && !isPaused() && !checkIfGameIsOver()) { if (!resuming) { // 603.3. Once an ability has triggered, its controller puts it on the stack as an object that's not a card the next time a player would receive priority checkStateAndTriggered(); @@ -1270,7 +1304,7 @@ public abstract class GameImpl implements Game, Serializable { resetLKI(); } saveState(false); - if (isPaused() || gameOver(null)) { + if (isPaused() || checkIfGameIsOver()) { return; } // resetPassed should be called if player performs any action @@ -1289,13 +1323,14 @@ public abstract class GameImpl implements Game, Serializable { } resetShortLivingLKI(); resuming = false; - if (isPaused() || gameOver(null)) { + if (isPaused() || checkIfGameIsOver()) { return; } if (allPassed()) { if (!state.getStack().isEmpty()) { //20091005 - 115.4 resolve(); + checkConcede(); applyEffects(); state.getPlayers().resetPassed(); fireUpdatePlayersEvent(); @@ -1609,11 +1644,11 @@ public abstract class GameImpl implements Game, Serializable { public boolean checkStateAndTriggered() { boolean somethingHappened = false; //20091005 - 115.5 - while (!isPaused() && !gameOver(null)) { + while (!isPaused() && !checkIfGameIsOver()) { if (!checkStateBasedActions()) { // nothing happened so check triggers state.handleSimultaneousEvent(this); - if (isPaused() || gameOver(null) || getTurn().isEndTurnRequested() || !checkTriggered()) { + if (isPaused() || checkIfGameIsOver() || getTurn().isEndTurnRequested() || !checkTriggered()) { break; } } @@ -1621,6 +1656,7 @@ public abstract class GameImpl implements Game, Serializable { applyEffects(); // needed e.g if boost effects end and cause creatures to die somethingHappened = true; } + checkConcede(); return somethingHappened; } @@ -1734,7 +1770,6 @@ public abstract class GameImpl implements Game, Serializable { } } - List planeswalkers = new ArrayList<>(); List legendary = new ArrayList<>(); List worldEnchantment = new ArrayList<>(); for (Permanent perm : getBattlefield().getAllActivePermanents()) { @@ -1781,7 +1816,6 @@ public abstract class GameImpl implements Game, Serializable { continue; } } - planeswalkers.add(perm); } if (perm.isWorld()) { worldEnchantment.add(perm); @@ -2288,7 +2322,6 @@ public abstract class GameImpl implements Game, Serializable { * @param playerId */ protected void leave(UUID playerId) { // needs to be executed from the game thread, not from the concede thread of conceding player! - Player player = getPlayer(playerId); if (player == null || player.hasLeft()) { logger.debug("Player already left " + (player != null ? player.getName() : playerId)); diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 1ad1948a1b5..135919b8abe 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -264,7 +264,7 @@ public class Combat implements Serializable, Copyable { player.selectAttackers(game, attackingPlayerId); } firstTime = false; - if (game.isPaused() || game.gameOver(null) || game.executingRollback()) { + if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) { return; } // because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!! @@ -461,7 +461,7 @@ public class Combat implements Serializable, Copyable { } while (choose) { controller.selectBlockers(game, defenderId); - if (game.isPaused() || game.gameOver(null) || game.executingRollback()) { + if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) { return; } if (!game.getCombat().checkBlockRestrictions(defender, game)) { diff --git a/Mage/src/main/java/mage/game/turn/Phase.java b/Mage/src/main/java/mage/game/turn/Phase.java index e4b276336e1..500709b399d 100644 --- a/Mage/src/main/java/mage/game/turn/Phase.java +++ b/Mage/src/main/java/mage/game/turn/Phase.java @@ -95,7 +95,7 @@ public abstract class Phase implements Serializable { } public boolean play(Game game, UUID activePlayerId) { - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } @@ -104,7 +104,7 @@ public abstract class Phase implements Serializable { if (beginPhase(game, activePlayerId)) { for (Step step : steps) { - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } if (game.getTurn().isEndTurnRequested() && step.getType()!=PhaseStep.CLEANUP) { @@ -122,7 +122,7 @@ public abstract class Phase implements Serializable { } } - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } count++; @@ -143,7 +143,7 @@ public abstract class Phase implements Serializable { } public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) { - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } @@ -157,7 +157,7 @@ public abstract class Phase implements Serializable { resumeStep(game, wasPaused); while (it.hasNext()) { step = it.next(); - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } currentStep = step; @@ -169,7 +169,7 @@ public abstract class Phase implements Serializable { } } - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } count++; @@ -206,13 +206,13 @@ public abstract class Phase implements Serializable { if (!currentStep.skipStep(game, activePlayerId)) { game.getState().increaseStepNum(); prePriority(game, activePlayerId); - if (!game.isPaused() && !game.gameOver(null) && !game.executingRollback()) { + if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) { currentStep.priority(game, activePlayerId, false); if (game.executingRollback()) { return; } } - if (!game.isPaused() && !game.gameOver(null) && !game.executingRollback()) { + if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) { postPriority(game, activePlayerId); } } @@ -233,11 +233,11 @@ public abstract class Phase implements Serializable { prePriority(game, activePlayerId); } case PRIORITY: - if (!game.isPaused() && !game.gameOver(null)) { + if (!game.isPaused() && !game.checkIfGameIsOver()) { currentStep.priority(game, activePlayerId, resuming); } case POST: - if (!game.isPaused() && !game.gameOver(null)) { + if (!game.isPaused() && !game.checkIfGameIsOver()) { postPriority(game, activePlayerId); } } diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index 4ae01ed55e1..6b910ad1e62 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -127,7 +127,7 @@ public class Turn implements Serializable { public boolean play(Game game, Player activePlayer) { activePlayer.becomesActivePlayer(); this.setDeclareAttackersStepStarted(false); - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } @@ -143,7 +143,7 @@ public class Turn implements Serializable { resetCounts(); game.getPlayer(activePlayer.getId()).beginTurn(game); for (Phase phase : phases) { - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return false; } if (!isEndTurnRequested() || phase.getType() == TurnPhase.END) { @@ -189,7 +189,7 @@ public class Turn implements Serializable { } while (it.hasNext()) { phase = it.next(); - if (game.isPaused() || game.gameOver(null)) { + if (game.isPaused() || game.checkIfGameIsOver()) { return; } currentPhase = phase; diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 261a24c22f2..71478eb2f98 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -444,6 +444,8 @@ public interface Player extends MageItem, Copyable { void abortReset(); + void signalPlayerConcede(); + void skip(); // priority, undo, ... diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 1567246fdae..17929004682 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2039,9 +2039,9 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void concede(Game game) { - game.gameOver(playerId); + game.setConcedingPlayer(playerId); lost(game); - this.left = true; +// this.left = true; } @Override @@ -2136,7 +2136,7 @@ public abstract class PlayerImpl implements Player, Serializable { // for draw - first all players that have lost have to be set to lost if (!hasLeft()) { logger.debug("Game over playerId: " + playerId); - game.gameOver(playerId); + game.setConcedingPlayer(playerId); } } @@ -2197,7 +2197,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.draws = true; game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DRAW_PLAYER, null, null, playerId)); game.informPlayers("For " + this.getLogName() + " the game is a draw."); - game.gameOver(playerId); + game.setConcedingPlayer(playerId); } } @@ -3578,6 +3578,11 @@ public abstract class PlayerImpl implements Player, Serializable { abort = false; } + @Override + public void signalPlayerConcede() { + + } + @Override public boolean scry(int value, Ability source, Game game From 3f62c8d8969ba8685baf32db74cb7aef96ead6c9 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sat, 21 Oct 2017 13:07:54 -0400 Subject: [PATCH 05/77] Implemented Prismatic Ward --- Mage.Sets/src/mage/cards/p/PrismaticWard.java | 129 ++++++++++++++++++ Mage.Sets/src/mage/sets/FifthEdition.java | 1 + Mage.Sets/src/mage/sets/IceAge.java | 1 + 3 files changed, 131 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PrismaticWard.java diff --git a/Mage.Sets/src/mage/cards/p/PrismaticWard.java b/Mage.Sets/src/mage/cards/p/PrismaticWard.java new file mode 100644 index 00000000000..3d5c31ec3b7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrismaticWard.java @@ -0,0 +1,129 @@ +/* + * 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.MageObject; +import mage.ObjectColor; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ChooseColorEffect; +import mage.constants.Outcome; +import mage.target.TargetPermanent; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author TheElk801 + */ +public class PrismaticWard extends CardImpl { + + public PrismaticWard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // As Prismatic Ward enters the battlefield, choose a color. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseColorEffect(Outcome.Benefit))); + + // Prevent all damage that would be dealt to enchanted creature by sources of the chosen color. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PrismaticWardPreventDamageEffect())); + } + + public PrismaticWard(final PrismaticWard card) { + super(card); + } + + @Override + public PrismaticWard copy() { + return new PrismaticWard(this); + } +} + +class PrismaticWardPreventDamageEffect extends PreventionEffectImpl { + + public PrismaticWardPreventDamageEffect() { + super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false); + staticText = "Prevent all damage that would be dealt to enchanted creature by sources of the chosen color."; + } + + public PrismaticWardPreventDamageEffect(final PrismaticWardPreventDamageEffect effect) { + super(effect); + } + + @Override + public PrismaticWardPreventDamageEffect copy() { + return new PrismaticWardPreventDamageEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!super.applies(event, source, game)) { + return false; + } + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null) { + return false; + } + ObjectColor color = (ObjectColor) game.getState().getValue(permanent.getId() + "_color"); + if (color == null) { + return false; + } + MageObject sourceObject = game.getObject(event.getSourceId()); + if (sourceObject == null || !sourceObject.getColor(game).shares(color)) { + return false; + } + Permanent attachment = game.getPermanent(source.getSourceId()); + if (attachment != null + && attachment.getAttachedTo() != null + && event.getTargetId().equals(attachment.getAttachedTo())) { + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/FifthEdition.java b/Mage.Sets/src/mage/sets/FifthEdition.java index 58f3d4975ca..95969ca6bb0 100644 --- a/Mage.Sets/src/mage/sets/FifthEdition.java +++ b/Mage.Sets/src/mage/sets/FifthEdition.java @@ -328,6 +328,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Pradesh Gypsies", 179, Rarity.COMMON, mage.cards.p.PradeshGypsies.class)); cards.add(new SetCardInfo("Primal Clay", 395, Rarity.RARE, mage.cards.p.PrimalClay.class)); cards.add(new SetCardInfo("Primal Order", 180, Rarity.RARE, mage.cards.p.PrimalOrder.class)); + cards.add(new SetCardInfo("Prismatic Ward", 329, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); cards.add(new SetCardInfo("Prodigal Sorcerer", 112, Rarity.COMMON, mage.cards.p.ProdigalSorcerer.class)); cards.add(new SetCardInfo("Psychic Venom", 113, Rarity.COMMON, mage.cards.p.PsychicVenom.class)); cards.add(new SetCardInfo("Pyroblast", 262, Rarity.UNCOMMON, mage.cards.p.Pyroblast.class)); diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index 8db2063fca7..e42c624e1f4 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -253,6 +253,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Portent", 90, Rarity.COMMON, mage.cards.p.Portent.class)); cards.add(new SetCardInfo("Power Sink", 91, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Pox", 46, Rarity.RARE, mage.cards.p.Pox.class)); + cards.add(new SetCardInfo("Prismatic Ward", 271, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); cards.add(new SetCardInfo("Pygmy Allosaurus", 145, Rarity.RARE, mage.cards.p.PygmyAllosaurus.class)); cards.add(new SetCardInfo("Pyknite", 146, Rarity.COMMON, mage.cards.p.Pyknite.class)); cards.add(new SetCardInfo("Pyroblast", 213, Rarity.COMMON, mage.cards.p.Pyroblast.class)); From f29141605b9325340470a565a69d94dbb805ed5f Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 22 Oct 2017 09:16:51 -0400 Subject: [PATCH 06/77] fixed Nissa, Genesis Mage -10 forcing players to put 10 cards into play in order to continue --- .../src/mage/cards/n/NissaGenesisMage.java | 20 ++++++++++++------- .../LookLibraryAndPickControllerEffect.java | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/n/NissaGenesisMage.java b/Mage.Sets/src/mage/cards/n/NissaGenesisMage.java index f06bd347c39..9322cd16cbf 100644 --- a/Mage.Sets/src/mage/cards/n/NissaGenesisMage.java +++ b/Mage.Sets/src/mage/cards/n/NissaGenesisMage.java @@ -42,8 +42,7 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.common.FilterLandPermanent; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.target.common.TargetCreaturePermanent; @@ -55,6 +54,15 @@ import mage.target.common.TargetLandPermanent; */ public class NissaGenesisMage extends CardImpl { + private static final FilterCard filter = new FilterCard("any number of creature and/or land cards"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.LAND) + )); + } + public NissaGenesisMage(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{5}{G}{G}"); this.addSuperType(SuperType.LEGENDARY); @@ -64,8 +72,8 @@ public class NissaGenesisMage extends CardImpl { //+2: Untap up to two target creatures and up to two target lands. Ability ability = new LoyaltyAbility(new UntapTargetEffect(false).setText("Untap up to two target creatures and up to two target lands"), +2); - ability.addTarget(new TargetCreaturePermanent(0, 2, new FilterCreaturePermanent("target creatures"), false)); - ability.addTarget(new TargetLandPermanent(0, 2, new FilterLandPermanent("target land"), false)); + ability.addTarget(new TargetCreaturePermanent(0, 2, StaticFilters.FILTER_PERMANENT_CREATURES, false)); + ability.addTarget(new TargetLandPermanent(0, 2, StaticFilters.FILTER_LANDS, false)); this.addAbility(ability); //-3: Target creature gets +5/+5 until end of turn. @@ -74,10 +82,8 @@ public class NissaGenesisMage extends CardImpl { this.addAbility(ability); //-10: Look at the top ten cards of your library. You may put any number of creature and/or land cards from among them onto the battlefield. Put the rest on the bottom of your library in a random order.); - FilterCard filter = new FilterCard("creature and/or land cards"); - filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); this.addAbility(new LoyaltyAbility( - new LookLibraryAndPickControllerEffect(10, 10, filter, false, false, Zone.BATTLEFIELD, true).setBackInRandomOrder(true), + new LookLibraryAndPickControllerEffect(10, 10, filter, false, true, Zone.BATTLEFIELD, false).setBackInRandomOrder(true), -10)); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java index 5a93cbae466..99958198d35 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LookLibraryAndPickControllerEffect.java @@ -270,7 +270,7 @@ public class LookLibraryAndPickControllerEffect extends LookLibraryControllerEff } else { sb.append('P'); } - sb.append("put ").append(filter.getMessage()).append(" from among them onto the "); + sb.append("ut ").append(filter.getMessage()).append(" from among them onto the "); } else { sb.append(". Put "); if (numberToPick.calculate(null, null, this) > 1) { From db8e38b5873e49c6541d926f03ca5d3c8fd653a0 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:48:47 +0200 Subject: [PATCH 07/77] Implemented Johan --- .../common/combat/CantAttackSourceEffect.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackSourceEffect.java diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackSourceEffect.java new file mode 100644 index 00000000000..c84508d18a7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackSourceEffect.java @@ -0,0 +1,70 @@ +/* + * 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.abilities.effects.common.combat; + +import mage.constants.Duration; +import mage.abilities.Ability; +import mage.abilities.effects.RestrictionEffect; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author BetaSteward_at_googlemail.com & L_J + */ +public class CantAttackSourceEffect extends RestrictionEffect { + + public CantAttackSourceEffect(Duration duration) { + super(duration); + this.staticText = "{this} can't attack"; + } + + public CantAttackSourceEffect(final CantAttackSourceEffect effect) { + super(effect); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + if (permanent.getId().equals(source.getSourceId())) { + return true; + } + return false; + } + + @Override + public boolean canAttack(Game game) { + return false; + } + + @Override + public CantAttackSourceEffect copy() { + return new CantAttackSourceEffect(this); + } + +} From 465d4e9020a4cf534be83f3a5b68cae6ede605ab Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:50:20 +0200 Subject: [PATCH 08/77] Implemented Johan --- Mage.Sets/src/mage/cards/j/Johan.java | 82 +++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/j/Johan.java diff --git a/Mage.Sets/src/mage/cards/j/Johan.java b/Mage.Sets/src/mage/cards/j/Johan.java new file mode 100644 index 00000000000..5c92952c347 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/Johan.java @@ -0,0 +1,82 @@ +/* + * 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.j; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.InvertCondition; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.combat.CantAttackSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.common.FilterControlledCreaturePermanent; + +/** + * + * @author L_J + */ +public class Johan extends CardImpl { + + public Johan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}{W}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // At the beginning of combat on your turn, you may have Johan gain "Johan can't attack" until end of combat. If you do, attacking doesn't cause creatures you control to tap this combat if Johan is untapped. + Ability ability = new BeginningOfCombatTriggeredAbility(new CantAttackSourceEffect(Duration.EndOfCombat).setText("you may have {this} gain \"{this} can't attack\" until end of combat"), TargetController.YOU, true); + ability.addEffect(new ConditionalContinuousEffect( + new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfCombat, new FilterControlledCreaturePermanent("creatures")), + new InvertCondition(SourceTappedCondition.instance), + "If you do, attacking doesn't cause creatures you control to tap this combat if {this} is untapped")); + this.addAbility(ability); + } + + public Johan(final Johan card) { + super(card); + } + + @Override + public Johan copy() { + return new Johan(this); + } +} From 75af668430c4b2fc181a7a350e2e14ba57716728 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:51:40 +0200 Subject: [PATCH 09/77] Added text message for cardChoice --- Mage.Sets/src/mage/cards/c/ConundrumSphinx.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java b/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java index 2c13d773f78..5b1080aba7a 100644 --- a/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java +++ b/Mage.Sets/src/mage/cards/c/ConundrumSphinx.java @@ -101,6 +101,7 @@ class ConundrumSphinxEffect extends OneShotEffect { if (player != null) { if (player.getLibrary().hasCards()) { cardChoice.clearChoice(); + cardChoice.setMessage("Name a card"); while (!player.choose(Outcome.DrawCard, cardChoice, game) && player.canRespond()) { if (!player.canRespond()) { continue Players; From a4f511a42d481f8ce43cc6d062f3ce3ee3647ad9 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:52:47 +0200 Subject: [PATCH 10/77] Implemented Petra Sphinx --- Mage.Sets/src/mage/cards/p/PetraSphinx.java | 139 ++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PetraSphinx.java diff --git a/Mage.Sets/src/mage/cards/p/PetraSphinx.java b/Mage.Sets/src/mage/cards/p/PetraSphinx.java new file mode 100644 index 00000000000..295303e920e --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PetraSphinx.java @@ -0,0 +1,139 @@ +/* + * 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.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.cards.repository.CardRepository; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; + +/** + * + * @author BetaSteward_at_googlemail.com & L_J + */ +public class PetraSphinx extends CardImpl { + + public PetraSphinx(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}{W}"); + this.subtype.add(SubType.SPHINX); + + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // {tap}: Target player chooses a card name, then reveals the top card of his or her library. If that card has the chosen name, that player puts it into his or her hand. If it doesn't, the player puts it into his or her graveyard. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PetraSphinxEffect(), new TapSourceCost()); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + public PetraSphinx(final PetraSphinx card) { + super(card); + } + + @Override + public PetraSphinx copy() { + return new PetraSphinx(this); + } + +} + +class PetraSphinxEffect extends OneShotEffect { + + public PetraSphinxEffect() { + super(Outcome.DrawCard); + staticText = "Target player chooses a card name, then reveals the top card of his or her library. If that card has the chosen name, that player puts it into his or her hand. If it doesn't, the player puts it into his or her graveyard"; + } + + public PetraSphinxEffect(final PetraSphinxEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + Player controller = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + if (controller != null && sourceObject != null && player != null) { + + + //~ Players: + //~ for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + //~ Player player = game.getPlayer(playerId); + //~ if (player != null) { + if (player.getLibrary().hasCards()) { + Choice cardChoice = new ChoiceImpl(); + cardChoice.setChoices(CardRepository.instance.getNames()); + //~ cardChoice.clearChoice(); + cardChoice.setMessage("Name a card"); + while (!player.choose(Outcome.DrawCard, cardChoice, game)) { + if (!player.canRespond()) { + return false; + } + } + String cardName = cardChoice.getChoice(); + game.informPlayers(sourceObject.getLogName() + ", player: " + player.getLogName() + ", named: [" + cardName + ']'); + Card card = player.getLibrary().removeFromTop(game); + if (card != null) { + Cards cards = new CardsImpl(card); + player.revealCards(sourceObject.getIdName(), cards, game); + if (card.getName().equals(cardName)) { + player.moveCards(cards, Zone.HAND, source, game); + } else { + player.moveCards(cards, Zone.GRAVEYARD, source, game); + //~ player.damage(2, source.getSourceId(), game, false, true); + //~ player.putCardsOnBottomOfLibrary(cards, game, source, false); + //~ } + //~ } + } + } + } + return true; + } + return false; + } + + @Override + public PetraSphinxEffect copy() { + return new PetraSphinxEffect(this); + } + +} From 1c49350f54192caa98455f0ca4c9a2fb482fec4c Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:54:15 +0200 Subject: [PATCH 11/77] Implemented Vexing Arcanix --- Mage.Sets/src/mage/cards/v/VexingArcanix.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/v/VexingArcanix.java diff --git a/Mage.Sets/src/mage/cards/v/VexingArcanix.java b/Mage.Sets/src/mage/cards/v/VexingArcanix.java new file mode 100644 index 00000000000..62573bbae5e --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VexingArcanix.java @@ -0,0 +1,127 @@ +/* + * 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.v; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.cards.repository.CardRepository; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; + +/** + * + * @author BetaSteward_at_googlemail.com & L_J + */ +public class VexingArcanix extends CardImpl { + + public VexingArcanix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); + + // {3}, {tap}: Target player chooses a card name, then reveals the top card of his or her library. If that card has the chosen name, the player puts it into his or her hand. Otherwise, the player puts it into his or her graveyard and Vexing Arcanix deals 2 damage to him or her. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new VexingArcanixEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + public VexingArcanix(final VexingArcanix card) { + super(card); + } + + @Override + public VexingArcanix copy() { + return new VexingArcanix(this); + } + +} + +class VexingArcanixEffect extends OneShotEffect { + + public VexingArcanixEffect() { + super(Outcome.DrawCard); + staticText = "Target player chooses a card name, then reveals the top card of his or her library. If that card has the chosen name, the player puts it into his or her hand. Otherwise, the player puts it into his or her graveyard and {this} deals 2 damage to him or her"; + } + + public VexingArcanixEffect(final VexingArcanixEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + Player controller = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + if (controller != null && sourceObject != null && player != null) { + + + if (player.getLibrary().hasCards()) { + Choice cardChoice = new ChoiceImpl(); + cardChoice.setChoices(CardRepository.instance.getNames + cardChoice.setMessage("Name a card"); + while (!player.choose(Outcome.DrawCard, cardChoice, game)) { + if (!player.canRespond()) { + return false; + } + } + String cardName = cardChoice.getChoice(); + game.informPlayers(sourceObject.getLogName() + ", player: " + player.getLogName() + ", named: [" + cardName + ']'); + Card card = player.getLibrary().removeFromTop(game); + if (card != null) { + Cards cards = new CardsImpl(card); + player.revealCards(sourceObject.getIdName(), cards, game); + if (card.getName().equals(cardName)) { + player.moveCards(cards, Zone.HAND, source, game); + } else { + player.moveCards(cards, Zone.GRAVEYARD, source, game); + player.damage(2, source.getSourceId(), game, false, true); + } + } + } + return true; + } + return false; + } + + @Override + public VexingArcanixEffect copy() { + return new VexingArcanixEffect(this); + } + +} From 0c862c01f65297d703acd71bdbd8a8aecfb0ea7d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:55:25 +0200 Subject: [PATCH 12/77] Removed some garbage --- Mage.Sets/src/mage/cards/p/PetraSphinx.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PetraSphinx.java b/Mage.Sets/src/mage/cards/p/PetraSphinx.java index 295303e920e..a1292fd7d07 100644 --- a/Mage.Sets/src/mage/cards/p/PetraSphinx.java +++ b/Mage.Sets/src/mage/cards/p/PetraSphinx.java @@ -95,14 +95,9 @@ class PetraSphinxEffect extends OneShotEffect { if (controller != null && sourceObject != null && player != null) { - //~ Players: - //~ for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { - //~ Player player = game.getPlayer(playerId); - //~ if (player != null) { if (player.getLibrary().hasCards()) { Choice cardChoice = new ChoiceImpl(); cardChoice.setChoices(CardRepository.instance.getNames()); - //~ cardChoice.clearChoice(); cardChoice.setMessage("Name a card"); while (!player.choose(Outcome.DrawCard, cardChoice, game)) { if (!player.canRespond()) { @@ -119,10 +114,6 @@ class PetraSphinxEffect extends OneShotEffect { player.moveCards(cards, Zone.HAND, source, game); } else { player.moveCards(cards, Zone.GRAVEYARD, source, game); - //~ player.damage(2, source.getSourceId(), game, false, true); - //~ player.putCardsOnBottomOfLibrary(cards, game, source, false); - //~ } - //~ } } } } From 527257e6c1aa34e54ea71e747c1cf147e8afba21 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:56:29 +0200 Subject: [PATCH 13/77] Implemented Teleport --- Mage.Sets/src/mage/cards/t/Teleport.java | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/Teleport.java diff --git a/Mage.Sets/src/mage/cards/t/Teleport.java b/Mage.Sets/src/mage/cards/t/Teleport.java new file mode 100644 index 00000000000..3fea5398ba5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/Teleport.java @@ -0,0 +1,64 @@ +/* + * 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.common.CastOnlyDuringPhaseStepSourceAbility; +import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author L_J + */ +public class Teleport extends CardImpl { + + public Teleport(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{U}{U}"); + + // Cast Teleport only during the declare attackers step. + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, PhaseStep.DECLARE_ATTACKERS, null, "Cast Teleport only during the declare attackers step")); + + // Target creature can't be blocked this turn. + this.getSpellAbility().addEffect(new CantBeBlockedTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + public Teleport(final Teleport card) { + super(card); + } + + @Override + public Teleport copy() { + return new Teleport(this); + } +} From 43201a362b5cf9c446b752c2ed03bae49aa4cea0 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 16:57:09 +0200 Subject: [PATCH 14/77] Implemented Time Elemental --- Mage.Sets/src/mage/cards/t/TimeElemental.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TimeElemental.java diff --git a/Mage.Sets/src/mage/cards/t/TimeElemental.java b/Mage.Sets/src/mage/cards/t/TimeElemental.java new file mode 100644 index 00000000000..62dc23201d9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TimeElemental.java @@ -0,0 +1,92 @@ +/* + * 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.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.AttacksOrBlocksTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.EnchantedPredicate; +import mage.target.TargetPermanent; + +/** + * + * @author L_J + */ +public class TimeElemental extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("permanent that isn't enchanted"); + + static { + filter.add(Predicates.not(new EnchantedPredicate())); + } + + public TimeElemental(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(0); + this.toughness = new MageInt(2); + + // When Time Elemental attacks or blocks, at end of combat, sacrifice it and it deals 5 damage to you. + DelayedTriggeredAbility ability = new AtTheEndOfCombatDelayedTriggeredAbility(new SacrificeSourceEffect().setText("at end of combat, sacrifice it")); + ability.addEffect(new DamageControllerEffect(5).setText("and it deals 5 damage to you")); + this.addAbility(new AttacksOrBlocksTriggeredAbility(new CreateDelayedTriggeredAbilityEffect(ability, true), false)); + + // {2}{U}{U}, {tap}: Return target permanent that isn't enchanted to its owner's hand. + Ability ability2 = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new ManaCostsImpl("{2}{U}{U}")); + ability2.addCost(new TapSourceCost()); + ability2.addTarget(new TargetPermanent(filter)); + this.addAbility(ability2); + } + + public TimeElemental(final TimeElemental card) { + super(card); + } + + @Override + public TimeElemental copy() { + return new TimeElemental(this); + } +} From 0123a7dbb38b47c946a387937b655800b731c045 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:00:10 +0200 Subject: [PATCH 15/77] Implemented Vexing Arcanix --- Mage.Sets/src/mage/sets/EighthEdition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/EighthEdition.java b/Mage.Sets/src/mage/sets/EighthEdition.java index 1d6c7f307ac..e9d4a733f68 100644 --- a/Mage.Sets/src/mage/sets/EighthEdition.java +++ b/Mage.Sets/src/mage/sets/EighthEdition.java @@ -350,6 +350,7 @@ public class EighthEdition extends ExpansionSet { cards.add(new SetCardInfo("Venerable Monk", 55, Rarity.COMMON, mage.cards.v.VenerableMonk.class)); cards.add(new SetCardInfo("Verduran Enchantress", 285, Rarity.RARE, mage.cards.v.VerduranEnchantress.class)); cards.add(new SetCardInfo("Vernal Bloom", 286, Rarity.RARE, mage.cards.v.VernalBloom.class)); + cards.add(new SetCardInfo("Vexing Arcanix", 319, Rarity.RARE, mage.cards.v.VexingArcanix.class)); cards.add(new SetCardInfo("Viashino Sandstalker", 230, Rarity.UNCOMMON, mage.cards.v.ViashinoSandstalker.class)); cards.add(new SetCardInfo("Vicious Hunger", 171, Rarity.COMMON, mage.cards.v.ViciousHunger.class)); cards.add(new SetCardInfo("Vine Trellis", 287, Rarity.COMMON, mage.cards.v.VineTrellis.class)); From 98d87abb4f53be86c72b0289630edd86eaeca43a Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:00:43 +0200 Subject: [PATCH 16/77] Implemented Vexing Arcanix --- Mage.Sets/src/mage/sets/IceAge.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index 8db2063fca7..ba9a8161859 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -167,7 +167,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Hyalopterous Lemure", 21, Rarity.UNCOMMON, mage.cards.h.HyalopterousLemure.class)); cards.add(new SetCardInfo("Hydroblast", 72, Rarity.COMMON, mage.cards.h.Hydroblast.class)); cards.add(new SetCardInfo("Hymn of Rebirth", 373, Rarity.UNCOMMON, mage.cards.h.HymnOfRebirth.class)); - cards.add(new SetCardInfo("Ice Floe", 333, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); + cards.add(new SetCardInfo("Ice Floe", 333, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); cards.add(new SetCardInfo("Iceberg", 73, Rarity.UNCOMMON, mage.cards.i.Iceberg.class)); cards.add(new SetCardInfo("Icequake", 22, Rarity.UNCOMMON, mage.cards.i.Icequake.class)); cards.add(new SetCardInfo("Icy Manipulator", 297, Rarity.UNCOMMON, mage.cards.i.IcyManipulator.class)); @@ -212,7 +212,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Magus of the Unseen", 82, Rarity.RARE, mage.cards.m.MagusOfTheUnseen.class)); cards.add(new SetCardInfo("Malachite Talisman", 303, Rarity.UNCOMMON, mage.cards.m.MalachiteTalisman.class)); cards.add(new SetCardInfo("Marton Stromgald", 199, Rarity.RARE, mage.cards.m.MartonStromgald.class)); - cards.add(new SetCardInfo("Melting", 201, Rarity.UNCOMMON, mage.cards.m.Melting.class)); + cards.add(new SetCardInfo("Melting", 201, Rarity.UNCOMMON, mage.cards.m.Melting.class)); cards.add(new SetCardInfo("Merieke Ri Berit", 375, Rarity.RARE, mage.cards.m.MeriekeRiBerit.class)); cards.add(new SetCardInfo("Mesmeric Trance", 83, Rarity.RARE, mage.cards.m.MesmericTrance.class)); cards.add(new SetCardInfo("Meteor Shower", 202, Rarity.COMMON, mage.cards.m.MeteorShower.class)); @@ -274,7 +274,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Shambling Strider", 151, Rarity.COMMON, mage.cards.s.ShamblingStrider.class)); cards.add(new SetCardInfo("Shatter", 216, Rarity.COMMON, mage.cards.s.Shatter.class)); cards.add(new SetCardInfo("Shield of the Ages", 310, Rarity.UNCOMMON, mage.cards.s.ShieldOfTheAges.class)); - cards.add(new SetCardInfo("Shyft", 96, Rarity.RARE, mage.cards.s.Shyft.class)); + cards.add(new SetCardInfo("Shyft", 96, Rarity.RARE, mage.cards.s.Shyft.class)); cards.add(new SetCardInfo("Sibilant Spirit", 97, Rarity.RARE, mage.cards.s.SibilantSpirit.class)); cards.add(new SetCardInfo("Silver Erne", 98, Rarity.UNCOMMON, mage.cards.s.SilverErne.class)); cards.add(new SetCardInfo("Skeleton Ship", 379, Rarity.RARE, mage.cards.s.SkeletonShip.class)); @@ -323,6 +323,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Urza's Bauble", 318, Rarity.UNCOMMON, mage.cards.u.UrzasBauble.class)); cards.add(new SetCardInfo("Veldt", 358, Rarity.RARE, mage.cards.v.Veldt.class)); cards.add(new SetCardInfo("Vertigo", 222, Rarity.UNCOMMON, mage.cards.v.Vertigo.class)); + cards.add(new SetCardInfo("Vexing Arcanix", 319, Rarity.RARE, mage.cards.v.VexingArcanix.class)); cards.add(new SetCardInfo("Vibrating Sphere", 320, Rarity.RARE, mage.cards.v.VibratingSphere.class)); cards.add(new SetCardInfo("Walking Wall", 321, Rarity.UNCOMMON, mage.cards.w.WalkingWall.class)); cards.add(new SetCardInfo("Wall of Lava", 223, Rarity.UNCOMMON, mage.cards.w.WallOfLava.class)); From 6cc0f06e3e5ac9c2f26eea1dd5fac3c7ba55f7c5 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:02:06 +0200 Subject: [PATCH 17/77] Implemented Time Elemental --- 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 f44d101070d..263ec33b0d7 100644 --- a/Mage.Sets/src/mage/sets/FourthEdition.java +++ b/Mage.Sets/src/mage/sets/FourthEdition.java @@ -359,6 +359,7 @@ public class FourthEdition extends ExpansionSet { cards.add(new SetCardInfo("Thicket Basilisk", 158, Rarity.UNCOMMON, mage.cards.t.ThicketBasilisk.class)); cards.add(new SetCardInfo("Thoughtlace", 107, Rarity.RARE, mage.cards.t.Thoughtlace.class)); cards.add(new SetCardInfo("Throne of Bone", 371, Rarity.UNCOMMON, mage.cards.t.ThroneOfBone.class)); + cards.add(new SetCardInfo("Time Elemental", 108, Rarity.RARE, mage.cards.t.TimeElemental.class)); cards.add(new SetCardInfo("Titania's Song", 160, Rarity.RARE, mage.cards.t.TitaniasSong.class)); cards.add(new SetCardInfo("Tranquility", 161, Rarity.COMMON, mage.cards.t.Tranquility.class)); cards.add(new SetCardInfo("Triskelion", 372, Rarity.RARE, mage.cards.t.Triskelion.class)); From 20d1ccd197b24202011925ac2cf33f1998e5345a Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:02:33 +0200 Subject: [PATCH 18/77] Implemented Time Elemental --- Mage.Sets/src/mage/sets/FifthEdition.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/sets/FifthEdition.java b/Mage.Sets/src/mage/sets/FifthEdition.java index 58f3d4975ca..441de782bb1 100644 --- a/Mage.Sets/src/mage/sets/FifthEdition.java +++ b/Mage.Sets/src/mage/sets/FifthEdition.java @@ -217,7 +217,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Icatian Scout", 313, Rarity.COMMON, IcatianScout.class)); cards.add(new SetCardInfo("Icatian Store", 423, Rarity.RARE, mage.cards.i.IcatianStore.class)); cards.add(new SetCardInfo("Icatian Town", 314, Rarity.RARE, mage.cards.i.IcatianTown.class)); - cards.add(new SetCardInfo("Ice Floe", 424, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); + cards.add(new SetCardInfo("Ice Floe", 424, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); cards.add(new SetCardInfo("Imposing Visage", 241, Rarity.COMMON, mage.cards.i.ImposingVisage.class)); cards.add(new SetCardInfo("Incinerate", 242, Rarity.COMMON, mage.cards.i.Incinerate.class)); cards.add(new SetCardInfo("Inferno", 243, Rarity.RARE, mage.cards.i.Inferno.class)); @@ -253,7 +253,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Knight of Stromgald", 33, Rarity.UNCOMMON, mage.cards.k.KnightOfStromgald.class)); cards.add(new SetCardInfo("Krovikan Fetish", 34, Rarity.COMMON, mage.cards.k.KrovikanFetish.class)); cards.add(new SetCardInfo("Krovikan Sorcerer", 96, Rarity.COMMON, mage.cards.k.KrovikanSorcerer.class)); - cards.add(new SetCardInfo("Labyrinth Minotaur", 97, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); + cards.add(new SetCardInfo("Labyrinth Minotaur", 97, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); cards.add(new SetCardInfo("Leshrac's Rite", 35, Rarity.UNCOMMON, mage.cards.l.LeshracsRite.class)); cards.add(new SetCardInfo("Leviathan", 98, Rarity.RARE, mage.cards.l.Leviathan.class)); cards.add(new SetCardInfo("Ley Druid", 170, Rarity.COMMON, mage.cards.l.LeyDruid.class)); @@ -301,7 +301,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Orcish Artillery", 253, Rarity.UNCOMMON, mage.cards.o.OrcishArtillery.class)); cards.add(new SetCardInfo("Orcish Captain", 254, Rarity.UNCOMMON, mage.cards.o.OrcishCaptain.class)); cards.add(new SetCardInfo("Orcish Oriflamme", 257, Rarity.UNCOMMON, mage.cards.o.OrcishOriflamme.class)); - cards.add(new SetCardInfo("Orcish Squatters", 258, Rarity.RARE, mage.cards.o.OrcishSquatters.class)); + cards.add(new SetCardInfo("Orcish Squatters", 258, Rarity.RARE, mage.cards.o.OrcishSquatters.class)); cards.add(new SetCardInfo("Order of the Sacred Torch", 324, Rarity.RARE, mage.cards.o.OrderOfTheSacredTorch.class)); cards.add(new SetCardInfo("Order of the White Shield", 325, Rarity.UNCOMMON, mage.cards.o.OrderOfTheWhiteShield.class)); cards.add(new SetCardInfo("Orgg", 259, Rarity.RARE, mage.cards.o.Orgg.class)); @@ -401,6 +401,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Throne of Bone", 403, Rarity.UNCOMMON, mage.cards.t.ThroneOfBone.class)); cards.add(new SetCardInfo("Thrull Retainer", 60, Rarity.UNCOMMON, mage.cards.t.ThrullRetainer.class)); cards.add(new SetCardInfo("Time Bomb", 404, Rarity.RARE, mage.cards.t.TimeBomb.class)); + cards.add(new SetCardInfo("Time Elemental", 129, Rarity.RARE, mage.cards.t.TimeElemental.class)); cards.add(new SetCardInfo("Titania's Song", 194, Rarity.RARE, mage.cards.t.TitaniasSong.class)); cards.add(new SetCardInfo("Torture", 61, Rarity.COMMON, Torture.class)); cards.add(new SetCardInfo("Touch of Death", 62, Rarity.COMMON, mage.cards.t.TouchOfDeath.class)); @@ -438,7 +439,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Wild Growth", 204, Rarity.COMMON, mage.cards.w.WildGrowth.class)); cards.add(new SetCardInfo("Winds of Change", 275, Rarity.RARE, mage.cards.w.WindsOfChange.class)); cards.add(new SetCardInfo("Wind Spirit", 136, Rarity.UNCOMMON, mage.cards.w.WindSpirit.class)); - cards.add(new SetCardInfo("Winter Blast", 205, Rarity.UNCOMMON, mage.cards.w.WinterBlast.class)); + cards.add(new SetCardInfo("Winter Blast", 205, Rarity.UNCOMMON, mage.cards.w.WinterBlast.class)); cards.add(new SetCardInfo("Winter Orb", 408, Rarity.RARE, mage.cards.w.WinterOrb.class)); cards.add(new SetCardInfo("Wolverine Pack", 206, Rarity.UNCOMMON, mage.cards.w.WolverinePack.class)); cards.add(new SetCardInfo("Wooden Sphere", 409, Rarity.UNCOMMON, mage.cards.w.WoodenSphere.class)); From 4f938cb0bb424c806d5904fc584ea4367f329552 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:03:58 +0200 Subject: [PATCH 19/77] Implemented Time Elemental and Petra Sphinx --- Mage.Sets/src/mage/sets/MastersEdition.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/MastersEdition.java b/Mage.Sets/src/mage/sets/MastersEdition.java index 5adcbf72bac..a6029ef856c 100644 --- a/Mage.Sets/src/mage/sets/MastersEdition.java +++ b/Mage.Sets/src/mage/sets/MastersEdition.java @@ -108,7 +108,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Dwarven Catapult", 91, Rarity.UNCOMMON, mage.cards.d.DwarvenCatapult.class)); cards.add(new SetCardInfo("Dwarven Soldier", 92, Rarity.COMMON, DwarvenSoldier.class)); cards.add(new SetCardInfo("Eater of the Dead", 67, Rarity.UNCOMMON, mage.cards.e.EaterOfTheDead.class)); - cards.add(new SetCardInfo("Elder Land Wurm", 11, Rarity.UNCOMMON, mage.cards.e.ElderLandWurm.class)); + cards.add(new SetCardInfo("Elder Land Wurm", 11, Rarity.UNCOMMON, mage.cards.e.ElderLandWurm.class)); cards.add(new SetCardInfo("Erg Raiders", 68, Rarity.COMMON, mage.cards.e.ErgRaiders.class)); cards.add(new SetCardInfo("Eureka", 117, Rarity.RARE, mage.cards.e.Eureka.class)); cards.add(new SetCardInfo("Exile", 12, Rarity.COMMON, mage.cards.e.Exile.class)); @@ -183,9 +183,10 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Order of the Ebon Hand", 78, Rarity.COMMON, OrderOfTheEbonHand.class)); cards.add(new SetCardInfo("Oubliette", 79, Rarity.COMMON, Oubliette.class)); cards.add(new SetCardInfo("Paralyze", 80, Rarity.COMMON, mage.cards.p.Paralyze.class)); + cards.add(new SetCardInfo("Petra Sphinx", 23, Rarity.RARE, mage.cards.p.PetraSphinx.class)); cards.add(new SetCardInfo("Phantom Monster", 43, Rarity.COMMON, mage.cards.p.PhantomMonster.class)); cards.add(new SetCardInfo("Phelddagrif", 150, Rarity.RARE, mage.cards.p.Phelddagrif.class)); - cards.add(new SetCardInfo("Phyrexian Boon", 81, Rarity.COMMON, mage.cards.p.PhyrexianBoon.class)); + cards.add(new SetCardInfo("Phyrexian Boon", 81, Rarity.COMMON, mage.cards.p.PhyrexianBoon.class)); cards.add(new SetCardInfo("Phyrexian War Beast", 162, Rarity.UNCOMMON, PhyrexianWarBeast.class)); cards.add(new SetCardInfo("Plains", 181, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 182, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); @@ -229,6 +230,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Thrull Champion", 83, Rarity.RARE, mage.cards.t.ThrullChampion.class)); cards.add(new SetCardInfo("Thrull Retainer", 84, Rarity.COMMON, mage.cards.t.ThrullRetainer.class)); cards.add(new SetCardInfo("Thunder Spirit", 27, Rarity.UNCOMMON, mage.cards.t.ThunderSpirit.class)); + cards.add(new SetCardInfo("Time Elemental", 53, Rarity.RARE, mage.cards.t.TimeElemental.class)); cards.add(new SetCardInfo("Tivadar's Crusade", 28, Rarity.UNCOMMON, mage.cards.t.TivadarsCrusade.class)); cards.add(new SetCardInfo("Tornado", 136, Rarity.RARE, mage.cards.t.Tornado.class)); cards.add(new SetCardInfo("Urza's Bauble", 170, Rarity.UNCOMMON, mage.cards.u.UrzasBauble.class)); @@ -239,7 +241,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Walking Wall", 172, Rarity.UNCOMMON, mage.cards.w.WalkingWall.class)); cards.add(new SetCardInfo("Wanderlust", 137, Rarity.COMMON, mage.cards.w.Wanderlust.class)); cards.add(new SetCardInfo("Winds of Change", 111, Rarity.UNCOMMON, mage.cards.w.WindsOfChange.class)); - cards.add(new SetCardInfo("Winter Blast", 138, Rarity.UNCOMMON, mage.cards.w.WinterBlast.class)); + cards.add(new SetCardInfo("Winter Blast", 138, Rarity.UNCOMMON, mage.cards.w.WinterBlast.class)); cards.add(new SetCardInfo("Winter Orb", 173, Rarity.RARE, mage.cards.w.WinterOrb.class)); cards.add(new SetCardInfo("Wyluli Wolf", 139, Rarity.COMMON, mage.cards.w.WyluliWolf.class)); cards.add(new SetCardInfo("Yavimaya Ants", 140, Rarity.UNCOMMON, mage.cards.y.YavimayaAnts.class)); From 00ef226cfa61fdc091268c2c01af67f8fcf25ff1 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:05:33 +0200 Subject: [PATCH 20/77] Implemented Johan, Petra Sphinx, Teleport --- Mage.Sets/src/mage/sets/Chronicles.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/Chronicles.java b/Mage.Sets/src/mage/sets/Chronicles.java index add6a9345ff..fac22fcc4f3 100644 --- a/Mage.Sets/src/mage/sets/Chronicles.java +++ b/Mage.Sets/src/mage/sets/Chronicles.java @@ -57,7 +57,7 @@ public class Chronicles extends ExpansionSet { cards.add(new SetCardInfo("Active Volcano", 43, Rarity.COMMON, mage.cards.a.ActiveVolcano.class)); cards.add(new SetCardInfo("Akron Legionnaire", 58, Rarity.RARE, mage.cards.a.AkronLegionnaire.class)); cards.add(new SetCardInfo("Aladdin", 44, Rarity.RARE, mage.cards.a.Aladdin.class)); - cards.add(new SetCardInfo("Angelic Voices", 59, Rarity.RARE, mage.cards.a.AngelicVoices.class)); + cards.add(new SetCardInfo("Angelic Voices", 59, Rarity.RARE, mage.cards.a.AngelicVoices.class)); cards.add(new SetCardInfo("Arcades Sabboth", 106, Rarity.RARE, mage.cards.a.ArcadesSabboth.class)); cards.add(new SetCardInfo("Arena of the Ancients", 71, Rarity.RARE, mage.cards.a.ArenaOfTheAncients.class)); cards.add(new SetCardInfo("Argothian Pixies", 29, Rarity.COMMON, mage.cards.a.ArgothianPixies.class)); @@ -98,9 +98,10 @@ public class Chronicles extends ExpansionSet { cards.add(new SetCardInfo("Hasran Ogress", 6, Rarity.COMMON, mage.cards.h.HasranOgress.class)); cards.add(new SetCardInfo("Hell's Caretaker", 7, Rarity.RARE, mage.cards.h.HellsCaretaker.class)); cards.add(new SetCardInfo("Horn of Deafening", 80, Rarity.RARE, mage.cards.h.HornOfDeafening.class)); - cards.add(new SetCardInfo("Indestructible Aura", 63, Rarity.COMMON, mage.cards.i.IndestructibleAura.class)); + cards.add(new SetCardInfo("Indestructible Aura", 63, Rarity.COMMON, mage.cards.i.IndestructibleAura.class)); cards.add(new SetCardInfo("Ivory Guardians", 64, Rarity.UNCOMMON, mage.cards.i.IvoryGuardians.class)); cards.add(new SetCardInfo("Jalum Tome", 81, Rarity.RARE, mage.cards.j.JalumTome.class)); + cards.add(new SetCardInfo("Johan", 112, Rarity.RARE, mage.cards.j.Johan.class)); cards.add(new SetCardInfo("Juxtapose", 22, Rarity.RARE, mage.cards.j.Juxtapose.class)); cards.add(new SetCardInfo("Keepers of the Faith", 65, Rarity.COMMON, mage.cards.k.KeepersOfTheFaith.class)); cards.add(new SetCardInfo("Kei Takahashi", 113, Rarity.UNCOMMON, mage.cards.k.KeiTakahashi.class)); @@ -112,6 +113,7 @@ public class Chronicles extends ExpansionSet { cards.add(new SetCardInfo("Nicol Bolas", 116, Rarity.RARE, mage.cards.n.NicolBolas.class)); cards.add(new SetCardInfo("Obelisk of Undoing", 84, Rarity.RARE, mage.cards.o.ObeliskOfUndoing.class)); cards.add(new SetCardInfo("Palladia-Mors", 117, Rarity.RARE, mage.cards.p.PalladiaMors.class)); + cards.add(new SetCardInfo("Petra Sphinx", 66, Rarity.RARE, mage.cards.p.PetraSphinx.class)); cards.add(new SetCardInfo("Rabid Wombat", 39, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class)); cards.add(new SetCardInfo("Rakalite", 85, Rarity.RARE, mage.cards.r.Rakalite.class)); cards.add(new SetCardInfo("Recall", 24, Rarity.UNCOMMON, mage.cards.r.Recall.class)); @@ -127,6 +129,7 @@ public class Chronicles extends ExpansionSet { cards.add(new SetCardInfo("Sivitri Scarzam", 119, Rarity.UNCOMMON, mage.cards.s.SivitriScarzam.class)); cards.add(new SetCardInfo("Sol'kanar the Swamp King", 120, Rarity.RARE, mage.cards.s.SolkanarTheSwampKing.class)); cards.add(new SetCardInfo("Storm Seeker", 42, Rarity.UNCOMMON, mage.cards.s.StormSeeker.class)); + cards.add(new SetCardInfo("Teleport", 26, Rarity.RARE, mage.cards.t.Teleport.class)); cards.add(new SetCardInfo("The Wretched", 11, Rarity.RARE, mage.cards.t.TheWretched.class)); cards.add(new SetCardInfo("Tobias Andrion", 122, Rarity.UNCOMMON, mage.cards.t.TobiasAndrion.class)); cards.add(new SetCardInfo("Tormod's Crypt", 89, Rarity.COMMON, mage.cards.t.TormodsCrypt.class)); From 46817238fe9a7926c84fa6525225f2f79d24f834 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:07:01 +0200 Subject: [PATCH 21/77] Implemented Johan, Petra Sphinx, Teleport, Time Elemental --- Mage.Sets/src/mage/sets/Legends.java | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Mage.Sets/src/mage/sets/Legends.java b/Mage.Sets/src/mage/sets/Legends.java index 62385b2f40a..d7d93348332 100644 --- a/Mage.Sets/src/mage/sets/Legends.java +++ b/Mage.Sets/src/mage/sets/Legends.java @@ -101,13 +101,13 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("D'Avenant Archer", 176, Rarity.COMMON, mage.cards.d.DAvenantArcher.class)); cards.add(new SetCardInfo("Demonic Torment", 9, Rarity.UNCOMMON, mage.cards.d.DemonicTorment.class)); cards.add(new SetCardInfo("Devouring Deep", 50, Rarity.COMMON, mage.cards.d.DevouringDeep.class)); - cards.add(new SetCardInfo("Disharmony", 140, Rarity.RARE, mage.cards.d.Disharmony.class)); + cards.add(new SetCardInfo("Disharmony", 140, Rarity.RARE, mage.cards.d.Disharmony.class)); cards.add(new SetCardInfo("Divine Intervention", 177, Rarity.RARE, mage.cards.d.DivineIntervention.class)); cards.add(new SetCardInfo("Divine Offering", 178, Rarity.COMMON, mage.cards.d.DivineOffering.class)); cards.add(new SetCardInfo("Divine Transformation", 179, Rarity.RARE, mage.cards.d.DivineTransformation.class)); cards.add(new SetCardInfo("Durkwood Boars", 96, Rarity.COMMON, mage.cards.d.DurkwoodBoars.class)); cards.add(new SetCardInfo("Dwarven Song", 141, Rarity.UNCOMMON, mage.cards.d.DwarvenSong.class)); - cards.add(new SetCardInfo("Elder Land Wurm", 180, Rarity.RARE, mage.cards.e.ElderLandWurm.class)); + cards.add(new SetCardInfo("Elder Land Wurm", 180, Rarity.RARE, mage.cards.e.ElderLandWurm.class)); cards.add(new SetCardInfo("Elven Riders", 97, Rarity.RARE, mage.cards.e.ElvenRiders.class)); cards.add(new SetCardInfo("Emerald Dragonfly", 98, Rarity.COMMON, mage.cards.e.EmeraldDragonfly.class)); cards.add(new SetCardInfo("Energy Tap", 54, Rarity.COMMON, mage.cards.e.EnergyTap.class)); @@ -119,20 +119,20 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Fire Sprites", 100, Rarity.COMMON, mage.cards.f.FireSprites.class)); cards.add(new SetCardInfo("Flash Counter", 56, Rarity.COMMON, mage.cards.f.FlashCounter.class)); cards.add(new SetCardInfo("Flash Flood", 57, Rarity.COMMON, mage.cards.f.FlashFlood.class)); - cards.add(new SetCardInfo("Floral Spuzzem", 101, Rarity.UNCOMMON, mage.cards.f.FloralSpuzzem.class)); + cards.add(new SetCardInfo("Floral Spuzzem", 101, Rarity.UNCOMMON, mage.cards.f.FloralSpuzzem.class)); cards.add(new SetCardInfo("Force Spike", 58, Rarity.COMMON, mage.cards.f.ForceSpike.class)); cards.add(new SetCardInfo("Frost Giant", 146, Rarity.UNCOMMON, mage.cards.f.FrostGiant.class)); cards.add(new SetCardInfo("Gaseous Form", 59, Rarity.COMMON, mage.cards.g.GaseousForm.class)); cards.add(new SetCardInfo("Ghosts of the Damned", 12, Rarity.COMMON, mage.cards.g.GhostsOfTheDamned.class)); cards.add(new SetCardInfo("Giant Strength", 147, Rarity.COMMON, mage.cards.g.GiantStrength.class)); - cards.add(new SetCardInfo("Giant Turtle", 102, Rarity.COMMON, mage.cards.g.GiantTurtle.class)); + cards.add(new SetCardInfo("Giant Turtle", 102, Rarity.COMMON, mage.cards.g.GiantTurtle.class)); cards.add(new SetCardInfo("Gravity Sphere", 149, Rarity.RARE, mage.cards.g.GravitySphere.class)); cards.add(new SetCardInfo("Great Defender", 185, Rarity.UNCOMMON, mage.cards.g.GreatDefender.class)); cards.add(new SetCardInfo("Greater Realm of Preservation", 187, Rarity.UNCOMMON, mage.cards.g.GreaterRealmOfPreservation.class)); cards.add(new SetCardInfo("Greed", 15, Rarity.RARE, mage.cards.g.Greed.class)); cards.add(new SetCardInfo("Green Mana Battery", 223, Rarity.UNCOMMON, mage.cards.g.GreenManaBattery.class)); cards.add(new SetCardInfo("Gwendlyn Di Corci", 268, Rarity.RARE, mage.cards.g.GwendlynDiCorci.class)); - cards.add(new SetCardInfo("Hammerheim", 247, Rarity.UNCOMMON, mage.cards.h.Hammerheim.class)); + cards.add(new SetCardInfo("Hammerheim", 247, Rarity.UNCOMMON, mage.cards.h.Hammerheim.class)); cards.add(new SetCardInfo("Hazezon Tamar", 270, Rarity.RARE, mage.cards.h.HazezonTamar.class)); cards.add(new SetCardInfo("Headless Horseman", 16, Rarity.COMMON, mage.cards.h.HeadlessHorseman.class)); cards.add(new SetCardInfo("Heaven's Gate", 188, Rarity.UNCOMMON, mage.cards.h.HeavensGate.class)); @@ -147,13 +147,14 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Hyperion Blacksmith", 150, Rarity.UNCOMMON, mage.cards.h.HyperionBlacksmith.class)); cards.add(new SetCardInfo("Immolation", 151, Rarity.COMMON, mage.cards.i.Immolation.class)); cards.add(new SetCardInfo("In the Eye of Chaos", 61, Rarity.RARE, mage.cards.i.InTheEyeOfChaos.class)); - cards.add(new SetCardInfo("Indestructible Aura", 190, Rarity.COMMON, mage.cards.i.IndestructibleAura.class)); + cards.add(new SetCardInfo("Indestructible Aura", 190, Rarity.COMMON, mage.cards.i.IndestructibleAura.class)); cards.add(new SetCardInfo("Invoke Prejudice", 62, Rarity.RARE, mage.cards.i.InvokePrejudice.class)); cards.add(new SetCardInfo("Ivory Guardians", 192, Rarity.UNCOMMON, mage.cards.i.IvoryGuardians.class)); cards.add(new SetCardInfo("Jacques le Vert", 272, Rarity.RARE, mage.cards.j.JacquesLeVert.class)); cards.add(new SetCardInfo("Jasmine Boreal", 273, Rarity.UNCOMMON, mage.cards.j.JasmineBoreal.class)); cards.add(new SetCardInfo("Jedit Ojanen", 274, Rarity.UNCOMMON, mage.cards.j.JeditOjanen.class)); cards.add(new SetCardInfo("Jerrard of the Closed Fist", 275, Rarity.UNCOMMON, mage.cards.j.JerrardOfTheClosedFist.class)); + cards.add(new SetCardInfo("Johan", 276, Rarity.RARE, mage.cards.j.Johan.class)); cards.add(new SetCardInfo("Juxtapose", 63, Rarity.RARE, mage.cards.j.Juxtapose.class)); cards.add(new SetCardInfo("Karakas", 248, Rarity.UNCOMMON, mage.cards.k.Karakas.class)); cards.add(new SetCardInfo("Kasimir the Lone Wolf", 277, Rarity.UNCOMMON, mage.cards.k.KasimirTheLoneWolf.class)); @@ -191,6 +192,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Part Water", 66, Rarity.UNCOMMON, mage.cards.p.PartWater.class)); cards.add(new SetCardInfo("Pavel Maliki", 288, Rarity.UNCOMMON, mage.cards.p.PavelMaliki.class)); cards.add(new SetCardInfo("Pendelhaven", 250, Rarity.UNCOMMON, mage.cards.p.Pendelhaven.class)); + cards.add(new SetCardInfo("Petra Sphinx", 199, Rarity.RARE, mage.cards.p.PetraSphinx.class)); cards.add(new SetCardInfo("Pit Scorpion", 28, Rarity.COMMON, mage.cards.p.PitScorpion.class)); cards.add(new SetCardInfo("Pixie Queen", 110, Rarity.RARE, mage.cards.p.PixieQueen.class)); cards.add(new SetCardInfo("Planar Gate", 235, Rarity.RARE, mage.cards.p.PlanarGate.class)); @@ -216,7 +218,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Righteous Avengers", 203, Rarity.UNCOMMON, mage.cards.r.RighteousAvengers.class)); cards.add(new SetCardInfo("Ring of Immortals", 238, Rarity.RARE, mage.cards.r.RingOfImmortals.class)); cards.add(new SetCardInfo("Riven Turnbull", 294, Rarity.UNCOMMON, mage.cards.r.RivenTurnbull.class)); - cards.add(new SetCardInfo("Rohgahh of Kher Keep", 295, Rarity.RARE, mage.cards.r.RohgahhOfKherKeep.class)); + cards.add(new SetCardInfo("Rohgahh of Kher Keep", 295, Rarity.RARE, mage.cards.r.RohgahhOfKherKeep.class)); cards.add(new SetCardInfo("Rubinia Soulsinger", 296, Rarity.RARE, mage.cards.r.RubiniaSoulsinger.class)); cards.add(new SetCardInfo("Rust", 49, Rarity.COMMON, mage.cards.r.Rust.class)); cards.add(new SetCardInfo("Sea Kings' Blessing", 75, Rarity.UNCOMMON, mage.cards.s.SeaKingsBlessing.class)); @@ -227,7 +229,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Sir Shandlar of Eberyn", 297, Rarity.UNCOMMON, mage.cards.s.SirShandlarOfEberyn.class)); cards.add(new SetCardInfo("Sivitri Scarzam", 298, Rarity.UNCOMMON, mage.cards.s.SivitriScarzam.class)); cards.add(new SetCardInfo("Sol'kanar the Swamp King", 299, Rarity.RARE, mage.cards.s.SolkanarTheSwampKing.class)); - cards.add(new SetCardInfo("Spectral Cloak", 78, Rarity.UNCOMMON, mage.cards.s.SpectralCloak.class)); + cards.add(new SetCardInfo("Spectral Cloak", 78, Rarity.UNCOMMON, mage.cards.s.SpectralCloak.class)); cards.add(new SetCardInfo("Spinal Villain", 161, Rarity.RARE, mage.cards.s.SpinalVillain.class)); cards.add(new SetCardInfo("Spirit Link", 206, Rarity.UNCOMMON, mage.cards.s.SpiritLink.class)); cards.add(new SetCardInfo("Spirit Shackle", 31, Rarity.COMMON, mage.cards.s.SpiritShackle.class)); @@ -237,6 +239,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Sylvan Library", 121, Rarity.UNCOMMON, mage.cards.s.SylvanLibrary.class)); cards.add(new SetCardInfo("Sylvan Paradise", 122, Rarity.UNCOMMON, mage.cards.s.SylvanParadise.class)); cards.add(new SetCardInfo("Syphon Soul", 32, Rarity.COMMON, mage.cards.s.SyphonSoul.class)); + cards.add(new SetCardInfo("Teleport", 80, Rarity.RARE, mage.cards.t.Teleport.class)); cards.add(new SetCardInfo("Tetsuo Umezawa", 302, Rarity.RARE, mage.cards.t.TetsuoUmezawa.class)); cards.add(new SetCardInfo("The Abyss", 34, Rarity.RARE, mage.cards.t.TheAbyss.class)); cards.add(new SetCardInfo("The Brute", 164, Rarity.COMMON, mage.cards.t.TheBrute.class)); @@ -244,6 +247,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("The Tabernacle at Pendrell Vale", 252, Rarity.RARE, mage.cards.t.TheTabernacleAtPendrellVale.class)); cards.add(new SetCardInfo("The Wretched", 35, Rarity.RARE, mage.cards.t.TheWretched.class)); cards.add(new SetCardInfo("Thunder Spirit", 208, Rarity.RARE, mage.cards.t.ThunderSpirit.class)); + cards.add(new SetCardInfo("Time Elemental", 81, Rarity.RARE, mage.cards.t.TimeElemental.class)); cards.add(new SetCardInfo("Tobias Andrion", 304, Rarity.UNCOMMON, mage.cards.t.TobiasAndrion.class)); cards.add(new SetCardInfo("Torsten Von Ursus", 306, Rarity.UNCOMMON, mage.cards.t.TorstenVonUrsus.class)); cards.add(new SetCardInfo("Tor Wauki", 305, Rarity.UNCOMMON, mage.cards.t.TorWauki.class)); @@ -268,7 +272,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("White Mana Battery", 244, Rarity.UNCOMMON, mage.cards.w.WhiteManaBattery.class)); cards.add(new SetCardInfo("Willow Satyr", 126, Rarity.RARE, mage.cards.w.WillowSatyr.class)); cards.add(new SetCardInfo("Winds of Change", 169, Rarity.UNCOMMON, mage.cards.w.WindsOfChange.class)); - cards.add(new SetCardInfo("Winter Blast", 127, Rarity.RARE, mage.cards.w.WinterBlast.class)); + cards.add(new SetCardInfo("Winter Blast", 127, Rarity.RARE, mage.cards.w.WinterBlast.class)); cards.add(new SetCardInfo("Wolverine Pack", 128, Rarity.COMMON, mage.cards.w.WolverinePack.class)); cards.add(new SetCardInfo("Xira Arien", 310, Rarity.RARE, mage.cards.x.XiraArien.class)); cards.add(new SetCardInfo("Zephyr Falcon", 86, Rarity.COMMON, mage.cards.z.ZephyrFalcon.class)); From dcc6baf216b7db549404d5b09e6e4e5f2ccf60fb Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 17:29:33 +0200 Subject: [PATCH 22/77] Vexing Arcanix fix --- Mage.Sets/src/mage/cards/v/VexingArcanix.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/v/VexingArcanix.java b/Mage.Sets/src/mage/cards/v/VexingArcanix.java index 62573bbae5e..5278c1626ba 100644 --- a/Mage.Sets/src/mage/cards/v/VexingArcanix.java +++ b/Mage.Sets/src/mage/cards/v/VexingArcanix.java @@ -93,7 +93,7 @@ class VexingArcanixEffect extends OneShotEffect { if (player.getLibrary().hasCards()) { Choice cardChoice = new ChoiceImpl(); - cardChoice.setChoices(CardRepository.instance.getNames + cardChoice.setChoices(CardRepository.instance.getNames()); cardChoice.setMessage("Name a card"); while (!player.choose(Outcome.DrawCard, cardChoice, game)) { if (!player.canRespond()) { From 2b390dd77a5a3796357a1066b6fc18344d1b90a8 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 19:40:30 +0200 Subject: [PATCH 23/77] Implemented Spore Cloud --- Mage.Sets/src/mage/cards/s/SporeCloud.java | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SporeCloud.java diff --git a/Mage.Sets/src/mage/cards/s/SporeCloud.java b/Mage.Sets/src/mage/cards/s/SporeCloud.java new file mode 100644 index 00000000000..38dbaaf8c79 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SporeCloud.java @@ -0,0 +1,123 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; +import mage.abilities.effects.common.TapAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.filter.predicate.permanent.BlockingPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; + +/** + * + * @author L_J + */ +public class SporeCloud extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("blocking creatures"); + static { + filter.add(new BlockingPredicate()); + } + + public SporeCloud(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}{G}"); + + // Tap all blocking creatures. + this.getSpellAbility().addEffect(new TapAllEffect(filter)); + // Prevent all combat damage that would be dealt this turn. + this.getSpellAbility().addEffect(new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true)); + // Each attacking creature and each blocking creature doesn't untap during its controller's next untap step. + this.getSpellAbility().addEffect(new SporeCloudEffect()); + } + + public SporeCloud(final SporeCloud card) { + super(card); + } + + @Override + public SporeCloud copy() { + return new SporeCloud(this); + } +} + +class SporeCloudEffect extends OneShotEffect { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Each attacking creature and each blocking creature"); + static { + filter.add(Predicates.or(new AttackingPredicate(), new BlockingPredicate())); + } + + public SporeCloudEffect() { + super(Outcome.Benefit); + this.staticText = "Each attacking creature and each blocking creature doesn't untap during its controller's next untap step"; + } + + public SporeCloudEffect(final SporeCloudEffect effect) { + super(effect); + } + + @Override + public SporeCloudEffect copy() { + return new SporeCloudEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + List doNotUntapNextUntapStep = new ArrayList<>(); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + doNotUntapNextUntapStep.add(permanent); + } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); + game.addEffect(effect, source); + } + return true; + } + return false; + } +} From baf8fbfc9ba33b42c714a5193775667a446b9edd Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 19:41:40 +0200 Subject: [PATCH 24/77] Implemented Spore Cloud --- Mage.Sets/src/mage/sets/MastersEditionII.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index f93ecf7023e..383e2b38af4 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -148,7 +148,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Helm of Obedience", 210, Rarity.RARE, mage.cards.h.HelmOfObedience.class)); cards.add(new SetCardInfo("Icatian Javelineers", 15, Rarity.COMMON, IcatianJavelineers.class)); cards.add(new SetCardInfo("Icatian Scout", 17, Rarity.COMMON, IcatianScout.class)); - cards.add(new SetCardInfo("Ice Floe", 232, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); + cards.add(new SetCardInfo("Ice Floe", 232, Rarity.UNCOMMON, mage.cards.i.IceFloe.class)); cards.add(new SetCardInfo("Iceberg", 49, Rarity.UNCOMMON, mage.cards.i.Iceberg.class)); cards.add(new SetCardInfo("Icequake", 94, Rarity.COMMON, mage.cards.i.Icequake.class)); cards.add(new SetCardInfo("Icy Prison", 50, Rarity.COMMON, mage.cards.i.IcyPrison.class)); @@ -170,7 +170,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Kjeldoran Outpost", 233, Rarity.RARE, mage.cards.k.KjeldoranOutpost.class)); cards.add(new SetCardInfo("Knight of Stromgald", 99, Rarity.UNCOMMON, mage.cards.k.KnightOfStromgald.class)); cards.add(new SetCardInfo("Krovikan Fetish", 100, Rarity.COMMON, mage.cards.k.KrovikanFetish.class)); - cards.add(new SetCardInfo("Krovikan Horror", 101, Rarity.RARE, mage.cards.k.KrovikanHorror.class)); + cards.add(new SetCardInfo("Krovikan Horror", 101, Rarity.RARE, mage.cards.k.KrovikanHorror.class)); cards.add(new SetCardInfo("Krovikan Sorcerer", 51, Rarity.COMMON, mage.cards.k.KrovikanSorcerer.class)); cards.add(new SetCardInfo("Leaping Lizard", 171, Rarity.COMMON, mage.cards.l.LeapingLizard.class)); cards.add(new SetCardInfo("Lim-Dul's High Guard", 103, Rarity.UNCOMMON, LimDulsHighGuard.class)); @@ -217,7 +217,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Sea Drake", 64, Rarity.RARE, mage.cards.s.SeaDrake.class)); cards.add(new SetCardInfo("Sea Spirit", 65, Rarity.UNCOMMON, mage.cards.s.SeaSpirit.class)); cards.add(new SetCardInfo("Shrink", 175, Rarity.COMMON, mage.cards.s.Shrink.class)); - cards.add(new SetCardInfo("Shyft", 66, Rarity.COMMON, mage.cards.s.Shyft.class)); + cards.add(new SetCardInfo("Shyft", 66, Rarity.COMMON, mage.cards.s.Shyft.class)); cards.add(new SetCardInfo("Sibilant Spirit", 67, Rarity.RARE, mage.cards.s.SibilantSpirit.class)); cards.add(new SetCardInfo("Skeleton Ship", 197, Rarity.RARE, mage.cards.s.SkeletonShip.class)); cards.add(new SetCardInfo("Skull Catapult", 219, Rarity.UNCOMMON, mage.cards.s.SkullCatapult.class)); @@ -233,6 +233,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Songs of the Damned", 110, Rarity.COMMON, mage.cards.s.SongsOfTheDamned.class)); cards.add(new SetCardInfo("Soul Exchange", 111, Rarity.UNCOMMON, mage.cards.s.SoulExchange.class)); cards.add(new SetCardInfo("Soul Kiss", 112, Rarity.UNCOMMON, mage.cards.s.SoulKiss.class)); + cards.add(new SetCardInfo("Spore Cloud", 176, Rarity.UNCOMMON, mage.cards.s.SporeCloud.class)); cards.add(new SetCardInfo("Spore Flower", 177, Rarity.UNCOMMON, mage.cards.s.SporeFlower.class)); cards.add(new SetCardInfo("Stampede", 178, Rarity.UNCOMMON, mage.cards.s.Stampede.class)); cards.add(new SetCardInfo("Stonehands", 151, Rarity.COMMON, mage.cards.s.Stonehands.class)); From 9cb5298b9c94ddbb3bab7a2b3b5dd99e61b869f9 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 19:42:00 +0200 Subject: [PATCH 25/77] Implemented Spore Cloud --- Mage.Sets/src/mage/sets/FallenEmpires.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage.Sets/src/mage/sets/FallenEmpires.java b/Mage.Sets/src/mage/sets/FallenEmpires.java index e8b7e2d03fb..bbfed147903 100644 --- a/Mage.Sets/src/mage/sets/FallenEmpires.java +++ b/Mage.Sets/src/mage/sets/FallenEmpires.java @@ -219,6 +219,9 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Seasinger", 52, Rarity.UNCOMMON, mage.cards.s.Seasinger.class)); cards.add(new SetCardInfo("Soul Exchange", 28, Rarity.UNCOMMON, mage.cards.s.SoulExchange.class)); cards.add(new SetCardInfo("Spirit Shield", 175, Rarity.RARE, mage.cards.s.SpiritShield.class)); + cards.add(new SetCardInfo("Spore Cloud", 83, Rarity.COMMON, mage.cards.s.SporeCloud.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spore Cloud", 84, Rarity.COMMON, mage.cards.s.SporeCloud.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spore Cloud", 85, Rarity.COMMON, mage.cards.s.SporeCloud.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spore Flower", 86, Rarity.UNCOMMON, mage.cards.s.SporeFlower.class)); cards.add(new SetCardInfo("Svyelunite Priest", 53, Rarity.UNCOMMON, mage.cards.s.SvyelunitePriest.class)); cards.add(new SetCardInfo("Svyelunite Temple", 187, Rarity.UNCOMMON, mage.cards.s.SvyeluniteTemple.class)); From ac7c4d9d64ce6ebd643eebc18f83e97c9c4ec878 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 20:31:05 +0200 Subject: [PATCH 26/77] Implemented General's Regalia --- .../src/mage/cards/g/GeneralsRegalia.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GeneralsRegalia.java diff --git a/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java b/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java new file mode 100644 index 00000000000..3b5bc599ac8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GeneralsRegalia.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.g; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.RedirectionEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetSource; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author L_J + */ +public class GeneralsRegalia extends CardImpl { + + public GeneralsRegalia(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + + // {3}: The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature you control instead. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GeneralsRegaliaEffect(), new GenericManaCost(3)); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + public GeneralsRegalia(final GeneralsRegalia card) { + super(card); + } + + @Override + public GeneralsRegalia copy() { + return new GeneralsRegalia(this); + } +} + +class GeneralsRegaliaEffect extends RedirectionEffect { + + private final TargetSource damageSource; + + public GeneralsRegaliaEffect() { + super(Duration.EndOfTurn, Integer.MAX_VALUE, true); + staticText = "The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature you control instead"; + this.damageSource = new TargetSource(); + } + + public GeneralsRegaliaEffect(final GeneralsRegaliaEffect effect) { + super(effect); + this.damageSource = effect.damageSource.copy(); + } + + @Override + public GeneralsRegaliaEffect copy() { + return new GeneralsRegaliaEffect(this); + } + + @Override + public void init(Ability source, Game game) { + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), game); + super.init(source, game); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + // check source + MageObject object = game.getObject(event.getSourceId()); + if (object == null) { + game.informPlayers("Couldn't find source of damage"); + return false; + } + + if (!object.getId().equals(damageSource.getFirstTarget()) + && (!(object instanceof Spell) || !((Spell) object).getSourceId().equals(damageSource.getFirstTarget()))) { + return false; + } + this.redirectTarget = source.getTargets().get(0); + + // check target + // check permanent first + //~ Permanent permanent = game.getPermanent(event.getTargetId()); + //~ if (permanent != null) { + //~ if (permanent.getControllerId().equals(source.getControllerId())) { + //~ // it's your permanent + //~ return true; + //~ } + //~ } + // check player + Player player = game.getPlayer(event.getTargetId()); + if (player != null) { + if (player.getId().equals(source.getControllerId())) { + return true; + } + } + return false; + } + +} From 4df30269ee9339fa39267540969bef671475eb37 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 20:31:43 +0200 Subject: [PATCH 27/77] Implemented Nova Pentacle --- Mage.Sets/src/mage/cards/n/NovaPentacle.java | 129 +++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/n/NovaPentacle.java diff --git a/Mage.Sets/src/mage/cards/n/NovaPentacle.java b/Mage.Sets/src/mage/cards/n/NovaPentacle.java new file mode 100644 index 00000000000..e6571a1a874 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NovaPentacle.java @@ -0,0 +1,129 @@ +/* + * 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.n; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.RedirectionEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetSource; +import mage.target.common.TargetOpponentsChoicePermanent; + +/** + * + * @author L_J + */ +public class NovaPentacle extends CardImpl { + + public NovaPentacle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}"); + + // {3}, {tap}: The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new NovaPentacleEffect(), new GenericManaCost(3)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterCreaturePermanent(), false, true)); + this.addAbility(ability); + } + + public NovaPentacle(final NovaPentacle card) { + super(card); + } + + @Override + public NovaPentacle copy() { + return new NovaPentacle(this); + } +} + +class NovaPentacleEffect extends RedirectionEffect { + + private final TargetSource damageSource; + + public NovaPentacleEffect() { + super(Duration.EndOfTurn, Integer.MAX_VALUE, true); + staticText = "The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead"; + this.damageSource = new TargetSource(); + } + + public NovaPentacleEffect(final NovaPentacleEffect effect) { + super(effect); + this.damageSource = effect.damageSource.copy(); + } + + @Override + public NovaPentacleEffect copy() { + return new NovaPentacleEffect(this); + } + + @Override + public void init(Ability source, Game game) { + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), game); + super.init(source, game); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + // check source + MageObject object = game.getObject(event.getSourceId()); + if (object == null) { + game.informPlayers("Couldn't find source of damage"); + return false; + } + + if (!object.getId().equals(damageSource.getFirstTarget()) + && (!(object instanceof Spell) || !((Spell) object).getSourceId().equals(damageSource.getFirstTarget()))) { + return false; + } + this.redirectTarget = source.getTargets().get(0); + + // check player + Player player = game.getPlayer(event.getTargetId()); + if (player != null) { + if (player.getId().equals(source.getControllerId())) { + return true; + } + } + return false; + } + +} From 6430faa8a9f25f977ac2ec4aee629af88c2d5170 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 20:32:36 +0200 Subject: [PATCH 28/77] Implemented General's Regalia --- Mage.Sets/src/mage/sets/MercadianMasques.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index d8ceae02198..c0bde8f6e10 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -158,6 +158,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Fresh Volunteers", 20, Rarity.COMMON, mage.cards.f.FreshVolunteers.class)); cards.add(new SetCardInfo("Furious Assault", 191, Rarity.COMMON, mage.cards.f.FuriousAssault.class)); cards.add(new SetCardInfo("Game Preserve", 248, Rarity.RARE, mage.cards.g.GamePreserve.class)); + cards.add(new SetCardInfo("General's Regalia", 295, Rarity.RARE, mage.cards.g.GeneralsRegalia.class)); cards.add(new SetCardInfo("Gerrard's Irregulars", 192, Rarity.COMMON, mage.cards.g.GerrardsIrregulars.class)); cards.add(new SetCardInfo("Ghoul's Feast", 137, Rarity.UNCOMMON, mage.cards.g.GhoulsFeast.class)); cards.add(new SetCardInfo("Giant Caterpillar", 249, Rarity.COMMON, mage.cards.g.GiantCaterpillar.class)); @@ -329,7 +330,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Tidal Bore", 109, Rarity.COMMON, mage.cards.t.TidalBore.class)); cards.add(new SetCardInfo("Tidal Kraken", 110, Rarity.RARE, mage.cards.t.TidalKraken.class)); cards.add(new SetCardInfo("Tiger Claws", 279, Rarity.COMMON, mage.cards.t.TigerClaws.class)); - cards.add(new SetCardInfo("Timid Drake", 111, Rarity.UNCOMMON, mage.cards.t.TimidDrake.class)); + cards.add(new SetCardInfo("Timid Drake", 111, Rarity.UNCOMMON, mage.cards.t.TimidDrake.class)); cards.add(new SetCardInfo("Tonic Peddler", 54, Rarity.UNCOMMON, mage.cards.t.TonicPeddler.class)); cards.add(new SetCardInfo("Tooth of Ramos", 313, Rarity.RARE, mage.cards.t.ToothOfRamos.class)); cards.add(new SetCardInfo("Tower of the Magistrate", 330, Rarity.RARE, mage.cards.t.TowerOfTheMagistrate.class)); From 669f1678c54ea92645c81ab1bc0b2000c2a30634 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 20:33:23 +0200 Subject: [PATCH 29/77] Implemented Nova Pentacle --- Mage.Sets/src/mage/sets/MastersEditionIII.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/sets/MastersEditionIII.java b/Mage.Sets/src/mage/sets/MastersEditionIII.java index a9a8ee82a1f..4289a331685 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIII.java @@ -100,10 +100,10 @@ public class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Desperate Charge", 63, Rarity.COMMON, mage.cards.d.DesperateCharge.class)); cards.add(new SetCardInfo("Didgeridoo", 194, Rarity.UNCOMMON, mage.cards.d.Didgeridoo.class)); cards.add(new SetCardInfo("Disenchant", 7, Rarity.COMMON, mage.cards.d.Disenchant.class)); - cards.add(new SetCardInfo("Disharmony", 95, Rarity.UNCOMMON, mage.cards.d.Disharmony.class)); + cards.add(new SetCardInfo("Disharmony", 95, Rarity.UNCOMMON, mage.cards.d.Disharmony.class)); cards.add(new SetCardInfo("Divine Intervention", 8, Rarity.RARE, mage.cards.d.DivineIntervention.class)); cards.add(new SetCardInfo("Dong Zhou, the Tyrant", 96, Rarity.RARE, mage.cards.d.DongZhouTheTyrant.class)); - cards.add(new SetCardInfo("Eightfold Maze", 9, Rarity.UNCOMMON, mage.cards.e.EightfoldMaze.class)); + cards.add(new SetCardInfo("Eightfold Maze", 9, Rarity.UNCOMMON, mage.cards.e.EightfoldMaze.class)); cards.add(new SetCardInfo("Elves of Deep Shadow", 116, Rarity.COMMON, mage.cards.e.ElvesOfDeepShadow.class)); cards.add(new SetCardInfo("Evil Presence", 64, Rarity.COMMON, mage.cards.e.EvilPresence.class)); cards.add(new SetCardInfo("Exorcist", 10, Rarity.UNCOMMON, mage.cards.e.Exorcist.class)); @@ -130,7 +130,7 @@ public class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Guan Yu's 1,000-Li March", 13, Rarity.RARE, mage.cards.g.GuanYus1000LiMarch.class)); cards.add(new SetCardInfo("Guan Yu, Sainted Warrior", 12, Rarity.UNCOMMON, mage.cards.g.GuanYuSaintedWarrior.class)); cards.add(new SetCardInfo("Gwendlyn Di Corci", 149, Rarity.RARE, mage.cards.g.GwendlynDiCorci.class)); - cards.add(new SetCardInfo("Hammerheim", 207, Rarity.UNCOMMON, mage.cards.h.Hammerheim.class)); + cards.add(new SetCardInfo("Hammerheim", 207, Rarity.UNCOMMON, mage.cards.h.Hammerheim.class)); cards.add(new SetCardInfo("Hazezon Tamar", 151, Rarity.RARE, mage.cards.h.HazezonTamar.class)); cards.add(new SetCardInfo("Heal", 14, Rarity.COMMON, mage.cards.h.Heal.class)); cards.add(new SetCardInfo("Hellfire", 70, Rarity.RARE, mage.cards.h.Hellfire.class)); @@ -156,7 +156,7 @@ public class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Kobolds of Kher Keep", 107, Rarity.COMMON, mage.cards.k.KoboldsOfKherKeep.class)); cards.add(new SetCardInfo("Kobold Taskmaster", 106, Rarity.COMMON, mage.cards.k.KoboldTaskmaster.class)); cards.add(new SetCardInfo("Kongming, 'Sleeping Dragon'", 16, Rarity.RARE, mage.cards.k.KongmingSleepingDragon.class)); - cards.add(new SetCardInfo("Labyrinth Minotaur", 39, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); + cards.add(new SetCardInfo("Labyrinth Minotaur", 39, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); cards.add(new SetCardInfo("Lady Caleria", 157, Rarity.UNCOMMON, mage.cards.l.LadyCaleria.class)); cards.add(new SetCardInfo("Lady Evangela", 158, Rarity.UNCOMMON, mage.cards.l.LadyEvangela.class)); cards.add(new SetCardInfo("Lady Orca", 159, Rarity.COMMON, mage.cards.l.LadyOrca.class)); @@ -183,6 +183,7 @@ public class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 227, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nether Void", 73, Rarity.RARE, mage.cards.n.NetherVoid.class)); cards.add(new SetCardInfo("Nicol Bolas", 163, Rarity.RARE, mage.cards.n.NicolBolas.class)); + cards.add(new SetCardInfo("Nova Pentacle", 200, Rarity.RARE, mage.cards.n.NovaPentacle.class)); cards.add(new SetCardInfo("Old Man of the Sea", 45, Rarity.RARE, mage.cards.o.OldManOfTheSea.class)); cards.add(new SetCardInfo("Palladia-Mors", 164, Rarity.RARE, mage.cards.p.PalladiaMors.class)); cards.add(new SetCardInfo("Pavel Maliki", 165, Rarity.UNCOMMON, mage.cards.p.PavelMaliki.class)); @@ -204,7 +205,7 @@ public class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Reveka, Wizard Savant", 49, Rarity.UNCOMMON, mage.cards.r.RevekaWizardSavant.class)); cards.add(new SetCardInfo("Riding the Dilu Horse", 131, Rarity.UNCOMMON, mage.cards.r.RidingTheDiluHorse.class)); cards.add(new SetCardInfo("Riven Turnbull", 171, Rarity.UNCOMMON, mage.cards.r.RivenTurnbull.class)); - cards.add(new SetCardInfo("Rohgahh of Kher Keep", 172, Rarity.RARE, mage.cards.r.RohgahhOfKherKeep.class)); + cards.add(new SetCardInfo("Rohgahh of Kher Keep", 172, Rarity.RARE, mage.cards.r.RohgahhOfKherKeep.class)); cards.add(new SetCardInfo("Rolling Earthquake", 110, Rarity.RARE, mage.cards.r.RollingEarthquake.class)); cards.add(new SetCardInfo("Rubinia Soulsinger", 173, Rarity.RARE, mage.cards.r.RubiniaSoulsinger.class)); cards.add(new SetCardInfo("Scrubland", 210, Rarity.RARE, mage.cards.s.Scrubland.class)); From 6e885a8314a7e1e80a1e40e49bea0afd4d317a45 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 20:33:50 +0200 Subject: [PATCH 30/77] Implemented Nova Pentacle --- Mage.Sets/src/mage/sets/Legends.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Legends.java b/Mage.Sets/src/mage/sets/Legends.java index d7d93348332..5b74674e2a0 100644 --- a/Mage.Sets/src/mage/sets/Legends.java +++ b/Mage.Sets/src/mage/sets/Legends.java @@ -187,6 +187,7 @@ public class Legends extends ExpansionSet { cards.add(new SetCardInfo("Mountain Yeti", 156, Rarity.UNCOMMON, mage.cards.m.MountainYeti.class)); cards.add(new SetCardInfo("Nether Void", 27, Rarity.RARE, mage.cards.n.NetherVoid.class)); cards.add(new SetCardInfo("Nicol Bolas", 286, Rarity.RARE, mage.cards.n.NicolBolas.class)); + cards.add(new SetCardInfo("Nova Pentacle", 234, Rarity.RARE, mage.cards.n.NovaPentacle.class)); cards.add(new SetCardInfo("Osai Vultures", 198, Rarity.COMMON, mage.cards.o.OsaiVultures.class)); cards.add(new SetCardInfo("Palladia-Mors", 287, Rarity.RARE, mage.cards.p.PalladiaMors.class)); cards.add(new SetCardInfo("Part Water", 66, Rarity.UNCOMMON, mage.cards.p.PartWater.class)); From 4f373ad41afff1b017a0b9452d714b35f8fd8ba3 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 20:53:19 +0200 Subject: [PATCH 31/77] Removed more garbage --- Mage.Sets/src/mage/cards/g/GeneralsRegalia.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java b/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java index 3b5bc599ac8..e3c90b0b6c6 100644 --- a/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java +++ b/Mage.Sets/src/mage/cards/g/GeneralsRegalia.java @@ -113,15 +113,6 @@ class GeneralsRegaliaEffect extends RedirectionEffect { } this.redirectTarget = source.getTargets().get(0); - // check target - // check permanent first - //~ Permanent permanent = game.getPermanent(event.getTargetId()); - //~ if (permanent != null) { - //~ if (permanent.getControllerId().equals(source.getControllerId())) { - //~ // it's your permanent - //~ return true; - //~ } - //~ } // check player Player player = game.getPlayer(event.getTargetId()); if (player != null) { From b153a7c2a27f177adea59e98847f7f460a731557 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 23:39:31 +0200 Subject: [PATCH 32/77] Implemented Custodi Soulcaller --- Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java b/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java index 04d14eff93e..0d98ce3a17e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java @@ -58,6 +58,10 @@ public class MeleeAbility extends AttacksTriggeredAbility { super(ability); } + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return new MeleeDynamicValue().calculate(game, sourceAbility, effect); + } + @Override public MeleeAbility copy() { return new MeleeAbility(this); From 0082d45877b4c0e2a1b8702d580951d8706353df Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 23:40:35 +0200 Subject: [PATCH 33/77] Implemented Custodi Soulcaller --- .../src/mage/cards/c/CustodiSoulcaller.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java diff --git a/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java b/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java new file mode 100644 index 00000000000..fd6d1da1c09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java @@ -0,0 +1,116 @@ +/* + * 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.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MeleeAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author L_J + */ +public class CustodiSoulcaller extends CardImpl { + + public CustodiSoulcaller(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}{W}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Melee + this.addAbility(new MeleeAbility()); + + // Whenever Custodi Soulcaller attacks, return target creature card with converted mana cost X or less from your graveyard to the battlefield, where X is the number of players you attacked with a creature this combat. + this.addAbility(new AttacksTriggeredAbility(new CustodiSoulcallerEffect(), false)); + } + + public CustodiSoulcaller(final CustodiSoulcaller card) { + super(card); + } + + @Override + public CustodiSoulcaller copy() { + return new CustodiSoulcaller(this); + } +} + +class CustodiSoulcallerEffect extends OneShotEffect { + + public CustodiSoulcallerEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "return target creature card with converted mana cost X or less from your graveyard to the battlefield, where X is the number of players you attacked with a creature this combat"; + } + + public CustodiSoulcallerEffect(final CustodiSoulcallerEffect effect) { + super(effect); + } + + @Override + public CustodiSoulcallerEffect copy() { + return new CustodiSoulcallerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int costX = new MeleeAbility().calculate(game, source, null); + FilterCard filter = new FilterCard("creature card with converted mana cost " + costX + " or less"); + filter.add(new CardTypePredicate(CardType.CREATURE)); + filter.add(Predicates.or(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, costX), new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, costX))); + TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(filter); + if (controller.chooseTarget(outcome, target, source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + } + } + return true; + } + return false; + + } +} From 22f30005144d1e5f759b49a9ebb740b8bec8ca71 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 22 Oct 2017 23:41:07 +0200 Subject: [PATCH 34/77] Implemented Custodi Soulcaller --- Mage.Sets/src/mage/sets/ConspiracyTakeTheCrown.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/ConspiracyTakeTheCrown.java b/Mage.Sets/src/mage/sets/ConspiracyTakeTheCrown.java index 0cda5865fda..630eca1b6cd 100644 --- a/Mage.Sets/src/mage/sets/ConspiracyTakeTheCrown.java +++ b/Mage.Sets/src/mage/sets/ConspiracyTakeTheCrown.java @@ -86,6 +86,7 @@ public class ConspiracyTakeTheCrown extends ExpansionSet { cards.add(new SetCardInfo("Coveted Peacock", 29, Rarity.UNCOMMON, mage.cards.c.CovetedPeacock.class)); cards.add(new SetCardInfo("Crown-Hunter Hireling", 50, Rarity.COMMON, mage.cards.c.CrownHunterHireling.class)); cards.add(new SetCardInfo("Custodi Lich", 41, Rarity.RARE, mage.cards.c.CustodiLich.class)); + cards.add(new SetCardInfo("Custodi Soulcaller", 15, Rarity.UNCOMMON, mage.cards.c.CustodiSoulcaller.class)); cards.add(new SetCardInfo("Daretti, Ingenious Iconoclast", 74, Rarity.MYTHIC, mage.cards.d.DarettiIngeniousIconoclast.class)); cards.add(new SetCardInfo("Deadly Designs", 42, Rarity.UNCOMMON, mage.cards.d.DeadlyDesigns.class)); cards.add(new SetCardInfo("Death Wind", 131, Rarity.COMMON, mage.cards.d.DeathWind.class)); From ee703f8fcd1796fe09b05b2e0c3a94d6f8ce7b36 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 23 Oct 2017 17:54:02 +0200 Subject: [PATCH 35/77] Implemented Dream Tides --- Mage.Sets/src/mage/cards/d/DreamTides.java | 128 +++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DreamTides.java diff --git a/Mage.Sets/src/mage/cards/d/DreamTides.java b/Mage.Sets/src/mage/cards/d/DreamTides.java new file mode 100644 index 00000000000..c47b8dc3d45 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DreamTides.java @@ -0,0 +1,128 @@ +/* + * 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.d; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author spjspj & L_J + */ +public class DreamTides extends CardImpl { + + public DreamTides(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}"); + + // Creatures don't untap during their controllers' untap steps. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DontUntapInControllersUntapStepAllEffect(Duration.WhileOnBattlefield, TargetController.ANY, new FilterCreaturePermanent("Creatures")))); + + // At the beginning of each player's upkeep, that player may choose any number of tapped nongreen creatures he or she controls and pay {2} for each creature chosen this way. If the player does, untap those creatures. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new DreamTidesEffect(), TargetController.ANY, false)); + } + + public DreamTides(final DreamTides card) { + super(card); + } + + @Override + public DreamTides copy() { + return new DreamTides(this); + } +} + +class DreamTidesEffect extends OneShotEffect { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("tapped nongreen creature"); + + static { + filter.add(Predicates.not(new ColorPredicate(ObjectColor.GREEN))); + filter.add(new TappedPredicate()); + } + + DreamTidesEffect() { + super(Outcome.Benefit); + staticText = "that player may choose any number of tapped nongreen creatures he or she controls and pay {2} for each creature chosen this way. If the player does, untap those creatures"; + } + + DreamTidesEffect(DreamTidesEffect effect) { + super(effect); + } + + @Override + public DreamTidesEffect copy() { + return new DreamTidesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (player != null && sourcePermanent != null) { + int countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); + while (player.canRespond() && countBattlefield > 0 && player.chooseUse(Outcome.AIDontUseIt, "Pay {2} and untap a tapped nongreen creature under your control?", source, game)) { + Target tappedCreatureTarget = new TargetControlledCreaturePermanent(1, 1, filter, true); + if (player.choose(Outcome.Detriment, tappedCreatureTarget, source.getSourceId(), game)) { + GenericManaCost cost = new GenericManaCost(2); + Permanent tappedCreature = game.getPermanent(tappedCreatureTarget.getFirstTarget()); + + if (cost.pay(source, game, source.getSourceId(), player.getId(), false)) { + tappedCreature.untap(game); + } + } + countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); + } + return true; + } + return false; + } +} From 3c0ad4368f6667415c33fdc3458c63c0589acb16 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 23 Oct 2017 17:54:31 +0200 Subject: [PATCH 36/77] Implemented Dream Tides --- Mage.Sets/src/mage/sets/Visions.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/Visions.java b/Mage.Sets/src/mage/sets/Visions.java index 7f7d25ed48b..e016bce9fa2 100644 --- a/Mage.Sets/src/mage/sets/Visions.java +++ b/Mage.Sets/src/mage/sets/Visions.java @@ -78,6 +78,7 @@ public class Visions extends ExpansionSet { cards.add(new SetCardInfo("Diamond Kaleidoscope", 143, Rarity.RARE, mage.cards.d.DiamondKaleidoscope.class)); cards.add(new SetCardInfo("Dormant Volcano", 161, Rarity.UNCOMMON, mage.cards.d.DormantVolcano.class)); cards.add(new SetCardInfo("Dragon Mask", 144, Rarity.UNCOMMON, mage.cards.d.DragonMask.class)); + cards.add(new SetCardInfo("Dream Tides", 31, Rarity.UNCOMMON, mage.cards.d.DreamTides.class)); cards.add(new SetCardInfo("Dwarven Vigilantes", 77, Rarity.COMMON, mage.cards.d.DwarvenVigilantes.class)); cards.add(new SetCardInfo("Elephant Grass", 54, Rarity.UNCOMMON, mage.cards.e.ElephantGrass.class)); cards.add(new SetCardInfo("Elven Cache", 55, Rarity.COMMON, mage.cards.e.ElvenCache.class)); @@ -147,7 +148,7 @@ public class Visions extends ExpansionSet { cards.add(new SetCardInfo("Resistance Fighter", 118, Rarity.COMMON, mage.cards.r.ResistanceFighter.class)); cards.add(new SetCardInfo("Retribution of the Meek", 119, Rarity.RARE, mage.cards.r.RetributionOfTheMeek.class)); cards.add(new SetCardInfo("Righteous Aura", 120, Rarity.COMMON, mage.cards.r.RighteousAura.class)); - cards.add(new SetCardInfo("Righteous War", 134, Rarity.RARE, mage.cards.r.RighteousWar.class)); + cards.add(new SetCardInfo("Righteous War", 134, Rarity.RARE, mage.cards.r.RighteousWar.class)); cards.add(new SetCardInfo("River Boa", 68, Rarity.COMMON, mage.cards.r.RiverBoa.class)); cards.add(new SetCardInfo("Rock Slide", 92, Rarity.COMMON, mage.cards.r.RockSlide.class)); cards.add(new SetCardInfo("Rowen", 69, Rarity.RARE, mage.cards.r.Rowen.class)); @@ -162,7 +163,7 @@ public class Visions extends ExpansionSet { cards.add(new SetCardInfo("Spitting Drake", 95, Rarity.UNCOMMON, mage.cards.s.SpittingDrake.class)); cards.add(new SetCardInfo("Squandered Resources", 137, Rarity.RARE, mage.cards.s.SquanderedResources.class)); cards.add(new SetCardInfo("Stampeding Wildebeests", 71, Rarity.UNCOMMON, mage.cards.s.StampedingWildebeests.class)); - cards.add(new SetCardInfo("Suleiman's Legacy", 138, Rarity.RARE, mage.cards.s.SuleimansLegacy.class)); + cards.add(new SetCardInfo("Suleiman's Legacy", 138, Rarity.RARE, mage.cards.s.SuleimansLegacy.class)); cards.add(new SetCardInfo("Summer Bloom", 72, Rarity.UNCOMMON, mage.cards.s.SummerBloom.class)); cards.add(new SetCardInfo("Sun Clasp", 121, Rarity.COMMON, mage.cards.s.SunClasp.class)); cards.add(new SetCardInfo("Suq'Ata Assassin", 19, Rarity.UNCOMMON, mage.cards.s.SuqAtaAssassin.class)); From 2809db6bb2da64e6ed3fef6ea1533ec729a1dffe Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 23 Oct 2017 22:02:59 -0400 Subject: [PATCH 37/77] Implemented Shade's Breath --- .../src/mage/cards/s/SquealingDevil.java | 32 ++++++++----------- Mage.Sets/src/mage/sets/Onslaught.java | 1 + 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SquealingDevil.java b/Mage.Sets/src/mage/cards/s/SquealingDevil.java index eb94a17919f..6ee5d217566 100644 --- a/Mage.Sets/src/mage/cards/s/SquealingDevil.java +++ b/Mage.Sets/src/mage/cards/s/SquealingDevil.java @@ -33,7 +33,6 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.ManaWasSpentCondition; -import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffect; @@ -48,10 +47,8 @@ import mage.constants.ColoredManaSymbol; import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetpointer.FixedTarget; import mage.watchers.common.ManaSpentToCastWatcher; /** @@ -105,23 +102,20 @@ class SquealingDevilEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); ManaCosts cost = new ManaCostsImpl("{X}"); - if (player != null) { - if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); - cost.add(new GenericManaCost(costX)); - if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null && permanent.isCreature()) { - ContinuousEffect effect = new BoostTargetEffect(costX, 0, Duration.EndOfTurn); - effect.setTargetPointer(new FixedTarget(permanent.getId())); - game.addEffect(effect, source); - return true; - } - return false; - } - } + if (player == null) { + return false; } - return false; + if (!player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { + return false; + } + int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + cost.setX(costX); + if (!cost.pay(source, game, source.getSourceId(), source.getControllerId(), false)) { + return false; + } + ContinuousEffect effect = new BoostTargetEffect(costX, 0, Duration.EndOfTurn); + game.addEffect(effect, source); + return true; } @Override diff --git a/Mage.Sets/src/mage/sets/Onslaught.java b/Mage.Sets/src/mage/sets/Onslaught.java index f8311aadc5f..9171d2dcfec 100644 --- a/Mage.Sets/src/mage/sets/Onslaught.java +++ b/Mage.Sets/src/mage/sets/Onslaught.java @@ -260,6 +260,7 @@ public class Onslaught extends ExpansionSet { cards.add(new SetCardInfo("Secluded Steppe", 324, Rarity.COMMON, mage.cards.s.SecludedSteppe.class)); cards.add(new SetCardInfo("Serpentine Basilisk", 280, Rarity.UNCOMMON, mage.cards.s.SerpentineBasilisk.class)); cards.add(new SetCardInfo("Severed Legion", 166, Rarity.COMMON, mage.cards.s.SeveredLegion.class)); + cards.add(new SetCardInfo("Shade's Breath", 167, Rarity.UNCOMMON, mage.cards.s.ShadesBreath.class)); cards.add(new SetCardInfo("Shaleskin Bruiser", 226, Rarity.UNCOMMON, mage.cards.s.ShaleskinBruiser.class)); cards.add(new SetCardInfo("Shared Triumph", 53, Rarity.RARE, mage.cards.s.SharedTriumph.class)); cards.add(new SetCardInfo("Shepherd of Rot", 168, Rarity.COMMON, mage.cards.s.ShepherdOfRot.class)); From a6e066b2672e98931b69138db97756dc2fd30d4c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 23 Oct 2017 22:15:08 -0400 Subject: [PATCH 38/77] Implemented Goblin Machinist --- .../src/mage/cards/g/GoblinMachinist.java | 122 ++++++++++++++++++ Mage.Sets/src/mage/sets/Onslaught.java | 1 + 2 files changed, 123 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GoblinMachinist.java diff --git a/Mage.Sets/src/mage/cards/g/GoblinMachinist.java b/Mage.Sets/src/mage/cards/g/GoblinMachinist.java new file mode 100644 index 00000000000..4253d92c1bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoblinMachinist.java @@ -0,0 +1,122 @@ +/* + * 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.g; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.Card; +import mage.constants.SubType; +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.game.Game; +import mage.players.Library; +import mage.players.Player; + +/** + * + * @author TheElk801 + */ +public class GoblinMachinist extends CardImpl { + + public GoblinMachinist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.GOBLIN); + this.power = new MageInt(0); + this.toughness = new MageInt(5); + + // {2}{R}: Reveal cards from the top of your library until you reveal a nonland card. Goblin Machinist gets +X/+0 until end of turn, where X is that card's converted mana cost. Put the revealed cards on the bottom of your library in any order. + this.addAbility(new SimpleActivatedAbility(new GoblinMachinistEffect(), new ManaCostsImpl("{2}{R}"))); + } + + public GoblinMachinist(final GoblinMachinist card) { + super(card); + } + + @Override + public GoblinMachinist copy() { + return new GoblinMachinist(this); + } +} + +class GoblinMachinistEffect extends OneShotEffect { + + public GoblinMachinistEffect() { + super(Outcome.DrawCard); + this.staticText = "Reveal cards from the top of your library until you reveal a nonland card. {this} gets +X/+0 until end of turn, where X is that card's converted mana cost. Put the revealed cards on the bottom of your library in any order"; + } + + public GoblinMachinistEffect(final GoblinMachinistEffect effect) { + super(effect); + } + + @Override + public GoblinMachinistEffect copy() { + return new GoblinMachinistEffect(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) { + if (controller.getLibrary().hasCards()) { + + CardsImpl cards = new CardsImpl(); + Library library = controller.getLibrary(); + Card card = null; + do { + card = library.removeFromTop(game); + if (card != null) { + cards.add(card); + } + } while (library.hasCards() && card != null && card.isLand()); + if (!cards.isEmpty()) { + controller.revealCards(sourceObject.getIdName(), cards, game); + } + boolean retVal = false; + if (card != null) { + retVal = new BoostSourceEffect(card.getConvertedManaCost(), 0, Duration.EndOfTurn).apply(game, source); + } + return controller.putCardsOnBottomOfLibrary(cards, game, source, true) && retVal; + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Onslaught.java b/Mage.Sets/src/mage/sets/Onslaught.java index 9171d2dcfec..335a165dbd4 100644 --- a/Mage.Sets/src/mage/sets/Onslaught.java +++ b/Mage.Sets/src/mage/sets/Onslaught.java @@ -145,6 +145,7 @@ public class Onslaught extends ExpansionSet { cards.add(new SetCardInfo("Glory Seeker", 31, Rarity.COMMON, mage.cards.g.GlorySeeker.class)); cards.add(new SetCardInfo("Gluttonous Zombie", 151, Rarity.UNCOMMON, mage.cards.g.GluttonousZombie.class)); cards.add(new SetCardInfo("Goblin Burrows", 318, Rarity.UNCOMMON, mage.cards.g.GoblinBurrows.class)); + cards.add(new SetCardInfo("Goblin Machinist", 204, Rarity.UNCOMMON, mage.cards.g.GoblinMachinist.class)); cards.add(new SetCardInfo("Goblin Piledriver", 205, Rarity.RARE, mage.cards.g.GoblinPiledriver.class)); cards.add(new SetCardInfo("Goblin Pyromancer", 206, Rarity.RARE, mage.cards.g.GoblinPyromancer.class)); cards.add(new SetCardInfo("Goblin Sharpshooter", 207, Rarity.RARE, mage.cards.g.GoblinSharpshooter.class)); From 7c5562881b9e7d7cc067d2b456c1a4b2335eaf57 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 06:10:06 +0200 Subject: [PATCH 39/77] Implemented Johan --- .../keyword/JohanVigilanceAbility.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java diff --git a/Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java b/Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java new file mode 100644 index 00000000000..0fb4dd2da79 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java @@ -0,0 +1,67 @@ +/* + * 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.abilities.keyword; + +import mage.constants.Zone; +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * + * @author BetaSteward_at_googlemail.com & L_J + */ +public class JohanVigilanceAbility extends StaticAbility implements MageSingleton { // special instance of "attacking doesn't cause this to tap" granted by Johan's ability + + private static final JohanVigilanceAbility instance = new JohanVigilanceAbility(); + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static JohanVigilanceAbility getInstance() { + return instance; + } + + private JohanVigilanceAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return ""; + } + + @Override + public JohanVigilanceAbility copy() { + return instance; + } + +} From e82d640060ed4b244920171df5ab91425b7fca10 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 06:12:10 +0200 Subject: [PATCH 40/77] Implemented Johan --- Mage/src/main/java/mage/game/combat/Combat.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 1ad1948a1b5..d09bad02a60 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -33,6 +33,7 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; +import mage.abilities.keyword.JohanVigilanceAbility; import mage.abilities.keyword.VigilanceAbility; import mage.constants.Outcome; import mage.constants.Zone; @@ -1091,7 +1092,7 @@ public class Combat implements Serializable, Copyable { @SuppressWarnings("deprecation") public boolean declareAttacker(UUID creatureId, UUID defenderId, UUID playerId, Game game) { Permanent attacker = game.getPermanent(creatureId); - if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId())) { + if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId()) && !attacker.getAbilities().containsKey(JohanVigilanceAbility.getInstance().getId())) { if (!attacker.isTapped()) { attacker.setTapped(true); attackersTappedByAttack.add(attacker.getId()); From c057797906685dc25eb32b6d8eec068886f207b1 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 06:16:08 +0200 Subject: [PATCH 41/77] Split off Johan's "vigilance" granting ability --- Mage.Sets/src/mage/cards/j/Johan.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/j/Johan.java b/Mage.Sets/src/mage/cards/j/Johan.java index 5c92952c347..02acc22e0a2 100644 --- a/Mage.Sets/src/mage/cards/j/Johan.java +++ b/Mage.Sets/src/mage/cards/j/Johan.java @@ -32,12 +32,14 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; import mage.abilities.condition.InvertCondition; +import mage.abilities.condition.common.SourceOnBattlefieldCondition; import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.combat.CantAttackSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; -import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -58,15 +60,17 @@ public class Johan extends CardImpl { addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); - this.power = new MageInt(5); this.toughness = new MageInt(4); // At the beginning of combat on your turn, you may have Johan gain "Johan can't attack" until end of combat. If you do, attacking doesn't cause creatures you control to tap this combat if Johan is untapped. + Condition condition = new CompoundCondition("if {this} is untapped", + new InvertCondition(SourceTappedCondition.instance), + SourceOnBattlefieldCondition.instance); Ability ability = new BeginningOfCombatTriggeredAbility(new CantAttackSourceEffect(Duration.EndOfCombat).setText("you may have {this} gain \"{this} can't attack\" until end of combat"), TargetController.YOU, true); ability.addEffect(new ConditionalContinuousEffect( - new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfCombat, new FilterControlledCreaturePermanent("creatures")), - new InvertCondition(SourceTappedCondition.instance), + new GainAbilityControlledEffect(JohanVigilanceAbility.getInstance(), Duration.EndOfCombat, new FilterControlledCreaturePermanent("creatures")), + condition, "If you do, attacking doesn't cause creatures you control to tap this combat if {this} is untapped")); this.addAbility(ability); } From 90f2a017abb8f4d1364e537184bb1fca61af9a85 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 06:19:45 +0200 Subject: [PATCH 42/77] Update MuragandaPetroglyphs.java --- Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java b/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java index 9721414ce63..410898da785 100644 --- a/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java +++ b/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java @@ -35,6 +35,7 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.keyword.JohanVigilanceAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -92,7 +93,9 @@ class NoAbilityPredicate implements Predicate { if (isFaceDown) { for (Ability ability : abilities) { if (!ability.getSourceId().equals(input.getId())) { - return false; + if (!(ability instanceof JohanVigilanceAbility)) { + return false; + } } } return true; @@ -100,8 +103,9 @@ class NoAbilityPredicate implements Predicate { for (Ability ability : abilities) { if (!Objects.equals(ability.getClass(), SpellAbility.class)) { - - return false; + if (!(ability instanceof JohanVigilanceAbility)) { + return false; + } } } return true; From 61c53bb7b8563780fe6837ac0fb6a6f77ca3c2fc Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 06:20:38 +0200 Subject: [PATCH 43/77] Added missing import --- Mage.Sets/src/mage/cards/j/Johan.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/j/Johan.java b/Mage.Sets/src/mage/cards/j/Johan.java index 02acc22e0a2..8757027588a 100644 --- a/Mage.Sets/src/mage/cards/j/Johan.java +++ b/Mage.Sets/src/mage/cards/j/Johan.java @@ -40,6 +40,7 @@ import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.combat.CantAttackSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.JohanVigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; From 2e3c916717646815626dc94f633f48273efe0f74 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Tue, 24 Oct 2017 07:39:19 -0400 Subject: [PATCH 44/77] fixed an incorrect commit --- Mage.Sets/src/mage/cards/s/ShadesBreath.java | 143 ++++++++++++++++++ .../src/mage/cards/s/SquealingDevil.java | 32 ++-- 2 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/ShadesBreath.java diff --git a/Mage.Sets/src/mage/cards/s/ShadesBreath.java b/Mage.Sets/src/mage/cards/s/ShadesBreath.java new file mode 100644 index 00000000000..c3580f87f4d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShadesBreath.java @@ -0,0 +1,143 @@ +/* + * 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.List; +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.SubTypeList; + +/** + * + * @author TheElk801 + */ +public class ShadesBreath extends CardImpl { + + public ShadesBreath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}"); + + // Until end of turn, each creature you control becomes a black Shade and gains "{B}: This creature gets +1/+1 until end of turn." + this.getSpellAbility().addEffect(new ShadesBreathSetColorEffect()); + this.getSpellAbility().addEffect(new ShadesBreathSetSubtypeEffect()); + this.getSpellAbility().addEffect( + new GainAbilityControlledEffect(new SimpleActivatedAbility( + Zone.BATTLEFIELD, + new BoostSourceEffect(1, 1, Duration.EndOfTurn).setText("this creature gets +1/+1 until end of turn"), + new ManaCostsImpl("{B}") + ), Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_A_CREATURE) + .setText("and gains \"{B}: This creature gets +1/+1 until end of turn.\"") + ); + } + + public ShadesBreath(final ShadesBreath card) { + super(card); + } + + @Override + public ShadesBreath copy() { + return new ShadesBreath(this); + } +} + +class ShadesBreathSetColorEffect extends ContinuousEffectImpl { + + public ShadesBreathSetColorEffect() { + super(Duration.EndOfTurn, Layer.ColorChangingEffects_5, SubLayer.NA, Outcome.Benefit); + staticText = "Until end of turn, each creature you control becomes a black"; + } + + public ShadesBreathSetColorEffect(final ShadesBreathSetColorEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game); + for (Permanent permanent : permanents) { + if (permanent != null) { + permanent.getColor(game).setColor(ObjectColor.BLACK); + } + } + return true; + } + + @Override + public ShadesBreathSetColorEffect copy() { + return new ShadesBreathSetColorEffect(this); + } +} + +class ShadesBreathSetSubtypeEffect extends ContinuousEffectImpl { + + public ShadesBreathSetSubtypeEffect() { + super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + staticText = "Shade"; + } + + public ShadesBreathSetSubtypeEffect(final ShadesBreathSetSubtypeEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game); + for (Permanent permanent : permanents) { + if (permanent != null) { + SubTypeList subtype = permanent.getSubtype(game); + if (subtype != null && subtype.size() != 1 || !subtype.contains(SubType.SHADE)) { + subtype.removeAll(SubType.getCreatureTypes(false)); + subtype.add(SubType.SHADE); + } + } + } + return true; + } + + @Override + public ShadesBreathSetSubtypeEffect copy() { + return new ShadesBreathSetSubtypeEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SquealingDevil.java b/Mage.Sets/src/mage/cards/s/SquealingDevil.java index 6ee5d217566..eb94a17919f 100644 --- a/Mage.Sets/src/mage/cards/s/SquealingDevil.java +++ b/Mage.Sets/src/mage/cards/s/SquealingDevil.java @@ -33,6 +33,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.ManaWasSpentCondition; +import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffect; @@ -47,8 +48,10 @@ import mage.constants.ColoredManaSymbol; import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; import mage.watchers.common.ManaSpentToCastWatcher; /** @@ -102,20 +105,23 @@ class SquealingDevilEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); ManaCosts cost = new ManaCostsImpl("{X}"); - if (player == null) { - return false; + if (player != null) { + if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { + int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + cost.add(new GenericManaCost(costX)); + if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null && permanent.isCreature()) { + ContinuousEffect effect = new BoostTargetEffect(costX, 0, Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + return true; + } + return false; + } + } } - if (!player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { - return false; - } - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); - cost.setX(costX); - if (!cost.pay(source, game, source.getSourceId(), source.getControllerId(), false)) { - return false; - } - ContinuousEffect effect = new BoostTargetEffect(costX, 0, Duration.EndOfTurn); - game.addEffect(effect, source); - return true; + return false; } @Override From 58ff5e017a1c24ce8cd7a110c3af8683c7b54d9a Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 17:01:46 +0200 Subject: [PATCH 45/77] Moved JohanVigilanceAbility --- .../special/JohanVigilanceAbility.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Mage/src/main/java/mage/abilities/keyword/special/JohanVigilanceAbility.java diff --git a/Mage/src/main/java/mage/abilities/keyword/special/JohanVigilanceAbility.java b/Mage/src/main/java/mage/abilities/keyword/special/JohanVigilanceAbility.java new file mode 100644 index 00000000000..2232b4739e9 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/special/JohanVigilanceAbility.java @@ -0,0 +1,67 @@ +/* + * 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.abilities.keyword.special; + +import mage.constants.Zone; +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * + * @author BetaSteward_at_googlemail.com & L_J + */ +public class JohanVigilanceAbility extends StaticAbility implements MageSingleton { // special instance of "attacking doesn't cause this to tap" granted by Johan's ability + + private static final JohanVigilanceAbility instance = new JohanVigilanceAbility(); + + private Object readResolve() throws ObjectStreamException { + return instance; + } + + public static JohanVigilanceAbility getInstance() { + return instance; + } + + private JohanVigilanceAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return ""; + } + + @Override + public JohanVigilanceAbility copy() { + return instance; + } + +} From 78e64fd909d78ce6829104239a4747a73ea85e33 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 17:02:32 +0200 Subject: [PATCH 46/77] Moved JohanVigilanceAbility --- Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java b/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java index 410898da785..c1bb9cce2e6 100644 --- a/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java +++ b/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.BoostAllEffect; -import mage.abilities.keyword.JohanVigilanceAbility; +import mage.abilities.keyword.special.JohanVigilanceAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -93,7 +93,7 @@ class NoAbilityPredicate implements Predicate { if (isFaceDown) { for (Ability ability : abilities) { if (!ability.getSourceId().equals(input.getId())) { - if (!(ability instanceof JohanVigilanceAbility)) { + if (ability.getClass().equals(JohanVigilanceAbility.class)) { return false; } } @@ -103,7 +103,7 @@ class NoAbilityPredicate implements Predicate { for (Ability ability : abilities) { if (!Objects.equals(ability.getClass(), SpellAbility.class)) { - if (!(ability instanceof JohanVigilanceAbility)) { + if (!ability.getClass().equals(JohanVigilanceAbility.class)) { return false; } } From d6f18beb9005be7bc2d305d4ea883b4141c4b2ff Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 17:03:21 +0200 Subject: [PATCH 47/77] Moved JohanVigilanceAbility --- Mage.Sets/src/mage/cards/j/Johan.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/j/Johan.java b/Mage.Sets/src/mage/cards/j/Johan.java index 8757027588a..4affd49c260 100644 --- a/Mage.Sets/src/mage/cards/j/Johan.java +++ b/Mage.Sets/src/mage/cards/j/Johan.java @@ -40,7 +40,7 @@ import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.combat.CantAttackSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; -import mage.abilities.keyword.JohanVigilanceAbility; +import mage.abilities.keyword.special.JohanVigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; From 549f89ac20160a6d6a347d0b77ec950d8575ee93 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 17:04:12 +0200 Subject: [PATCH 48/77] Update Combat.java --- Mage/src/main/java/mage/game/combat/Combat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index d09bad02a60..46ad0faa3e8 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -33,8 +33,8 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; -import mage.abilities.keyword.JohanVigilanceAbility; import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.special.JohanVigilanceAbility; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.StaticFilters; From 6a8ce4c6ea7752a882550ebab5f39e7db55e1361 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 17:04:35 +0200 Subject: [PATCH 49/77] Delete JohanVigilanceAbility.java --- .../keyword/JohanVigilanceAbility.java | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java diff --git a/Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java b/Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java deleted file mode 100644 index 0fb4dd2da79..00000000000 --- a/Mage/src/main/java/mage/abilities/keyword/JohanVigilanceAbility.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.abilities.keyword; - -import mage.constants.Zone; -import mage.abilities.MageSingleton; -import mage.abilities.StaticAbility; - -import java.io.ObjectStreamException; - -/** - * - * @author BetaSteward_at_googlemail.com & L_J - */ -public class JohanVigilanceAbility extends StaticAbility implements MageSingleton { // special instance of "attacking doesn't cause this to tap" granted by Johan's ability - - private static final JohanVigilanceAbility instance = new JohanVigilanceAbility(); - - private Object readResolve() throws ObjectStreamException { - return instance; - } - - public static JohanVigilanceAbility getInstance() { - return instance; - } - - private JohanVigilanceAbility() { - super(Zone.BATTLEFIELD, null); - } - - @Override - public String getRule() { - return ""; - } - - @Override - public JohanVigilanceAbility copy() { - return instance; - } - -} From e9a0b1a5ea82c0dc7a6351844a361dafff8856b6 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 24 Oct 2017 21:14:14 +0200 Subject: [PATCH 50/77] Fix for Manaweft Sliver --- Mage.Sets/src/mage/cards/m/ManaweftSliver.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/ManaweftSliver.java b/Mage.Sets/src/mage/cards/m/ManaweftSliver.java index f26076952dd..cab5585242a 100644 --- a/Mage.Sets/src/mage/cards/m/ManaweftSliver.java +++ b/Mage.Sets/src/mage/cards/m/ManaweftSliver.java @@ -29,9 +29,8 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.mana.AnyColorManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -43,7 +42,7 @@ import mage.filter.StaticFilters; /** * - * @author LevelX2 + * @author LevelX2 & L_J */ public class ManaweftSliver extends CardImpl { @@ -55,11 +54,9 @@ public class ManaweftSliver extends CardImpl { this.toughness = new MageInt(1); // Sliver creatures you control have "{T}: Add one mana of any color to your mana pool." - Ability ability = new AnyColorManaAbility(); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new GainAbilityAllEffect(ability, - Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURE_SLIVERS, - "Sliver creatures you control have \"{T}: Add one mana of any color to your mana pool.\""))); + new GainAbilityControlledEffect(new AnyColorManaAbility(), + Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURE_SLIVERS))); } public ManaweftSliver(final ManaweftSliver card) { From fe9ce16ed2f56de4463393d501fb8ffc48610f14 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 25 Oct 2017 01:10:02 +0200 Subject: [PATCH 51/77] Reverted MeleeAbility.calculate --- Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java b/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java index 0d98ce3a17e..04d14eff93e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MeleeAbility.java @@ -58,10 +58,6 @@ public class MeleeAbility extends AttacksTriggeredAbility { super(ability); } - public int calculate(Game game, Ability sourceAbility, Effect effect) { - return new MeleeDynamicValue().calculate(game, sourceAbility, effect); - } - @Override public MeleeAbility copy() { return new MeleeAbility(this); From 0f45dfe4fb2380843cc0e3edf44f44d9d41cadcd Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 25 Oct 2017 01:13:41 +0200 Subject: [PATCH 52/77] Revamped targetting as adjustTargets --- .../src/mage/cards/c/CustodiSoulcaller.java | 86 ++++++++++++------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java b/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java index fd6d1da1c09..b2f463323ce 100644 --- a/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java +++ b/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java @@ -27,25 +27,29 @@ */ package mage.cards.c; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.keyword.MeleeAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; import mage.game.Game; -import mage.players.Player; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.Watcher; /** * @@ -64,7 +68,26 @@ public class CustodiSoulcaller extends CardImpl { this.addAbility(new MeleeAbility()); // Whenever Custodi Soulcaller attacks, return target creature card with converted mana cost X or less from your graveyard to the battlefield, where X is the number of players you attacked with a creature this combat. - this.addAbility(new AttacksTriggeredAbility(new CustodiSoulcallerEffect(), false)); + Ability ability = new AttacksTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), false); + ability.addWatcher(new CustodiSoulcallerWatcher()); + ability.addTarget(new TargetCardInYourGraveyard(new FilterCreatureCard("creature card with converted mana cost X or less from your graveyard, where X is the number of players you attacked with a creature this combat"))); + this.addAbility(ability); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability.getClass().equals(AttacksTriggeredAbility.class)) { + ability.getTargets().clear(); + CustodiSoulcallerWatcher watcher = (CustodiSoulcallerWatcher) game.getState().getWatchers().get(CustodiSoulcallerWatcher.class.getSimpleName()); + Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); + if (watcher != null && watcher.playersAttacked != null) { + int xValue = watcher.getNumberOfAttackedPlayers(sourcePermanent.getControllerId()); + FilterCard filter = new FilterCard("creature card with converted mana cost " + xValue + " or less"); + filter.add(new CardTypePredicate(CardType.CREATURE)); + filter.add(Predicates.or(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, xValue), new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, xValue))); + ability.getTargets().add(new TargetCardInYourGraveyard(filter)); + } + } } public CustodiSoulcaller(final CustodiSoulcaller card) { @@ -77,40 +100,37 @@ public class CustodiSoulcaller extends CardImpl { } } -class CustodiSoulcallerEffect extends OneShotEffect { +class CustodiSoulcallerWatcher extends Watcher { - public CustodiSoulcallerEffect() { - super(Outcome.PutCardInPlay); - this.staticText = "return target creature card with converted mana cost X or less from your graveyard to the battlefield, where X is the number of players you attacked with a creature this combat"; + protected final HashMap> playersAttacked = new HashMap<>(0); + + CustodiSoulcallerWatcher() { + super("CustodiSoulcallerWatcher", WatcherScope.GAME); } - public CustodiSoulcallerEffect(final CustodiSoulcallerEffect effect) { - super(effect); + CustodiSoulcallerWatcher(final CustodiSoulcallerWatcher watcher) { + super(watcher); + this.playersAttacked.putAll(watcher.playersAttacked); } @Override - public CustodiSoulcallerEffect copy() { - return new CustodiSoulcallerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int costX = new MeleeAbility().calculate(game, source, null); - FilterCard filter = new FilterCard("creature card with converted mana cost " + costX + " or less"); - filter.add(new CardTypePredicate(CardType.CREATURE)); - filter.add(Predicates.or(new ConvertedManaCostPredicate(ComparisonType.EQUAL_TO, costX), new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, costX))); - TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(filter); - if (controller.chooseTarget(outcome, target, source, game)) { - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game); - } - } - return true; + public void watch(GameEvent event, Game game) { + if (event.getType() == EventType.BEGIN_COMBAT_STEP_PRE) { + this.playersAttacked.clear(); } - return false; + else if (event.getType() == EventType.ATTACKER_DECLARED) { + Set attackedPlayers = this.playersAttacked.getOrDefault(event.getPlayerId(), new HashSet<>(1)); + attackedPlayers.add(event.getTargetId()); + this.playersAttacked.put(event.getPlayerId(), attackedPlayers); + } + } + public int getNumberOfAttackedPlayers(UUID attackerId) { + return this.playersAttacked.get(attackerId).size(); + } + + @Override + public CustodiSoulcallerWatcher copy() { + return new CustodiSoulcallerWatcher(this); } } From 5b158fc7f10d0552af56aefe4cde7067ebc46cc7 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 25 Oct 2017 01:15:30 +0200 Subject: [PATCH 53/77] Included game.getPermanentOrLKIBattlefield in adjustTargets --- Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java b/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java index b2f463323ce..eb5a9756df7 100644 --- a/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java +++ b/Mage.Sets/src/mage/cards/c/CustodiSoulcaller.java @@ -79,7 +79,7 @@ public class CustodiSoulcaller extends CardImpl { if (ability.getClass().equals(AttacksTriggeredAbility.class)) { ability.getTargets().clear(); CustodiSoulcallerWatcher watcher = (CustodiSoulcallerWatcher) game.getState().getWatchers().get(CustodiSoulcallerWatcher.class.getSimpleName()); - Permanent sourcePermanent = game.getPermanent(ability.getSourceId()); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(ability.getSourceId()); if (watcher != null && watcher.playersAttacked != null) { int xValue = watcher.getNumberOfAttackedPlayers(sourcePermanent.getControllerId()); FilterCard filter = new FilterCard("creature card with converted mana cost " + xValue + " or less"); From 3746fddf54c8ed2b20cf265c2fd147449ec0c825 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 25 Oct 2017 07:24:49 -0400 Subject: [PATCH 54/77] fixed Angelic Accord displaying the wrong amount of life gain required (fixes #4132 and #4133) --- .../abilities/condition/common/YouGainedLifeCondition.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/condition/common/YouGainedLifeCondition.java b/Mage/src/main/java/mage/abilities/condition/common/YouGainedLifeCondition.java index f19ff14b8e7..a33a4d9bd03 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/YouGainedLifeCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/YouGainedLifeCondition.java @@ -27,7 +27,6 @@ public class YouGainedLifeCondition extends IntCompareCondition { @Override public String toString() { - return String.format("if you gained %s or more life this turn ", value); + return String.format("if you gained %s or more life this turn ", value + 1); } } - From 15764549d45135ef53c80fe9494c1b64a75b880f Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 25 Oct 2017 09:23:31 -0400 Subject: [PATCH 55/77] fixed Manaweft Sliver giving abilities to other player's creatures --- Mage.Sets/src/mage/cards/m/ManaweftSliver.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/ManaweftSliver.java b/Mage.Sets/src/mage/cards/m/ManaweftSliver.java index f26076952dd..ff16a2ed6ab 100644 --- a/Mage.Sets/src/mage/cards/m/ManaweftSliver.java +++ b/Mage.Sets/src/mage/cards/m/ManaweftSliver.java @@ -29,9 +29,8 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.mana.AnyColorManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -39,7 +38,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; /** * @@ -47,6 +46,8 @@ import mage.filter.StaticFilters; */ public class ManaweftSliver extends CardImpl { + public static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.SLIVER, "Sliver creatures"); + public ManaweftSliver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); this.subtype.add(SubType.SLIVER); @@ -55,11 +56,11 @@ public class ManaweftSliver extends CardImpl { this.toughness = new MageInt(1); // Sliver creatures you control have "{T}: Add one mana of any color to your mana pool." - Ability ability = new AnyColorManaAbility(); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new GainAbilityAllEffect(ability, - Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURE_SLIVERS, - "Sliver creatures you control have \"{T}: Add one mana of any color to your mana pool.\""))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityControlledEffect( + new AnyColorManaAbility(), + Duration.WhileOnBattlefield, + filter + ))); } public ManaweftSliver(final ManaweftSliver card) { From a8969b9676bc1a2c52d14f95206853984fc9e9ae Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 25 Oct 2017 09:47:49 -0400 Subject: [PATCH 56/77] small fix --- .../src/mage/cards/c/ChaliceOfTheVoid.java | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/ChaliceOfTheVoid.java b/Mage.Sets/src/mage/cards/c/ChaliceOfTheVoid.java index d6b829d2be7..cc53c2feb4d 100644 --- a/Mage.Sets/src/mage/cards/c/ChaliceOfTheVoid.java +++ b/Mage.Sets/src/mage/cards/c/ChaliceOfTheVoid.java @@ -28,16 +28,14 @@ package mage.cards.c; import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CounterTargetEffect; import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; @@ -53,7 +51,7 @@ import mage.target.targetpointer.FixedTarget; public class ChaliceOfTheVoid extends CardImpl { public ChaliceOfTheVoid(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{X}{X}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{X}{X}"); // Chalice of the Void enters the battlefield with X charge counters on it. this.addAbility(new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.CHARGE.createInstance()))); @@ -75,7 +73,7 @@ public class ChaliceOfTheVoid extends CardImpl { class ChaliceOfTheVoidTriggeredAbility extends TriggeredAbilityImpl { public ChaliceOfTheVoidTriggeredAbility() { - super(Zone.BATTLEFIELD, new CounterEffect()); + super(Zone.BATTLEFIELD, new CounterTargetEffect()); } public ChaliceOfTheVoidTriggeredAbility(final ChaliceOfTheVoidTriggeredAbility abiltity) { @@ -110,25 +108,3 @@ class ChaliceOfTheVoidTriggeredAbility extends TriggeredAbilityImpl { return "Whenever a player casts a spell with converted mana cost equal to the number of charge counters on {this}, counter that spell."; } } - -class CounterEffect extends OneShotEffect { - - public CounterEffect() { - super(Outcome.Detriment); - } - - public CounterEffect(final CounterEffect effect) { - super(effect); - } - - @Override - public CounterEffect copy() { - return new CounterEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return game.getStack().counter(this.getTargetPointer().getFirst(game, source), source.getSourceId(), game); - } - -} From e32fa1cddc2a49e49edaefc2bf00cb82b55cd17e Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 00:50:30 +0200 Subject: [PATCH 57/77] Implemented Misinformation --- .../src/mage/cards/m/Misinformation.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/Misinformation.java diff --git a/Mage.Sets/src/mage/cards/m/Misinformation.java b/Mage.Sets/src/mage/cards/m/Misinformation.java new file mode 100644 index 00000000000..b146b7d17da --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/Misinformation.java @@ -0,0 +1,95 @@ +/* + * 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.m; + +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInOpponentsGraveyard; + +/** + * + * @author L_J + */ +public class Misinformation extends CardImpl { + + public Misinformation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}"); + + // Put up to three target cards from an opponent's graveyard on top of his or her library in any order. + this.getSpellAbility().addTarget(new TargetCardInOpponentsGraveyard(0, 3, new FilterCard("cards from an opponent's graveyard"), true)); + this.getSpellAbility().addEffect(new MisinformationEffect()); + } + + public Misinformation(final Misinformation card) { + super(card); + } + + @Override + public Misinformation copy() { + return new Misinformation(this); + } +} + +class MisinformationEffect extends OneShotEffect { + + MisinformationEffect() { + super(Outcome.Detriment); + this.staticText = "Put up to three target cards from an opponent's graveyard on top of his or her library in any order"; + } + + MisinformationEffect(final MisinformationEffect effect) { + super(effect); + } + + @Override + public MisinformationEffect copy() { + return new MisinformationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + List targets = this.getTargetPointer().getTargets(game, source); + if (targets != null) { + Cards cards = new CardsImpl(targets); + controller.putCardsOnTopOfLibrary(cards, game, source, true); + return true; + } + } + return false; + } +} From 6a9708c6f3ae8da3e8fbc30b956b724c6e29dbe1 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 00:51:08 +0200 Subject: [PATCH 58/77] Implemented Misinformation --- Mage.Sets/src/mage/sets/Alliances.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Alliances.java b/Mage.Sets/src/mage/sets/Alliances.java index c60b00701c7..1a67bc92be8 100644 --- a/Mage.Sets/src/mage/sets/Alliances.java +++ b/Mage.Sets/src/mage/sets/Alliances.java @@ -114,6 +114,7 @@ public class Alliances extends ExpansionSet { cards.add(new SetCardInfo("Lim-Dul's Vault", 192, Rarity.UNCOMMON, mage.cards.l.LimDulsVault.class)); cards.add(new SetCardInfo("Lord of Tresserhorn", 193, Rarity.RARE, mage.cards.l.LordOfTresserhorn.class)); cards.add(new SetCardInfo("Mishra's Groundbreaker", 165, Rarity.UNCOMMON, mage.cards.m.MishrasGroundbreaker.class)); + cards.add(new SetCardInfo("Misinformation", 19, Rarity.UNCOMMON, mage.cards.m.Misinformation.class)); cards.add(new SetCardInfo("Mystic Compass", 166, Rarity.UNCOMMON, mage.cards.m.MysticCompass.class)); cards.add(new SetCardInfo("Nature's Chosen", 81, Rarity.UNCOMMON, mage.cards.n.NaturesChosen.class)); cards.add(new SetCardInfo("Nature's Wrath", 82, Rarity.RARE, mage.cards.n.NaturesWrath.class)); From a9962e9f3b5e4cc6ed187864311c7242ceece566 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 07:50:43 +0200 Subject: [PATCH 59/77] Implemented Misinformation --- Mage.Sets/src/mage/sets/MastersEditionII.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index 383e2b38af4..f7fbf2feaa0 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -180,6 +180,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Mesmeric Trance", 55, Rarity.RARE, mage.cards.m.MesmericTrance.class)); cards.add(new SetCardInfo("Meteor Shower", 135, Rarity.COMMON, mage.cards.m.MeteorShower.class)); cards.add(new SetCardInfo("Minion of Leshrac", 104, Rarity.RARE, mage.cards.m.MinionOfLeshrac.class)); + cards.add(new SetCardInfo("Misinformation", 105, Rarity.UNCOMMON, mage.cards.m.Misinformation.class)); cards.add(new SetCardInfo("Mudslide", 136, Rarity.RARE, mage.cards.m.Mudslide.class)); cards.add(new SetCardInfo("Narwhal", 57, Rarity.UNCOMMON, mage.cards.n.Narwhal.class)); cards.add(new SetCardInfo("Nature's Wrath", 172, Rarity.RARE, mage.cards.n.NaturesWrath.class)); From 1296537765688fe2d20e3c5bca27ca13b3660dee Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 15:54:52 +0200 Subject: [PATCH 60/77] Included new watcher for "you choose blockers..." effects --- .../ChooseBlockersRedundancyWatcher.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java diff --git a/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java b/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java new file mode 100644 index 00000000000..d4589fa342f --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java @@ -0,0 +1,105 @@ +/* + * 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.watchers.common; + +import java.util.*; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; +import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.RequirementEffect; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; +import mage.abilities.effects.common.combat.CantAttackTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; + +/** + * + * @author L_J + */ + +public class ChooseBlockersRedundancyWatcher extends Watcher { // workaround for solving timestamp issues regarding "you choose which creatures block and how those creatures block" effects + + public int copyCount = 0; + public int copyCountApply = 0; + + public ChooseBlockersRedundancyWatcher() { + super(ChooseBlockersRedundancyWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public ChooseBlockersRedundancyWatcher(final ChooseBlockersRedundancyWatcher watcher) { + super(watcher); + this.copyCount = watcher.copyCount; + this.copyCountApply = watcher.copyCountApply; + } + + @Override + public void reset() { + copyCount = 0; + copyCountApply = 0; + } + + @Override + public ChooseBlockersRedundancyWatcher copy() { + return new ChooseBlockersRedundancyWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + } + + public void increment() { + copyCount++; + copyCountApply = copyCount; + } + + public void decrement() { + if (copyCountApply > 0) { + copyCountApply--; + } + } +} From 6e1a3b1b11199ce8bcf33b9428bec0154807e1e4 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 16:06:16 +0200 Subject: [PATCH 61/77] Removed garbage imports --- .../ChooseBlockersRedundancyWatcher.java | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java b/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java index d4589fa342f..f39cbb2c006 100644 --- a/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java @@ -27,34 +27,9 @@ */ package mage.watchers.common; -import java.util.*; -import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; -import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.RequirementEffect; -import mage.abilities.effects.RestrictionEffect; -import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; -import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; -import mage.abilities.effects.common.combat.CantAttackTargetEffect; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.common.FilterCreaturePermanent; +import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.Spell; -import mage.players.Player; -import mage.target.Target; -import mage.target.common.TargetCreaturePermanent; -import mage.filter.predicate.permanent.ControllerPredicate; -import mage.target.targetpointer.FixedTarget; import mage.watchers.Watcher; /** From e3af939bd7cc73b79c931ee08412796f90444874 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 16:09:14 +0200 Subject: [PATCH 62/77] Included watcher for catching excess instances of "you choose blockers" effect --- .../src/mage/cards/b/BrutalHordechief.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java index 108280010f7..e9068ea94b9 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java +++ b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java @@ -34,6 +34,7 @@ import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.combat.BlocksIfAbleAllEffect; @@ -47,6 +48,7 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import mage.watchers.common.ChooseBlockersRedundancyWatcher; /** * @@ -72,6 +74,8 @@ public class BrutalHordechief extends CardImpl { // {3}{R/W}{R/W}: Creatures your opponents control block this turn if able, and you choose how those creatures block. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BlocksIfAbleAllEffect(filter, Duration.EndOfTurn), new ManaCostsImpl("{3}{R/W}{R/W}")); ability.addEffect(new BrutalHordechiefChooseBlockersEffect()); + ability.addWatcher(new ChooseBlockersRedundancyWatcher()); + ability.addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); this.addAbility(ability); } @@ -83,6 +87,32 @@ public class BrutalHordechief extends CardImpl { public BrutalHordechief copy() { return new BrutalHordechief(this); } + + private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { + + ChooseBlockersRedundancyWatcherIncrementEffect() { + super(Outcome.Neutral); + } + + ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + if (watcher != null) { + watcher.increment(); + return true; + } + return false; + } + + @Override + public ChooseBlockersRedundancyWatcherIncrementEffect copy() { + return new ChooseBlockersRedundancyWatcherIncrementEffect(this); + } + } } class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl { @@ -123,11 +153,11 @@ class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl { } } -class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { // TODO: reverse the resolution order in case of effect multiples +class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { public BrutalHordechiefChooseBlockersEffect() { super(Duration.EndOfTurn, Outcome.Benefit, false, false); - staticText = ", and you choose how those creatures block"; + staticText = "You choose which creatures block this turn and how those creatures block"; } public BrutalHordechiefChooseBlockersEffect(final BrutalHordechiefChooseBlockersEffect effect) { @@ -151,6 +181,13 @@ class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffect @Override public boolean applies(GameEvent event, Ability source, Game game) { + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + watcher.decrement(); + if (watcher.copyCountApply > 0) { + game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + return false; + } + watcher.copyCountApply = watcher.copyCount; Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { game.getCombat().selectBlockers(blockController, game); From 23f256c4b25bf7e9b1faa352f6ce975a3cc50873 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 16:10:59 +0200 Subject: [PATCH 63/77] Included watcher for catching excess instances of "you choose blockers" effect --- .../src/mage/cards/m/MasterWarcraft.java | 83 +++++++++++++------ 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 94c9c565893..e0e6f0bf4b1 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -56,6 +56,7 @@ import mage.target.common.TargetCreaturePermanent; import mage.filter.predicate.permanent.ControllerPredicate; import mage.target.targetpointer.FixedTarget; import mage.watchers.Watcher; +import mage.watchers.common.ChooseBlockersRedundancyWatcher; /** * @@ -79,6 +80,8 @@ public class MasterWarcraft extends CardImpl { // (only the last resolved Master Warcraft spell's effects apply) this.getSpellAbility().addWatcher(new MasterWarcraftCastWatcher()); this.getSpellAbility().addEffect(new MasterWarcraftCastWatcherIncrementEffect()); + this.getSpellAbility().addWatcher(new ChooseBlockersRedundancyWatcher()); + this.getSpellAbility().addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); } public MasterWarcraft(final MasterWarcraft card) { @@ -89,6 +92,58 @@ public class MasterWarcraft extends CardImpl { public MasterWarcraft copy() { return new MasterWarcraft(this); } + + private class MasterWarcraftCastWatcherIncrementEffect extends OneShotEffect { + + MasterWarcraftCastWatcherIncrementEffect() { + super(Outcome.Neutral); + } + + MasterWarcraftCastWatcherIncrementEffect(final MasterWarcraftCastWatcherIncrementEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + MasterWarcraftCastWatcher watcher = (MasterWarcraftCastWatcher) game.getState().getWatchers().get(MasterWarcraftCastWatcher.class.getSimpleName()); + if (watcher != null) { + watcher.increment(); + return true; + } + return false; + } + + @Override + public MasterWarcraftCastWatcherIncrementEffect copy() { + return new MasterWarcraftCastWatcherIncrementEffect(this); + } + } + + private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { + + ChooseBlockersRedundancyWatcherIncrementEffect() { + super(Outcome.Neutral); + } + + ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + if (watcher != null) { + watcher.increment(); + return true; + } + return false; + } + + @Override + public ChooseBlockersRedundancyWatcherIncrementEffect copy() { + return new ChooseBlockersRedundancyWatcherIncrementEffect(this); + } + } } class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectImpl { @@ -197,7 +252,7 @@ class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectIm @Override public boolean applies(GameEvent event, Ability source, Game game) { - MasterWarcraftCastWatcher watcher = (MasterWarcraftCastWatcher) game.getState().getWatchers().get(MasterWarcraftCastWatcher.class.getSimpleName()); + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); watcher.decrement(); if (watcher.copyCountApply > 0) { game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); @@ -254,29 +309,3 @@ class MasterWarcraftCastWatcher extends Watcher { } } } - -class MasterWarcraftCastWatcherIncrementEffect extends OneShotEffect { - - MasterWarcraftCastWatcherIncrementEffect() { - super(Outcome.Neutral); - } - - MasterWarcraftCastWatcherIncrementEffect(final MasterWarcraftCastWatcherIncrementEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - MasterWarcraftCastWatcher watcher = (MasterWarcraftCastWatcher) game.getState().getWatchers().get(MasterWarcraftCastWatcher.class.getSimpleName()); - if (watcher != null) { - watcher.increment(); - return true; - } - return false; - } - - @Override - public MasterWarcraftCastWatcherIncrementEffect copy() { - return new MasterWarcraftCastWatcherIncrementEffect(this); - } -} From aa49b8386f04fd0e980a3817fb614a5857433fad Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 16:18:07 +0200 Subject: [PATCH 64/77] Included watcher, changed Odric's ability to triggered ability --- .../mage/cards/o/OdricMasterTactician.java | 99 ++++++++++--------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java index ecdb3e6d795..006d4b64dbb 100644 --- a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java +++ b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java @@ -31,7 +31,8 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -40,6 +41,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.players.Player; +import mage.watchers.common.ChooseBlockersRedundancyWatcher; /** * @author noxx @@ -75,7 +77,9 @@ public class OdricMasterTactician extends CardImpl { class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl { public OdricMasterTacticianTriggeredAbility() { - super(Zone.BATTLEFIELD, new OdricMasterTacticianEffect()); + super(Zone.BATTLEFIELD, new OdricMasterTacticianChooseBlockersEffect()); + this.addWatcher(new ChooseBlockersRedundancyWatcher()); + this.addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); } public OdricMasterTacticianTriggeredAbility(final OdricMasterTacticianTriggeredAbility ability) { @@ -89,52 +93,55 @@ class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == EventType.DECLARED_ATTACKERS; + return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; } @Override public boolean checkTrigger(GameEvent event, Game game) { - resetEffect(); - if (game.getCombat().getAttackers().size() >= 4 && game.getCombat().getAttackers().contains(this.sourceId)) { - enableEffect(); - return true; + return game.getCombat().getAttackers().size() >= 4 && game.getCombat().getAttackers().contains(this.sourceId); + } + + private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { + + ChooseBlockersRedundancyWatcherIncrementEffect() { + super(Outcome.Neutral); + } + + ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + if (watcher != null) { + watcher.increment(); + return true; + } + return false; + } + + @Override + public ChooseBlockersRedundancyWatcherIncrementEffect copy() { + return new ChooseBlockersRedundancyWatcherIncrementEffect(this); } - return false; } - - @Override - public void reset(Game game) { - resetEffect(); - } - - private void resetEffect() { - getEffects().get(0).setValue("apply_" + sourceId, false); - } - - private void enableEffect() { - getEffects().get(0).setValue("apply_" + sourceId, true); - } - - @Override - public String getRule() { - return "Whenever {this} and at least three other creatures attack, you choose which creatures block this combat and how those creatures block."; - } - } -class OdricMasterTacticianEffect extends ReplacementEffectImpl { +class OdricMasterTacticianChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { - public OdricMasterTacticianEffect() { - super(Duration.EndOfCombat, Outcome.Benefit); + public OdricMasterTacticianChooseBlockersEffect() { + super(Duration.EndOfTurn, Outcome.Benefit, false, false); + staticText = "Whenever {this} and at least three other creatures attack, you choose which creatures block this turn and how those creatures block"; } - public OdricMasterTacticianEffect(final OdricMasterTacticianEffect effect) { + public OdricMasterTacticianChooseBlockersEffect(final OdricMasterTacticianChooseBlockersEffect effect) { super(effect); } @Override - public OdricMasterTacticianEffect copy() { - return new OdricMasterTacticianEffect(this); + public OdricMasterTacticianChooseBlockersEffect copy() { + return new OdricMasterTacticianChooseBlockersEffect(this); } @Override @@ -142,16 +149,6 @@ class OdricMasterTacticianEffect extends ReplacementEffectImpl { return false; } - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player blockController = game.getPlayer(source.getControllerId()); - if (blockController != null) { - game.getCombat().selectBlockers(blockController, game); - return true; - } - return false; - } - @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS; @@ -159,11 +156,17 @@ class OdricMasterTacticianEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - Object object = getValue("apply_" + source.getSourceId()); - if (object != null && object instanceof Boolean) { - if ((Boolean)object) { - return true; // replace event - } + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + watcher.decrement(); + if (watcher.copyCountApply > 0) { + game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + return false; + } + watcher.copyCountApply = watcher.copyCount; + Player blockController = game.getPlayer(source.getControllerId()); + if (blockController != null) { + game.getCombat().selectBlockers(blockController, game); + return true; } return false; } From 8338a788d6869dabdc8fc17a66aac84d124d681c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 26 Oct 2017 11:18:04 -0400 Subject: [PATCH 65/77] fixed Derevi, Empyrial Tactician triggering while Humility is out when entering using its activated ability --- Mage.Sets/src/mage/cards/d/DereviEmpyrialTactician.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DereviEmpyrialTactician.java b/Mage.Sets/src/mage/cards/d/DereviEmpyrialTactician.java index 38fd233b3ad..2ac726055cf 100644 --- a/Mage.Sets/src/mage/cards/d/DereviEmpyrialTactician.java +++ b/Mage.Sets/src/mage/cards/d/DereviEmpyrialTactician.java @@ -60,7 +60,7 @@ import mage.target.TargetPermanent; public class DereviEmpyrialTactician extends CardImpl { public DereviEmpyrialTactician(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}{W}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.BIRD); this.subtype.add(SubType.WIZARD); @@ -74,7 +74,7 @@ public class DereviEmpyrialTactician extends CardImpl { Ability ability = new DereviEmpyrialTacticianTriggeredAbility(new MayTapOrUntapTargetEffect()); ability.addTarget(new TargetPermanent()); this.addAbility(ability); - + // {1}{G}{W}{U}: Put Derevi onto the battlefield from the command zone. this.addAbility(new DereviEmpyrialTacticianAbility()); } @@ -132,7 +132,7 @@ class DereviEmpyrialTacticianTriggeredAbility extends TriggeredAbilityImpl { } } - class DereviEmpyrialTacticianAbility extends ActivatedAbilityImpl { +class DereviEmpyrialTacticianAbility extends ActivatedAbilityImpl { public DereviEmpyrialTacticianAbility() { super(Zone.COMMAND, new PutCommanderOnBattlefieldEffect(), new ManaCostsImpl("{1}{G}{W}{U}")); @@ -182,7 +182,7 @@ class PutCommanderOnBattlefieldEffect extends OneShotEffect { } Card card = game.getCard(source.getSourceId()); if (card != null) { - card.putOntoBattlefield(game, Zone.COMMAND, source.getSourceId(), source.getControllerId()); + player.moveCards(card, Zone.BATTLEFIELD, source, game); return true; } return false; From e9c2c6caa1657934853791929682b137c138bdad Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 21:20:28 +0200 Subject: [PATCH 66/77] Tiny text fix --- Mage.Sets/src/mage/cards/m/MasterWarcraft.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index e0e6f0bf4b1..a825bbebd2a 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -68,7 +68,7 @@ public class MasterWarcraft extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R/W}{R/W}"); // Cast Master Warcraft only before attackers are declared. - this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance, "Cast Master Warcraft only before attackers are declared")); + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance, "Cast {this} only before attackers are declared")); // You choose which creatures attack this turn. this.getSpellAbility().addEffect(new MasterWarcraftChooseAttackersEffect()); From a2ec35a616ec125ac540c9c40e5cf5bbaeb50e40 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 21:22:08 +0200 Subject: [PATCH 67/77] Fixed some bug --- Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java b/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java index c1bb9cce2e6..8a9471676d0 100644 --- a/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java +++ b/Mage.Sets/src/mage/cards/m/MuragandaPetroglyphs.java @@ -92,20 +92,16 @@ class NoAbilityPredicate implements Predicate { } if (isFaceDown) { for (Ability ability : abilities) { - if (!ability.getSourceId().equals(input.getId())) { - if (ability.getClass().equals(JohanVigilanceAbility.class)) { - return false; - } + if (!ability.getSourceId().equals(input.getId()) && !ability.getClass().equals(JohanVigilanceAbility.class)) { + return false; } } return true; } for (Ability ability : abilities) { - if (!Objects.equals(ability.getClass(), SpellAbility.class)) { - if (!ability.getClass().equals(JohanVigilanceAbility.class)) { - return false; - } + if (!Objects.equals(ability.getClass(), SpellAbility.class) && !ability.getClass().equals(JohanVigilanceAbility.class)) { + return false; } } return true; From 8ff6368aaf5bc54ed4a3fd1aed82fd736b5cd78b Mon Sep 17 00:00:00 2001 From: Faxn Date: Thu, 26 Oct 2017 16:27:16 -0400 Subject: [PATCH 68/77] #4130 Changed Decree of Justice to attach it's mana cost to the CycleTriggeredAbility instead handling the cost in the card. --- Mage.Sets/src/mage/cards/d/DecreeOfJustice.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java b/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java index a812b8ff22b..7a5cc0a9e80 100644 --- a/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java +++ b/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java @@ -34,6 +34,7 @@ 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.costs.mana.VariableManaCost; import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -66,6 +67,7 @@ public class DecreeOfJustice extends CardImpl { // When you cycle Decree of Justice, you may pay {X}. If you do, create X 1/1 white Soldier creature tokens. Ability ability = new CycleTriggeredAbility(new DecreeOfJusticeCycleEffect(), true); + ability.addCost(new ManaCostsImpl<>("{X}")); this.addAbility(ability); } @@ -98,14 +100,12 @@ class DecreeOfJusticeCycleEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - ManaCosts cost = new ManaCostsImpl<>("{X}"); if (player != null) { - int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); - cost.add(new GenericManaCost(costX)); - if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { - Token token = new SoldierToken(); - token.putOntoBattlefield(costX, game, source.getSourceId(), source.getControllerId()); - } + Token token = new SoldierToken(); + int X = new ManacostVariableValue().calculate(game, source, this); + token.putOntoBattlefield(X, game, source.getSourceId(), source.getControllerId()); + return true; + } return false; } From bd84c6be54e26b13247d498982e2a4b9e2fd5497 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 23:02:17 +0200 Subject: [PATCH 69/77] Implemented Melee --- Mage.Sets/src/mage/cards/m/Melee.java | 201 ++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/Melee.java diff --git a/Mage.Sets/src/mage/cards/m/Melee.java b/Mage.Sets/src/mage/cards/m/Melee.java new file mode 100644 index 00000000000..b26042109aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/Melee.java @@ -0,0 +1,201 @@ +/* + * 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.m; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.BeforeBlockersAreDeclaredCondition; +import mage.abilities.condition.common.IsPhaseCondition; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.RemoveFromCombatTargetEffect; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; +import mage.watchers.common.ChooseBlockersRedundancyWatcher; + +/** + * + * @author L_J + */ +public class Melee extends CardImpl { + + public Melee(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); + + // Cast Melee only during your turn and only during combat before blockers are declared. + Condition condition = new CompoundCondition(BeforeBlockersAreDeclaredCondition.instance, + new IsPhaseCondition(TurnPhase.COMBAT), + MyTurnCondition.instance); + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, condition, "Cast {this} only during your turn and only during combat before blockers are declared")); + + // You choose which creatures block this combat and how those creatures block. + // (only the last resolved Melee spell's blocking effect applies) + this.getSpellAbility().addEffect(new MeleeChooseBlockersEffect()); + this.getSpellAbility().addWatcher(new ChooseBlockersRedundancyWatcher()); + this.getSpellAbility().addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); + + // Whenever a creature attacks and isn't blocked this combat, untap it and remove it from combat. + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new MeleeTriggeredAbility())); + } + + public Melee(final Melee card) { + super(card); + } + + @Override + public Melee copy() { + return new Melee(this); + } + + private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { + + ChooseBlockersRedundancyWatcherIncrementEffect() { + super(Outcome.Neutral); + } + + ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + if (watcher != null) { + watcher.increment(); + return true; + } + return false; + } + + @Override + public ChooseBlockersRedundancyWatcherIncrementEffect copy() { + return new ChooseBlockersRedundancyWatcherIncrementEffect(this); + } + } +} + +class MeleeChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { + + public MeleeChooseBlockersEffect() { + super(Duration.EndOfCombat, Outcome.Benefit, false, false); + staticText = "You choose which creatures block this combat and how those creatures block"; + } + + public MeleeChooseBlockersEffect(final MeleeChooseBlockersEffect effect) { + super(effect); + } + + @Override + public MeleeChooseBlockersEffect copy() { + return new MeleeChooseBlockersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); + watcher.decrement(); + if (watcher.copyCountApply > 0) { + game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + return false; + } + watcher.copyCountApply = watcher.copyCount; + Player blockController = game.getPlayer(source.getControllerId()); + if (blockController != null) { + game.getCombat().selectBlockers(blockController, game); + return true; + } + return false; + } +} + +class MeleeTriggeredAbility extends DelayedTriggeredAbility { + + public MeleeTriggeredAbility() { + super(new UntapTargetEffect(), Duration.EndOfCombat, false); + this.addEffect(new RemoveFromCombatTargetEffect()); + } + + public MeleeTriggeredAbility(MeleeTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.UNBLOCKED_ATTACKER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + for (CombatGroup combatGroup : game.getCombat().getGroups()) { + if (combatGroup.getBlockers().isEmpty() && combatGroup.getAttackers().contains(event.getTargetId())) { + this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + return true; + } + } + } + return false; + } + + @Override + public MeleeTriggeredAbility copy() { + return new MeleeTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever a creature attacks and isn't blocked this combat, untap it and remove it from combat."; + } +} From f3cd07af4f7714f0aa78d4272f791afbf513c9c2 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 23:02:45 +0200 Subject: [PATCH 70/77] Implemented Melee --- Mage.Sets/src/mage/sets/IceAge.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index 3262245e7d2..277e614b3bd 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -212,6 +212,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Magus of the Unseen", 82, Rarity.RARE, mage.cards.m.MagusOfTheUnseen.class)); cards.add(new SetCardInfo("Malachite Talisman", 303, Rarity.UNCOMMON, mage.cards.m.MalachiteTalisman.class)); cards.add(new SetCardInfo("Marton Stromgald", 199, Rarity.RARE, mage.cards.m.MartonStromgald.class)); + cards.add(new SetCardInfo("Melee", 200, Rarity.UNCOMMON, mage.cards.m.Melee.class)); cards.add(new SetCardInfo("Melting", 201, Rarity.UNCOMMON, mage.cards.m.Melting.class)); cards.add(new SetCardInfo("Merieke Ri Berit", 375, Rarity.RARE, mage.cards.m.MeriekeRiBerit.class)); cards.add(new SetCardInfo("Mesmeric Trance", 83, Rarity.RARE, mage.cards.m.MesmericTrance.class)); @@ -253,7 +254,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Portent", 90, Rarity.COMMON, mage.cards.p.Portent.class)); cards.add(new SetCardInfo("Power Sink", 91, Rarity.COMMON, mage.cards.p.PowerSink.class)); cards.add(new SetCardInfo("Pox", 46, Rarity.RARE, mage.cards.p.Pox.class)); - cards.add(new SetCardInfo("Prismatic Ward", 271, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); + cards.add(new SetCardInfo("Prismatic Ward", 271, Rarity.COMMON, mage.cards.p.PrismaticWard.class)); cards.add(new SetCardInfo("Pygmy Allosaurus", 145, Rarity.RARE, mage.cards.p.PygmyAllosaurus.class)); cards.add(new SetCardInfo("Pyknite", 146, Rarity.COMMON, mage.cards.p.Pyknite.class)); cards.add(new SetCardInfo("Pyroblast", 213, Rarity.COMMON, mage.cards.p.Pyroblast.class)); From 84f9d8e01b745ed9d21037f33f740aeffa5cf712 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 23:20:03 +0200 Subject: [PATCH 71/77] Fixed effect duration --- Mage.Sets/src/mage/cards/o/OdricMasterTactician.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java index 006d4b64dbb..8b40563d9e0 100644 --- a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java +++ b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java @@ -131,8 +131,8 @@ class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl { class OdricMasterTacticianChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { public OdricMasterTacticianChooseBlockersEffect() { - super(Duration.EndOfTurn, Outcome.Benefit, false, false); - staticText = "Whenever {this} and at least three other creatures attack, you choose which creatures block this turn and how those creatures block"; + super(Duration.EndOfCombat, Outcome.Benefit, false, false); + staticText = "Whenever {this} and at least three other creatures attack, you choose which creatures block this combat and how those creatures block"; } public OdricMasterTacticianChooseBlockersEffect(final OdricMasterTacticianChooseBlockersEffect effect) { @@ -162,6 +162,7 @@ class OdricMasterTacticianChooseBlockersEffect extends ContinuousRuleModifyingEf game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); return false; } + watcher.copyCount--; watcher.copyCountApply = watcher.copyCount; Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { From af4cc4fc5d3081603c11bd4ac2fcaa72a1f5b3cd Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 26 Oct 2017 23:20:34 +0200 Subject: [PATCH 72/77] Minor watcher fix --- Mage.Sets/src/mage/cards/m/Melee.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/m/Melee.java b/Mage.Sets/src/mage/cards/m/Melee.java index b26042109aa..03467f5a239 100644 --- a/Mage.Sets/src/mage/cards/m/Melee.java +++ b/Mage.Sets/src/mage/cards/m/Melee.java @@ -149,6 +149,7 @@ class MeleeChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); return false; } + watcher.copyCount--; watcher.copyCountApply = watcher.copyCount; Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { From 8f5c4a2c8054a0c6d53fa45013943709ff9b879a Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 27 Oct 2017 01:26:55 +0200 Subject: [PATCH 73/77] Bug fixes --- Mage.Sets/src/mage/cards/o/OdricMasterTactician.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java index 8b40563d9e0..69b5f7aa669 100644 --- a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java +++ b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java @@ -158,17 +158,19 @@ class OdricMasterTacticianChooseBlockersEffect extends ContinuousRuleModifyingEf public boolean applies(GameEvent event, Ability source, Game game) { ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); watcher.decrement(); + watcher.copyCount--; if (watcher.copyCountApply > 0) { game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + this.discard(); return false; } - watcher.copyCount--; watcher.copyCountApply = watcher.copyCount; Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { game.getCombat().selectBlockers(blockController, game); return true; } + this.discard(); return false; } } From 752f3f67417b4df06be0cda29221febe0a090441 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 27 Oct 2017 01:28:32 +0200 Subject: [PATCH 74/77] Fug bixes --- Mage.Sets/src/mage/cards/m/Melee.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/Melee.java b/Mage.Sets/src/mage/cards/m/Melee.java index 03467f5a239..ac152d93f06 100644 --- a/Mage.Sets/src/mage/cards/m/Melee.java +++ b/Mage.Sets/src/mage/cards/m/Melee.java @@ -145,17 +145,19 @@ class MeleeChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { ChooseBlockersRedundancyWatcher watcher = (ChooseBlockersRedundancyWatcher) game.getState().getWatchers().get(ChooseBlockersRedundancyWatcher.class.getSimpleName()); watcher.decrement(); + watcher.copyCount--; if (watcher.copyCountApply > 0) { game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + this.discard(); return false; } - watcher.copyCount--; watcher.copyCountApply = watcher.copyCount; Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { game.getCombat().selectBlockers(blockController, game); return true; - } + } + this.discard(); return false; } } From df2da656d214b24cd086facda97e2bbf92eaf413 Mon Sep 17 00:00:00 2001 From: Faxn Date: Thu, 26 Oct 2017 19:58:10 -0400 Subject: [PATCH 75/77] #4130 Fixing the fix to be more reasonable. --- Mage.Sets/src/mage/cards/d/DecreeOfJustice.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java b/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java index 7a5cc0a9e80..6adb06403a5 100644 --- a/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java +++ b/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java @@ -30,6 +30,7 @@ package mage.cards.d; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.CycleTriggeredAbility; +import mage.abilities.costs.Cost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; @@ -67,7 +68,6 @@ public class DecreeOfJustice extends CardImpl { // When you cycle Decree of Justice, you may pay {X}. If you do, create X 1/1 white Soldier creature tokens. Ability ability = new CycleTriggeredAbility(new DecreeOfJusticeCycleEffect(), true); - ability.addCost(new ManaCostsImpl<>("{X}")); this.addAbility(ability); } @@ -101,12 +101,14 @@ class DecreeOfJusticeCycleEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Token token = new SoldierToken(); - int X = new ManacostVariableValue().calculate(game, source, this); - token.putOntoBattlefield(X, game, source.getSourceId(), source.getControllerId()); - return true; - + int X = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + Cost cost = new GenericManaCost(X); + if(cost.pay(source, game, source.getSourceId(), source.getControllerId(), false)){ + Token token = new SoldierToken(); + token.putOntoBattlefield(X, game, source.getSourceId(), source.getControllerId()); + return true; + } } - return false; + return false; } } From 80923dbce0745140e2cac98e3316733eb4b7212c Mon Sep 17 00:00:00 2001 From: Faxn Date: Fri, 27 Oct 2017 12:16:58 -0400 Subject: [PATCH 76/77] Clean up unused imports. --- Mage.Sets/src/mage/cards/d/DecreeOfJustice.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java b/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java index 6adb06403a5..b033167814f 100644 --- a/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java +++ b/Mage.Sets/src/mage/cards/d/DecreeOfJustice.java @@ -32,10 +32,7 @@ import mage.abilities.Ability; import mage.abilities.common.CycleTriggeredAbility; import mage.abilities.costs.Cost; 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.costs.mana.VariableManaCost; import mage.abilities.dynamicvalue.common.ManacostVariableValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; @@ -109,6 +106,6 @@ class DecreeOfJusticeCycleEffect extends OneShotEffect { return true; } } - return false; + return false; } } From c395038fce0ef253d5cf3374f0d9c522f23f605c Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 2 Nov 2017 11:17:14 +0100 Subject: [PATCH 77/77] Minor changes. --- .gitignore | 1 + Mage.Sets/src/mage/cards/w/WitchEngine.java | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c14e8080376..6c4a3781368 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ Mage.Server.Plugins/Mage.Game.CommanderDuel/target Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/target/ Mage.Server.Plugins/Mage.Game.FreeForAll/target Mage.Server.Plugins/Mage.Game.MomirDuel/target +Mage.Server.Plugins/Mage.Game.MomirGame/target/ Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/target Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/target Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/target diff --git a/Mage.Sets/src/mage/cards/w/WitchEngine.java b/Mage.Sets/src/mage/cards/w/WitchEngine.java index 2d5659e4d38..253c2e544f9 100644 --- a/Mage.Sets/src/mage/cards/w/WitchEngine.java +++ b/Mage.Sets/src/mage/cards/w/WitchEngine.java @@ -39,11 +39,11 @@ import mage.abilities.keyword.SwampwalkAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.SubLayer; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; @@ -56,15 +56,15 @@ import mage.target.common.TargetOpponent; public class WitchEngine extends CardImpl { public WitchEngine(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}"); this.subtype.add(SubType.HORROR); this.power = new MageInt(4); this.toughness = new MageInt(4); // Swampwalk this.addAbility(new SwampwalkAbility()); - - // {tap}: Add {B}{B}{B}{B} to your mana pool. Target opponent gains control of Witch Engine. + + // {tap}: Add {B}{B}{B}{B} to your mana pool. Target opponent gains control of Witch Engine. (Activate this ability only any time you could cast an instant.) Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BasicManaEffect(Mana.BlackMana(4)), new TapSourceCost()); ability.addEffect(new WitchEngineEffect()); ability.addTarget(new TargetOpponent());