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 995303191fa..bff6dea4eac 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 @@ -834,10 +834,23 @@ public class HumanPlayer extends PlayerImpl { return !controllingPlayer.getUserData().getUserSkipPrioritySteps().getOpponentTurn().isPhaseStepSet(game.getStep().getType()); } } catch (NullPointerException ex) { - String isNull = controllingPlayer.getUserData() == null ? "null" : "not null"; - logger.error("null pointer exception UserData = " + isNull); + if (controllingPlayer.getUserData() != null) { + if (controllingPlayer.getUserData().getUserSkipPrioritySteps() != null) { + if (game.getStep() != null) { + if (game.getStep().getType() == null) { + logger.error("game.getStep().getType() == null"); + } + } else { + logger.error("game.getStep() == null"); + } + } else { + logger.error("UserData.getUserSkipPrioritySteps == null"); + } + } else { + logger.error("UserData == null"); + } } - return true; + return false; } @Override @@ -1223,7 +1236,9 @@ public class HumanPlayer extends PlayerImpl { FilterCreatureForCombatBlock filter = filterCreatureForCombatBlock.copy(); filter.add(new ControllerIdPredicate(defendingPlayerId)); if (game.getBattlefield().count(filter, null, playerId, game) == 0 - && !getControllingPlayersUserData(game).getUserSkipPrioritySteps().isStopOnDeclareBlockerIfNoneAvailable()) { + && !getControllingPlayersUserData(game) + .getUserSkipPrioritySteps() + .isStopOnDeclareBlockerIfNoneAvailable()) { return; } while (!abort) { diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 1e5127dd9e8..018e6b4a1a3 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -374,11 +374,9 @@ public class Session { call.setMessageId(messageId++); callbackHandler.handleCallbackOneway(new Callback(call)); } catch (HandleCallbackException ex) { - // ex.printStackTrace(); UserManager.instance.getUser(userId).ifPresent(user -> { - logger.warn("SESSION CALLBACK EXCEPTION - " + user.getName() + " userId " + userId); - logger.warn(" - method: " + call.getMethod()); - logger.warn(" - cause: " + getBasicCause(ex).toString()); + user.setUserState(User.UserState.Disconnected); + logger.warn("SESSION CALLBACK EXCEPTION - " + user.getName() + " userId " + userId + " - cause: " + getBasicCause(ex).toString()); logger.trace("Stack trace:", ex); SessionManager.instance.disconnect(sessionId, LostConnection); }); diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 817ad3b313c..985b8fc7086 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -159,15 +159,15 @@ public class User { public void setSessionId(String sessionId) { this.sessionId = sessionId; if (sessionId.isEmpty()) { - userState = UserState.Disconnected; + setUserState(UserState.Disconnected); lostConnection(); logger.trace("USER - lost connection: " + userName + " id: " + userId); } else if (userState == UserState.Created) { - userState = UserState.Connected; + setUserState(UserState.Connected); logger.trace("USER - created: " + userName + " id: " + userId); } else { - userState = UserState.Connected; + setUserState(UserState.Connected); reconnect(); logger.trace("USER - reconnected: " + userName + " id: " + userId); } @@ -339,7 +339,7 @@ public class User { } lastActivity = new Date(); if (userState == UserState.Disconnected) { // this can happen if user reconnects very fast after disconnect - userState = UserState.Connected; + setUserState(UserState.Connected); } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 6ea79cf2bc0..e7ceb47f8ab 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -31,6 +31,9 @@ import java.io.*; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.zip.GZIPOutputStream; import mage.MageException; import mage.abilities.Ability; @@ -80,7 +83,11 @@ public class GameController implements GameCallback { protected static final ScheduledExecutorService timeoutIdleExecutor = ThreadExecutor.instance.getTimeoutIdleExecutor(); private final ConcurrentHashMap gameSessions = new ConcurrentHashMap<>(); + private final ReadWriteLock gameSessionsLock = new ReentrantReadWriteLock(); + private final ConcurrentHashMap watchers = new ConcurrentHashMap<>(); + private final ReadWriteLock gameWatchersLock = new ReentrantReadWriteLock(); + private final ConcurrentHashMap timers = new ConcurrentHashMap<>(); private final ConcurrentHashMap userPlayerMap; @@ -114,7 +121,7 @@ public class GameController implements GameCallback { public void cleanUp() { cancelTimeout(); - for (GameSessionPlayer gameSessionPlayer : gameSessions.values()) { + for (GameSessionPlayer gameSessionPlayer : getGameSessions()) { gameSessionPlayer.cleanUp(); } ChatManager.instance.destroyChatSession(chatId); @@ -301,7 +308,13 @@ public class GameController implements GameCallback { String joinType; if (gameSession == null) { gameSession = new GameSessionPlayer(game, userId, playerId); - gameSessions.put(playerId, gameSession); + final Lock w = gameSessionsLock.writeLock(); + w.lock(); + try { + gameSessions.put(playerId, gameSession); + } finally { + w.unlock(); + } joinType = "joined"; } else { joinType = "rejoined"; @@ -314,8 +327,8 @@ public class GameController implements GameCallback { private synchronized void startGame() { if (gameFuture == null) { - for (final Entry entry : gameSessions.entrySet()) { - entry.getValue().init(); + for (GameSessionPlayer gameSessionPlayer : getGameSessions()) { + gameSessionPlayer.init(); } GameWorker worker = new GameWorker(game, choosingPlayerId, this); @@ -413,7 +426,13 @@ public class GameController implements GameCallback { } UserManager.instance.getUser(userId).ifPresent(user -> { GameSessionWatcher gameWatcher = new GameSessionWatcher(userId, game, false); - watchers.put(userId, gameWatcher); + final Lock w = gameWatchersLock.writeLock(); + w.lock(); + try { + watchers.put(userId, gameWatcher); + } finally { + w.unlock(); + } gameWatcher.init(); user.addGameWatchInfo(game.getId()); ChatManager.instance.broadcast(chatId, user.getName(), " has started watching", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); @@ -422,7 +441,13 @@ public class GameController implements GameCallback { } public void stopWatching(UUID userId) { - watchers.remove(userId); + final Lock w = gameWatchersLock.writeLock(); + w.lock(); + try { + watchers.remove(userId); + } finally { + w.unlock(); + } UserManager.instance.getUser(userId).ifPresent(user -> { ChatManager.instance.broadcast(chatId, user.getName(), " has stopped watching", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); }); @@ -673,11 +698,11 @@ public class GameController implements GameCallback { } public void endGame(final String message) throws MageException { - for (final GameSessionPlayer gameSession : gameSessions.values()) { + for (final GameSessionPlayer gameSession : getGameSessions()) { gameSession.gameOver(message); gameSession.removeGame(); } - for (final GameSessionWatcher gameWatcher : watchers.values()) { + for (final GameSessionWatcher gameWatcher : getGameSessionWatchers()) { gameWatcher.gameOver(message); } TableManager.instance.endGame(tableId); @@ -722,10 +747,10 @@ public class GameController implements GameCallback { } } } - for (final GameSessionPlayer gameSession : gameSessions.values()) { + for (final GameSessionPlayer gameSession : getGameSessions()) { gameSession.update(); } - for (final GameSessionWatcher gameWatcher : watchers.values()) { + for (final GameSessionWatcher gameWatcher : getGameSessionWatchers()) { gameWatcher.update(); } } @@ -734,12 +759,12 @@ public class GameController implements GameCallback { Table table = TableManager.instance.getTable(tableId); if (table != null) { if (table.getMatch() != null) { - for (final GameSessionPlayer gameSession : gameSessions.values()) { + for (final GameSessionPlayer gameSession : getGameSessions()) { gameSession.endGameInfo(table); } + // TODO: inform watchers about game end and who won } } - // TODO: inform watchers about game end and who won } private synchronized void ask(UUID playerId, final String question, final Map options) throws MageException { @@ -814,12 +839,12 @@ public class GameController implements GameCallback { message.append(game.getStep().getType().toString()).append(" - "); } message.append("Waiting for ").append(game.getPlayer(playerId).getLogName()); - for (final Entry entry : gameSessions.entrySet()) { + for (final Entry entry : getGameSessionsMap().entrySet()) { if (!entry.getKey().equals(playerId)) { entry.getValue().inform(message.toString()); } } - for (final GameSessionWatcher watcher : watchers.values()) { + for (final GameSessionWatcher watcher : getGameSessionWatchers()) { watcher.inform(message.toString()); } } @@ -834,14 +859,13 @@ public class GameController implements GameCallback { return; } final String message = new StringBuilder(game.getStep().getType().toString()).append(" - Waiting for ").append(controller.getName()).toString(); - for (final Entry entry : gameSessions.entrySet()) { + for (final Entry entry : getGameSessionsMap().entrySet()) { boolean skip = players.stream().anyMatch(playerId -> entry.getKey().equals(playerId)); - if (!skip) { entry.getValue().inform(message); } } - for (final GameSessionWatcher watcher : watchers.values()) { + for (final GameSessionWatcher watcher : getGameSessionWatchers()) { watcher.inform(message); } } @@ -858,7 +882,7 @@ public class GameController implements GameCallback { for (StackTraceElement e : ex.getStackTrace()) { sb.append(e.toString()).append('\n'); } - for (final Entry entry : gameSessions.entrySet()) { + for (final Entry entry : getGameSessionsMap().entrySet()) { entry.getValue().gameError(sb.toString()); } } @@ -995,6 +1019,42 @@ public class GameController implements GameCallback { void execute(UUID player); } + private Map getGameSessionsMap() { + Map newGameSessionsMap = new HashMap<>(); + final Lock r = gameSessionsLock.readLock(); + r.lock(); + try { + newGameSessionsMap.putAll(gameSessions); + } finally { + r.unlock(); + } + return newGameSessionsMap; + } + + private List getGameSessions() { + List newGameSessions = new ArrayList<>(); + final Lock r = gameSessionsLock.readLock(); + r.lock(); + try { + newGameSessions.addAll(gameSessions.values()); + } finally { + r.unlock(); + } + return newGameSessions; + } + + private List getGameSessionWatchers() { + List newGameSessionWatchers = new ArrayList<>(); + final Lock r = gameSessionsLock.readLock(); + r.lock(); + try { + newGameSessionWatchers.addAll(watchers.values()); + } finally { + r.unlock(); + } + return newGameSessionWatchers; + } + private GameSessionPlayer getGameSession(UUID playerId) { if (!timers.isEmpty()) { Player player = game.getState().getPlayer(playerId); diff --git a/Mage.Server/src/main/java/mage/server/game/GameManager.java b/Mage.Server/src/main/java/mage/server/game/GameManager.java index fe36171175a..3f3a69c02b0 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GameManager.java @@ -24,13 +24,17 @@ * 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.server.game; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import mage.cards.decks.DeckCardLists; import mage.constants.ManaType; import mage.constants.PlayerAction; @@ -46,10 +50,17 @@ public enum GameManager { instance; private final ConcurrentHashMap gameControllers = new ConcurrentHashMap<>(); + private final ReadWriteLock gameControllersLock = new ReentrantReadWriteLock(); public UUID createGameSession(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) { GameController gameController = new GameController(game, userPlayerMap, tableId, choosingPlayerId, gameOptions); - gameControllers.put(game.getId(), gameController); + final Lock w = gameControllersLock.writeLock(); + w.lock(); + try { + gameControllers.put(game.getId(), gameController); + } finally { + w.unlock(); + } return gameController.getSessionId(); } @@ -109,10 +120,10 @@ public enum GameManager { gameController.quitMatch(userId); } } - + public void sendPlayerAction(PlayerAction playerAction, UUID gameId, UUID userId, Object data) { GameController gameController = gameControllers.get(gameId); - if (gameController != null) { + if (gameController != null) { gameController.sendPlayerAction(playerAction, userId, data); } } @@ -151,7 +162,13 @@ public enum GameManager { GameController gameController = gameControllers.get(gameId); if (gameController != null) { gameController.cleanUp(); - gameControllers.remove(gameId); + final Lock w = gameControllersLock.writeLock(); + w.lock(); + try { + gameControllers.remove(gameId); + } finally { + w.unlock(); + } } } @@ -174,8 +191,16 @@ public enum GameManager { public int getNumberActiveGames() { return gameControllers.size(); } - - public ConcurrentHashMap getGameController() { - return gameControllers; + + public Map getGameController() { + Map newControllers = new HashMap<>(); + final Lock r = gameControllersLock.readLock(); + r.lock(); + try { + newControllers.putAll(gameControllers); + } finally { + r.unlock(); + } + return newControllers; } } diff --git a/Mage.Sets/src/mage/cards/a/AngrathsMarauders.java b/Mage.Sets/src/mage/cards/a/AngrathsMarauders.java new file mode 100644 index 00000000000..74e35b6fc42 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AngrathsMarauders.java @@ -0,0 +1,110 @@ +/* + * 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.a; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +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; + +/** + * + * @author TheElk801 + */ +public class AngrathsMarauders extends CardImpl { + + public AngrathsMarauders(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{R}{R}"); + + this.subtype.add("Human"); + this.subtype.add("Pirate"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // If a source you control would deal damage to a permanent or player, it deals double that damage to that permanent or player instead. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new AngrathsMaraudersEffect())); + } + + public AngrathsMarauders(final AngrathsMarauders card) { + super(card); + } + + @Override + public AngrathsMarauders copy() { + return new AngrathsMarauders(this); + } +} +class AngrathsMaraudersEffect extends ReplacementEffectImpl { + + public AngrathsMaraudersEffect() { + super(Duration.WhileOnBattlefield, Outcome.Damage); + staticText = "If a source you control would deal damage to a permanent or player, it deals double that damage to that permanent or player instead"; + } + + public AngrathsMaraudersEffect(final AngrathsMaraudersEffect effect) { + super(effect); + } + + @Override + public AngrathsMaraudersEffect copy() { + return new AngrathsMaraudersEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_PLAYER: + return true; + case DAMAGE_CREATURE: + return true; + case DAMAGE_PLANESWALKER: + return true; + } + return false; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return event.getSourceId().equals(source.getControllerId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(2 * event.getAmount()); + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArchmageAscension.java b/Mage.Sets/src/mage/cards/a/ArchmageAscension.java index 2cfe71062bb..dc913c10050 100644 --- a/Mage.Sets/src/mage/cards/a/ArchmageAscension.java +++ b/Mage.Sets/src/mage/cards/a/ArchmageAscension.java @@ -55,8 +55,7 @@ import mage.watchers.common.CardsAmountDrawnThisTurnWatcher; public class ArchmageAscension extends CardImpl { public ArchmageAscension(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); // At the beginning of each end step, if you drew two or more cards this turn, you may put a quest counter on Archmage Ascension. this.addAbility(new ArchmageAscensionTriggeredAbility(), new CardsAmountDrawnThisTurnWatcher()); @@ -64,7 +63,7 @@ public class ArchmageAscension extends CardImpl { // As long as Archmage Ascension has six or more quest counters on it, if you would draw a card, // you may instead search your library for a card, put that card into your hand, then shuffle your library. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ArchmageAscensionReplacementEffect())); - + } public ArchmageAscension(final ArchmageAscension card) { @@ -91,17 +90,17 @@ class ArchmageAscensionTriggeredAbility extends TriggeredAbilityImpl { public ArchmageAscensionTriggeredAbility copy() { return new ArchmageAscensionTriggeredAbility(this); } - + @Override public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; } - + @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent archmage = game.getPermanent(super.getSourceId()); - CardsAmountDrawnThisTurnWatcher watcher = - (CardsAmountDrawnThisTurnWatcher) game.getState().getWatchers().get(CardsAmountDrawnThisTurnWatcher.class.getSimpleName()); + CardsAmountDrawnThisTurnWatcher watcher + = (CardsAmountDrawnThisTurnWatcher) game.getState().getWatchers().get(CardsAmountDrawnThisTurnWatcher.class.getSimpleName()); return archmage != null && watcher != null && watcher.getAmountCardsDrawn(this.getControllerId()) >= 2; } @@ -115,8 +114,8 @@ class ArchmageAscensionReplacementEffect extends ReplacementEffectImpl { public ArchmageAscensionReplacementEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "As long as {this} has six or more quest counters on it, if you would draw a card, " + - "you may instead search your library for a card, put that card into your hand, then shuffle your library"; + staticText = "As long as {this} has six or more quest counters on it, if you would draw a card, " + + "you may instead search your library for a card, put that card into your hand, then shuffle your library"; } public ArchmageAscensionReplacementEffect(final ArchmageAscensionReplacementEffect effect) { @@ -141,19 +140,19 @@ class ArchmageAscensionReplacementEffect extends ReplacementEffectImpl { if (player.searchLibrary(target, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { - card.moveToZone(Zone.HAND, id, game, false); + card.moveToZone(Zone.HAND, source.getSourceId(), game, false); player.shuffleLibrary(source, game); } } } return true; } - + @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DRAW_CARD; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { Permanent archmage = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java b/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java index dfc3f882eca..9b36bf77d95 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheCabal.java @@ -82,7 +82,7 @@ public class CurseOfTheCabal extends CardImpl { } } -class CurseOfTheCabalSacrificeEffect extends OneShotEffect{ +class CurseOfTheCabalSacrificeEffect extends OneShotEffect { private static final FilterControlledPermanent FILTER = new FilterControlledPermanent(); // ggf filter.FilterPermanent @@ -103,10 +103,11 @@ class CurseOfTheCabalSacrificeEffect extends OneShotEffect{ @Override public boolean apply(Game game, Ability source) { Player targetPlayer = game.getPlayer(source.getFirstTarget()); - if(targetPlayer != null) { + if (targetPlayer != null) { int amount = game.getBattlefield().countAll(FILTER, targetPlayer.getId(), game) / 2; - if(amount < 1) + if (amount < 1) { return true; + } Target target = new TargetControlledPermanent(amount, amount, FILTER, true); if (target.canChoose(targetPlayer.getId(), game)) { while (!target.isChosen() && target.canChoose(targetPlayer.getId(), game) && targetPlayer.canRespond()) { @@ -129,9 +130,9 @@ class CurseOfTheCabalTriggeredAbility extends ConditionalTriggeredAbility { public CurseOfTheCabalTriggeredAbility() { super(new BeginningOfUpkeepTriggeredAbility( - Zone.EXILED, new CurseOfTheCabalTriggeredAbilityConditionalDelay(), - TargetController.ANY, false, true - ), + Zone.EXILED, new CurseOfTheCabalTriggeredAbilityConditionalDelay(), + TargetController.ANY, false, true + ), SuspendedCondition.instance, "At the beginning of each player's upkeep, if {this} is suspended, that player may sacrifice a permanent. If he or she does, put two time counters on {this}." ); @@ -149,21 +150,22 @@ class CurseOfTheCabalTriggeredAbility extends ConditionalTriggeredAbility { } } -class CurseOfTheCabalTriggeredAbilityConditionalDelay extends AddCountersSourceEffect{ +class CurseOfTheCabalTriggeredAbilityConditionalDelay extends AddCountersSourceEffect { - public CurseOfTheCabalTriggeredAbilityConditionalDelay(){ + public CurseOfTheCabalTriggeredAbilityConditionalDelay() { super(CounterType.TIME.createInstance(), new StaticValue(2), false, true); } public boolean apply(Game game, Ability source) { - UUID id = game.getActivePlayerId(); - Player target = game.getPlayer(id); + UUID activePlayerId = game.getActivePlayerId(); + Player target = game.getPlayer(activePlayerId); Cost cost = new SacrificeTargetCost(new TargetControlledPermanent(new FilterControlledPermanent())); - if(target == null) + if (target == null) { return false; - if (cost.canPay(source, source.getSourceId(), id, game) + } + if (cost.canPay(source, source.getSourceId(), activePlayerId, game) && target.chooseUse(Outcome.Sacrifice, "Sacrifice a permanent to delay Curse of the Cabal?", source, game) - && cost.pay(source, game, source.getSourceId(), id, true, null)) { + && cost.pay(source, game, source.getSourceId(), activePlayerId, true, null)) { return super.apply(game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/d/DaringSaboteur.java b/Mage.Sets/src/mage/cards/d/DaringSaboteur.java new file mode 100644 index 00000000000..729b34dfbe0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DaringSaboteur.java @@ -0,0 +1,75 @@ +/* + * 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.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Zone; + +/** + * + * @author TheElk801 + */ +public class DaringSaboteur extends CardImpl { + + public DaringSaboteur(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add("Human"); + this.subtype.add("Pirate"); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // {2}{U}: Daring Saboteur can't be blocked this turn. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CantBeBlockedSourceEffect(Duration.EndOfTurn), new ManaCostsImpl("{2}{U}"))); + + // Whenever Daring Saboteur deals combat damage to a player, you may draw a card. If you do, discard a card. + Effect effect = new DrawDiscardControllerEffect(1, 1); + effect.setText("you may draw a card. If you do, discard a card"); + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(effect, true)); + } + + public DaringSaboteur(final DaringSaboteur card) { + super(card); + } + + @Override + public DaringSaboteur copy() { + return new DaringSaboteur(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DeadReckoning.java b/Mage.Sets/src/mage/cards/d/DeadReckoning.java index 1595bab2c2b..0435e24eabd 100644 --- a/Mage.Sets/src/mage/cards/d/DeadReckoning.java +++ b/Mage.Sets/src/mage/cards/d/DeadReckoning.java @@ -50,7 +50,7 @@ import mage.target.common.TargetCreaturePermanent; public class DeadReckoning extends CardImpl { public DeadReckoning(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}"); // You may put target creature card from your graveyard on top of your library. If you do, Dead Reckoning deals damage equal to that card's power to target creature. this.getSpellAbility().addEffect(new DeadReckoningEffect()); @@ -96,7 +96,7 @@ class DeadReckoningEffect extends OneShotEffect { && you.choose(Outcome.Damage, target2, source.getSourceId(), game)) { Card creatureInGraveyard = game.getCard(target1.getFirstTarget()); if (creatureInGraveyard != null) { - if (creatureInGraveyard.moveToZone(Zone.LIBRARY, id, game, true)) { + if (creatureInGraveyard.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true)) { int power = creatureInGraveyard.getPower().getValue(); Permanent creature = game.getPermanent(target2.getFirstTarget()); if (creature != null) { diff --git a/Mage.Sets/src/mage/cards/d/DecreeOfAnnihilation.java b/Mage.Sets/src/mage/cards/d/DecreeOfAnnihilation.java index a0f0d5c2b07..dc7170d11d8 100644 --- a/Mage.Sets/src/mage/cards/d/DecreeOfAnnihilation.java +++ b/Mage.Sets/src/mage/cards/d/DecreeOfAnnihilation.java @@ -1,4 +1,4 @@ - /* +/* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are @@ -27,7 +27,7 @@ */ package mage.cards.d; - import java.util.UUID; +import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.CycleTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; @@ -54,14 +54,14 @@ import mage.players.Player; public class DecreeOfAnnihilation extends CardImpl { public DecreeOfAnnihilation(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{8}{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{8}{R}{R}"); // Exile all artifacts, creatures, and lands from the battlefield, all cards from all graveyards, and all cards from all hands. this.getSpellAbility().addEffect(new DecreeOfAnnihilationEffect()); // Cycling {5}{R}{R} this.addAbility(new CyclingAbility(new ManaCostsImpl("{5}{R}{R}"))); - + // When you cycle Decree of Annihilation, destroy all lands. Ability ability = new CycleTriggeredAbility(new DestroyAllEffect(StaticFilters.FILTER_LANDS), false); this.addAbility(ability); @@ -78,16 +78,16 @@ public class DecreeOfAnnihilation extends CardImpl { } class DecreeOfAnnihilationEffect extends OneShotEffect { - + private static final FilterPermanent filter = new FilterPermanent(""); - + static { filter.add(Predicates.or( new CardTypePredicate(CardType.ARTIFACT), new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); } - + public DecreeOfAnnihilationEffect() { super(Outcome.Detriment); staticText = "Exile all artifacts, creatures, and lands from the battlefield, all cards from all graveyards, and all cards from all hands"; @@ -105,7 +105,7 @@ class DecreeOfAnnihilationEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { - permanent.moveToExile(id, "all artifacts, creatures, and land", id, game); + permanent.moveToExile(null, "", source.getSourceId(), game); } for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); diff --git a/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java b/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java index 7baf0fba2ca..b1753aa8a0e 100644 --- a/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java +++ b/Mage.Sets/src/mage/cards/e/ElsewhereFlask.java @@ -151,19 +151,19 @@ class ElsewhereFlaskContinuousEffect extends ContinuousEffectImpl { if (sublayer == SubLayer.NA) { land.getAbilities().clear(); if (choice.equals("Forest")) { - land.addAbility(new GreenManaAbility(), id, game); + land.addAbility(new GreenManaAbility(), source.getSourceId(), game); } if (choice.equals("Plains")) { - land.addAbility(new WhiteManaAbility(), id, game); + land.addAbility(new WhiteManaAbility(), source.getSourceId(), game); } if (choice.equals("Mountain")) { - land.addAbility(new RedManaAbility(), id, game); + land.addAbility(new RedManaAbility(), source.getSourceId(), game); } if (choice.equals("Island")) { - land.addAbility(new BlueManaAbility(), id, game); + land.addAbility(new BlueManaAbility(), source.getSourceId(), game); } if (choice.equals("Swamp")) { - land.addAbility(new BlackManaAbility(), id, game); + land.addAbility(new BlackManaAbility(), source.getSourceId(), game); } } break; diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java b/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java index bffd7367125..c1707a1e654 100644 --- a/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java +++ b/Mage.Sets/src/mage/cards/e/ExplosiveRevelation.java @@ -28,6 +28,7 @@ package mage.cards.e; import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; @@ -50,8 +51,7 @@ import mage.target.common.TargetCreatureOrPlayer; public class ExplosiveRevelation extends CardImpl { public ExplosiveRevelation(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{R}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); // Choose target creature or player. Reveal cards from the top of your library until you reveal a nonland card. Explosive Revelation deals damage equal to that card's converted mana cost to that creature or player. Put the nonland card into your hand and the rest on the bottom of your library in any order. this.getSpellAbility().addEffect(new ExplosiveRevelationEffect()); @@ -86,42 +86,46 @@ class ExplosiveRevelationEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null && player.getLibrary().hasCards()) { - CardsImpl cards = new CardsImpl(); - Library library = player.getLibrary(); - Card card = null; - do { - card = library.removeFromTop(game); - if (card != null) { - cards.add(card); + 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()); + // reveal cards + if (!cards.isEmpty()) { + controller.revealCards(sourceObject.getIdName(), cards, game); } - } while (library.hasCards() && card != null && card.isLand()); - // reveal cards - if (!cards.isEmpty()) { - player.revealCards("Explosive Revelation", cards, game); - } - // the nonland card - int damage = card.getConvertedManaCost(); - // assign damage to target - for (UUID targetId: targetPointer.getTargets(game, source)) { - Permanent targetedCreature = game.getPermanent(targetId); - if (targetedCreature != null) { - targetedCreature.damage(damage, source.getSourceId(), game, false, true); - } - else { - Player targetedPlayer = game.getPlayer(targetId); - if (targetedPlayer != null) { - targetedPlayer.damage(damage, source.getSourceId(), game, false, true); + // the nonland card + int damage = card.getConvertedManaCost(); + // assign damage to target + for (UUID targetId : targetPointer.getTargets(game, source)) { + Permanent targetedCreature = game.getPermanent(targetId); + if (targetedCreature != null) { + targetedCreature.damage(damage, source.getSourceId(), game, false, true); + } else { + Player targetedPlayer = game.getPlayer(targetId); + if (targetedPlayer != null) { + targetedPlayer.damage(damage, source.getSourceId(), game, false, true); + } } } + // move nonland card to hand + card.moveToZone(Zone.HAND, source.getSourceId(), game, true); + // remove nonland card from revealed card list + cards.remove(card); + // put the rest of the cards on the bottom of the library in any order + return controller.putCardsOnBottomOfLibrary(cards, game, source, true); } - // move nonland card to hand - card.moveToZone(Zone.HAND, id, game, true); - // remove nonland card from revealed card list - cards.remove(card); - // put the rest of the cards on the bottom of the library in any order - return player.putCardsOnBottomOfLibrary(cards, game, source, true); + return true; } return false; } diff --git a/Mage.Sets/src/mage/cards/h/HurkylsRecall.java b/Mage.Sets/src/mage/cards/h/HurkylsRecall.java index baeab7390e9..76785338f7a 100644 --- a/Mage.Sets/src/mage/cards/h/HurkylsRecall.java +++ b/Mage.Sets/src/mage/cards/h/HurkylsRecall.java @@ -36,7 +36,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterArtifactPermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.other.OwnerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPlayer; @@ -48,8 +48,7 @@ import mage.target.TargetPlayer; public class HurkylsRecall extends CardImpl { public HurkylsRecall(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Return all artifacts target player owns to his or her hand. this.getSpellAbility().addEffect(new HurkylsRecallReturnToHandEffect()); @@ -81,7 +80,7 @@ class HurkylsRecallReturnToHandEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { if (targetPointer.getFirst(game, source) != null) { FilterArtifactPermanent filter = new FilterArtifactPermanent(); - filter.add(new ControllerIdPredicate(targetPointer.getFirst(game, source))); + filter.add(new OwnerIdPredicate(targetPointer.getFirst(game, source))); for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { permanent.moveToZone(Zone.HAND, source.getSourceId(), game, true); } diff --git a/Mage.Sets/src/mage/cards/i/IgniteMemories.java b/Mage.Sets/src/mage/cards/i/IgniteMemories.java index a797cac2942..30b01dd623a 100644 --- a/Mage.Sets/src/mage/cards/i/IgniteMemories.java +++ b/Mage.Sets/src/mage/cards/i/IgniteMemories.java @@ -28,6 +28,7 @@ package mage.cards.i; import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.StormAbility; @@ -49,8 +50,7 @@ import mage.target.TargetPlayer; public class IgniteMemories extends CardImpl { public IgniteMemories(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}"); // Target player reveals a card at random from his or her hand. Ignite Memories deals damage to that player equal to that card's converted mana cost. this.getSpellAbility().addTarget(new TargetPlayer()); @@ -69,7 +69,6 @@ public class IgniteMemories extends CardImpl { } } - class IgniteMemoriesEffect extends OneShotEffect { public IgniteMemoriesEffect() { @@ -83,13 +82,17 @@ class IgniteMemoriesEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(targetPointer.getFirst(game, source)); - if (player != null && !player.getHand().isEmpty()) { - Cards revealed = new CardsImpl(); - Card card = player.getHand().getRandom(game); - revealed.add(card); - player.revealCards("Ignite Memories", revealed, game); - player.damage(card.getConvertedManaCost(), id, game, false, true); + Player controller = game.getPlayer(targetPointer.getFirst(game, source)); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + if (!controller.getHand().isEmpty()) { + Cards revealed = new CardsImpl(); + Card card = controller.getHand().getRandom(game); + revealed.add(card); + controller.revealCards(sourceObject.getIdName(), revealed, game); + controller.damage(card.getConvertedManaCost(), source.getSourceId(), game, false, true); + + } return true; } return false; @@ -100,4 +103,4 @@ class IgniteMemoriesEffect extends OneShotEffect { return new IgniteMemoriesEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java new file mode 100644 index 00000000000..c41c4329800 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JaceCunningCastaway.java @@ -0,0 +1,186 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.common.PutTokenOntoBattlefieldCopyTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.JaceCunningCastawayIllusionToken; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class JaceCunningCastaway extends CardImpl { + + public JaceCunningCastaway(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{U}{U}"); + + addSuperType(SuperType.LEGENDARY); + this.subtype.add("Jace"); + + this.addAbility(new PlanswalkerEntersWithLoyalityCountersAbility(3)); + + // +1: Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card. + this.addAbility(new LoyaltyAbility(new JaceArchitectOfThouStartEffect1(), 1)); + + // -2: Create a 2/2 blue Illusion creature token with "When this creature becomes the target of a spell, sacrifice it." + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new JaceCunningCastawayIllusionToken()), -2)); + + // -5: Create two tokens that are copies of Jace, Cunning Castaway, except they're not legendary. + this.addAbility(new LoyaltyAbility(new JaceCunningCastawayCopyEffect(), -5)); + } + + public JaceCunningCastaway(final JaceCunningCastaway card) { + super(card); + } + + @Override + public JaceCunningCastaway copy() { + return new JaceCunningCastaway(this); + } +} + +class JaceArchitectOfThouStartEffect1 extends OneShotEffect { + + public JaceArchitectOfThouStartEffect1() { + super(Outcome.DrawCard); + this.staticText = "Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card"; + } + + public JaceArchitectOfThouStartEffect1(final JaceArchitectOfThouStartEffect1 effect) { + super(effect); + } + + @Override + public JaceArchitectOfThouStartEffect1 copy() { + return new JaceArchitectOfThouStartEffect1(this); + } + + @Override + public boolean apply(Game game, Ability source) { + DelayedTriggeredAbility delayedAbility = new ThopterSpyNetwoDamageTriggeredAbility(); + game.addDelayedTriggeredAbility(delayedAbility, source); + return true; + } +} + +class ThopterSpyNetwoDamageTriggeredAbility extends DelayedTriggeredAbility { + + List damagedPlayerIds = new ArrayList<>(); + + public ThopterSpyNetwoDamageTriggeredAbility() { + super(new DrawDiscardControllerEffect(1, 1), Duration.EndOfTurn, false); + } + + public ThopterSpyNetwoDamageTriggeredAbility(final ThopterSpyNetwoDamageTriggeredAbility ability) { + super(ability); + } + + @Override + public ThopterSpyNetwoDamageTriggeredAbility copy() { + return new ThopterSpyNetwoDamageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER + || event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { + if (((DamagedPlayerEvent) event).isCombatDamage()) { + Permanent creature = game.getPermanent(event.getSourceId()); + if (creature != null && creature.getControllerId().equals(controllerId) + && !damagedPlayerIds.contains(event.getTargetId())) { + damagedPlayerIds.add(event.getTargetId()); + return true; + } + } + } + if (event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST) { + damagedPlayerIds.clear(); + } + return false; + } + + @Override + public String getRule() { + return "Whenever one or more creatures you control deal combat damage to a player this turn, draw a card, then discard a card"; + } +} + +class JaceCunningCastawayCopyEffect extends OneShotEffect { + + JaceCunningCastawayCopyEffect() { + super(Outcome.Benefit); + this.staticText = "Create two tokens that are copies of {this}, except they're not legendary"; + } + + JaceCunningCastawayCopyEffect(final JaceCunningCastawayCopyEffect effect) { + super(effect); + } + + @Override + public JaceCunningCastawayCopyEffect copy() { + return new JaceCunningCastawayCopyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (permanent != null) { + PutTokenOntoBattlefieldCopyTargetEffect effect = new PutTokenOntoBattlefieldCopyTargetEffect(source.getControllerId(), null, false, 2); + effect.setTargetPointer(new FixedTarget(source.getSourceId())); + effect.setIsntLegendary(true); + return effect.apply(game, source); + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LabyrinthGuardian.java b/Mage.Sets/src/mage/cards/l/LabyrinthGuardian.java index 09a13cb50fb..54c6e27df66 100644 --- a/Mage.Sets/src/mage/cards/l/LabyrinthGuardian.java +++ b/Mage.Sets/src/mage/cards/l/LabyrinthGuardian.java @@ -99,7 +99,7 @@ class LabyrinthGuardianTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { MageObject eventSourceObject = game.getObject(event.getSourceId()); - if (eventSourceObject != null && event.getTargetId().equals(this.getSourceId())&& eventSourceObject instanceof Spell ) { + if (eventSourceObject != null && event.getTargetId().equals(this.getSourceId()) && eventSourceObject instanceof Spell) { getEffects().get(0).setTargetPointer(new FixedTarget(event.getPlayerId())); return true; } @@ -108,7 +108,7 @@ class LabyrinthGuardianTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever {this} becomes the target of a spell, sacrifice it."; + return "When {this} becomes the target of a spell, sacrifice it."; } } diff --git a/Mage.Sets/src/mage/cards/m/Meglonoth.java b/Mage.Sets/src/mage/cards/m/Meglonoth.java index 0215248f7dc..affbc2684a3 100644 --- a/Mage.Sets/src/mage/cards/m/Meglonoth.java +++ b/Mage.Sets/src/mage/cards/m/Meglonoth.java @@ -96,7 +96,7 @@ class MeglonothEffect extends OneShotEffect { Permanent meglonoth = game.getPermanent(source.getSourceId()); Permanent blocked = game.getPermanent(targetPointer.getFirst(game, source)); if (blocked != null && meglonoth != null) { - game.getPlayer(blocked.getControllerId()).damage(meglonoth.getPower().getValue(), id, game, false, true); + game.getPlayer(blocked.getControllerId()).damage(meglonoth.getPower().getValue(), source.getSourceId(), game, false, true); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/p/PerishTheThought.java b/Mage.Sets/src/mage/cards/p/PerishTheThought.java index aad7daa150d..6fc5469a4c7 100644 --- a/Mage.Sets/src/mage/cards/p/PerishTheThought.java +++ b/Mage.Sets/src/mage/cards/p/PerishTheThought.java @@ -49,8 +49,7 @@ import mage.target.common.TargetOpponent; public class PerishTheThought extends CardImpl { public PerishTheThought(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); // Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. this.getSpellAbility().addEffect(new PerishTheThoughtEffect()); @@ -68,9 +67,9 @@ public class PerishTheThought extends CardImpl { } class PerishTheThoughtEffect extends OneShotEffect { - + private static final FilterCard filter = new FilterCard("card in target opponent's hand"); - + public PerishTheThoughtEffect() { super(Outcome.Neutral); this.staticText = "Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library"; @@ -99,7 +98,7 @@ class PerishTheThoughtEffect extends OneShotEffect { Card chosenCard = targetOpponent.getHand().get(target.getFirstTarget(), game); if (chosenCard != null) { if (targetOpponent != null) { - chosenCard.moveToZone(Zone.LIBRARY, id, game, false); + chosenCard.moveToZone(Zone.LIBRARY, source.getSourceId(), game, false); targetOpponent.shuffleLibrary(source, game); } } diff --git a/Mage.Sets/src/mage/cards/r/RazorBoomerang.java b/Mage.Sets/src/mage/cards/r/RazorBoomerang.java index bf6134d1fb1..b41dee1ffd3 100644 --- a/Mage.Sets/src/mage/cards/r/RazorBoomerang.java +++ b/Mage.Sets/src/mage/cards/r/RazorBoomerang.java @@ -52,7 +52,7 @@ import mage.target.common.TargetCreatureOrPlayer; public class RazorBoomerang extends CardImpl { public RazorBoomerang(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); this.subtype.add("Equipment"); // Equipped creature has "{tap}, Unattach Razor Boomerang: Razor Boomerang deals 1 damage to target creature or player. Return Razor Boomerang to its owner's hand." @@ -105,7 +105,7 @@ class RazorBoomerangEffect extends OneShotEffect { } Permanent razor = game.getPermanent(attachmentid); if (razor != null) { - razor.moveToZone(Zone.HAND, id, game, true); + razor.moveToZone(Zone.HAND, source.getSourceId(), game, true); } return true; } diff --git a/Mage.Sets/src/mage/cards/r/RiversRebuke.java b/Mage.Sets/src/mage/cards/r/RiversRebuke.java new file mode 100644 index 00000000000..5ce62f4a540 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiversRebuke.java @@ -0,0 +1,96 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.r; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPlayer; + +/** + * + * @author TheElk801 + */ +public class RiversRebuke extends CardImpl { + + public RiversRebuke(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{U}{U}"); + + // Return all nonland permanents target player controls to their owner's hand. + this.getSpellAbility().addEffect(new RiversRebukeReturnToHandEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public RiversRebuke(final RiversRebuke card) { + super(card); + } + + @Override + public RiversRebuke copy() { + return new RiversRebuke(this); + } +} + +class RiversRebukeReturnToHandEffect extends OneShotEffect { + + public RiversRebukeReturnToHandEffect() { + super(Outcome.ReturnToHand); + staticText = "Return all nonland permanents target player controls to their owner's hand"; + } + + public RiversRebukeReturnToHandEffect(final RiversRebukeReturnToHandEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + if (targetPointer.getFirst(game, source) != null) { + FilterNonlandPermanent filter = new FilterNonlandPermanent(); + filter.add(new ControllerIdPredicate(targetPointer.getFirst(game, source))); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + permanent.moveToZone(Zone.HAND, source.getSourceId(), game, true); + } + return true; + } + return false; + } + + @Override + public RiversRebukeReturnToHandEffect copy() { + return new RiversRebukeReturnToHandEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java b/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java index 92f3c67be93..dfaeef31dc1 100644 --- a/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java +++ b/Mage.Sets/src/mage/cards/s/ShellOfTheLastKappa.java @@ -64,13 +64,14 @@ import mage.util.CardUtil; public class ShellOfTheLastKappa extends CardImpl { private static final FilterSpell filter = new FilterSpell("instant or sorcery spell that targets you"); + static { filter.add(new TargetYouPredicate()); filter.add(Predicates.or(new CardTypePredicate(CardType.INSTANT), new CardTypePredicate(CardType.SORCERY))); } public ShellOfTheLastKappa(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); addSuperType(SuperType.LEGENDARY); // {3}, {tap}: Exile target instant or sorcery spell that targets you. @@ -117,14 +118,14 @@ class ShellOfTheLastKappaEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); if (spell != null) { - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (sourcePermanent == null) { sourcePermanent = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); } if (sourcePermanent != null) { game.getStack().counter(spell.getId(), source.getSourceId(), game); Card card = spell.getCard(); - card.moveToExile(CardUtil.getCardExileZoneId(game, source), sourcePermanent.getName(), id, game); + card.moveToExile(CardUtil.getCardExileZoneId(game, source), sourcePermanent.getName(), source.getSourceId(), game); } } return false; @@ -164,7 +165,6 @@ class ShellOfTheLastKappaCastEffect extends OneShotEffect { } } - class TargetYouPredicate implements ObjectPlayerPredicate> { @Override diff --git a/Mage.Sets/src/mage/cards/s/ShiftyDoppelganger.java b/Mage.Sets/src/mage/cards/s/ShiftyDoppelganger.java new file mode 100644 index 00000000000..546790b9da7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShiftyDoppelganger.java @@ -0,0 +1,174 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInHand; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class ShiftyDoppelganger extends CardImpl { + + public ShiftyDoppelganger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add("Shapeshifter"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {3}{U}, Exile Shifty Doppelganger: You may put a creature card from your hand onto the battlefield. If you do, that creature gains haste until end of turn. At the beginning of the next end step, sacrifice that creature. If you do, return Shifty Doppelganger to the battlefield. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShiftyDoppelgangerExileEffect(), new ManaCostsImpl("{3}{U}")); + ability.addCost(new ExileSourceCost(true)); + this.addAbility(ability); + + } + + public ShiftyDoppelganger(final ShiftyDoppelganger card) { + super(card); + } + + @Override + public ShiftyDoppelganger copy() { + return new ShiftyDoppelganger(this); + } +} + +class ShiftyDoppelgangerExileEffect extends OneShotEffect { + + public ShiftyDoppelgangerExileEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "You may put a creature card from your hand onto the battlefield. If you do, that creature gains haste until end of turn. At the beginning of the next end step, sacrifice that creature. If you do, return {this} to the battlefield"; + } + + public ShiftyDoppelgangerExileEffect(final ShiftyDoppelgangerExileEffect effect) { + super(effect); + } + + @Override + public ShiftyDoppelgangerExileEffect copy() { + return new ShiftyDoppelgangerExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + FilterCreatureCard filter = new FilterCreatureCard("a creature card"); + boolean putCreature = false; + UUID creatureId = UUID.randomUUID(); + Player player = game.getPlayer(source.getControllerId()); + if (player.chooseUse(Outcome.PutCardInPlay, "Put " + filter.getMessage() + " from your hand onto the battlefield?", source, game)) { + TargetCardInHand target = new TargetCardInHand(filter); + if (player.choose(Outcome.PutCreatureInPlay, target, source.getSourceId(), game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + putCreature = player.moveCards(card, Zone.BATTLEFIELD, source, game); + if (putCreature) { + creatureId = card.getId(); + } + } + } + } + if (putCreature) { + Permanent creature = game.getPermanent(creatureId); + if (creature != null) { + ContinuousEffect hasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); + hasteEffect.setTargetPointer(new FixedTarget(creature, game)); + game.addEffect(hasteEffect, source); + DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new ShiftyDoppelgangerReturnEffect(creature.getId(), creature.getZoneChangeCounter(game), (int) game.getState().getValue(source.getSourceId().toString()))); + game.addDelayedTriggeredAbility(delayedAbility, source); + } + } + return true; + } +} + +class ShiftyDoppelgangerReturnEffect extends OneShotEffect { + + private final UUID creatureId; + private final int creatureZoneCount; + private final int sourceZoneCount; + + ShiftyDoppelgangerReturnEffect(UUID creatureId, int creatureZoneCount, int sourceZoneCount) { + super(Outcome.Benefit); + this.staticText = "sacrifice that creature. If you do, return {this} to the battlefield"; + this.creatureId = creatureId; + this.creatureZoneCount = creatureZoneCount; + this.sourceZoneCount = sourceZoneCount; + } + + ShiftyDoppelgangerReturnEffect(final ShiftyDoppelgangerReturnEffect effect) { + super(effect); + this.creatureId = effect.creatureId; + this.creatureZoneCount = effect.creatureZoneCount; + this.sourceZoneCount = effect.sourceZoneCount; + } + + @Override + public ShiftyDoppelgangerReturnEffect copy() { + return new ShiftyDoppelgangerReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(creatureId); + Player player = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (creature != null && creature.getZoneChangeCounter(game) == this.creatureZoneCount && creature.sacrifice(source.getSourceId(), game)) { + if (player != null && sourceObject != null && sourceObject.getZoneChangeCounter(game) == this.sourceZoneCount) { + player.moveCards(game.getCard(source.getSourceId()), Zone.BATTLEFIELD, source, game); + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/StalkingLeonin.java b/Mage.Sets/src/mage/cards/s/StalkingLeonin.java new file mode 100644 index 00000000000..c917e94728c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StalkingLeonin.java @@ -0,0 +1,241 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import static mage.cards.s.StalkingLeonin.SECRET_OPPONENT; +import static mage.cards.s.StalkingLeonin.SECRET_OWNER; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingCreature; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponent; +import mage.util.CardUtil; + +/** + * + * @author LevelX2 + */ +public class StalkingLeonin extends CardImpl { + + static final String SECRET_OPPONENT = "_secOpp"; + static final String SECRET_OWNER = "_secOwn"; + + public StalkingLeonin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.CAT, SubType.ARCHER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Stalking Leonin enters the battlefield, secretly choose an opponent. + this.addAbility(new EntersBattlefieldTriggeredAbility(new StalkingLeoninChooseOpponent(), false)); + // Reveal the player you chose: Exile target creature that's attacking you if it's controlled by the chosen player. Activate this ability only once. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new StalkingLeoninEffect(), new StalkingLeoninRevealOpponentCost()); + ability.addTarget(new TargetCreaturePermanent(new StalkingLeoninFilter())); + this.addAbility(ability); + } + + public StalkingLeonin(final StalkingLeonin card) { + super(card); + } + + @Override + public StalkingLeonin copy() { + return new StalkingLeonin(this); + } +} + +class StalkingLeoninChooseOpponent extends OneShotEffect { + + public StalkingLeoninChooseOpponent() { + super(Outcome.Neutral); + staticText = "secretly choose an opponent"; + } + + public StalkingLeoninChooseOpponent(final StalkingLeoninChooseOpponent effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject mageObject = game.getPermanentEntering(source.getSourceId()); + if (mageObject == null) { + mageObject = game.getObject(source.getSourceId()); + } + if (controller != null && mageObject != null) { + TargetOpponent targetOpponent = new TargetOpponent(); + targetOpponent.setTargetName("opponent (secretly)"); + while (!controller.choose(outcome, targetOpponent, source.getSourceId(), game)) { + if (!controller.canRespond()) { + return false; + } + } + if (targetOpponent.getTargets().isEmpty()) { + return false; + } + if (!game.isSimulation()) { + game.informPlayers(mageObject.getName() + ": " + controller.getLogName() + " has secretly chosen an opponent."); + } + game.getState().setValue(mageObject.getId() + SECRET_OPPONENT, targetOpponent.getTargets().get(0)); + game.getState().setValue(mageObject.getId() + SECRET_OWNER, controller.getId()); + if (mageObject instanceof Permanent) { + ((Permanent) mageObject).addInfo(SECRET_OPPONENT, + CardUtil.addToolTipMarkTags(controller.getLogName() + " has secretly chosen an opponent."), game); + } + } + return false; + } + + @Override + public StalkingLeoninChooseOpponent copy() { + return new StalkingLeoninChooseOpponent(this); + } + +} + +class StalkingLeoninRevealOpponentCost extends CostImpl { + + public StalkingLeoninRevealOpponentCost() { + this.text = "Reveal the player you chose"; + } + + public StalkingLeoninRevealOpponentCost(final StalkingLeoninRevealOpponentCost cost) { + super(cost); + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + UUID playerThatChoseId = (UUID) game.getState().getValue(sourceId + SECRET_OWNER); + if (playerThatChoseId == null || !playerThatChoseId.equals(controllerId)) { + return false; + } + UUID opponentId = (UUID) game.getState().getValue(sourceId + SECRET_OPPONENT); + return opponentId != null; + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + UUID playerThatChoseId = (UUID) game.getState().getValue(sourceId + SECRET_OWNER); + if (playerThatChoseId == null || !playerThatChoseId.equals(controllerId)) { + return false; + } + UUID opponentId = (UUID) game.getState().getValue(sourceId + SECRET_OPPONENT); + if (opponentId != null) { + game.getState().setValue(sourceId + SECRET_OWNER, null); // because only once, the vale is set to null + Player controller = game.getPlayer(controllerId); + Player opponent = game.getPlayer(opponentId); + MageObject sourceObject = game.getObject(sourceId); + if (controller != null && opponent != null && sourceObject != null) { + if (sourceObject instanceof Permanent) { + ((Permanent) sourceObject).addInfo(SECRET_OPPONENT, null, game); + } + game.informPlayers(sourceObject.getLogName() + ": " + controller.getLogName() + " reveals the secretly chosen opponent " + opponent.getLogName()); + } + paid = true; + } + return paid; + } + + @Override + public StalkingLeoninRevealOpponentCost copy() { + return new StalkingLeoninRevealOpponentCost(this); + } + +} + +class StalkingLeoninEffect extends OneShotEffect { + + public StalkingLeoninEffect() { + super(Outcome.Exile); + this.staticText = "Exile target creature that's attacking you if it's controlled by the chosen player. Activate this ability only once"; + } + + public StalkingLeoninEffect(final StalkingLeoninEffect effect) { + super(effect); + } + + @Override + public StalkingLeoninEffect copy() { + return new StalkingLeoninEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (targetCreature != null) { + UUID opponentId = (UUID) game.getState().getValue(source.getSourceId() + SECRET_OPPONENT); + if (opponentId != null && opponentId.equals(targetCreature.getControllerId())) { + controller.moveCards(targetCreature, Zone.EXILED, source, game); + } + } + return true; + } + return false; + } +} + +class StalkingLeoninFilter extends FilterAttackingCreature { + + public StalkingLeoninFilter() { + super("creature that's attacking you"); + } + + public StalkingLeoninFilter(final StalkingLeoninFilter filter) { + super(filter); + } + + @Override + public StalkingLeoninFilter copy() { + return new StalkingLeoninFilter(this); + } + + @Override + public boolean match(Permanent permanent, UUID sourceId, UUID playerId, Game game) { + return super.match(permanent, sourceId, playerId, game) + && playerId.equals(game.getCombat().getDefenderId(permanent.getId())); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SuddenDemise.java b/Mage.Sets/src/mage/cards/s/SuddenDemise.java index bb7fe7900c7..e0607a9321e 100644 --- a/Mage.Sets/src/mage/cards/s/SuddenDemise.java +++ b/Mage.Sets/src/mage/cards/s/SuddenDemise.java @@ -49,10 +49,9 @@ import mage.players.Player; public class SuddenDemise extends CardImpl { public SuddenDemise(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{X}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}"); - - // Choose a color. Sudden Demise deals X damage to each creature of the chosen color. + // Choose a color. Sudden Demise deals X damage to each creature of the chosen color. this.getSpellAbility().addEffect(new SuddenDemiseDamageEffect()); } @@ -71,7 +70,7 @@ class SuddenDemiseDamageEffect extends OneShotEffect { public SuddenDemiseDamageEffect() { super(Outcome.Damage); - this.staticText = "Choose a color. Sudden Demise deals X damage to each creature of the chosen color"; + this.staticText = "Choose a color. {this} deals X damage to each creature of the chosen color"; } public SuddenDemiseDamageEffect(final SuddenDemiseDamageEffect effect) { @@ -93,7 +92,7 @@ class SuddenDemiseDamageEffect extends OneShotEffect { final int damage = source.getManaCostsToPay().getX(); FilterPermanent filter = new FilterCreaturePermanent(); filter.add(new ColorPredicate(choice.getColor())); - for (Permanent permanent:game.getBattlefield().getActivePermanents(filter, source.getControllerId(), id, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { permanent.damage(damage, source.getSourceId(), game, false, true); } return true; diff --git a/Mage.Sets/src/mage/cards/t/Terraformer.java b/Mage.Sets/src/mage/cards/t/Terraformer.java index 830fcdd0466..77f66d4128a 100644 --- a/Mage.Sets/src/mage/cards/t/Terraformer.java +++ b/Mage.Sets/src/mage/cards/t/Terraformer.java @@ -160,19 +160,19 @@ class TerraformerContinuousEffect extends ContinuousEffectImpl { if (sublayer == SubLayer.NA) { land.getAbilities().clear(); if (choice.equals("Forest")) { - land.addAbility(new GreenManaAbility(), id, game); + land.addAbility(new GreenManaAbility(), source.getSourceId(), game); } if (choice.equals("Plains")) { - land.addAbility(new WhiteManaAbility(), id, game); + land.addAbility(new WhiteManaAbility(), source.getSourceId(), game); } if (choice.equals("Mountain")) { - land.addAbility(new RedManaAbility(), id, game); + land.addAbility(new RedManaAbility(), source.getSourceId(), game); } if (choice.equals("Island")) { - land.addAbility(new BlueManaAbility(), id, game); + land.addAbility(new BlueManaAbility(), source.getSourceId(), game); } if (choice.equals("Swamp")) { - land.addAbility(new BlackManaAbility(), id, game); + land.addAbility(new BlackManaAbility(), source.getSourceId(), game); } } break; diff --git a/Mage.Sets/src/mage/cards/w/Worldfire.java b/Mage.Sets/src/mage/cards/w/Worldfire.java index c56ac04a6a2..40a0c987c2f 100644 --- a/Mage.Sets/src/mage/cards/w/Worldfire.java +++ b/Mage.Sets/src/mage/cards/w/Worldfire.java @@ -47,8 +47,7 @@ import mage.players.Player; public class Worldfire extends CardImpl { public Worldfire(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{6}{R}{R}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{6}{R}{R}{R}"); // Exile all permanents. Exile all cards from all hands and graveyards. Each player's life total becomes 1. this.getSpellAbility().addEffect(new WorldfireEffect()); @@ -65,9 +64,9 @@ public class Worldfire extends CardImpl { } class WorldfireEffect extends OneShotEffect { - + private static FilterPermanent filter = new FilterPermanent(); - + public WorldfireEffect() { super(Outcome.Detriment); staticText = "Exile all permanents. Exile all cards from all hands and graveyards. Each player's life total becomes 1"; @@ -85,7 +84,7 @@ class WorldfireEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { - permanent.moveToExile(id, "all permanents", id, game); + permanent.moveToExile(null, "", source.getSourceId(), game); } for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); diff --git a/Mage.Sets/src/mage/sets/Amonkhet.java b/Mage.Sets/src/mage/sets/Amonkhet.java index b8ae0a7253c..006da239d64 100644 --- a/Mage.Sets/src/mage/sets/Amonkhet.java +++ b/Mage.Sets/src/mage/sets/Amonkhet.java @@ -152,7 +152,7 @@ public class Amonkhet extends ExpansionSet { cards.add(new SetCardInfo("Flameblade Adept", 131, Rarity.UNCOMMON, mage.cards.f.FlamebladeAdept.class)); cards.add(new SetCardInfo("Fling", 132, Rarity.COMMON, mage.cards.f.Fling.class)); cards.add(new SetCardInfo("Floodwaters", 53, Rarity.COMMON, mage.cards.f.Floodwaters.class)); - cards.add(new SetCardInfo("Forest", 254, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Forest", 254, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Forest", 267, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 268, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 269, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); @@ -202,7 +202,7 @@ public class Amonkhet extends ExpansionSet { cards.add(new SetCardInfo("Initiate's Companion", 174, Rarity.COMMON, mage.cards.i.InitiatesCompanion.class)); cards.add(new SetCardInfo("Insult // Injury", 213, Rarity.RARE, mage.cards.i.InsultInjury.class)); cards.add(new SetCardInfo("Irrigated Farmland", 245, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); - cards.add(new SetCardInfo("Island", 251, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 251, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Island", 258, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 259, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 260, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); @@ -227,7 +227,7 @@ public class Amonkhet extends ExpansionSet { cards.add(new SetCardInfo("Miasmic Mummy", 100, Rarity.COMMON, mage.cards.m.MiasmicMummy.class)); cards.add(new SetCardInfo("Mighty Leap", 20, Rarity.COMMON, mage.cards.m.MightyLeap.class)); cards.add(new SetCardInfo("Minotaur Sureshot", 143, Rarity.COMMON, mage.cards.m.MinotaurSureshot.class)); - cards.add(new SetCardInfo("Mountain", 253, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 253, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Mountain", 264, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 265, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 266, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); @@ -255,7 +255,7 @@ public class Amonkhet extends ExpansionSet { cards.add(new SetCardInfo("Pitiless Vizier", 103, Rarity.COMMON, mage.cards.p.PitilessVizier.class)); cards.add(new SetCardInfo("Plague Belcher", 104, Rarity.RARE, mage.cards.p.PlagueBelcher.class)); cards.add(new SetCardInfo("Pyramid of the Pantheon", 235, Rarity.RARE, mage.cards.p.PyramidOfThePantheon.class)); - cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Plains", 255, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 256, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 257, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); @@ -307,7 +307,7 @@ public class Amonkhet extends ExpansionSet { cards.add(new SetCardInfo("Sunscorched Desert", 249, Rarity.COMMON, mage.cards.s.SunscorchedDesert.class)); cards.add(new SetCardInfo("Supernatural Stamina", 111, Rarity.COMMON, mage.cards.s.SupernaturalStamina.class)); cards.add(new SetCardInfo("Supply Caravan", 30, Rarity.COMMON, mage.cards.s.SupplyCaravan.class)); - cards.add(new SetCardInfo("Swamp", 252, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Swamp", 252, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Swamp", 261, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 262, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 263, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/Commander2017.java b/Mage.Sets/src/mage/sets/Commander2017.java index 770048533a0..b8c087fde0f 100644 --- a/Mage.Sets/src/mage/sets/Commander2017.java +++ b/Mage.Sets/src/mage/sets/Commander2017.java @@ -300,6 +300,7 @@ public class Commander2017 extends ExpansionSet { cards.add(new SetCardInfo("Spelltwine", 94, Rarity.RARE, mage.cards.s.Spelltwine.class)); cards.add(new SetCardInfo("Spirit of the Hearth", 73, Rarity.RARE, mage.cards.s.SpiritOfTheHearth.class)); cards.add(new SetCardInfo("Staff of Nin", 224, Rarity.RARE, mage.cards.s.StaffOfNin.class)); + cards.add(new SetCardInfo("Stalking Leonin", 7, Rarity.RARE, mage.cards.s.StalkingLeonin.class)); cards.add(new SetCardInfo("Steel Hellkite", 225, Rarity.RARE, mage.cards.s.SteelHellkite.class)); cards.add(new SetCardInfo("Stirring Wildwood", 281, Rarity.RARE, mage.cards.s.StirringWildwood.class)); cards.add(new SetCardInfo("Stone Quarry", 282, Rarity.UNCOMMON, mage.cards.s.StoneQuarry.class)); diff --git a/Mage.Sets/src/mage/sets/Ixalan.java b/Mage.Sets/src/mage/sets/Ixalan.java index 32681a83a76..b375fd392f9 100644 --- a/Mage.Sets/src/mage/sets/Ixalan.java +++ b/Mage.Sets/src/mage/sets/Ixalan.java @@ -31,10 +31,12 @@ public class Ixalan extends ExpansionSet { this.numBoosterUncommon = 3; this.numBoosterRare = 1; this.ratioBoosterMythic = 8; + cards.add(new SetCardInfo("Angrath's Marauders", 132, Rarity.RARE, mage.cards.a.AngrathsMarauders.class)); cards.add(new SetCardInfo("Bloodcrazed Paladin", 93, Rarity.RARE, mage.cards.b.BloodcrazedPaladin.class)); cards.add(new SetCardInfo("Burning Sun's Avatar", 135, Rarity.RARE, mage.cards.b.BurningSunsAvatar.class)); cards.add(new SetCardInfo("Captain Lannery Storm", 136, Rarity.RARE, mage.cards.c.CaptainLanneryStorm.class)); cards.add(new SetCardInfo("Carnage Tyrant", 179, Rarity.MYTHIC, mage.cards.c.CarnageTyrant.class)); + cards.add(new SetCardInfo("Daring Saboteur", 49, Rarity.RARE, mage.cards.d.DaringSaboteur.class)); cards.add(new SetCardInfo("Deadeye Tormentor", 98, Rarity.COMMON, mage.cards.d.DeadeyeTormentor.class)); cards.add(new SetCardInfo("Deadeye Tracker", 99, Rarity.RARE, mage.cards.d.DeadeyeTracker.class)); cards.add(new SetCardInfo("Deeproot Champion", 185, Rarity.RARE, mage.cards.d.DeeprootChampion.class)); @@ -46,11 +48,13 @@ public class Ixalan extends ExpansionSet { cards.add(new SetCardInfo("Gishath, Sun's Avatar", 222, Rarity.MYTHIC, mage.cards.g.GishathSunsAvatar.class)); cards.add(new SetCardInfo("Glacial Fortress", 255, Rarity.RARE, mage.cards.g.GlacialFortress.class)); cards.add(new SetCardInfo("Herald of Secret Streams", 59, Rarity.RARE, mage.cards.h.HeraldOfSecretStreams.class)); + cards.add(new SetCardInfo("Jace, Cunning Castaway", 60, Rarity.MYTHIC, mage.cards.j.JaceCunningCastaway.class)); cards.add(new SetCardInfo("Old-Growth Dryads", 199, Rarity.RARE, mage.cards.o.OldGrowthDryads.class)); cards.add(new SetCardInfo("Prosperous Pirates", 69, Rarity.COMMON, mage.cards.p.ProsperousPirates.class)); cards.add(new SetCardInfo("Queen's Bay Soldier", 115, Rarity.COMMON, mage.cards.q.QueensBaySoldier.class)); cards.add(new SetCardInfo("Revel in Riches", 117, Rarity.RARE, mage.cards.r.RevelInRiches.class)); cards.add(new SetCardInfo("Ripjaw Raptor", 203, Rarity.RARE, mage.cards.r.RipjawRaptor.class)); + cards.add(new SetCardInfo("River's Rebuke", 71, Rarity.RARE, mage.cards.r.RiversRebuke.class)); cards.add(new SetCardInfo("Rootbound Crag", 256, Rarity.RARE, mage.cards.r.RootboundCrag.class)); cards.add(new SetCardInfo("Rowdy Crew", 159, Rarity.MYTHIC, mage.cards.r.RowdyCrew.class)); cards.add(new SetCardInfo("Ruin Raider", 118, Rarity.RARE, mage.cards.r.RuinRaider.class)); diff --git a/Mage.Sets/src/mage/sets/Odyssey.java b/Mage.Sets/src/mage/sets/Odyssey.java index 95185534b37..e5a06110704 100644 --- a/Mage.Sets/src/mage/sets/Odyssey.java +++ b/Mage.Sets/src/mage/sets/Odyssey.java @@ -302,6 +302,7 @@ public class Odyssey extends ExpansionSet { cards.add(new SetCardInfo("Shadowblood Ridge", 326, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); cards.add(new SetCardInfo("Shadowmage Infiltrator", 294, Rarity.RARE, mage.cards.s.ShadowmageInfiltrator.class)); cards.add(new SetCardInfo("Shelter", 46, Rarity.COMMON, mage.cards.s.Shelter.class)); + cards.add(new SetCardInfo("Shifty Doppelganger", 101, Rarity.RARE, mage.cards.s.ShiftyDoppelganger.class)); cards.add(new SetCardInfo("Shower of Coals", 221, Rarity.UNCOMMON, mage.cards.s.ShowerOfCoals.class)); cards.add(new SetCardInfo("Simplify", 269, Rarity.COMMON, mage.cards.s.Simplify.class)); cards.add(new SetCardInfo("Skeletal Scrying", 161, Rarity.UNCOMMON, mage.cards.s.SkeletalScrying.class)); diff --git a/Mage/src/main/java/mage/MageObjectReference.java b/Mage/src/main/java/mage/MageObjectReference.java index 0e04688fc4d..0c4cde8808f 100644 --- a/Mage/src/main/java/mage/MageObjectReference.java +++ b/Mage/src/main/java/mage/MageObjectReference.java @@ -81,9 +81,9 @@ public class MageObjectReference implements Comparable, Ser if (game.getPlayerList().contains(sourceId)) { this.zoneChangeCounter = 0; } else { - logger.error("The provided sourceId is not connected to an object in the game id:" + sourceId); + logger.error("The provided sourceId is not connected to an object in the game id: " + sourceId); for (StackObject stackObject : game.getStack()) { - logger.error("StackObject: " + stackObject.getId() + " sourceId" + stackObject.getSourceId() + " name" + stackObject.getName()); + logger.error("StackObject: " + stackObject.getId() + " sourceId " + stackObject.getSourceId() + " name " + stackObject.getName()); } mageObject = game.getLastKnownInformation(sourceId, Zone.STACK); if (mageObject != null) { diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileSourceCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileSourceCost.java index 9afd2fbd7bf..8678db2a988 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileSourceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileSourceCost.java @@ -47,7 +47,7 @@ public class ExileSourceCost extends CostImpl { private boolean toUniqueExileZone; public ExileSourceCost() { - this.text = "Exile {this}"; + this.text = "exile {this}"; } /** @@ -57,7 +57,7 @@ public class ExileSourceCost extends CostImpl { * Deadeye Navigator) can identify the card */ public ExileSourceCost(boolean toUniqueExileZone) { - this.text = "Exile {this}"; + this.text = "exile {this}"; this.toUniqueExileZone = toUniqueExileZone; } @@ -76,6 +76,7 @@ public class ExileSourceCost extends CostImpl { if (toUniqueExileZone) { exileZoneId = CardUtil.getExileZoneId(game, ability.getSourceId(), ability.getSourceObjectZoneChangeCounter()); exileZoneName = sourceObject.getName(); + game.getState().setValue(sourceObject.getId().toString(), ability.getSourceObjectZoneChangeCounter()); } controller.moveCardToExileWithInfo((Card) sourceObject, exileZoneId, exileZoneName, sourceId, game, game.getState().getZone(sourceObject.getId()), true); // 117.11. The actions performed when paying a cost may be modified by effects. diff --git a/Mage/src/main/java/mage/abilities/effects/PutTokenOntoBattlefieldCopySourceEffect.java b/Mage/src/main/java/mage/abilities/effects/PutTokenOntoBattlefieldCopySourceEffect.java index ac09d741514..5fdba542a45 100644 --- a/Mage/src/main/java/mage/abilities/effects/PutTokenOntoBattlefieldCopySourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/PutTokenOntoBattlefieldCopySourceEffect.java @@ -21,7 +21,7 @@ public class PutTokenOntoBattlefieldCopySourceEffect extends OneShotEffect { public PutTokenOntoBattlefieldCopySourceEffect(int copies) { super(Outcome.PutCreatureInPlay); this.number = copies; - staticText = "put a token onto the battlefield that's a copy of {this}"; + staticText = "create a token that's a copy of {this}"; } public PutTokenOntoBattlefieldCopySourceEffect(final PutTokenOntoBattlefieldCopySourceEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java index 412278414ff..3a9b80b9941 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java @@ -27,6 +27,8 @@ */ package mage.abilities.effects.common; +import java.util.LinkedHashSet; +import java.util.stream.Collectors; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -39,9 +41,6 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; -import java.util.LinkedHashSet; -import java.util.stream.Collectors; - /** * @author LevelX2 */ @@ -72,6 +71,9 @@ public class ChooseCreatureTypeEffect extends OneShotEffect { return false; } } + if (typeChoice.getChoice() == null) { + return false; + } if (!game.isSimulation()) { game.informPlayers(mageObject.getName() + ": " + controller.getLogName() + " has chosen " + typeChoice.getChoice()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java index 337f8c05d4f..9c3e4da898f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java @@ -41,6 +41,7 @@ import mage.abilities.keyword.HasteAbility; import mage.cards.Card; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.SuperType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.EmptyToken; @@ -71,6 +72,7 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { private boolean becomesArtifact; private ObjectColor color; private boolean useLKI = false; + private boolean isntLegendary = false; public PutTokenOntoBattlefieldCopyTargetEffect(boolean useLKI) { this(); @@ -156,12 +158,17 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { this.becomesArtifact = effect.becomesArtifact; this.color = effect.color; this.useLKI = effect.useLKI; + this.isntLegendary = effect.isntLegendary; } public void setBecomesArtifact(boolean becomesArtifact) { this.becomesArtifact = becomesArtifact; } + public void setIsntLegendary(boolean isntLegendary) { + this.isntLegendary = isntLegendary; + } + @Override public boolean apply(Game game, Ability source) { UUID targetId; @@ -211,6 +218,9 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { if (becomesArtifact) { token.addCardType(CardType.ARTIFACT); } + if (isntLegendary) { + token.getSuperType().remove(SuperType.LEGENDARY); + } if (additionalCardType != null && !token.getCardType().contains(additionalCardType)) { token.addCardType(additionalCardType); } diff --git a/Mage/src/main/java/mage/game/permanent/token/IllusionToken.java b/Mage/src/main/java/mage/game/permanent/token/IllusionToken.java index f1dce2ff382..280639a1ef8 100644 --- a/Mage/src/main/java/mage/game/permanent/token/IllusionToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/IllusionToken.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.game.permanent.token; import mage.constants.CardType; import mage.MageInt; diff --git a/Mage/src/main/java/mage/game/permanent/token/JaceCunningCastawayIllusionToken.java b/Mage/src/main/java/mage/game/permanent/token/JaceCunningCastawayIllusionToken.java new file mode 100644 index 00000000000..fee62abba18 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/JaceCunningCastawayIllusionToken.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.game.permanent.token; + +import mage.constants.CardType; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author TheElk801 + */ +public class JaceCunningCastawayIllusionToken extends Token { + + public JaceCunningCastawayIllusionToken() { + super("Illusion", "2/2 blue Illusion creature token with \"When this creature becomes the target of a spell, sacrifice it.\""); + cardType.add(CardType.CREATURE); + color.setBlue(true); + + subtype.add("Illusion"); + power = new MageInt(2); + toughness = new MageInt(2); + + this.addAbility(new IllusionTokenTriggeredAbility()); + } +} + +class IllusionTokenTriggeredAbility extends TriggeredAbilityImpl { + + public IllusionTokenTriggeredAbility() { + super(Zone.BATTLEFIELD, new SacrificeSourceEffect(), false); + } + + public IllusionTokenTriggeredAbility(final IllusionTokenTriggeredAbility ability) { + super(ability); + } + + @Override + public IllusionTokenTriggeredAbility copy() { + return new IllusionTokenTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TARGETED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + MageObject eventSourceObject = game.getObject(event.getSourceId()); + if (eventSourceObject != null && event.getTargetId().equals(this.getSourceId()) && eventSourceObject instanceof Spell) { + getEffects().get(0).setTargetPointer(new FixedTarget(event.getPlayerId())); + return true; + } + return false; + } + + @Override + public String getRule() { + return "When this creature becomes the target of a spell, sacrifice it."; + } + +} diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 9fa5e19e8a8..aa9321c2f68 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2959,7 +2959,7 @@ public abstract class PlayerImpl implements Player, Serializable { } public UserData getControllingPlayersUserData(Game game) { - if (isGameUnderControl()) { + if (!isGameUnderControl()) { Player player = game.getPlayer(getTurnControlledBy()); if (player.isHuman()) { return player.getUserData(); @@ -2969,8 +2969,7 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public void setUserData(UserData userData - ) { + public void setUserData(UserData userData) { this.userData = userData; getManaPool().setAutoPayment(userData.isManaPoolAutomatic()); getManaPool().setAutoPaymentRestricted(userData.isManaPoolAutomaticRestricted()); diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 4e9141d8402..607c2a74b6a 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -32329,6 +32329,7 @@ Zealot of the God-Pharaoh|Hour of Devastation|207|C|{3}{R}|Creature - Minotaur A Visage of Bolas|Hour of Devastation|208|R|{4}|Artifact|||When Visage of Bolas enters the battlefield, you may search your library and/or graveyard for a card named Nicol Bolas, the Deceiver, reveal it, and put it into your hand. If you search your library this way, shuffle it.${t}: Add {U}, {B}, or {R} to your mana pool.| Cinder Barrens|Hour of Devastation|209|C||Land|||Cinder Barrens enters the battlefield tapped.${t}: Add {B} or {R} to your mana pool.| Ashes of the Abhorrent|Ixalan|2|R|{1}{W}|Enchantment|||Players can't cast spells from graveyards or activate abilities from graveyards.$Whenever a creature dies, you gain 1 life.| +Bellowing Aegisaur|Ixalan|4|U|{5}{W}|Creature - Dinosaur|3|5|Enrage - Whenever Bellowing Aegisaur is dealt damage, put a +1/+1 counter on each other creature you control.| Bishop of Rebirth|Ixalan|5|R|Creature - Vampire Cleric|3|4|Vigilance$Whenever Bishop of Rebirth attacks, you may return target creature card with converted mana cost 3 or less from your graveyard to the battlefield.| Goring Ceratops|Ixalan|13|R|Creature - Dinosaur|3|3|Double strike$Whenever Goring Ceratops attacks, other creatures you control gain double strike until end of turn.| Kinjalli's Sunwing|Ixalan|19|R|{2}{W}|Creature - Dinosaur|2|3|Flying$Creatures your opponents control enter the battlefield tapped.| @@ -32351,6 +32352,7 @@ Bloodcrazed Paladin|Ixalan|93|R|{1}{B}|Creature - Vampire Knight|1|1|Flash$Blood Boneyard Parley|Ixalan|94|M|{5}{B}{B}|Sorcery|||Exile up to five target creature cards from graveyards. An opponent separates those cards into two piles. Put all cards from the pile of your choice onto the battlefield under your control and the rest into their owners' graveyards.| Deadeye Tormentor|Ixalan|98|C|{2}{B}|Creature - Human Pirate|2|2|Raid — When Deadeye Tormentor enters the battlefield, if you attacked with a creature this turn, target opponent discards a card.| Deadeye Tracker|Ixalan|99|R|{B}|Creature - Human Pirate|1|1|{1}{B}, {T}: Exile two target cards from an opponent's graveyard. Deadeye Tracker explores.| +Deathless Ancient|Ixalan|100|U|{4}{B}{B}|Creature - Vampire Knight|4|4|Flying$Tap three untapped Vampires you control: Return Deathless Ancient from your graveyard to your hand.| Fathom Fleet Captain|Ixalan|106|R|{1}{B}|Creature - Human Pirate|2|1|Menace$Whenever Fathom Fleet Captain attacks, if you control another nontoken Pirate, you may pay {2}. If you do, creature a 2/2 black Pirate creature token with menace.| Queen's Bay Soldier|Ixalan|115|C|{1}{B}|Creature - Vampire Soldier|2|2|| Revel in Riches|Ixalan|117|R|{4}{B}|Enchantment|||Whenever a creature an opponent controls dies, create a colorless Treasure artifact token with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."$At the beginning of your upkeep, if you control ten or more Treasures, you win the game.| @@ -32370,6 +32372,7 @@ Carnage Tyrant|Ixalan|179|M|{4}{G}{G}|Creature - Dinosaur|7|6|Carnage Tyrant can Deathgorge Scavenger|Ixalan|???|R|???|Creature - Dinosaur|3|2|Whenever Deathgorge Scavenger enters the battlefield or attacks, you may exile target card from a graveyard. If a creature card is exiled this way, you may gain 2 life. If a noncreature card is exiled this way, Deathgorge Scavenger gets +1/+1 until end of turn.| Deeproot Champion|Ixalan|185|R|{1}{G}|Creature - Merfolk Shaman|1|1|Whenever you cast a noncreature spell, put a +1/+1 counter on Deeproot Champion.| Emperor's Vanguard|Ixalan|189|R|{3}{G}|Creature - Human Scout|4|3|Whenever Emperor's Vanguard deals combat damage to a player, it explores.| +Kumena's Omenspeaker|Ixalan|196|U|{G}|Creature - Merfolk|1|1|Kumena's Omenspeaker gets +1/+1 as long as you control another Merfolk or Island.| Old-Growth Dryads|Ixalan|199|R|{G}|Creature - Dryad|3|3|When Old-Growth Dryads enters the battlefield, each opponent may search his or her library for a basic land card, put it onto the battlefield tapped, then shuffle his or her library.| Ripjaw Raptor|Ixalan|203|R|{2}{G}{G}|Creature - Dinosaur|4|5|Enrage — Whenever Ripjaw Raptor is dealt damage, draw a card.| Shapers' Sanctuary|Ixalan|206|R|{G}|Enchantment|||Whenever a creature you control becomes the target of a spell or ability an opponent controls, you may draw a card.| @@ -32377,17 +32380,21 @@ Tishana's Wayfinder|Ixalan|211|C|{2}{G}|Creature - Merfolk Scout|2|2|When Tishan Verdant Sun's Avatar|Ixalan|213|R|{5}{G}{G}|Creature - Dinosaur Avatar|5|5|When Verdant Sun's Avatar or another creature enters the battlefield under your control, you gain life equal to that creature's toughness.| Waker of the Wilds|Ixalan|215|R|{2}{G}{G}|Creature - Merfolk Shaman|3|3|{X}{G}{G}: Put X +1/+1 counters on target land you control. That land becomes a 0/0 Elemental creature with haste. It's still a land.| Admiral Beckett Brass|Ixalan|217|M|{1}{U}{B}{R}|Legendary Creature - Human Pirate|3|3|Other Pirates you control get +1/+1.$At the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt damage by three or more Pirates this turn.| -???|Ixalan|218|U|{5}{G}{W}|Creature - Dinosaur|4|6|Each creature you control assigns combats damage equal to its toughness rather than its power.| -???|Ixalan|219|U|{2}{W}{B}|Sorcery|||Create three 1/1 white Vampire creature tokens with lifelink.| +Militant Dinosaur|Ixalan|218|U|{5}{G}{W}|Creature - Dinosaur|4|6|Each creature you control assigns combats damage equal to its toughness rather than its power.| +Invite the Party|Ixalan|219|U|{2}{W}{B}|Sorcery|||Create three 1/1 white Vampire creature tokens with lifelink.| Deadeye Plunderers|Ixalan|220|U|{3}{U}{B}|Creature - Human Pirate|3|3|Deadeye Plunderers gets +1/+1 for each artifact you control.${2}{U}{B}: Create a colorless artifact token named Treasure with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."| Dire Fleet Captain|Ixalan|221|U|{B}{R}|Creature - Orc Pirate|2|2|Whenever Dire Fleet Captain attacks, it gets +1/+1 until end of turn for each other attacking Pirate.| Gishath, Sun's Avatar|Ixalan|222|M|{5}{R}{G}{W}|Legendary Creature - Dinosaur Avatar|7|6|Trample, vigilance, haste$Whenever Gishath, Sun's Avatar deals combat damage to a player, reveal that many cards from the top of your library. Put any number of Dinosaur creature cards from among them onto the battlefield and the rest on the bottom of your library in a random order.| Hostage Taker|Ixalan|223|R|{2}{U}{B}|Creature - Human Pirate|2|3|When Hostage Taker enters the battlefield, exile another target artifact or creature until Hostage Taker leaves the battlefield. You may cast that card as long as it remains exiled, and you may spend mana as though it were mana of any type to cast that spell.| -Tishana, Voice of Thunder|Ixalan|230|M|{5}{G}{U}||Legendary Creature - Merfolk Shaman|0|0|Tishana, Voice of Thunder's power and toughness are each equal to the number of cards in your hand.$You have no maximum hand size.$When Tishana enters the battlefield, draw a card for each creature you control.| +Marauding Looter|Ixalan|225|U|{2}{U}{R}|Creature - Human Pirate|4|3|Raid - At the beginning of your end step, if you attacked with a creature this turn, you may draw a card. If you do, discard a card.| +Infuriated Gladiodon|Ixalan|226|U|{3}{R}{G}|Creature - Dinosaur|5|5|Trample$When Infuriated Gladiodon enters the battlefield, it deals 1 damage to each other creature.| +Tishana, Voice of Thunder|Ixalan|230|M|{5}{G}{U}|Legendary Creature - Merfolk Shaman|*|*|Tishana, Voice of Thunder's power and toughness are each equal to the number of cards in your hand.$You have no maximum hand size.$When Tishana enters the battlefield, draw a card for each creature you control.| +Twilight Legion Battleship|Ixalan|236|U|{5}|Artifact - Vehicle|4|6|Vigilance$Crew 2| +Pillar of Genesis|Ixalan|241|U|{2}|Artifact|||As Pillar of Genesis enters the battlefield, choose a creature type.${T}: Add one mana of any color to your mana pool. Spend this mana only to cast a creature spell if the chosen type.| Sleek Schooner|Ixalan|247|U|{3}|Artifact - Vehicle|4|3|Crew 1| Sorcerous Spyglass|Ixalan|248|R|Artifact|||As Sorcerous Spyglass enters the battlefield, look at an opponent's hand, then choose any card name.$Activated abilities of sources with the chosen name can't be activated unless they're mana abilities.| -Treasure Map|Ixalan|250|R|{2}|Artifact|||{1}, {T}: Scry 1. Put a landmark counter on Treasure Map. Then if there are three or more landmark counters on it, remove those counters, transform Treasure Map, and create three colorless Treasure artifact tokens with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."| Treasure Cove|Ixalan|250|R||Land|||{T}: Add {C} to your mana pool.${T}, Sacrifice a Treasure: Draw a card.| +Treasure Map|Ixalan|250|R|{2}|Artifact|||{1}, {T}: Scry 1. Put a landmark counter on Treasure Map. Then if there are three or more landmark counters on it, remove those counters, transform Treasure Map, and create three colorless Treasure artifact tokens with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."| Vanquisher's Banner|Ixalan|251|R|{5}|Artifact|||As Vanquisher's Banner enters the battlefield, choose a creature type.$Creatures you control of the chosen type get +1/+1.$Whenever you cast a creature spell of the chosen type, draw a card.| Dragonskull Summit|Ixalan|252|R||Land|||Dragonskull Summit enters the battlefield tapped unless you control a Swamp or a Mountain.${T}: Add {B} or {R} to your mana pool.| Drowned Catacomb|Ixalan|253|R||Land|||Drowned Catacomb enters the battlefield tapped unless you control an Island or a Swamp.${T}: Add {U} or {B} to your mana pool.| @@ -32395,6 +32402,8 @@ Glacial Fortress|Ixalan|255|R||Land|||Glacial Fortress enters the battlefield ta Rootbound Crag|Ixalan|256|R||Land|||Rootbound Crag enters the battlefield tapped unless you control a Mountain or a Forest.${T}: Add {R} or {G} to your mana pool.| Sunpetal Grove|Ixalan|257|R||Land|||Sunpetal Grove enters the battlefield tapped unless you control a Forest or a Plains.${T}: Add {G} or {W} to your mana pool.| Unclaimed Territory|Ixalan|258|U||Land|||As Unclaimed Territory enters the battlefield, choose a creature type.${T}: Add {C} to your mana pool.${T}: Add one mana of any color to your mana pool. Spend this mana only to cast a creature spell of the chosen type.| +Jace, Ingenious Mindmage|Ixalan|280|M|{4}{U}{U}|Legendary Planeswalker - Jace|||[+1]: Draw a card.$[+1]: Untap all creatures you control.$[-9]: Gain control of up to three target creatures.| +Huatli, Dinosaur Knight|Ixalan|285|M|{4}{R}{W}|Legendary Planeswalker - Huatli|||[+2]: Put two +1/+1 counters on up to one target Dinosaur you control.$[-3]: Target Dinosaur you control deals damage equal to its power to target creature you don't control.$[-7]: Dinosaurs you control get +4/+4 until end of turn.| Sword of Dungeons and Dragons|Unstable|1|M|{3}|Artifact - Equipment|||Equipped creature gets +2/+2 and has protection from Rogues and from Clerics.$Whenever equipped creature deals combat damage to a player, create a 4/4 gold Dragon creature token with flying and roll a d20. If you roll a 20, repeat this process.$Equip {2}| Jhoira of the Ghitu|Duel Decks: Mind vs. Might|1|M|{1}{U}{R}|Legendary Creature - Human Wizard|2|2|{2}, Exile a nonland card from your hand: Put four time counters on the exiled card. If it doesn't have suspend, it gains suspend.| Beacon of Tomorrows|Duel Decks: Mind vs. Might|2|R|{6}{U}{U}|Sorcery|||Target player takes an extra turn after this one. Shuffle Beacon of Tomorrows into its owner's library.|