diff --git a/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java b/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java index 60068309b24..a2baeb67081 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DraftGrid.java @@ -183,19 +183,22 @@ public class DraftGrid extends javax.swing.JPanel implements MouseListener { @Override public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (e.getButton() == MouseEvent.BUTTON1) { + Object obj = e.getSource(); + if (obj instanceof MageCard) { + this.cardEventSource.doubleClick(((MageCard)obj).getOriginal(), "pick-a-card"); + this.hidePopup(); + AudioManager.playOnDraftSelect(); + } + } + } + } @Override public void mousePressed(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { // only left click select - Object obj = e.getSource(); - if (obj instanceof MageCard) { - this.cardEventSource.doubleClick(((MageCard)obj).getOriginal(), "pick-a-card"); - this.hidePopup(); - AudioManager.playOnDraftSelect(); - } - } - if (e.getButton() == MouseEvent.BUTTON3) { // only right click mark + if (e.getButton() == MouseEvent.BUTTON1 || e.getButton() == MouseEvent.BUTTON3) { // left or right click Object obj = e.getSource(); if (obj instanceof MageCard) { if (this.markedCard != null) { diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index ca80f4c1609..d5c84568dd4 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -154,6 +154,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_PASS_PRIORITY_CAST = "passPriorityCast"; public static final String KEY_PASS_PRIORITY_ACTIVATION = "passPriorityActivation"; public static final String KEY_AUTO_ORDER_TRIGGER = "autoOrderTrigger"; + public static final String KEY_USE_FIRST_MANA_ABILITY = "useFirstManaAbility"; // mana auto payment public static final String KEY_GAME_MANA_AUTOPAYMENT = "gameManaAutopayment"; @@ -3273,7 +3274,8 @@ public class PreferencesDialog extends javax.swing.JDialog { PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"), PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PASS_PRIORITY_CAST, "true").equals("true"), PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PASS_PRIORITY_ACTIVATION, "true").equals("true"), - PreferencesDialog.getCachedValue(PreferencesDialog.KEY_AUTO_ORDER_TRIGGER, "true").equals("true") + PreferencesDialog.getCachedValue(PreferencesDialog.KEY_AUTO_ORDER_TRIGGER, "true").equals("true"), + PreferencesDialog.getCachedValue(PreferencesDialog.KEY_USE_FIRST_MANA_ABILITY, "true").equals("true") ); } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index d062ed5d764..b28310e9e7a 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -129,6 +129,7 @@ import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_ABILITY_LAST; import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_NAME_FIRST; import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_NAME_LAST; import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL; +import mage.constants.UseFirstManaAbilityMode; import mage.constants.Zone; import mage.game.events.PlayerQueryEvent; import mage.remote.Session; @@ -594,7 +595,9 @@ public final class GamePanel extends javax.swing.JPanel { // default menu states setMenuStates( PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"), - PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true")); + PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"), + false + ); updateGame(game); } @@ -893,10 +896,11 @@ public final class GamePanel extends javax.swing.JPanel { * * @param manaPoolAutomatic * @param manaPoolAutomaticRestricted + * @param useFirstManaAbility */ - public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted) { + public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility) { for (PlayAreaPanel playAreaPanel : players.values()) { - playAreaPanel.setMenuStates(manaPoolAutomatic, manaPoolAutomaticRestricted); + playAreaPanel.setMenuStates(manaPoolAutomatic, manaPoolAutomaticRestricted, useFirstManaAbility); } } @@ -1589,6 +1593,19 @@ public final class GamePanel extends javax.swing.JPanel { } }); + KeyStroke ksAlt1 = KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.ALT_MASK); + this.getInputMap(c).put(ksAlt1, "USEFIRSTMANAABILITY"); + this.getActionMap().put("USEFIRSTMANAABILITY", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + session.sendPlayerAction(PlayerAction.USE_FIRST_MANA_ABILITY_ON, gameId, null); + setMenuStates( + PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"), + PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"), + true); + } + }); + final BasicSplitPaneUI myUi = (BasicSplitPaneUI) jSplitPane0.getUI(); final BasicSplitPaneDivider divider = myUi.getDivider(); final JButton upArrowButton = (JButton) divider.getComponent(0); @@ -1619,6 +1636,19 @@ public final class GamePanel extends javax.swing.JPanel { } }); + KeyStroke ksAlt1Released = KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.ALT_MASK, true); + this.getInputMap(c).put(ksAlt1Released, "USEFIRSTMANAABILITY_RELEASE"); + this.getActionMap().put("USEFIRSTMANAABILITY_RELEASE", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + session.sendPlayerAction(PlayerAction.USE_FIRST_MANA_ABILITY_OFF, gameId, null); + setMenuStates( + PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT, "true").equals("true"), + PreferencesDialog.getCachedValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, "true").equals("true"), + false); + } + }); + btnSwitchHands.setContentAreaFilled(false); btnSwitchHands.setBorder(new EmptyBorder(0, 0, 0, 0)); btnSwitchHands.setIcon(new ImageIcon(ImageManagerImpl.getInstance().getSwitchHandsButtonImage())); diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java index 0f1c215e28e..8d6627eaf2b 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java @@ -54,6 +54,7 @@ import mage.client.dialog.PreferencesDialog; import static mage.client.dialog.PreferencesDialog.KEY_GAME_ALLOW_REQUEST_SHOW_HAND_CARDS; import static mage.client.dialog.PreferencesDialog.KEY_GAME_MANA_AUTOPAYMENT; import static mage.client.dialog.PreferencesDialog.KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE; +import static mage.client.dialog.PreferencesDialog.KEY_USE_FIRST_MANA_ABILITY; import mage.client.util.GUISizeHelper; import mage.constants.PlayerAction; import mage.view.PlayerView; @@ -75,6 +76,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { private JCheckBoxMenuItem manaPoolMenuItem1; private JCheckBoxMenuItem manaPoolMenuItem2; + private JCheckBoxMenuItem useFirstManaAbilityItem; private JCheckBoxMenuItem allowViewHandCardsMenuItem; public static final int PANEL_HEIGHT = 242; @@ -263,7 +265,7 @@ public class PlayAreaPanel extends javax.swing.JPanel { public void actionPerformed(ActionEvent e) { boolean manaPoolAutomatic = ((JCheckBoxMenuItem) e.getSource()).getState(); PreferencesDialog.saveValue(KEY_GAME_MANA_AUTOPAYMENT, manaPoolAutomatic ? "true" : "false"); - gamePanel.setMenuStates(manaPoolAutomatic, manaPoolMenuItem2.getState()); + gamePanel.setMenuStates(manaPoolAutomatic, manaPoolMenuItem2.getState(), useFirstManaAbilityItem.getState()); gamePanel.getSession().sendPlayerAction(manaPoolAutomatic ? PlayerAction.MANA_AUTO_PAYMENT_ON : PlayerAction.MANA_AUTO_PAYMENT_OFF, gameId, null); } }); @@ -281,9 +283,27 @@ public class PlayAreaPanel extends javax.swing.JPanel { public void actionPerformed(ActionEvent e) { boolean manaPoolAutomaticRestricted = ((JCheckBoxMenuItem) e.getSource()).getState(); PreferencesDialog.saveValue(KEY_GAME_MANA_AUTOPAYMENT_ONLY_ONE, manaPoolAutomaticRestricted ? "true" : "false"); - gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolAutomaticRestricted); + gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolAutomaticRestricted, useFirstManaAbilityItem.getState()); gamePanel.getSession().sendPlayerAction(manaPoolAutomaticRestricted ? PlayerAction.MANA_AUTO_PAYMENT_RESTRICTED_ON : PlayerAction.MANA_AUTO_PAYMENT_RESTRICTED_OFF, gameId, null); } + }); + + useFirstManaAbilityItem = new JCheckBoxMenuItem("Use first mana ability when tapping lands", false); + useFirstManaAbilityItem.setMnemonic(KeyEvent.VK_F); + useFirstManaAbilityItem.setToolTipText("Use the first mana ability when
" + + " tapping lands for mana
" + + "You can hold Alt+1 whilst tapping lands to use this feature"); + manaPoolMenu.add(useFirstManaAbilityItem); + + // Use first mana ability of lands + useFirstManaAbilityItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + boolean useFirstManaAbility = ((JCheckBoxMenuItem) e.getSource()).getState(); + PreferencesDialog.saveValue(KEY_USE_FIRST_MANA_ABILITY, useFirstManaAbility ? "true" : "false"); + gamePanel.setMenuStates(manaPoolMenuItem1.getState(), manaPoolMenuItem2.getState(), useFirstManaAbility); + gamePanel.getSession().sendPlayerAction(useFirstManaAbility ? PlayerAction.USE_FIRST_MANA_ABILITY_ON: PlayerAction.USE_FIRST_MANA_ABILITY_OFF, gameId, null); + } }); JMenu automaticConfirmsMenu = new JMenu("Automatic confirms"); @@ -610,13 +630,16 @@ public class PlayAreaPanel extends javax.swing.JPanel { this.playingMode = playingMode; } - public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted) { + public void setMenuStates(boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, boolean useFirstManaAbility) { if (manaPoolMenuItem1 != null) { manaPoolMenuItem1.setSelected(manaPoolAutomatic); } if (manaPoolMenuItem2 != null) { manaPoolMenuItem2.setSelected(manaPoolAutomaticRestricted); } + if (useFirstManaAbilityItem != null) { + useFirstManaAbilityItem.setSelected(useFirstManaAbility); + } } private mage.client.game.BattlefieldPanel battlefieldPanel; diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 1ba022d1896..8a375e0005f 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -397,7 +397,8 @@ public class CallbackClientImpl implements CallbackClient { .append("
F5 - Skip to next end step but stop on declare attackers/blockers and something on the stack") .append("
F7 - Skip to next main phase but stop on declare attackers/blockers and something on the stack") .append("
F9 - Skip everything until your next turn") - .append("
F3 - Undo F4/F5/F7/F9").toString(), + .append("
F11 - Skip everything until the end step just prior to your turn") + .append("
F3 - Undo F4/F5/F7/F9/F11").toString(), null, MessageType.USER_INFO, ChatMessage.MessageColor.BLUE); break; case TOURNAMENT: diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java index 2e19b6a0bd9..bba9c23d268 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java @@ -32,7 +32,7 @@ package mage.deck; * @author LevelX2 */ public class DuelCommander extends Commander { - + public DuelCommander() { super("Duel Commander"); banned.add("Ancestral Recall"); @@ -44,6 +44,7 @@ public class DuelCommander extends Commander { banned.add("Entomb"); banned.add("Fastbond"); banned.add("Food Chain"); + banned.add("Gaea's Cradle"); banned.add("Gifts Ungiven"); banned.add("Grim Monolith"); banned.add("Grindstone"); @@ -86,6 +87,8 @@ public class DuelCommander extends Commander { bannedCommander.add("Erayo, Soratami Ascendant"); bannedCommander.add("Oloro, Ageless Ascetic"); bannedCommander.add("Rofellos, Llanowar Emissary"); + bannedCommander.add("Tasigur, the Golden Fang"); + bannedCommander.add("Yisan, the Wanderer Bard"); bannedCommander.add("Zur the Enchanter"); } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java index ff6ca105df0..c4ac0595f1c 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java @@ -24,7 +24,7 @@ * 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.deck; import java.util.Date; @@ -38,20 +38,19 @@ import mage.constants.SetType; * * @author LevelX2 */ - public class Modern extends Constructed { + public Modern() { super("Constructed - Modern"); Date cutoff = new GregorianCalendar(2003, 7, 28).getTime(); // Eight edition release date - for (ExpansionSet set: Sets.getInstance().values()) { - if ((set.getReleaseDate().after(cutoff) || set.getReleaseDate().equals(cutoff)) && - (set.getSetType() == SetType.CORE || set.getSetType() == SetType.EXPANSION)) { + for (ExpansionSet set : Sets.getInstance().values()) { + if ((set.getReleaseDate().after(cutoff) || set.getReleaseDate().equals(cutoff)) + && (set.getSetType() == SetType.CORE || set.getSetType() == SetType.EXPANSION)) { setCodes.add(set.getCode()); } } - banned.add("Ancestral Vision"); banned.add("Ancient Den"); banned.add("Birthing Pod"); banned.add("Blazing Shoal"); @@ -62,6 +61,7 @@ public class Modern extends Constructed { banned.add("Deathrite Shaman"); banned.add("Dig Through Time"); banned.add("Dread Return"); + banned.add("Eye of Ugin"); banned.add("Glimpse of Nature"); banned.add("Great Furnace"); banned.add("Green Sun's Zenith"); @@ -80,7 +80,6 @@ public class Modern extends Constructed { banned.add("Skullclamp"); banned.add("Splinter Twin"); banned.add("Summer Bloom"); - banned.add("Sword of the Meek"); banned.add("Treasure Cruise"); banned.add("Tree of Tales"); banned.add("Umezawa's Jitte"); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java index ef3efea54f2..8646bcf2905 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java @@ -24,8 +24,7 @@ * 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.deck; import mage.cards.decks.Constructed; @@ -64,7 +63,7 @@ public class Vintage extends Constructed { banned.add("Timmerian Fiends"); banned.add("Unexpected Potential"); banned.add("Worldknit"); - + restricted.add("Ancestral Recall"); restricted.add("Balance"); restricted.add("Black Lotus"); @@ -79,6 +78,7 @@ public class Vintage extends Constructed { restricted.add("Imperial Seal"); restricted.add("Library of Alexandria"); restricted.add("Lion’s Eye Diamond"); + restricted.add("Lodestone Golem"); restricted.add("Lotus Petal"); restricted.add("Mana Crypt"); restricted.add("Mana Vault"); @@ -108,6 +108,5 @@ public class Vintage extends Constructed { restricted.add("Yawgmoth’s Bargain"); restricted.add("Yawgmoth’s Will"); - } } 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 e8ebf4ce6df..2a127d04742 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 @@ -59,6 +59,7 @@ import mage.cards.decks.Deck; import mage.choices.Choice; import mage.choices.ChoiceImpl; import mage.constants.AbilityType; +import mage.constants.CardType; import mage.constants.Constants; import mage.constants.ManaType; import mage.constants.Outcome; @@ -1249,7 +1250,16 @@ public class HumanPlayer extends PlayerImpl { return; } } + if (userData.isUseFirstManaAbility() && object instanceof Permanent && object.getCardType().contains(CardType.LAND)){ + ActivatedAbility ability = abilities.values().iterator().next(); + if (ability instanceof ManaAbility) { + activateAbility(ability, game); + return; + } + } + game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(abilities.values())); + waitForResponse(game); if (response.getUUID() != null && isInGame()) { if (abilities.containsKey(response.getUUID())) { 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 07a5763a51a..9fb6c6492db 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -556,6 +556,12 @@ public class GameController implements GameCallback { case MANA_AUTO_PAYMENT_RESTRICTED_ON: game.setManaPaymentModeRestricted(getPlayerId(userId), true); break; + case USE_FIRST_MANA_ABILITY_ON: + game.setUseFirstManaAbility(getPlayerId(userId), true); + break; + case USE_FIRST_MANA_ABILITY_OFF: + game.setUseFirstManaAbility(getPlayerId(userId), false); + break; case ADD_PERMISSION_TO_SEE_HAND_CARDS: if (data instanceof UUID) { UUID playerId = getPlayerId(userId); diff --git a/Mage.Sets/src/mage/sets/alarareborn/MaelstromNexus.java b/Mage.Sets/src/mage/sets/alarareborn/MaelstromNexus.java index 655dea82e26..34dba1ce9a6 100644 --- a/Mage.Sets/src/mage/sets/alarareborn/MaelstromNexus.java +++ b/Mage.Sets/src/mage/sets/alarareborn/MaelstromNexus.java @@ -27,8 +27,6 @@ */ package mage.sets.alarareborn; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -41,15 +39,13 @@ import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.SubLayer; -import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; -import mage.watchers.Watcher; +import mage.watchers.common.FirstSpellCastThisTurnWatcher; + /** * @@ -63,7 +59,6 @@ public class MaelstromNexus extends CardImpl { // The first spell you cast each turn has cascade. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MaelstromNexusGainCascadeFirstSpellEffect()), new FirstSpellCastThisTurnWatcher()); - } public MaelstromNexus(final MaelstromNexus card) { @@ -112,55 +107,4 @@ class MaelstromNexusGainCascadeFirstSpellEffect extends ContinuousEffectImpl { } return false; } -} - -class FirstSpellCastThisTurnWatcher extends Watcher { - - Map playerFirstSpellCast = new HashMap<>(); - Map playerFirstCastSpell = new HashMap<>(); - - public FirstSpellCastThisTurnWatcher() { - super("FirstSpellCastThisTurn", WatcherScope.GAME); - } - - public FirstSpellCastThisTurnWatcher(final FirstSpellCastThisTurnWatcher watcher) { - super(watcher); - } - - @Override - public void watch(GameEvent event, Game game) { - switch (event.getType()) { - case SPELL_CAST: - case CAST_SPELL: - Spell spell = (Spell) game.getObject(event.getTargetId()); - if (spell != null && !playerFirstSpellCast.containsKey(spell.getControllerId())) { - if (event.getType().equals(EventType.SPELL_CAST)) { - playerFirstSpellCast.put(spell.getControllerId(), spell.getId()); - } else if (event.getType().equals(EventType.CAST_SPELL)) { - playerFirstCastSpell.put(spell.getControllerId(), spell.getId()); - } - - } - } - } - - @Override - public FirstSpellCastThisTurnWatcher copy() { - return new FirstSpellCastThisTurnWatcher(this); - } - - @Override - public void reset() { - super.reset(); - playerFirstSpellCast.clear(); - playerFirstCastSpell.clear(); - } - - public UUID getIdOfFirstCastSpell(UUID playerId) { - if (playerFirstSpellCast.get(playerId) == null) { - return playerFirstCastSpell.get(playerId); - } else { - return playerFirstSpellCast.get(playerId); - } - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/bornofthegods/PillarOfWar.java b/Mage.Sets/src/mage/sets/bornofthegods/PillarOfWar.java index cc7caa3973d..b9eb41f5ac7 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/PillarOfWar.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/PillarOfWar.java @@ -29,9 +29,8 @@ package mage.sets.bornofthegods; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalAsThoughEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect; @@ -61,7 +60,7 @@ public class PillarOfWar extends CardImpl { // As long as Pillar of War is enchanted, it can attack as though it didn't have defender. Effect effect = new ConditionalAsThoughEffect( new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.WhileOnBattlefield), - new EnchantedCondition()); + new EnchantedSourceCondition()); effect.setText("As long as {this} is enchanted, it can attack as though it didn't have defender"); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/KitsuneMystic.java b/Mage.Sets/src/mage/sets/championsofkamigawa/KitsuneMystic.java index 1d5d17b9d2f..b16d6d221eb 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/KitsuneMystic.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/KitsuneMystic.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.OnEventTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -72,7 +72,7 @@ public class KitsuneMystic extends CardImpl { // At the beginning of the end step, if Kitsune Mystic is enchanted by two or more Auras, flip it. this.addAbility(new ConditionalTriggeredAbility( new OnEventTriggeredAbility(GameEvent.EventType.END_TURN_STEP_PRE, "beginning of the end step", true, new FlipSourceEffect(new AutumnTailKitsuneSage())), - new EnchantedCondition(2), "At the beginning of the end step, if {this} is enchanted by two or more Auras, flip it.")); + new EnchantedSourceCondition(2), "At the beginning of the end step, if {this} is enchanted by two or more Auras, flip it.")); } public KitsuneMystic(final KitsuneMystic card) { diff --git a/Mage.Sets/src/mage/sets/darkascension/AfflictedDeserter.java b/Mage.Sets/src/mage/sets/darkascension/AfflictedDeserter.java index 25f8113f256..a427b792729 100644 --- a/Mage.Sets/src/mage/sets/darkascension/AfflictedDeserter.java +++ b/Mage.Sets/src/mage/sets/darkascension/AfflictedDeserter.java @@ -28,11 +28,7 @@ package mage.sets.darkascension; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.condition.common.NoSpellsWereCastLastTurnCondition; @@ -40,6 +36,8 @@ import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.constants.TargetController; /** @@ -60,11 +58,6 @@ public class AfflictedDeserter extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(2); - // Whenever this creature transforms into Werewolf Ransacker, you may destroy target artifact. If that artifact is put into a graveyard this way, Werewolf Ransacker deals 3 damage to that artifact's controller. - Ability ability1 = new WerewolfRansackerAbility(); - ability1.setRuleVisible(false); // rule will be shown only on the other face of the card but triggers only on this side - this.addAbility(ability1); - // At the beginning of each upkeep, if no spells were cast last turn, transform Afflicted Deserter. this.addAbility(new TransformAbility()); TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(true), TargetController.ANY, false); @@ -80,5 +73,3 @@ public class AfflictedDeserter extends CardImpl { return new AfflictedDeserter(this); } } - - diff --git a/Mage.Sets/src/mage/sets/darkascension/CurseOfEchoes.java b/Mage.Sets/src/mage/sets/darkascension/CurseOfEchoes.java index dfee5dd7f9c..cb83b54e46f 100644 --- a/Mage.Sets/src/mage/sets/darkascension/CurseOfEchoes.java +++ b/Mage.Sets/src/mage/sets/darkascension/CurseOfEchoes.java @@ -151,7 +151,7 @@ class CurseOfEchoesEffect extends OneShotEffect { if (!playerId.equals(spell.getControllerId())) { Player player = game.getPlayer(playerId); if (player.chooseUse(Outcome.Copy, chooseMessage, source, game)) { - Spell copy = spell.copySpell(source.getControllerId());; + Spell copy = spell.copySpell(source.getControllerId()); game.getStack().push(copy); copy.chooseNewTargets(game, playerId); } diff --git a/Mage.Sets/src/mage/sets/darkascension/HuntmasterOfTheFells.java b/Mage.Sets/src/mage/sets/darkascension/HuntmasterOfTheFells.java index 22098f98e2f..715c63ce32a 100644 --- a/Mage.Sets/src/mage/sets/darkascension/HuntmasterOfTheFells.java +++ b/Mage.Sets/src/mage/sets/darkascension/HuntmasterOfTheFells.java @@ -27,36 +27,24 @@ */ package mage.sets.darkascension; -import mage.constants.*; +import java.util.UUID; import mage.MageInt; import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.condition.common.NoSpellsWereCastLastTurnCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; -import mage.filter.common.FilterCreaturePermanent; +import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.game.permanent.token.Token; import mage.game.permanent.token.WolfToken; -import mage.game.stack.StackObject; -import mage.players.Player; -import mage.target.Target; -import mage.target.TargetPermanent; -import mage.target.common.TargetOpponent; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; /** * @@ -79,9 +67,6 @@ public class HuntmasterOfTheFells extends CardImpl { // Whenever this creature enters the battlefield or transforms into Huntmaster of the Fells, put a 2/2 green Wolf creature token onto the battlefield and you gain 2 life. this.addAbility(new HuntmasterOfTheFellsAbility()); - // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. - this.addAbility(new RavagerOfTheFellsAbility()); - // At the beginning of each upkeep, if no spells were cast last turn, transform Huntmaster of the Fells. this.addAbility(new TransformAbility()); TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(true), TargetController.ANY, false); @@ -113,8 +98,6 @@ class HuntmasterOfTheFellsAbility extends TriggeredAbilityImpl { public HuntmasterOfTheFellsAbility copy() { return new HuntmasterOfTheFellsAbility(this); } - - @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { @@ -126,12 +109,12 @@ class HuntmasterOfTheFellsAbility extends TriggeredAbilityImpl { } return super.isInUseableZone(game, source, event); } - + @Override public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.TRANSFORMED || event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; } - + @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.TRANSFORMED && event.getTargetId().equals(this.getSourceId())) { @@ -150,141 +133,4 @@ class HuntmasterOfTheFellsAbility extends TriggeredAbilityImpl { public String getRule() { return "Whenever this creature enters the battlefield or transforms into {this}, put a 2/2 green Wolf creature token onto the battlefield and you gain 2 life."; } - -} - -class RavagerOfTheFellsAbility extends TriggeredAbilityImpl { - - public RavagerOfTheFellsAbility() { - super(Zone.BATTLEFIELD, new RavagerOfTheFellsEffect(), false); - Target target1 = new TargetOpponent(); - this.addTarget(target1); - this.addTarget(new RavagerOfTheFellsTarget()); - // Rule only shown on the night side - this.setRuleVisible(false); - } - - public RavagerOfTheFellsAbility(final RavagerOfTheFellsAbility ability) { - super(ability); - } - - @Override - public RavagerOfTheFellsAbility copy() { - return new RavagerOfTheFellsAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.TRANSFORMED; - } - - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); - if (currentSourceObject != null && currentSourceObject.isNightCard()) { - return true; - } - return super.isInUseableZone(game, source, event); - } - - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(sourceId)) { - Permanent permanent = game.getPermanent(sourceId); - if (permanent != null && permanent.isTransformed()) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls."; - } - -} - -class RavagerOfTheFellsEffect extends OneShotEffect { - - public RavagerOfTheFellsEffect() { - super(Outcome.Damage); - } - - public RavagerOfTheFellsEffect(final RavagerOfTheFellsEffect effect) { - super(effect); - } - - @Override - public RavagerOfTheFellsEffect copy() { - return new RavagerOfTheFellsEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getTargets().get(0).getFirstTarget()); - if (player != null) { - player.damage(2, source.getSourceId(), game, false, true); - } - Permanent creature = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - if (creature != null) { - creature.damage(2, source.getSourceId(), game, false, true); - } - return true; - } - -} - -class RavagerOfTheFellsTarget extends TargetPermanent { - - public RavagerOfTheFellsTarget() { - super(0, 1, new FilterCreaturePermanent(), false); - } - - public RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { - super(target); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - UUID firstTarget = source.getFirstTarget(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.getControllerId().equals(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceId, sourceControllerId, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(sourceId); - - for (StackObject item: game.getState().getStack()) { - if (item.getId().equals(sourceId)) { - object = item; - } - if (item.getSourceId().equals(sourceId)) { - object = item; - } - } - - if (object instanceof StackObject) { - UUID playerId = ((StackObject)object).getStackAbility().getFirstTarget(); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if(permanent != null && permanent.getControllerId().equals(playerId)){ - possibleTargets.add(targetId); - } - } - } - return possibleTargets; - } - - @Override - public RavagerOfTheFellsTarget copy() { - return new RavagerOfTheFellsTarget(this); - } } diff --git a/Mage.Sets/src/mage/sets/darkascension/RavagerOfTheFells.java b/Mage.Sets/src/mage/sets/darkascension/RavagerOfTheFells.java index 4a0cd73206e..84b6c9c713a 100644 --- a/Mage.Sets/src/mage/sets/darkascension/RavagerOfTheFells.java +++ b/Mage.Sets/src/mage/sets/darkascension/RavagerOfTheFells.java @@ -27,24 +27,36 @@ */ package mage.sets.darkascension; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; import mage.abilities.TriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.TwoOrMoreSpellsWereCastLastTurnCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.constants.CardType; +import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.TargetController; import mage.constants.Zone; - - +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.common.TargetOpponent; /** * @@ -52,8 +64,6 @@ import mage.constants.Zone; */ public class RavagerOfTheFells extends CardImpl { - private static final String rule = "Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls"; - public RavagerOfTheFells(UUID ownerId) { super(ownerId, 140, "Ravager of the Fells", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, ""); this.expansionSetCode = "DKA"; @@ -71,7 +81,7 @@ public class RavagerOfTheFells extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect(rule))); + this.addAbility(new RavagerOfTheFellsAbility()); // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Ravager of the Fells. TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(false), TargetController.ANY, false); @@ -87,3 +97,136 @@ public class RavagerOfTheFells extends CardImpl { return new RavagerOfTheFells(this); } } + +class RavagerOfTheFellsAbility extends TriggeredAbilityImpl { + + public RavagerOfTheFellsAbility() { + super(Zone.BATTLEFIELD, new RavagerOfTheFellsEffect(), false); + Target target1 = new TargetOpponent(); + this.addTarget(target1); + this.addTarget(new RavagerOfTheFellsTarget()); + } + + public RavagerOfTheFellsAbility(final RavagerOfTheFellsAbility ability) { + super(ability); + } + + @Override + public RavagerOfTheFellsAbility copy() { + return new RavagerOfTheFellsAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMED; + } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); + if (currentSourceObject != null && currentSourceObject.isNightCard()) { + return true; + } + return super.isInUseableZone(game, source, event); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(sourceId)) { + Permanent permanent = game.getPermanent(sourceId); + if (permanent != null && permanent.isTransformed()) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls."; + } + +} + +class RavagerOfTheFellsEffect extends OneShotEffect { + + public RavagerOfTheFellsEffect() { + super(Outcome.Damage); + } + + public RavagerOfTheFellsEffect(final RavagerOfTheFellsEffect effect) { + super(effect); + } + + @Override + public RavagerOfTheFellsEffect copy() { + return new RavagerOfTheFellsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getTargets().get(0).getFirstTarget()); + if (player != null) { + player.damage(2, source.getSourceId(), game, false, true); + } + Permanent creature = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (creature != null) { + creature.damage(2, source.getSourceId(), game, false, true); + } + return true; + } + +} + +class RavagerOfTheFellsTarget extends TargetPermanent { + + public RavagerOfTheFellsTarget() { + super(0, 1, new FilterCreaturePermanent(), false); + } + + public RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { + super(target); + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + UUID firstTarget = source.getFirstTarget(); + Permanent permanent = game.getPermanent(id); + if (firstTarget != null && permanent != null && permanent.getControllerId().equals(firstTarget)) { + return super.canTarget(id, source, game); + } + return false; + } + + @Override + public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { + Set availablePossibleTargets = super.possibleTargets(sourceId, sourceControllerId, game); + Set possibleTargets = new HashSet<>(); + MageObject object = game.getObject(sourceId); + + for (StackObject item : game.getState().getStack()) { + if (item.getId().equals(sourceId)) { + object = item; + } + if (item.getSourceId().equals(sourceId)) { + object = item; + } + } + + if (object instanceof StackObject) { + UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); + for (UUID targetId : availablePossibleTargets) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null && permanent.getControllerId().equals(playerId)) { + possibleTargets.add(targetId); + } + } + } + return possibleTargets; + } + + @Override + public RavagerOfTheFellsTarget copy() { + return new RavagerOfTheFellsTarget(this); + } +} diff --git a/Mage.Sets/src/mage/sets/darkascension/WerewolfRansacker.java b/Mage.Sets/src/mage/sets/darkascension/WerewolfRansacker.java index bf1a2905c5d..d360a4b34d0 100644 --- a/Mage.Sets/src/mage/sets/darkascension/WerewolfRansacker.java +++ b/Mage.Sets/src/mage/sets/darkascension/WerewolfRansacker.java @@ -33,11 +33,9 @@ import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.TwoOrMoreSpellsWereCastLastTurnCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; @@ -75,7 +73,7 @@ public class WerewolfRansacker extends CardImpl { this.toughness = new MageInt(4); // Whenever this creature transforms into Werewolf Ransacker, you may destroy target artifact. If that artifact is put into a graveyard this way, Werewolf Ransacker deals 3 damage to that artifact's controller. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect(WerewolfRansackerAbility.RULE_TEXT))); + this.addAbility(new WerewolfRansackerAbility()); // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf Ransacker. TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(false), TargetController.ANY, false); @@ -93,9 +91,9 @@ public class WerewolfRansacker extends CardImpl { } class WerewolfRansackerAbility extends TriggeredAbilityImpl { - + public static final String RULE_TEXT = "Whenever this creature transforms into Werewolf Ransacker, you may destroy target artifact. If that artifact is put into a graveyard this way, Werewolf Ransacker deals 3 damage to that artifact's controller"; - + public WerewolfRansackerAbility() { super(Zone.BATTLEFIELD, new WerewolfRansackerEffect(), true); Target target = new TargetPermanent(new FilterArtifactPermanent()); @@ -159,8 +157,9 @@ class WerewolfRansackerEffect extends OneShotEffect { affectedTargets++; if (game.getState().getZone(permanent.getId()) == Zone.GRAVEYARD) { Player player = game.getPlayer(permanent.getControllerId()); - if (player != null) + if (player != null) { player.damage(3, source.getSourceId(), game, false, true); + } } } } diff --git a/Mage.Sets/src/mage/sets/darksteel/AuriokGlaivemaster.java b/Mage.Sets/src/mage/sets/darksteel/AuriokGlaivemaster.java index 0ffacee69d4..96000c67e3e 100644 --- a/Mage.Sets/src/mage/sets/darksteel/AuriokGlaivemaster.java +++ b/Mage.Sets/src/mage/sets/darksteel/AuriokGlaivemaster.java @@ -30,7 +30,7 @@ package mage.sets.darksteel; import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedCondition; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; @@ -58,9 +58,9 @@ public class AuriokGlaivemaster extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); - ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), EquippedCondition.getInstance(), rule1); + ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), EquippedSourceCondition.getInstance(), rule1); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect1)); - ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), EquippedCondition.getInstance(), rule2); + ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), EquippedSourceCondition.getInstance(), rule2); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect2)); } diff --git a/Mage.Sets/src/mage/sets/dissension/FlaringFlameKin.java b/Mage.Sets/src/mage/sets/dissension/FlaringFlameKin.java index 1cfe02e7dc6..758f4d792bc 100644 --- a/Mage.Sets/src/mage/sets/dissension/FlaringFlameKin.java +++ b/Mage.Sets/src/mage/sets/dissension/FlaringFlameKin.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; @@ -60,7 +60,7 @@ public class FlaringFlameKin extends CardImpl { this.toughness = new MageInt(2); // As long as Flaring Flame-Kin is enchanted, it gets +2/+2, has trample, and has "{R}: Flaring Flame-Kin gets +1/+0 until end of turn." - EnchantedCondition enchanted = new EnchantedCondition(); + EnchantedSourceCondition enchanted = new EnchantedSourceCondition(); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), enchanted, "As long as {this} is enchanted, it gets +2/+2")); diff --git a/Mage.Sets/src/mage/sets/dissension/FreewindEquenaut.java b/Mage.Sets/src/mage/sets/dissension/FreewindEquenaut.java index 2bf6f553e7b..c4774c37783 100644 --- a/Mage.Sets/src/mage/sets/dissension/FreewindEquenaut.java +++ b/Mage.Sets/src/mage/sets/dissension/FreewindEquenaut.java @@ -35,7 +35,7 @@ import mage.constants.Zone; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.DamageTargetEffect; @@ -68,7 +68,7 @@ public class FreewindEquenaut extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new GainAbilitySourceEffect(ability, Duration.WhileOnBattlefield), - new EnchantedCondition(), + new EnchantedSourceCondition(), "As long as {this} is enchanted, it has \"{T}: {this} deals 2 damage to target attacking or blocking creature\""))); } diff --git a/Mage.Sets/src/mage/sets/fifthdawn/RakshaGoldenCub.java b/Mage.Sets/src/mage/sets/fifthdawn/RakshaGoldenCub.java index fd0c0b20528..90c7da8120d 100644 --- a/Mage.Sets/src/mage/sets/fifthdawn/RakshaGoldenCub.java +++ b/Mage.Sets/src/mage/sets/fifthdawn/RakshaGoldenCub.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedCondition; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostAllEffect; @@ -74,12 +74,12 @@ public class RakshaGoldenCub extends CardImpl { // As long as Raksha Golden Cub is equipped, Cat creatures you control get +2/+2 and have double strike. Effect effect1 = new ConditionalContinuousEffect( new BoostAllEffect(2, 2, Duration.WhileOnBattlefield, filter, false), - EquippedCondition.getInstance(), + EquippedSourceCondition.getInstance(), "As long as {this} is equipped, Cat creatures you control get +2/+2"); Effect effect2 = new ConditionalContinuousEffect( new GainAbilityAllEffect(DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter, false), - EquippedCondition.getInstance(), + EquippedSourceCondition.getInstance(), "As long as {this} is equipped, Cat creatures you control have double strike"); effect2.setText("and have double strike"); diff --git a/Mage.Sets/src/mage/sets/guildpact/SkyriderTrainee.java b/Mage.Sets/src/mage/sets/guildpact/SkyriderTrainee.java index 91f4138918a..7bf8a5ac968 100644 --- a/Mage.Sets/src/mage/sets/guildpact/SkyriderTrainee.java +++ b/Mage.Sets/src/mage/sets/guildpact/SkyriderTrainee.java @@ -28,17 +28,17 @@ package mage.sets.guildpact; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Rarity; -import mage.constants.Zone; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; /** * @@ -58,7 +58,7 @@ public class SkyriderTrainee extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), - new EnchantedCondition(), + new EnchantedSourceCondition(), "{this} has flying as long as it's enchanted"))); } diff --git a/Mage.Sets/src/mage/sets/homelands/Leeches.java b/Mage.Sets/src/mage/sets/homelands/Leeches.java new file mode 100644 index 00000000000..3cdeeff2d8c --- /dev/null +++ b/Mage.Sets/src/mage/sets/homelands/Leeches.java @@ -0,0 +1,100 @@ +/* + * 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.sets.homelands; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.counters.CounterType; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; + +/** + * + * @author spjspj + */ +public class Leeches extends CardImpl { + + public Leeches(UUID ownerId) { + super(ownerId, 111, "Leeches", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{1}{W}{W}"); + this.expansionSetCode = "HML"; + + // Target player loses all poison counters. Leeches deals that much damage to that player. + this.getSpellAbility().addTarget(new TargetPlayer()); + this.getSpellAbility().addEffect(new LeechesEffect()); + } + + public Leeches(final Leeches card) { + super(card); + } + + @Override + public Leeches copy() { + return new Leeches(this); + } +} + + +class LeechesEffect extends OneShotEffect { + + public LeechesEffect() { + super(Outcome.Benefit); + this.staticText = "Target player loses all poison counters. Leeches deals that much damage to that player"; + } + + public LeechesEffect(final LeechesEffect effect) { + super(effect); + } + + @Override + public LeechesEffect copy() { + return new LeechesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if (targetPlayer == null) { + return false; + } + + int countPoisonCounters = targetPlayer.getCounters().getCount(CounterType.POISON); + if (countPoisonCounters > 0) { + targetPlayer.getCounters().removeCounter(CounterType.POISON, countPoisonCounters); + targetPlayer.damage(countPoisonCounters, source.getSourceId(), game, false, true); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/magic2012/ThranGolem.java b/Mage.Sets/src/mage/sets/magic2012/ThranGolem.java index b8e679ff356..4521ba79604 100644 --- a/Mage.Sets/src/mage/sets/magic2012/ThranGolem.java +++ b/Mage.Sets/src/mage/sets/magic2012/ThranGolem.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; @@ -59,7 +59,7 @@ public class ThranGolem extends CardImpl { this.toughness = new MageInt(3); // As long as Thran Golem is enchanted, it gets +2/+2 and has flying, first strike, and trample. - EnchantedCondition enchanted = new EnchantedCondition(); + EnchantedSourceCondition enchanted = new EnchantedSourceCondition(); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), enchanted, "As long as {this} is enchanted, it gets +2/+2")); diff --git a/Mage.Sets/src/mage/sets/masterseditioniv/Leeches.java b/Mage.Sets/src/mage/sets/masterseditioniv/Leeches.java new file mode 100644 index 00000000000..bfeb687e2ff --- /dev/null +++ b/Mage.Sets/src/mage/sets/masterseditioniv/Leeches.java @@ -0,0 +1,52 @@ +/* + * 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.sets.masterseditioniv; + +import java.util.UUID; + +/** + * + * @author spjspj + */ +public class Leeches extends mage.sets.homelands.Leeches { + + public Leeches(UUID ownerId) { + super(ownerId); + this.cardNumber = 18; + this.expansionSetCode = "ME4"; + } + + public Leeches(final Leeches card) { + super(card); + } + + @Override + public Leeches copy() { + return new Leeches(this); + } +} diff --git a/Mage.Sets/src/mage/sets/mirrodin/AuriokSteelshaper.java b/Mage.Sets/src/mage/sets/mirrodin/AuriokSteelshaper.java index fdd7ffdd470..ae5b61646a3 100644 --- a/Mage.Sets/src/mage/sets/mirrodin/AuriokSteelshaper.java +++ b/Mage.Sets/src/mage/sets/mirrodin/AuriokSteelshaper.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedCondition; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; @@ -73,7 +73,7 @@ public class AuriokSteelshaper extends CardImpl { // As long as Auriok Steelshaper is equipped, each creature you control that's a Soldier or a Knight gets +1/+1. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, soldiersOrKnights, false), - EquippedCondition.getInstance(), + EquippedSourceCondition.getInstance(), "As long as {this} is equipped, each creature you control that's a Soldier or a Knight gets +1/+1" ))); } diff --git a/Mage.Sets/src/mage/sets/mirrodin/LeoninDenGuard.java b/Mage.Sets/src/mage/sets/mirrodin/LeoninDenGuard.java index 075c2178133..88baee81a38 100644 --- a/Mage.Sets/src/mage/sets/mirrodin/LeoninDenGuard.java +++ b/Mage.Sets/src/mage/sets/mirrodin/LeoninDenGuard.java @@ -33,7 +33,7 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedCondition; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; @@ -62,9 +62,9 @@ public class LeoninDenGuard extends CardImpl { this.toughness = new MageInt(3); // As long as Leonin Den-Guard is equipped, it gets +1/+1 and has vigilance. - ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), EquippedCondition.getInstance(), rule1); + ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), EquippedSourceCondition.getInstance(), rule1); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect1)); - ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(VigilanceAbility.getInstance()), EquippedCondition.getInstance(), rule2); + ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(VigilanceAbility.getInstance()), EquippedSourceCondition.getInstance(), rule2); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect2)); } diff --git a/Mage.Sets/src/mage/sets/planechase2012/DreampodDruid.java b/Mage.Sets/src/mage/sets/planechase2012/DreampodDruid.java index 000d9c2c08b..2aaf42b2c2b 100644 --- a/Mage.Sets/src/mage/sets/planechase2012/DreampodDruid.java +++ b/Mage.Sets/src/mage/sets/planechase2012/DreampodDruid.java @@ -33,7 +33,7 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.MageInt; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; @@ -59,7 +59,7 @@ public class DreampodDruid extends CardImpl { // At the beginning of each upkeep, if Dreampod Druid is enchanted, put a 1/1 green Saproling creature token onto the battlefield. this.addAbility(new ConditionalTriggeredAbility( new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new SaprolingToken(),1), TargetController.ANY, false, false), - new EnchantedCondition(), + new EnchantedSourceCondition(), "At the beginning of each upkeep, if Dreampod Druid is enchanted, put a 1/1 green Saproling creature token onto the battlefield.")); } diff --git a/Mage.Sets/src/mage/sets/planechase2012/KrondTheDawnClad.java b/Mage.Sets/src/mage/sets/planechase2012/KrondTheDawnClad.java index 75c25f891d0..03c609cde39 100644 --- a/Mage.Sets/src/mage/sets/planechase2012/KrondTheDawnClad.java +++ b/Mage.Sets/src/mage/sets/planechase2012/KrondTheDawnClad.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -63,7 +63,7 @@ public class KrondTheDawnClad extends CardImpl { // Whenever Krond the Dawn-Clad attacks, if it's enchanted, exile target permanent. Ability ability = new ConditionalTriggeredAbility( new AttacksTriggeredAbility(new ExileTargetEffect(), false), - new EnchantedCondition(), + new EnchantedSourceCondition(), "Whenever {this} attacks, if it's enchanted, exile target permanent."); ability.addTarget(new TargetPermanent()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/sets/ravnica/ConcertedEffort.java b/Mage.Sets/src/mage/sets/ravnica/ConcertedEffort.java index f4998a9b5b1..99fe6fab4c2 100644 --- a/Mage.Sets/src/mage/sets/ravnica/ConcertedEffort.java +++ b/Mage.Sets/src/mage/sets/ravnica/ConcertedEffort.java @@ -85,6 +85,7 @@ class ConcertedEffortEffect extends OneShotEffect { private static final FilterControlledCreaturePermanent filterProtection = new FilterControlledCreaturePermanent(); private static final FilterControlledCreaturePermanent filterTrample = new FilterControlledCreaturePermanent(); private static final FilterControlledCreaturePermanent filterVigilance = new FilterControlledCreaturePermanent(); + private static final FilterControlledCreaturePermanent filterCreatures = new FilterControlledCreaturePermanent(); static { filterFlying.add(new AbilityPredicate(FlyingAbility.class)); @@ -115,29 +116,29 @@ class ConcertedEffortEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { // Flying if (game.getBattlefield().contains(filterFlying, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Fear if (game.getBattlefield().contains(filterFear, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(FearAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(FearAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // First strike if (game.getBattlefield().contains(filterFirstStrike, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Double strike if (game.getBattlefield().contains(filterDoubleStrike, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Landwalk for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filterLandwalk, source.getControllerId(), game)) { for (Ability ability : permanent.getAbilities(game)) { if (ability instanceof LandwalkAbility) { - game.addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn, filterCreatures), source); } } } @@ -146,19 +147,19 @@ class ConcertedEffortEffect extends OneShotEffect { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filterProtection, source.getControllerId(), game)) { for (Ability ability : permanent.getAbilities(game)) { if (ability instanceof ProtectionAbility) { - game.addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(ability, Duration.EndOfTurn, filterCreatures), source); } } } // Trample if (game.getBattlefield().contains(filterTrample, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Vigilance if (game.getBattlefield().contains(filterVigilance, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } return true; } diff --git a/Mage.Sets/src/mage/sets/ravnica/EyeOfTheStorm.java b/Mage.Sets/src/mage/sets/ravnica/EyeOfTheStorm.java new file mode 100644 index 00000000000..cdd4354217d --- /dev/null +++ b/Mage.Sets/src/mage/sets/ravnica/EyeOfTheStorm.java @@ -0,0 +1,200 @@ +/* + * 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.sets.ravnica; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardsImpl; +import mage.cards.SplitCard; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author spjspj + */ +public class EyeOfTheStorm extends CardImpl { + + public EyeOfTheStorm(UUID ownerId) { + super(ownerId, 48, "Eye of the Storm", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{5}{U}{U}"); + this.expansionSetCode = "RAV"; + + // Whenever a player casts an instant or sorcery card, exile it. Then that player copies each instant or sorcery card exiled with Eye of the Storm. For each copy, the player may cast the copy without paying its mana cost. + this.addAbility(new EyeOfTheStormAbility()); + } + + public EyeOfTheStorm(final EyeOfTheStorm card) { + super(card); + } + + @Override + public EyeOfTheStorm copy() { + return new EyeOfTheStorm(this); + } +} + +class EyeOfTheStormAbility extends TriggeredAbilityImpl { + + public EyeOfTheStormAbility() { + super(Zone.BATTLEFIELD, new EyeOfTheStormEffect1(), false); + } + + public EyeOfTheStormAbility(final EyeOfTheStormAbility ability) { + super(ability); + } + + @Override + public EyeOfTheStormAbility copy() { + return new EyeOfTheStormAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getZone() == Zone.HAND) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + } + return true; + } + } + return false; + } + +} + +class EyeOfTheStormEffect1 extends OneShotEffect { + + private static final FilterInstantOrSorceryCard instantOrSorceryfilter = new FilterInstantOrSorceryCard(); + + private static final FilterSpell filter = new FilterSpell("instant or sorcery card"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.INSTANT), + new CardTypePredicate(CardType.SORCERY))); + } + + public EyeOfTheStormEffect1() { + super(Outcome.Neutral); + staticText = "Whenever a player casts an instant or sorcery card, exile it. Then that player copies each instant or sorcery card exiled with {this}. For each copy, the player may cast the copy without paying its mana cost"; + } + + public EyeOfTheStormEffect1(final EyeOfTheStormEffect1 effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + Permanent EyeOfTheStorm = game.getPermanentOrLKIBattlefield(source.getSourceId()); + Player controller = game.getPlayer(source.getControllerId()); + + if (controller != null && spell != null && EyeOfTheStorm != null) { + Card card = spell.getCard(); + if (card == null || !instantOrSorceryfilter.match(card, game)) { + return false; + } + + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), EyeOfTheStorm.getZoneChangeCounter(game)); + if (controller.moveCardsToExile(spell, source, game, true, exileZoneId, EyeOfTheStorm.getIdName())) { + EyeOfTheStorm.imprint(card.getId(), game); + + Player player = game.getPlayer(spell.getControllerId()); + + if (EyeOfTheStorm != null && EyeOfTheStorm.getImprinted() != null && EyeOfTheStorm.getImprinted().size() > 0 && controller != null) { + CardsImpl copiedCards = new CardsImpl(); + for (UUID uuid : EyeOfTheStorm.getImprinted()) { + card = game.getCard(uuid); + if (card.isSplitCard()) { + copiedCards.add(((SplitCard) card).getLeftHalfCard()); + copiedCards.add(((SplitCard) card).getRightHalfCard()); + } else { + copiedCards.add(card); + } + } + + boolean continueCasting = true; + while (continueCasting) { + continueCasting = copiedCards.size() > 1 && controller.chooseUse(outcome, "Cast one of the copied cards without paying its mana cost?", source, game); + + Card cardToCopy; + if (copiedCards.size() == 1) { + cardToCopy = copiedCards.getCards(game).iterator().next(); + } else { + TargetCard target = new TargetCard(1, Zone.EXILED, new FilterCard("card to copy")); + controller.choose(Outcome.Copy, copiedCards, target, game); + cardToCopy = copiedCards.get(target.getFirstTarget(), game); + copiedCards.remove(cardToCopy); + } + if (cardToCopy != null) { + Card copy = game.copyCard(cardToCopy, source, source.getControllerId()); + if (controller.chooseUse(outcome, "Cast the copied card without paying mana cost?", source, game)) { + controller.cast(copy.getSpellAbility(), game, true); + } + } + } + return true; + } + } + } + return false; + } + + @Override + public EyeOfTheStormEffect1 copy() { + return new EyeOfTheStormEffect1(this); + } +} diff --git a/Mage.Sets/src/mage/sets/ravnica/GateHound.java b/Mage.Sets/src/mage/sets/ravnica/GateHound.java index 27c622cdf80..984b9890203 100644 --- a/Mage.Sets/src/mage/sets/ravnica/GateHound.java +++ b/Mage.Sets/src/mage/sets/ravnica/GateHound.java @@ -34,7 +34,7 @@ import mage.constants.Rarity; import mage.constants.Zone; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.VigilanceAbility; @@ -58,7 +58,7 @@ public class GateHound extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, new FilterCreaturePermanent()), - new EnchantedCondition(), + new EnchantedSourceCondition(), "Creatures you control have vigilance as long as {this} is enchanted"))); } diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java index a52650da1c4..fdfa3953574 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java @@ -28,18 +28,17 @@ package mage.sets.scarsofmirrodin; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedCondition; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.constants.Zone; /** @@ -59,10 +58,10 @@ public class SunspearShikari extends CardImpl { // As long as Sunspear Shikari is equipped, it has first strike and lifelink. ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), - EquippedCondition.getInstance(), "As long as {this} is equipped, it has first strike"); + EquippedSourceCondition.getInstance(), "As long as {this} is equipped, it has first strike"); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect1); ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(LifelinkAbility.getInstance()), - EquippedCondition.getInstance(), "and lifelink"); + EquippedSourceCondition.getInstance(), "and lifelink"); ability.addEffect(effect2); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java new file mode 100644 index 00000000000..090d8f1efe3 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AccursedWitch.java @@ -0,0 +1,161 @@ +/* + * 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.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.keyword.TransformAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.util.CardUtil; + +/** + * + * @author halljared + */ +public class AccursedWitch extends CardImpl { + + public AccursedWitch(UUID ownerId) { + super(ownerId, 97, "Accursed Witch", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Human"); + this.subtype.add("Shaman"); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + this.canTransform = true; + this.secondSideCard = new InfectiousCurse(ownerId); + + // Spells your opponents cast that target Accursed Witch cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new AccursedWitchSpellsCostReductionEffect())); + // When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent. + this.addAbility(new TransformAbility()); + this.addAbility(new DiesTriggeredAbility(new AccursedWitchReturnTransformedEffect())); + } + + public AccursedWitch(final AccursedWitch card) { + super(card); + } + + @Override + public AccursedWitch copy() { + return new AccursedWitch(this); + } +} + +class AccursedWitchReturnTransformedEffect extends OneShotEffect { + + public AccursedWitchReturnTransformedEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "Put {this} from your graveyard onto the battlefield transformed"; + } + + public AccursedWitchReturnTransformedEffect(final AccursedWitchReturnTransformedEffect effect) { + super(effect); + } + + @Override + public AccursedWitchReturnTransformedEffect copy() { + return new AccursedWitchReturnTransformedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + if (game.getState().getZone(source.getSourceId()).equals(Zone.GRAVEYARD)) { + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); + //note: should check for null after game.getCard + Card card = game.getCard(source.getSourceId()); + if (card != null) { + card.putOntoBattlefield(game, Zone.BATTLEFIELD, source.getSourceId(), source.getControllerId(), false); + } + } + return true; + } + return false; + } +} + +class AccursedWitchSpellsCostReductionEffect extends CostModificationEffectImpl { + + public AccursedWitchSpellsCostReductionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.REDUCE_COST); + this.staticText = "Spells your opponents cast that target {this} cost {1} less to cast."; + } + + protected AccursedWitchSpellsCostReductionEffect(AccursedWitchSpellsCostReductionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + CardUtil.reduceCost(abilityToModify, 1); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify instanceof SpellAbility) { + if(game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { + for (Target target : abilityToModify.getTargets()) { + for (UUID targetUUID : target.getTargets()) { + Permanent permanent = game.getPermanent(targetUUID); + if(permanent != null && permanent.getId().equals(source.getSourceId())) { + return true; + } + } + } + } + } + return false; + } + + @Override + public AccursedWitchSpellsCostReductionEffect copy() { + return new AccursedWitchSpellsCostReductionEffect(this); + } +} + + diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArchangelAvacyn.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArchangelAvacyn.java index e93a9654b4b..4a6bae9cd89 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArchangelAvacyn.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArchangelAvacyn.java @@ -27,12 +27,9 @@ */ package mage.sets.shadowsoverinnistrad; -import java.util.List; import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; @@ -50,17 +47,12 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.TargetController; -import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.SubtypePredicate; -import mage.filter.predicate.permanent.AnotherPredicate; import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; /** * @@ -105,8 +97,6 @@ public class ArchangelAvacyn extends CardImpl { this.addAbility(new TransformAbility()); this.addAbility(new DiesCreatureTriggeredAbility(new ArchangelAvacynEffect(), false, filter)); - // When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent. - this.addAbility(new AvacynThePurifierAbility()); } public ArchangelAvacyn(final ArchangelAvacyn card) { @@ -145,85 +135,3 @@ class ArchangelAvacynEffect extends OneShotEffect { return new ArchangelAvacynEffect(this); } } - -class AvacynThePurifierAbility extends TriggeredAbilityImpl { - - public AvacynThePurifierAbility() { - super(Zone.BATTLEFIELD, new AvacynThePurifierEffect(), false); - // Rule only shown on the night side - this.setRuleVisible(false); - } - - public AvacynThePurifierAbility(final AvacynThePurifierAbility ability) { - super(ability); - } - - @Override - public AvacynThePurifierAbility copy() { - return new AvacynThePurifierAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.TRANSFORMED; - } - - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); - if (currentSourceObject != null && currentSourceObject.isNightCard()) { - return true; - } - return super.isInUseableZone(game, source, event); - } - - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(sourceId)) { - Permanent permanent = game.getPermanent(sourceId); - if (permanent != null && permanent.isTransformed()) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent."; - } -} - -class AvacynThePurifierEffect extends OneShotEffect { - - public AvacynThePurifierEffect() { - super(Outcome.Damage); - } - - public AvacynThePurifierEffect(final AvacynThePurifierEffect effect) { - super(effect); - } - - @Override - public AvacynThePurifierEffect copy() { - return new AvacynThePurifierEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("each other creature"); - filter.add(new AnotherPredicate()); - List permanents = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game); - for (Permanent permanent: permanents) { - permanent.damage(3, source.getSourceId(), game, false, true); - } - for(UUID opponentId : game.getOpponents(source.getControllerId())) { - Player opponent = game.getPlayer(opponentId); - if (opponent != null) { - opponent.damage(3, source.getSourceId(), game, false, true); - } - } - return true; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java index c09542d4732..f3860600798 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java @@ -27,15 +27,25 @@ */ package mage.sets.shadowsoverinnistrad; +import java.util.List; import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.constants.CardType; +import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; /** * @@ -43,8 +53,6 @@ import mage.constants.Zone; */ public class AvacynThePurifier extends CardImpl { - private static final String rule = "Whenever this creature transforms into {this}, it deals 3 damage to each other creature and each opponent"; - public AvacynThePurifier(UUID ownerId) { super(ownerId, 5, "Avacyn, the Purifier", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, ""); this.expansionSetCode = "SOI"; @@ -61,7 +69,7 @@ public class AvacynThePurifier extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect(rule))); + this.addAbility(new AvacynThePurifierAbility()); } public AvacynThePurifier(final AvacynThePurifier card) { @@ -73,3 +81,82 @@ public class AvacynThePurifier extends CardImpl { return new AvacynThePurifier(this); } } + +class AvacynThePurifierAbility extends TriggeredAbilityImpl { + + public AvacynThePurifierAbility() { + super(Zone.BATTLEFIELD, new AvacynThePurifierEffect(), false); + } + + public AvacynThePurifierAbility(final AvacynThePurifierAbility ability) { + super(ability); + } + + @Override + public AvacynThePurifierAbility copy() { + return new AvacynThePurifierAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMED; + } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); + if (currentSourceObject != null && currentSourceObject.isNightCard()) { + return true; + } + return super.isInUseableZone(game, source, event); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(sourceId)) { + Permanent permanent = game.getPermanent(sourceId); + if (permanent != null && permanent.isTransformed()) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent."; + } +} + +class AvacynThePurifierEffect extends OneShotEffect { + + public AvacynThePurifierEffect() { + super(Outcome.Damage); + } + + public AvacynThePurifierEffect(final AvacynThePurifierEffect effect) { + super(effect); + } + + @Override + public AvacynThePurifierEffect copy() { + return new AvacynThePurifierEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + FilterCreaturePermanent filter = new FilterCreaturePermanent("each other creature"); + filter.add(new AnotherPredicate()); + List permanents = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game); + for (Permanent permanent : permanents) { + permanent.damage(3, source.getSourceId(), game, false, true); + } + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent != null) { + opponent.damage(3, source.getSourceId(), game, false, true); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynianMissionaries.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynianMissionaries.java index af0ed74a5b4..74d87e41f56 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynianMissionaries.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynianMissionaries.java @@ -29,28 +29,15 @@ package mage.sets.shadowsoverinnistrad; import java.util.UUID; import mage.MageInt; -import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.BeginningOfEndStepTriggeredAbility; -import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility; -import mage.abilities.condition.common.EquippedCondition; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; -import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.TargetController; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.common.TargetCreaturePermanent; -import mage.util.CardUtil; /** * @@ -71,13 +58,8 @@ public class AvacynianMissionaries extends CardImpl { // At the beginning of your end step, if Avacynian Missionaries is equipped, transform it. this.addAbility(new TransformAbility()); - this.addAbility(new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new TransformSourceEffect(true), TargetController.YOU, new EquippedCondition(), false)); + this.addAbility(new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, new TransformSourceEffect(true), TargetController.YOU, new EquippedSourceCondition(), false)); - // When this creature transforms into Lunarch Inquisitors, you may exile another target creature until Lunarch Inquisitors leaves the battlefield. - Ability ability = new LunarchInquisitorsAbility(); - ability.addTarget(new TargetCreaturePermanent()); - ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); - this.addAbility(ability); } public AvacynianMissionaries(final AvacynianMissionaries card) { @@ -89,80 +71,3 @@ public class AvacynianMissionaries extends CardImpl { return new AvacynianMissionaries(this); } } - -class LunarchInquisitorsAbility extends TriggeredAbilityImpl { - - public LunarchInquisitorsAbility() { - super(Zone.BATTLEFIELD, new LunarchInquisitorsExileEffect(), true); - // Rule only shown on the night side - this.setRuleVisible(false); - } - - public LunarchInquisitorsAbility(final LunarchInquisitorsAbility ability) { - super(ability); - } - - @Override - public LunarchInquisitorsAbility copy() { - return new LunarchInquisitorsAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.TRANSFORMED; - } - - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); - if (currentSourceObject != null && currentSourceObject.isNightCard()) { - return true; - } - return super.isInUseableZone(game, source, event); - } - - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(sourceId)) { - Permanent permanent = game.getPermanent(sourceId); - if (permanent != null && permanent.isTransformed()) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever this creature transforms into Lunarch Inquisitors, you may exile another target creature until Lunarch Inquisitors leaves the battlefield."; - } -} - -class LunarchInquisitorsExileEffect extends OneShotEffect { - - public LunarchInquisitorsExileEffect() { - super(Outcome.Benefit); - this.staticText = "exile target creature until {this} leaves the battlefield"; - } - - public LunarchInquisitorsExileEffect(final LunarchInquisitorsExileEffect effect) { - super(effect); - } - - @Override - public LunarchInquisitorsExileEffect copy() { - return new LunarchInquisitorsExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - // If Lunarch Inquisitors leaves the battlefield before its triggered ability resolves, - // the target won't be exiled. - if (permanent != null) { - return new ExileTargetEffect(CardUtil.getCardExileZoneId(game, source), permanent.getIdName()).apply(game, source); - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java index 0f864b229c2..a1705b9cb75 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java @@ -29,12 +29,18 @@ package mage.sets.shadowsoverinnistrad; import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.ReturnToHandFromBattlefieldAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; /** * @@ -42,8 +48,6 @@ import mage.constants.Zone; */ public class AwokenHorror extends CardImpl { - private static final String rule = "Whenever this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands"; - public AwokenHorror(UUID ownerId) { super(ownerId, 92, "Awoken Horror", Rarity.RARE, new CardType[]{CardType.CREATURE}, ""); this.expansionSetCode = "SOI"; @@ -56,7 +60,7 @@ public class AwokenHorror extends CardImpl { this.nightCard = true; // When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect(rule))); + this.addAbility(new AwokenHorrorAbility()); } public AwokenHorror(final AwokenHorror card) { @@ -68,3 +72,55 @@ public class AwokenHorror extends CardImpl { return new AwokenHorror(this); } } + +class AwokenHorrorAbility extends TriggeredAbilityImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("non-Horror creatures"); + + static { + filter.add(Predicates.not(new SubtypePredicate("Horror"))); + } + + public AwokenHorrorAbility() { + super(Zone.BATTLEFIELD, new ReturnToHandFromBattlefieldAllEffect(filter), false); + } + + public AwokenHorrorAbility(final AwokenHorrorAbility ability) { + super(ability); + } + + @Override + public AwokenHorrorAbility copy() { + return new AwokenHorrorAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMED; + } + +// @Override +// public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { +// Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); +// if (currentSourceObject != null && currentSourceObject.isTransformed()) { +// // hard to check if the not transformed source hat the ability. But if it was transformed it probably had it, but maybe no perfect solution +// return true; +// } +// return false; +// } + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(sourceId)) { + Permanent permanent = game.getPermanent(sourceId); + if (permanent != null && permanent.isTransformed()) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands."; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/CreepingDread.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/CreepingDread.java new file mode 100644 index 00000000000..be70020b0a1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/CreepingDread.java @@ -0,0 +1,168 @@ +/* + * 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.sets.shadowsoverinnistrad; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class CreepingDread extends CardImpl { + + public CreepingDread(UUID ownerId) { + super(ownerId, 104, "Creeping Dread", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}"); + this.expansionSetCode = "SOI"; + + // At the beginning of your upkeep, each player discards a card. Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CreepingDreadEffect(), TargetController.YOU, false)); + } + + public CreepingDread(final CreepingDread card) { + super(card); + } + + @Override + public CreepingDread copy() { + return new CreepingDread(this); + } +} + +class CreepingDreadEffect extends OneShotEffect { + + public CreepingDreadEffect() { + super(Outcome.Detriment); + this.staticText = "each player discards a card. Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life."; + } + + public CreepingDreadEffect(final CreepingDreadEffect effect) { + super(effect); + } + + @Override + public CreepingDreadEffect copy() { + return new CreepingDreadEffect(this); + } + + /* + * When a spell or ability instructs each player to discard a card, + starting with the player whose turn it is and proceeding in turn order, + each player selects a card from his or her hand without revealing it, + sets it aside, and then all of those cards are revealed and discarded at once. + + http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=409851 + */ + @Override + public boolean apply(Game game, Ability source) { + + // controller discards a card - store info on card type + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + + Set typesChosen = new HashSet<>(); + Map cardsChosen = new HashMap<>(); + if(!controller.getHand().isEmpty()) { + + TargetCard controllerTarget = new TargetCard(Zone.HAND, new FilterCard()); + if(controller.choose(Outcome.Discard, controller.getHand(), controllerTarget, game)) { + Card card = controller.getHand().get(controllerTarget.getFirstTarget(), game); + if (card != null) { + typesChosen = new HashSet<>(card.getCardType()); + cardsChosen.put(controller, card); + } + } + } + + Set opponentsAffected = new HashSet<>(); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + // opponent discards a card - if it is same card type as controller, add to opponentsAffected + if(!opponent.getHand().isEmpty()) { + TargetCard target = new TargetCard(Zone.HAND, new FilterCard()); + if(opponent.choose(Outcome.Discard, opponent.getHand(), target, game)) { + Card card = opponent.getHand().get(target.getFirstTarget(), game); + if (card != null) { + if (!typesChosen.isEmpty()) { + for (CardType cType : typesChosen) { + for (CardType oType : card.getCardType()) { + if (cType == oType) { + opponentsAffected.add(opponent); + break; + } + } + } + } + + cardsChosen.put(opponent, card); + } + } + } + } + + // everyone discards the card at the same time + if (!cardsChosen.isEmpty()) { + for (Map.Entry entry : cardsChosen.entrySet()) { + Player player = entry.getKey(); + Card cardChosen = entry.getValue(); + if (player != null && cardChosen != null) { + player.discard(cardChosen, source, game); + } + } + } + + // each opponent who discarded a card of the same type loses 3 life + if (!opponentsAffected.isEmpty()) { + for(Player opponent : opponentsAffected) { + opponent.loseLife(3, game); + } + } + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeclarationInStone.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeclarationInStone.java index 00d84a83758..934221e2e6a 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeclarationInStone.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeclarationInStone.java @@ -74,6 +74,7 @@ public class DeclarationInStone extends CardImpl { class DeclarationInStoneEffect extends OneShotEffect { + private static final FilterCreaturePermanent creaturesOnly = new FilterCreaturePermanent(); private static final FilterCreaturePermanent nonTokenFilter = new FilterCreaturePermanent("nontoken creature"); static{ nonTokenFilter.add(Predicates.not(new TokenPredicate())); @@ -106,7 +107,11 @@ class DeclarationInStoneEffect extends OneShotEffect { String name = targetPermanent.getName(); for (Permanent permanent : game.getBattlefield().getAllActivePermanents(controllerPermanentId)) { if (permanent != null && permanent.getName().equals(name)) { - you.moveCardToExileWithInfo(permanent, exileId, sourceObject.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true); + + // only exile creatures (reported bug on awakened lands targetted exiling all other lands of same name) + if (creaturesOnly.match(permanent, game)) { + you.moveCardToExileWithInfo(permanent, exileId, sourceObject.getIdName(), source.getSourceId(), game, Zone.BATTLEFIELD, true); + } // exiled count only matters for non-tokens if (nonTokenFilter.match(permanent, game)) { diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EngulfTheShore.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EngulfTheShore.java index 2530228ac6a..7f34957c4c8 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EngulfTheShore.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EngulfTheShore.java @@ -27,12 +27,13 @@ */ package mage.sets.shadowsoverinnistrad; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; import mage.cards.CardImpl; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; @@ -100,7 +101,7 @@ class EngulfTheShoreEffect extends OneShotEffect { int islands = game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game); FilterPermanent creatureFilter = new FilterCreaturePermanent(); creatureFilter.add(new ToughnessPredicate(Filter.ComparisonType.LessThan, islands + 1)); - Cards cardsToHand = new CardsImpl(); + Set cardsToHand = new HashSet<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(creatureFilter, source.getControllerId(), source.getSourceId(), game)) { cardsToHand.add(permanent); } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ErdwalIlluminator.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ErdwalIlluminator.java index 8f8c13bd45c..4f944f756fb 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ErdwalIlluminator.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ErdwalIlluminator.java @@ -77,6 +77,7 @@ class ErdwalIlluminatorTriggeredAbility extends TriggeredAbilityImpl { public ErdwalIlluminatorTriggeredAbility() { super(Zone.BATTLEFIELD, new InvestigateEffect(), false); + addWatcher(new InvestigatedWatcher()); } public ErdwalIlluminatorTriggeredAbility(final ErdwalIlluminatorTriggeredAbility ability) { @@ -126,9 +127,9 @@ class InvestigatedWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (EventType.INVESTIGATED.equals(event.getType())) { if (!timesInvestigated.containsKey(event.getPlayerId())) { - timesInvestigated.put(event.getPlayerId(), timesInvestigated.get(event.getPlayerId()) + 1); - } else { timesInvestigated.put(event.getPlayerId(), 1); + } else { + timesInvestigated.put(event.getPlayerId(), timesInvestigated.get(event.getPlayerId()) + 1); } } } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java index 7c03974f59c..7f74e16efd5 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java @@ -38,7 +38,6 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamageEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -90,7 +89,9 @@ class FlamebladeAngelTriggeredAbility extends TriggeredAbilityImpl { @java.lang.Override public boolean checkEventType(GameEvent event, Game game) { - return event instanceof DamageEvent; + return event.getType() == GameEvent.EventType.DAMAGED_CREATURE + || event.getType() == GameEvent.EventType.DAMAGED_PLANESWALKER + || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; } @java.lang.Override diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FromUnderTheFloorboards.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FromUnderTheFloorboards.java new file mode 100644 index 00000000000..c5b53b162f6 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FromUnderTheFloorboards.java @@ -0,0 +1,104 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.MadnessAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.game.Game; +import mage.game.permanent.token.ZombieToken; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class FromUnderTheFloorboards extends CardImpl { + + public FromUnderTheFloorboards(UUID ownerId) { + super(ownerId, 111, "From Under the Floorboards", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); + this.expansionSetCode = "SOI"; + + // Madness {X}{B}{B} (If you discard this card discard it into exile. When you do cast it for its madness cost or put it into your graveyard. + Ability ability = (new MadnessAbility(this, new ManaCostsImpl("{X}{B}{B}"))); + ability.setRuleAtTheTop(true); + this.addAbility(ability); + + // Put three 2/2 black Zombie creature tokens onto the battlefield tapped and you gain 3 life. + // If From Under the Floorboards's madness cost was paid, instead put X of those tokens onto the battlefield tapped and you gain X life. + DynamicValue xValue = new FromUnderTheFloorboardsManacostVariableValue(); + Effect effect = new CreateTokenEffect(new ZombieToken(), xValue, true, false); + effect.setText("Put three 2/2 black Zombie creature tokens onto the battlefield tapped and you gain 3 life. If {this} madness cost was paid, instead put X of those tokens onto the battlefield tapped and you gain X life."); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new GainLifeEffect(xValue)); + } + + public FromUnderTheFloorboards(final FromUnderTheFloorboards card) { + super(card); + } + + @Override + public FromUnderTheFloorboards copy() { + return new FromUnderTheFloorboards(this); + } +} + +class FromUnderTheFloorboardsManacostVariableValue implements DynamicValue { + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + ManaCosts manaCosts = sourceAbility.getManaCostsToPay(); + if (manaCosts.getVariableCosts().isEmpty()) { + return 3; + } + return sourceAbility.getManaCostsToPay().getX(); + } + + @Override + public FromUnderTheFloorboardsManacostVariableValue copy() { + return new FromUnderTheFloorboardsManacostVariableValue(); + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GoldknightCastigator.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GoldnightCastigator.java similarity index 80% rename from Mage.Sets/src/mage/sets/shadowsoverinnistrad/GoldknightCastigator.java rename to Mage.Sets/src/mage/sets/shadowsoverinnistrad/GoldnightCastigator.java index d184018c27c..25ed1f299b3 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GoldknightCastigator.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GoldnightCastigator.java @@ -51,10 +51,10 @@ import mage.game.permanent.Permanent; * * @author fireshoes */ -public class GoldknightCastigator extends CardImpl { +public class GoldnightCastigator extends CardImpl { - public GoldknightCastigator(UUID ownerId) { - super(ownerId, 162, "Goldknight Castigator", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); + public GoldnightCastigator(UUID ownerId) { + super(ownerId, 162, "Goldnight Castigator", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); this.expansionSetCode = "SOI"; this.subtype.add("Angel"); this.power = new MageInt(4); @@ -67,35 +67,35 @@ public class GoldknightCastigator extends CardImpl { this.addAbility(HasteAbility.getInstance()); // If a source would deal damage to you, it deals double that damage to you instead. - // If a source would deal damage to Goldknight Castigator, it deals double that damage to Goldknight Castigator instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GoldknightCastigatorDoubleDamageEffect())); + // If a source would deal damage to Goldnight Castigator, it deals double that damage to Goldkight Castigator instead. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GoldnightCastigatorDoubleDamageEffect())); } - public GoldknightCastigator(final GoldknightCastigator card) { + public GoldnightCastigator(final GoldnightCastigator card) { super(card); } @Override - public GoldknightCastigator copy() { - return new GoldknightCastigator(this); + public GoldnightCastigator copy() { + return new GoldnightCastigator(this); } } -class GoldknightCastigatorDoubleDamageEffect extends ReplacementEffectImpl { +class GoldnightCastigatorDoubleDamageEffect extends ReplacementEffectImpl { - public GoldknightCastigatorDoubleDamageEffect() { + public GoldnightCastigatorDoubleDamageEffect() { super(Duration.WhileOnBattlefield, Outcome.Damage); staticText = "If a source would deal damage to you, it deals double that damage to you instead." - + "
If a source would deal damage to Goldknight Castigator, it deals double that damage to {this} instead."; + + "
If a source would deal damage to Goldnight Castigator, it deals double that damage to {this} instead."; } - public GoldknightCastigatorDoubleDamageEffect(final GoldknightCastigatorDoubleDamageEffect effect) { + public GoldnightCastigatorDoubleDamageEffect(final GoldnightCastigatorDoubleDamageEffect effect) { super(effect); } @Override - public GoldknightCastigatorDoubleDamageEffect copy() { - return new GoldknightCastigatorDoubleDamageEffect(this); + public GoldnightCastigatorDoubleDamageEffect copy() { + return new GoldnightCastigatorDoubleDamageEffect(this); } @Override diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GrafMole.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GrafMole.java index 1e853e48349..f527824ffc7 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GrafMole.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/GrafMole.java @@ -29,13 +29,15 @@ package mage.sets.shadowsoverinnistrad; import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SacrificeAllTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.TargetController; -import mage.filter.common.FilterCreaturePermanent; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; /** * @@ -52,7 +54,7 @@ public class GrafMole extends CardImpl { this.toughness = new MageInt(4); // Whenever you sacrifice a Clue, you gain 3 life. - this.addAbility(new SacrificeAllTriggeredAbility(new GainLifeEffect(3), new FilterCreaturePermanent("Clue", "a Clue"), TargetController.YOU, false)); + this.addAbility(new GrafMoleTriggeredAbility()); } public GrafMole(final GrafMole card) { @@ -64,3 +66,36 @@ public class GrafMole extends CardImpl { return new GrafMole(this); } } + +class GrafMoleTriggeredAbility extends TriggeredAbilityImpl { + + public GrafMoleTriggeredAbility() { + super(Zone.BATTLEFIELD, new GainLifeEffect(3)); + setLeavesTheBattlefieldTrigger(true); + } + + public GrafMoleTriggeredAbility(final GrafMoleTriggeredAbility ability) { + super(ability); + } + + @Override + public GrafMoleTriggeredAbility copy() { + return new GrafMoleTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.SACRIFICED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getPlayerId().equals(this.getControllerId()) + && game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD).getSubtype().contains("Clue"); + } + + @Override + public String getRule() { + return "Whenever you sacrifice a Clue, " + super.getRule(); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HarnessTheStorm.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HarnessTheStorm.java index ff0265d173b..21077af919c 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HarnessTheStorm.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HarnessTheStorm.java @@ -103,8 +103,8 @@ class HarnessTheStormTriggeredAbility extends SpellCastControllerTriggeredAbilit } @Override - public SpellCastControllerTriggeredAbility copy() { - return super.copy(); //To change body of generated methods, choose Tools | Templates. + public HarnessTheStormTriggeredAbility copy() { + return new HarnessTheStormTriggeredAbility(this); } } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.java new file mode 100644 index 00000000000..7ddcdf42b4f --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InfectiousCurse.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.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author halljared + */ +public class InfectiousCurse extends CardImpl { + + public InfectiousCurse(UUID ownerId) { + super(ownerId, 97, "Infectious Curse", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, ""); + this.expansionSetCode = "SOI"; + this.subtype.add("Aura"); + this.subtype.add("Curse"); + + this.nightCard = true; + this.canTransform = true; + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Damage)); + this.addAbility(new EnchantAbility(auraTarget.getTargetName())); + + // Spells you cast that target enchanted player cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfectiousCurseCostReductionEffect())); + // At the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life. + InfectiousCurseAbility curseAbility = new InfectiousCurseAbility(); + curseAbility.addEffect(new GainLifeEffect(1)); + this.addAbility(curseAbility); + } + + public InfectiousCurse(final InfectiousCurse card) { + super(card); + } + + @Override + public InfectiousCurse copy() { + return new InfectiousCurse(this); + } +} + +class InfectiousCurseAbility extends TriggeredAbilityImpl { + + public InfectiousCurseAbility() { + super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(1)); + } + + public InfectiousCurseAbility(final InfectiousCurseAbility ability) { + super(ability); + } + + @Override + public InfectiousCurseAbility copy() { + return new InfectiousCurseAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent enchantment = game.getPermanent(this.sourceId); + if (enchantment != null && enchantment.getAttachedTo() != null) { + Player player = game.getPlayer(enchantment.getAttachedTo()); + if (player != null && game.getActivePlayerId().equals(player.getId())) { + this.getEffects().get(0).setTargetPointer(new FixedTarget(player.getId())); + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "At the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life."; + } + +} + +class InfectiousCurseCostReductionEffect extends CostModificationEffectImpl { + + public InfectiousCurseCostReductionEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + this.staticText = "Spells you cast that target enchanted player cost {1} less to cast."; + } + + protected InfectiousCurseCostReductionEffect(InfectiousCurseCostReductionEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + CardUtil.reduceCost(abilityToModify, 1); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify instanceof SpellAbility) { + if (source.getControllerId().equals(abilityToModify.getControllerId())) { + for (Target target : abilityToModify.getTargets()) { + for (UUID targetUUID : target.getTargets()) { + Permanent enchantment = game.getPermanent(source.getSourceId()); + UUID attachedTo = enchantment.getAttachedTo(); + if (targetUUID.equals(attachedTo)) { + return true; + } + } + } + } + } + return false; + } + + @Override + public InfectiousCurseCostReductionEffect copy() { + return new InfectiousCurseCostReductionEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JaceUnravelerOfSecrets.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JaceUnravelerOfSecrets.java index 02aa48cb22a..15c2fd7470f 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JaceUnravelerOfSecrets.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JaceUnravelerOfSecrets.java @@ -99,23 +99,23 @@ class JaceUnravelerOfSecretsEmblem extends Emblem { this.setName("EMBLEM: Jace, Unraveler of Secrets"); Effect effect = new CounterTargetEffect(); effect.setText("counter that spell"); - this.getAbilities().add(new JaceUnravelerOfSecretsTriggertAbility(effect, false)); + this.getAbilities().add(new JaceUnravelerOfSecretsTriggeredAbility(effect, false)); } } -class JaceUnravelerOfSecretsTriggertAbility extends SpellCastOpponentTriggeredAbility { +class JaceUnravelerOfSecretsTriggeredAbility extends SpellCastOpponentTriggeredAbility { - public JaceUnravelerOfSecretsTriggertAbility(Effect effect, boolean optional) { + public JaceUnravelerOfSecretsTriggeredAbility(Effect effect, boolean optional) { super(effect, optional); } - public JaceUnravelerOfSecretsTriggertAbility(SpellCastOpponentTriggeredAbility ability) { + public JaceUnravelerOfSecretsTriggeredAbility(SpellCastOpponentTriggeredAbility ability) { super(ability); } @Override public SpellCastOpponentTriggeredAbility copy() { - return super.copy(); //To change body of generated methods, choose Tools | Templates. + return super.copy(); } @Override @@ -137,5 +137,4 @@ class JaceUnravelerOfSecretsTriggertAbility extends SpellCastOpponentTriggeredAb public String getRule() { return "Whenever an opponent casts his or her first spell each turn, counter that spell."; } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java index 1eb05772650..179f45dc05e 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java @@ -29,12 +29,23 @@ package mage.sets.shadowsoverinnistrad; import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; +import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; /** * @@ -57,7 +68,10 @@ public class LunarchInquisitors extends CardImpl { this.nightCard = true; // When this creature transforms into Lunarch Inquisitors, you may exile another target creature until Lunarch Inquisitors leaves the battlefield. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect(rule))); + Ability ability = new LunarchInquisitorsAbility(); + ability.addTarget(new TargetCreaturePermanent()); + ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); + this.addAbility(ability); } public LunarchInquisitors(final LunarchInquisitors card) { @@ -69,3 +83,79 @@ public class LunarchInquisitors extends CardImpl { return new LunarchInquisitors(this); } } + +class LunarchInquisitorsAbility extends TriggeredAbilityImpl { + + public LunarchInquisitorsAbility() { + super(Zone.BATTLEFIELD, new LunarchInquisitorsExileEffect(), true); + // Rule only shown on the night side + this.setRuleVisible(false); + } + + public LunarchInquisitorsAbility(final LunarchInquisitorsAbility ability) { + super(ability); + } + + @Override + public LunarchInquisitorsAbility copy() { + return new LunarchInquisitorsAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TRANSFORMED; + } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); + if (currentSourceObject != null && currentSourceObject.isNightCard()) { + return true; + } + return super.isInUseableZone(game, source, event); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(sourceId)) { + Permanent permanent = game.getPermanent(sourceId); + if (permanent != null && permanent.isTransformed()) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever this creature transforms into Lunarch Inquisitors, you may exile another target creature until Lunarch Inquisitors leaves the battlefield."; + } +} + +class LunarchInquisitorsExileEffect extends OneShotEffect { + + public LunarchInquisitorsExileEffect() { + super(Outcome.Benefit); + this.staticText = "exile target creature until {this} leaves the battlefield"; + } + + public LunarchInquisitorsExileEffect(final LunarchInquisitorsExileEffect effect) { + super(effect); + } + + @Override + public LunarchInquisitorsExileEffect copy() { + return new LunarchInquisitorsExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + // If Lunarch Inquisitors leaves the battlefield before its triggered ability resolves, + // the target won't be exiled. + if (permanent != null) { + return new ExileTargetEffect(CardUtil.getCardExileZoneId(game, source), permanent.getIdName()).apply(game, source); + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OdricLunarchMarshal.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OdricLunarchMarshal.java index 4b001d93e4d..0381d05aab0 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OdricLunarchMarshal.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OdricLunarchMarshal.java @@ -100,6 +100,7 @@ class OdricLunarchMarshalEffect extends OneShotEffect { private static final FilterControlledCreaturePermanent filterSkulk = new FilterControlledCreaturePermanent(); private static final FilterControlledCreaturePermanent filterTrample = new FilterControlledCreaturePermanent(); private static final FilterControlledCreaturePermanent filterVigilance = new FilterControlledCreaturePermanent(); + private static final FilterControlledCreaturePermanent filterCreatures = new FilterControlledCreaturePermanent(); static { filterFirstStrike.add(new AbilityPredicate(FirstStrikeAbility.class)); @@ -136,67 +137,67 @@ class OdricLunarchMarshalEffect extends OneShotEffect { // First strike if (game.getBattlefield().contains(filterFirstStrike, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Flying if (game.getBattlefield().contains(filterFlying, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Deathtouch if (game.getBattlefield().contains(filterDeathtouch, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Double strike if (game.getBattlefield().contains(filterDoubleStrike, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Haste if (game.getBattlefield().contains(filterHaste, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Hexproof if (game.getBattlefield().contains(filterHexproof, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Indestructible if (game.getBattlefield().contains(filterIndestructible, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(IndestructibleAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Lifelink if (game.getBattlefield().contains(filterLifelink, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Menace if (game.getBattlefield().contains(filterMenace, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(new MenaceAbility(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(new MenaceAbility(), Duration.EndOfTurn, filterCreatures), source); } // Reach if (game.getBattlefield().contains(filterReach, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(ReachAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(ReachAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Skulk if (game.getBattlefield().contains(filterSkulk, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(new SkulkAbility(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(new SkulkAbility(), Duration.EndOfTurn, filterCreatures), source); } // Trample if (game.getBattlefield().contains(filterTrample, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } // Vigilance if (game.getBattlefield().contains(filterVigilance, source.getControllerId(), 1, game)) { - game.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn), source); + game.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn, filterCreatures), source); } return true; } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PickTheBrain.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PickTheBrain.java new file mode 100644 index 00000000000..fbd4458a1a8 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PickTheBrain.java @@ -0,0 +1,128 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.effects.common.search.SearchTargetGraveyardHandLibraryForCardNameAndExileEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetOpponent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class PickTheBrain extends CardImpl { + + public PickTheBrain(UUID ownerId) { + super(ownerId, 129, "Pick the Brain", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{2}{B}"); + this.expansionSetCode = "SOI"; + + // Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. + // Delirium — If there are four or more card types among cards in your graveyard, search that player's graveyard, hand, and library for any number of cards with the same name as the exiled card, exile those cards, then that player shuffles his or her library. + this.getSpellAbility().addEffect(new PickTheBrainEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + public PickTheBrain(final PickTheBrain card) { + super(card); + } + + @Override + public PickTheBrain copy() { + return new PickTheBrain(this); + } +} + +class PickTheBrainEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExileEffect { + + private static final FilterCard filter = new FilterCard("a nonland card"); + + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.LAND))); + } + + public PickTheBrainEffect() { + super(true, "that card's controller", "all cards with the same name as that card"); + } + + public PickTheBrainEffect(final PickTheBrainEffect effect) { + super(effect); + } + + @Override + public PickTheBrainEffect copy() { + return new PickTheBrainEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player opponent = game.getPlayer(this.getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (opponent != null && controller != null) { + if (!opponent.getHand().isEmpty()) { + opponent.revealCards("Exile " + filter.getMessage(), opponent.getHand(), game); + TargetCard target = new TargetCard(Zone.HAND, filter); + if (controller.choose(Outcome.Exile, opponent.getHand(), target, game)) { + Card card = opponent.getHand().get(target.getFirstTarget(), game); + if (card != null) { + controller.moveCardToExileWithInfo(card, null, "", source.getSourceId(), game, Zone.HAND, true); + + // Check the Delirium condition + if (!DeliriumCondition.getInstance().apply(game, source)) { + return true; + } + return this.applySearchAndExile(game, source, card.getName(), opponent.getId()); + } + } + } + } + return false; + } + + @Override + public String getText(Mode mode) { + return "Target opponent reveals his or her hand. You choose a nonland card from it and exile that card.

" + + "Delirium — If there are four or more card types among cards in your graveyard, " + + "search that player's graveyard, hand, and library for any number of cards " + + "with the same name as the exiled card, exile those cards, then that player shuffles his or her library"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java index d7474149370..1f8bc6cacce 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PrizedAmalgam.java @@ -41,20 +41,22 @@ import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.ControllerPredicate; +import mage.filter.predicate.other.OwnerPredicate; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; +import mage.watchers.common.CastFromGraveyardWatcher; /** * * @author LevelX2 */ public class PrizedAmalgam extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature"); + static { - filter.add(new ControllerPredicate(TargetController.YOU)); + filter.add(new OwnerPredicate(TargetController.YOU)); } public PrizedAmalgam(UUID ownerId) { @@ -66,7 +68,8 @@ public class PrizedAmalgam extends CardImpl { // Whenever a creature enters the battlefield, if it entered from your graveyard or you cast it from your graveyard, return Prized Amalgam from your graveyard to the battlefield tapped at the beginning of the next end step. this.addAbility(new PrizedAmalgamTriggerdAbility(new CreateDelayedTriggeredAbilityEffect( - new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ReturnSourceFromGraveyardToBattlefieldEffect(true))), filter)); + new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new ReturnSourceFromGraveyardToBattlefieldEffect(true))), filter), + new CastFromGraveyardWatcher()); } public PrizedAmalgam(final PrizedAmalgam card) { @@ -96,7 +99,20 @@ class PrizedAmalgamTriggerdAbility extends EntersBattlefieldAllTriggeredAbility @Override public boolean checkTrigger(GameEvent event, Game game) { - return super.checkTrigger(event, game) && ((EntersTheBattlefieldEvent) event).getFromZone().equals(Zone.GRAVEYARD); + if (super.checkTrigger(event, game)) { + EntersTheBattlefieldEvent entersEvent = (EntersTheBattlefieldEvent) event; + if (entersEvent.getFromZone().equals(Zone.GRAVEYARD)) { + return true; + } + if (entersEvent.getFromZone().equals(Zone.STACK) && entersEvent.getTarget().getControllerId().equals(getControllerId())) { + CastFromGraveyardWatcher watcher = (CastFromGraveyardWatcher) game.getState().getWatchers().get(CastFromGraveyardWatcher.class.getName()); + if (watcher != null) { + int zcc = game.getState().getZoneChangeCounter(event.getSourceId()); + return watcher.spellWasCastFromGraveyard(event.getSourceId(), zcc - 1); + } + } + } + return false; } @Override diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StartledAwake.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StartledAwake.java index 1c3c3e8d901..01d6a0b5e05 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StartledAwake.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StartledAwake.java @@ -33,7 +33,6 @@ import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.PutLibraryIntoGraveTargetEffect; -import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -64,7 +63,7 @@ public class StartledAwake extends CardImpl { // {3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery. this.addAbility(new TransformAbility()); - Ability ability = new ActivateAsSorceryActivatedAbility(Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(), new ManaCostsImpl("{3}{U}{U}")); + Ability ability = new ActivateAsSorceryActivatedAbility(Zone.GRAVEYARD, new StartledAwakeReturnTransformedEffect(), new ManaCostsImpl("{3}{U}{U}")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ThingInTheIce.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ThingInTheIce.java index 1ba63cf46b1..96c1431df0a 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ThingInTheIce.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ThingInTheIce.java @@ -29,15 +29,12 @@ package mage.sets.shadowsoverinnistrad; import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.ReturnToHandFromBattlefieldAllEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; @@ -46,16 +43,10 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterSpell; -import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.filter.predicate.mageobject.SubtypePredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; /** * @@ -99,8 +90,6 @@ public class ThingInTheIce extends CardImpl { ability.addEffect(effect); this.addAbility(ability); - // When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands. - this.addAbility(new AwokenHorrorAbility()); } public ThingInTheIce(final ThingInTheIce card) { @@ -112,58 +101,3 @@ public class ThingInTheIce extends CardImpl { return new ThingInTheIce(this); } } - -class AwokenHorrorAbility extends TriggeredAbilityImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblue creatures"); - - static { - filter.add(Predicates.not(new SubtypePredicate("Horror"))); - } - - public AwokenHorrorAbility() { - super(Zone.BATTLEFIELD, new ReturnToHandFromBattlefieldAllEffect(filter), false); - // Rule only shown on the night side - this.setRuleVisible(false); - } - - public AwokenHorrorAbility(final AwokenHorrorAbility ability) { - super(ability); - } - - @Override - public AwokenHorrorAbility copy() { - return new AwokenHorrorAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.TRANSFORMED; - } - - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent currentSourceObject = (Permanent) getSourceObjectIfItStillExists(game); - if (currentSourceObject != null && currentSourceObject.isNightCard()) { - return true; - } - return super.isInUseableZone(game, source, event); - } - - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(sourceId)) { - Permanent permanent = game.getPermanent(sourceId); - if (permanent != null && permanent.isTransformed()) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands."; - } -} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java index b6a5659b6e7..1a226328c49 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java @@ -31,14 +31,20 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.dynamicvalue.common.DiscardCostCardConvertedMana; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DoIfCostPaid; +import mage.cards.Card; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; +import mage.game.Game; import mage.target.common.TargetCreatureOrPlaneswalker; /** @@ -55,12 +61,15 @@ public class WolfOfDevilsBreach extends CardImpl { this.power = new MageInt(5); this.toughness = new MageInt(5); - // Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals + // Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals // damage to target creature or planeswalker equal to the discarded card's converted mana cost. - Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid(new DamageTargetEffect(new DiscardCostCardConvertedMana()), new ManaCostsImpl("{1}{R}")), true, + Costs toPay = new CostsImpl<>(); + toPay.add(new ManaCostsImpl<>("{1}{R}")); + toPay.add(new DiscardCardCost()); + Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid(new DamageTargetEffect(new WolfOfDevilsBreachDiscardCostCardConvertedMana()), toPay, + "Pay {1}{R} and discard a card to let {this} do damage to target creature or planeswalker equal to the discarded card's converted mana cost?", true), false, "Whenever {this} attacks you may pay {1}{R} and discard a card. If you do, {this} deals damage to target creature or planeswalker " - + "equal to the discarded card's converted mana cost."); - ability.addCost(new DiscardCardCost()); + + "equal to the discarded card's converted mana cost."); ability.addTarget(new TargetCreatureOrPlaneswalker()); this.addAbility(ability); } @@ -74,3 +83,44 @@ public class WolfOfDevilsBreach extends CardImpl { return new WolfOfDevilsBreach(this); } } + +class WolfOfDevilsBreachDiscardCostCardConvertedMana implements DynamicValue { + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + for (Effect sourceEffect : sourceAbility.getEffects()) { + if (sourceEffect instanceof DoIfCostPaid) { + Cost doCosts = ((DoIfCostPaid) sourceEffect).getCost(); + if (doCosts instanceof Costs) { + Costs costs = (Costs) doCosts; + for (Object cost : costs) { + if (cost instanceof DiscardCardCost) { + DiscardCardCost discardCost = (DiscardCardCost) cost; + int cmc = 0; + for (Card card : discardCost.getCards()) { + cmc += card.getManaCost().convertedManaCost(); + } + return cmc; + } + } + } + } + } + return 0; + } + + @Override + public WolfOfDevilsBreachDiscardCostCardConvertedMana copy() { + return new WolfOfDevilsBreachDiscardCostCardConvertedMana(); + } + + @Override + public String toString() { + return ""; + } + + @Override + public String getMessage() { + return "the discarded card's converted mana cost"; + } +} diff --git a/Mage.Sets/src/mage/sets/urzasdestiny/RayneAcademyChancellor.java b/Mage.Sets/src/mage/sets/urzasdestiny/RayneAcademyChancellor.java index 1edf03ea2da..235f1ae6edb 100644 --- a/Mage.Sets/src/mage/sets/urzasdestiny/RayneAcademyChancellor.java +++ b/Mage.Sets/src/mage/sets/urzasdestiny/RayneAcademyChancellor.java @@ -30,7 +30,7 @@ package mage.sets.urzasdestiny; import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.condition.common.EnchantedCondition; +import mage.abilities.condition.common.EnchantedSourceCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; @@ -76,7 +76,7 @@ public class RayneAcademyChancellor extends CardImpl { class RayneAcademyChancellorTriggeredAbility extends TriggeredAbilityImpl { RayneAcademyChancellorTriggeredAbility() { - super(Zone.BATTLEFIELD, new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(2), new DrawCardSourceControllerEffect(1), new EnchantedCondition(), "you may draw a card. You may draw an additional card if {this} is enchanted."), true); + super(Zone.BATTLEFIELD, new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(2), new DrawCardSourceControllerEffect(1), new EnchantedSourceCondition(), "you may draw a card. You may draw an additional card if {this} is enchanted."), true); } RayneAcademyChancellorTriggeredAbility(final RayneAcademyChancellorTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/sets/visions/Desertion.java b/Mage.Sets/src/mage/sets/visions/Desertion.java index 42d35efc92c..12118749278 100644 --- a/Mage.Sets/src/mage/sets/visions/Desertion.java +++ b/Mage.Sets/src/mage/sets/visions/Desertion.java @@ -27,6 +27,8 @@ */ package mage.sets.visions; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; @@ -36,10 +38,8 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; import mage.constants.ZoneDetail; -import mage.filter.FilterSpell; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Game; +import mage.game.stack.Spell; import mage.players.Player; import mage.target.TargetSpell; @@ -70,14 +70,6 @@ public class Desertion extends CardImpl { class DesertionEffect extends OneShotEffect { - private static final FilterSpell filter = new FilterSpell("artifact or creature spell"); - - static { - filter.add(Predicates.or( - new CardTypePredicate(CardType.ARTIFACT), - new CardTypePredicate(CardType.CREATURE))); - } - public DesertionEffect() { super(Outcome.Detriment); this.staticText = "Counter target spell. If an artifact or creature spell is countered this way, put that card onto the battlefield under your control instead of into its owner's graveyard."; @@ -96,7 +88,18 @@ class DesertionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - return game.getStack().counter(targetPointer.getFirst(game, source), source.getSourceId(), game, Zone.BATTLEFIELD, false, ZoneDetail.NONE); + Spell targetSpell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + if (targetSpell != null) { + Set cardTypes = new HashSet<>(targetSpell.getCardType()); + if (!cardTypes.isEmpty()) { + //targetPointer.getFirst(game, source) + if (cardTypes.contains(CardType.ARTIFACT) || cardTypes.contains(CardType.CREATURE)) { + return game.getStack().counter(targetSpell.getId(), source.getSourceId(), game, Zone.BATTLEFIELD, false, ZoneDetail.NONE); + } else { + return game.getStack().counter(targetSpell.getId(), source.getSourceId(), game); + } + } + } } return false; } diff --git a/Mage.Sets/src/mage/sets/worldwake/KitesailApprentice.java b/Mage.Sets/src/mage/sets/worldwake/KitesailApprentice.java index a81ad0a4a19..4877b213aaa 100644 --- a/Mage.Sets/src/mage/sets/worldwake/KitesailApprentice.java +++ b/Mage.Sets/src/mage/sets/worldwake/KitesailApprentice.java @@ -33,7 +33,7 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.EquippedCondition; +import mage.abilities.condition.common.EquippedSourceCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; @@ -61,9 +61,9 @@ public class KitesailApprentice extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); - ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), EquippedCondition.getInstance(), rule1); + ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), EquippedSourceCondition.getInstance(), rule1); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect1)); - ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FlyingAbility.getInstance()), EquippedCondition.getInstance(), rule2); + ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FlyingAbility.getInstance()), EquippedSourceCondition.getInstance(), rule2); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect2)); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java index e1a6b44e2f7..4af008e533e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java @@ -27,9 +27,12 @@ */ package org.mage.test.cards.abilities.keywords; +import mage.constants.CardType; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; +import mage.game.permanent.Permanent; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -152,7 +155,62 @@ public class TransformTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Autumnal Gloom", 0); assertPermanentCount(playerA, "Ancient of the Equinox", 1); + } + /** + * 4G Creature - Human Shaman Whenever a permanent you control transforms + * into a non-Human creature, put a 2/2 green Wolf creature token onto the + * battlefield. + * + * Reported bug: "It appears to trigger either when a non-human creature + * transforms OR when a creature transforms from a non-human into a human + * (as in when a werewolf flips back to the sun side), rather than when a + * creature transforms into a non-human, as is the intended function and + * wording of the card." + */ + @Test + public void testCultOfTheWaxingMoon() { + // Whenever a permanent you control transforms into a non-Human creature, put a 2/2 green Wolf creature token onto the battlefield. + addCard(Zone.BATTLEFIELD, playerA, "Cult of the Waxing Moon"); + // {1}{G} - Human Werewolf + // At the beginning of each upkeep, if no spells were cast last turn, transform Hinterland Logger. + addCard(Zone.BATTLEFIELD, playerA, "Hinterland Logger"); + + // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Timber Shredder. + setStopAt(2, PhaseStep.DRAW); + execute(); + + assertPermanentCount(playerA, "Cult of the Waxing Moon", 1); + assertPermanentCount(playerA, "Timber Shredder", 1); // Night-side card of Hinterland Logger, Werewolf (non-human) + assertPermanentCount(playerA, "Wolf", 1); // wolf token created + } + + /** + * Yeah, it sounds like the same thing. When Startled Awake is in the + * graveyard, you can pay CMC 5 to return it, flipped, to the battlefield as + * a 1/1 creature. However, after paying the 5 it returns unflipped and just + * stays on the battlefield as a sorcery, of which it can't be interacted + * with at all wording of the card." + */ + @Test + public void testStartledAwake() { + // Target opponent puts the top thirteen cards of his or her library into his or her graveyard. + // {3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery. + addCard(Zone.HAND, playerA, "Startled Awake"); // SORCERY {2}{U}{U}" + addCard(Zone.BATTLEFIELD, playerA, "Island", 9); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Startled Awake"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{U}{U}"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, 13); + assertGraveyardCount(playerA, "Startled Awake", 0); + assertPermanentCount(playerA, "Persistent Nightmare", 1); // Night-side card of Startled Awake + Permanent nightmare = getPermanent("Persistent Nightmare", playerA); + Assert.assertTrue("Has to have creature card type", nightmare.getCardType().contains(CardType.CREATURE)); + Assert.assertFalse("Has not to have sorcery card type", nightmare.getCardType().contains(CardType.SORCERY)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java index 388b4a85469..f8e148e2df9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java @@ -88,6 +88,38 @@ public class CopySpellTest extends CardTestPlayerBase { assertPowerToughness(playerB, "Silvercoat Lion", 2, 2); assertAbility(playerB, "Silvercoat Lion", FlyingAbility.getInstance(), false); } + + /** + * Reported bug: "Silverfur Partisan and fellow wolves did not trigger off of copies of Strength of Arms made by Zada, Hedron Grinder. + * Not sure about other spells, but I imagine similar results." + */ + @Test + public void ZadaHedronSilverfurPartisan() { + + // {2}{G} + // Trample + // Whenever a Wolf or Werewolf you control becomes the target of an instant or sorcery spell, put a 2/2 green Wolf creature token onto the battlefield. + addCard(Zone.BATTLEFIELD, playerA, "Silverfur Partisan"); // 2/2 Wolf Warrior + + // Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for each other creature you control that the spell could target. Each copy targets a different one of those creatures. + addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); + + // Target creature gets +3/+3 until end of turn. + addCard(Zone.HAND, playerA, "Giant Growth", 1); // {G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + + //castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Village Messenger"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Zada, Hedron Grinder"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Giant Growth", 1); + assertPowerToughness(playerA, "Silverfur Partisan", 5, 5); + assertPowerToughness(playerA, "Zada, Hedron Grinder", 6, 6); + assertPermanentCount(playerA, "Wolf", 1); // created from Silverfur ability + } @Test public void ZadaHedronGrinderBoostWithCharm() { @@ -172,5 +204,36 @@ public class CopySpellTest extends CardTestPlayerBase { assertHandCount(playerA, "Evermind", 1); assertHandCount(playerA, 3); // Evermind + 1 card from Evermind spliced on cast Into the fray and 1 from the copied spell with splice } + + /** + * {4}{U} Enchantment (Enchant Player) + * Whenever enchanted player casts an instant or sorcery spell, each other player may copy that spell + * and may choose new targets for the copy he or she controls. + * + * Reported bug: "A player with Curse of Echoes attached to them played Bribery and the player who controlled the curse had control + * of all 3 copies. This seems to be the case for all spells." + */ + @Test + public void testCurseOfEchoes() { + + addCard(Zone.HAND, playerA, "Curse of Echoes"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curse of Echoes"); + addTarget(playerA, playerB); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt"); + addTarget(playerB, playerA); // original target + setChoice(playerA, "Yes"); + addTarget(playerA, playerB); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertLife(playerA, 17); // still takes original spell's damage + assertLife(playerB, 17); // copy redirected + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/WerewolfRansackerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/WerewolfRansackerTest.java index 798ae387a0b..92484919e88 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/WerewolfRansackerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/WerewolfRansackerTest.java @@ -19,11 +19,11 @@ public class WerewolfRansackerTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); - assertLife(playerA, 20); - assertLife(playerB, 17); assertPermanentCount(playerB, "Ornithopter", 0); assertPermanentCount(playerA, "Afflicted Deserter", 0); assertPermanentCount(playerA, "Werewolf Ransacker", 1); + assertLife(playerA, 20); + assertLife(playerB, 17); } @Test @@ -69,5 +69,5 @@ public class WerewolfRansackerTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Ornithopter", 0); assertPermanentCount(playerA, "Afflicted Deserter", 1); assertPermanentCount(playerA, "Werewolf Ransacker", 0); - } + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ArchangelAvacynTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ArchangelAvacynTest.java new file mode 100644 index 00000000000..5dcfc479d8b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ArchangelAvacynTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.soi; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {3}{W}{W} Angel - day + * + * Flash Flying, vigilance When Archangel Avacyn enters the battlefield, + * creatures you control gain indestructible until end of turn. When a non-Angel + * creature you control dies, transform Archangel Avacyn at the beginning of the + * next upkeep. + * + * (Night) - red card When this creature transforms into Avacyn, the Purifier, + * it deals 3 damage to each other creature and each opponent. + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class ArchangelAvacynTest extends CardTestPlayerBase { + + /** + * Reported bug: "Archangel Avacyn damages herself when she transforms" + */ + @Test + public void basicTransformTest() { + // Flash + // Flying + // Vigilance + // When Archangel Avacyn enters the battlefield, creatures you control gain indestructible until end of turn. + // When a non-Angel creature you control dies, transform Archangel Avacyn at the beginning of the next upkeep. + addCard(Zone.BATTLEFIELD, playerA, "Archangel Avacyn"); + addCard(Zone.BATTLEFIELD, playerA, "Wall of Omens"); // 0/4 + addCard(Zone.HAND, playerA, "Elite Vanguard"); // 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Hill Giant"); // 3/1 + addCard(Zone.BATTLEFIELD, playerB, "Wall of Roots"); // 0/5 + addCard(Zone.HAND, playerB, "Shock"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elite Vanguard"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Shock"); + addTarget(playerB, "Elite Vanguard"); + setStopAt(3, PhaseStep.DRAW); + execute(); + + assertPermanentCount(playerA, "Avacyn, the Purifier", 1); + assertPermanentCount(playerA, "Wall of Omens", 1); + assertGraveyardCount(playerA, "Elite Vanguard", 1); + assertPermanentCount(playerB, "Wall of Roots", 1); + assertGraveyardCount(playerB, "Hill Giant", 1); + assertGraveyardCount(playerB, "Shock", 1); + + Permanent avacyn = getPermanent("Avacyn, the Purifier", playerA); + Assert.assertEquals("Damage to Avacyn, the Purifier should be 0 not 3", 0, avacyn.getDamage()); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ErdwalIlluminatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ErdwalIlluminatorTest.java new file mode 100644 index 00000000000..de915c58289 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ErdwalIlluminatorTest.java @@ -0,0 +1,78 @@ + +package org.mage.test.cards.single.soi; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author halljared + */ +public class ErdwalIlluminatorTest extends CardTestPlayerBase { + /** + * Whenever you investigate for the first time each turn, investigate an additional time. + */ + @Test + public void investigateFirstTimeTriggers() { + + addCard(Zone.HAND, playerA, "Thraben Inspector", 1); + addCard(Zone.BATTLEFIELD, playerA, "Erdwal Illuminator", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thraben Inspector"); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Clue", 2); + } + + @Test + public void ignoresOpponentInvestigateTriggers() { + + addCard(Zone.HAND, playerB, "Thraben Inspector", 1); + addCard(Zone.BATTLEFIELD, playerA, "Erdwal Illuminator", 1); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Thraben Inspector"); + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerB, "Clue", 1); + } + + @Test + public void ignoresSecondInvestigateTriggers() { + + addCard(Zone.HAND, playerA, "Thraben Inspector", 2); + addCard(Zone.BATTLEFIELD, playerA, "Erdwal Illuminator", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thraben Inspector"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thraben Inspector"); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Clue", 3); + } + + @Test + public void separateTurnsInvestigateTriggers() { + + addCard(Zone.HAND, playerA, "Thraben Inspector", 2); + addCard(Zone.BATTLEFIELD, playerA, "Erdwal Illuminator", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thraben Inspector"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Thraben Inspector"); + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Clue", 4); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/FlamebladeAngelTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/FlamebladeAngelTest.java new file mode 100644 index 00000000000..fdf74c8533d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/FlamebladeAngelTest.java @@ -0,0 +1,101 @@ +package org.mage.test.cards.single.soi; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * 4RR + * Creature - Angel + * Flying + * + * Whenever a source an opponent controls deals damage to you or a permanent you control, + * you may have Flameblade Angel deal 1 damage to that source's controller. + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class FlamebladeAngelTest extends CardTestPlayerBase { + + /** + * Reported bug: Not triggering when damage is dealt to the creatures I control. + */ + @Test + public void testDamageToCreature() { + + addCard(Zone.BATTLEFIELD, playerA, "Flameblade Angel"); + addCard(Zone.BATTLEFIELD, playerA, "Wall of Roots"); // 0/5 + addCard(Zone.HAND, playerB, "Shock"); // instant deals 2 dmg to creature/player + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Shock"); + addTarget(playerB, "Wall of Roots"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + Permanent roots = getPermanent("Wall of Roots", playerA); + Assert.assertEquals("Wall of Roots should have 2 damage dealt to it", 2, roots.getDamage()); + assertGraveyardCount(playerB, "Shock", 1); + assertLife(playerA, 20); + assertLife(playerB, 19); // Angel should deal 1 damage to Shock's controller + } + + /** + * Reported bug: Not triggering when damage is dealt to the creatures I control. + */ + @Test + public void testDamageToMultipleCreatures() { + + addCard(Zone.BATTLEFIELD, playerA, "Flameblade Angel"); + addCard(Zone.BATTLEFIELD, playerA, "Wall of Roots"); // 0/5 + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2 + addCard(Zone.HAND, playerB, "Shock", 2); // instant deals 2 dmg to creature/player + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Shock"); + addTarget(playerB, "Wall of Roots"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Shock"); + addTarget(playerB, "Grizzly Bears"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + Permanent roots = getPermanent("Wall of Roots", playerA); + Assert.assertEquals("Wall of Roots should have 2 damage dealt to it", 2, roots.getDamage()); + assertGraveyardCount(playerB, "Shock", 2); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertLife(playerA, 20); + assertLife(playerB, 18); // Angel should deal 1 damage twice to Shock's controller + } + + /** + * Reported bug: Not triggering when damage is dealt to the creatures I control. + */ + @Test + public void testCombatDamageToCreatures() { + + addCard(Zone.BATTLEFIELD, playerA, "Flameblade Angel"); + addCard(Zone.BATTLEFIELD, playerA, "Wall of Roots"); // 0/5 + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Elite Vanguard"); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Hill Giant"); // 3/3 + + attack(2, playerB, "Elite Vanguard"); + attack(2, playerB, "Hill Giant"); + block(2, playerA, "Wall of Roots", "Hill Giant"); + block(2, playerA, "Grizzly Bears", "Elite Vanguard"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + Permanent roots = getPermanent("Wall of Roots", playerA); + Assert.assertEquals("Wall of Roots should have 3 damage dealt to it", 3, roots.getDamage()); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerB, "Elite Vanguard", 1); + assertLife(playerA, 20); + assertLife(playerB, 18); // Angel should deal 1 damage twice + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java index 293af53834d..0cd8182c34f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/PrizedAmalgamTest.java @@ -6,98 +6,103 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - *1UB -* Creature - Zombie -* Whenever a creature enters the battlefield, if it entered from your graveyard or you cast it from your graveyard, -* return Prized Amalgam from your graveyard to the battlefield tapped at the beginning of the next end step. + * 1UB Creature - Zombie Whenever a creature enters the battlefield, if it + * entered from your graveyard or you cast it from your graveyard, return Prized + * Amalgam from your graveyard to the battlefield tapped at the beginning of the + * next end step. + * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public class PrizedAmalgamTest extends CardTestPlayerBase { - + /** * Reanimated creature recurs Prized Amalgam */ @Test public void testReanimation() { - + addCard(Zone.HAND, playerA, "Reanimate", 1); // {B} Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. addCard(Zone.GRAVEYARD, playerA, "Bronze Sable", 1); // (2) 2/1 addCard(Zone.GRAVEYARD, playerA, "Prized Amalgam", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate"); addTarget(playerA, "Bronze Sable"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertLife(playerA, 18); // loss of 2 from reanimate assertGraveyardCount(playerA, "Reanimate", 1); assertPermanentCount(playerA, "Bronze Sable", 1); assertPermanentCount(playerA, "Prized Amalgam", 1); assertTapped("Prized Amalgam", true); } - + /** - * Reported bug - Gravecrawler cast from grave does not trigger Prized Amalgam. + * Reported bug - Gravecrawler cast from grave does not trigger Prized + * Amalgam. */ @Test public void testGravecrawlerCastFromGrave() { - + addCard(Zone.GRAVEYARD, playerA, "Gravecrawler", 1); addCard(Zone.GRAVEYARD, playerA, "Prized Amalgam", 1); addCard(Zone.BATTLEFIELD, playerA, "Gnawing Zombie", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gravecrawler"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertPermanentCount(playerA, "Gravecrawler", 1); assertPermanentCount(playerA, "Prized Amalgam", 1); - assertTapped("Prized Amalgam", true); + assertTapped("Prized Amalgam", true); } - + /** - * Reported bug - creature returned from opponent's graveyard to battlefield by Ojutai's Command incorrectly triggers Prized Amalgam + * Reported bug - creature returned from opponent's graveyard to battlefield + * by Ojutai's Command incorrectly triggers Prized Amalgam */ @Test public void testOpponentReturnsCreatureFromGrave() { - + addCard(Zone.HAND, playerA, "Reanimate", 1); addCard(Zone.GRAVEYARD, playerA, "Hill Giant", 1); // {3}{R} 3/3 addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); addCard(Zone.GRAVEYARD, playerB, "Prized Amalgam", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate"); addTarget(playerA, "Hill Giant"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertLife(playerA, 16); // lose 4 life from reanimate 4 CMC assertPermanentCount(playerA, "Hill Giant", 1); assertPermanentCount(playerB, "Prized Amalgam", 0); // should not recur assertGraveyardCount(playerB, "Prized Amalgam", 1); // stays in grave } - - /* + + /* * Test opponent returning a card from your graveyard to battlefield. - */ + */ @Test public void testOpponentReturnsCreatureFromYourGrave() { - - addCard(Zone.HAND, playerA, "Necromantic Summons", 1); // Put target creature card from a graveyard onto the battlefield under your control. + + addCard(Zone.HAND, playerA, "Necromantic Summons", 1); // Put target creature card from a graveyard onto the battlefield under your control. addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); - addCard(Zone.GRAVEYARD, playerB, "Merfolk Looter", 1); // {U} 1/1 + addCard(Zone.GRAVEYARD, playerB, "Merfolk Looter", 1); // {U} 1/1 + // Whenever a creature enters the battlefield, if it entered from your graveyard or you cast it from your graveyard, return Prized Amalgam from your graveyard to the battlefield tapped at the beginning of the next end step. addCard(Zone.GRAVEYARD, playerB, "Prized Amalgam", 1); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Necromantic Summons"); addTarget(playerA, "Merfolk Looter"); setStopAt(1, PhaseStep.END_TURN); execute(); - + assertPermanentCount(playerA, "Merfolk Looter", 1); - assertPermanentCount(playerB, "Prized Amalgam", 0); // should not recur - assertGraveyardCount(playerB, "Prized Amalgam", 1); // stays in grave + assertPermanentCount(playerB, "Prized Amalgam", 1); + assertGraveyardCount(playerB, "Prized Amalgam", 0); + assertTapped("Prized Amalgam", true); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java index 6327a43e623..4373220e34e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java @@ -1,75 +1,71 @@ package org.mage.test.cards.single.soi; -import java.util.Set; -import mage.cards.Card; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * 3BG - Legendary Creature - Frog Horror - Deathtouch - - At the beginning of your upkeep, sacrifice The Gitrog Monster unless you sacrifice a land. - - You may play an additional land on each of your turns. - - Whenever one or more land cards are put into your graveyard from anywhere, draw a card. + * 3BG Legendary Creature - Frog Horror Deathtouch + * + * At the beginning of your upkeep, sacrifice The Gitrog Monster unless you + * sacrifice a land. + * + * You may play an additional land on each of your turns. + * + * Whenever one or more land cards are put into your graveyard from anywhere, + * draw a card. * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ -public class TheGitrogMonsterTest extends CardTestPlayerBase { - +public class TheGitrogMonsterTest extends CardTestPlayerBase { + /** * Basic sacrifice test when no lands are present */ @Test public void noLandsSacrificeGitrog() { - + addCard(Zone.HAND, playerA, "The Gitrog Monster", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - + addCard(Zone.HAND, playerB, "Armageddon", 1); // destroy all lands addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Gitrog Monster"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Armageddon"); setStopAt(3, PhaseStep.DRAW); execute(); - - Set hand = playerA.getHand().getCards(currentGame); + assertGraveyardCount(playerA, "Swamp", 3); assertGraveyardCount(playerA, "Forest", 2); assertGraveyardCount(playerA, "The Gitrog Monster", 1); assertGraveyardCount(playerB, "Plains", 4); assertGraveyardCount(playerB, "Armageddon", 1); assertPermanentCount(playerA, "The Gitrog Monster", 0); - assertHandCount(playerA, 6); // 1 for turn, 5 more for lands that hit the grave + assertHandCount(playerA, 2); // 1 for turn, 1 more for lands that hit the grave } - + /** * Basic sacrifice test when there is a land */ @Test public void hasLandsSacrificeLand() { - + addCard(Zone.HAND, playerA, "The Gitrog Monster", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Gitrog Monster"); - + // on 3rd turn during upkeep opt to sacrifice a land setChoice(playerA, "Yes"); addTarget(playerA, "Swamp"); setStopAt(3, PhaseStep.DRAW); execute(); - - Set hand = playerA.getHand().getCards(currentGame); + assertGraveyardCount(playerA, "Swamp", 1); assertPermanentCount(playerA, "The Gitrog Monster", 1); assertHandCount(playerA, 2); // 1 for turn, 1 more for land sacrificed diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ThingInTheIceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ThingInTheIceTest.java new file mode 100644 index 00000000000..045b838f7bd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/ThingInTheIceTest.java @@ -0,0 +1,62 @@ +package org.mage.test.cards.single.soi; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * Defender + * + * Thing in the Ice enters the battlefield with four ice counters on it. + * + * Whenever you cast an instant or sorcery spell, remove an ice counter from + * Thing in the Ice. Then if it has no ice counters on it, transform it. When + * this creature transforms into Awoken Horror, return all non-Horror creatures + * to their owners' hands. + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class ThingInTheIceTest extends CardTestPlayerBase { + + /** + * Reported bug: When Thing in the Ice transforms, it bounces Clue tokens. + * + */ + @Test + public void testClueTokens() { + // Whenever a land enters the battlefield under your control, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.") + // Whenever you sacrifice a Clue, put a +1/+1 counter on Tireless Tracker. + addCard(Zone.BATTLEFIELD, playerA, "Tireless Tracker", 1); // Human, Scout 3/2 + addCard(Zone.HAND, playerA, "Forest", 1); + // Defender + // Thing in the Ice enters the battlefield with four ice counters on it. + // Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it. + // When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands. + addCard(Zone.BATTLEFIELD, playerB, "Thing in the Ice", 1); + // Target creature gains haste until end of turn. + // Draw a card. + addCard(Zone.HAND, playerB, "Expedite", 4); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 4); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); // creates a clue + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Expedite"); + addTarget(playerB, "Thing in the Ice"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Expedite"); + addTarget(playerB, "Thing in the Ice"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Expedite"); + addTarget(playerB, "Thing in the Ice"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Expedite"); + addTarget(playerB, "Thing in the Ice"); // remove all 4 ice counters to transform it + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Clue", 1); + assertHandCount(playerA, "Tireless Tracker", 1); // returned to hand + assertPermanentCount(playerA, "Tireless Tracker", 0); + assertPermanentCount(playerB, "Awoken Horror", 1); // transformed + assertGraveyardCount(playerB, "Expedite", 4); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/WolfOfDevilsBreachTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/WolfOfDevilsBreachTest.java index 0223b44a08f..1e3dbf00dad 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/WolfOfDevilsBreachTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/WolfOfDevilsBreachTest.java @@ -6,54 +6,54 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * 3RR -Creature - Elemental Wolf - *Whenever Wolf of Devil's Breach attacks, you may pay 1R and discard a card. - * If you do, Wolf of Devil's Breach deals damage to target creature or planeswalker equal to the discarded card's converted mana cost. + * 3RR Creature - Elemental Wolf Whenever Wolf of Devil's Breach attacks, you + * may pay 1R and discard a card. If you do, Wolf of Devil's Breach deals damage + * to target creature or planeswalker equal to the discarded card's converted + * mana cost. + * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public class WolfOfDevilsBreachTest extends CardTestPlayerBase { - - /** - * - */ + @Test public void attackChooseToPay() { - + // Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals + // damage to target creature or planeswalker equal to the discarded card's converted mana cost. addCard(Zone.BATTLEFIELD, playerA, "Wolf of Devil's Breach", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.HAND, playerA, "Bronze Sable", 1); // (2) 2/1 addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); // 2/2 - + attack(1, playerA, "Wolf of Devil's Breach"); setChoice(playerA, "Yes"); setChoice(playerA, "Bronze Sable"); addTarget(playerA, "Grizzly Bears"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); - + assertGraveyardCount(playerA, "Bronze Sable", 1); assertGraveyardCount(playerB, "Grizzly Bears", 1); } - + /** * */ @Test public void attackDoNotPay() { - + // Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals + // damage to target creature or planeswalker equal to the discarded card's converted mana cost. addCard(Zone.BATTLEFIELD, playerA, "Wolf of Devil's Breach", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.HAND, playerA, "Bronze Sable", 1); // (2) 2/1 addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); // 2/2 - + attack(1, playerA, "Wolf of Devil's Breach"); setChoice(playerA, "No"); setChoice(playerA, "Bronze Sable"); addTarget(playerA, "Grizzly Bears"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); - + assertHandCount(playerA, "Bronze Sable", 1); // never discarded assertGraveyardCount(playerA, "Bronze Sable", 0); assertGraveyardCount(playerB, "Grizzly Bears", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellCastTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellCastTriggerTest.java index ce7aaa7db61..16fd900b355 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellCastTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellCastTriggerTest.java @@ -36,7 +36,6 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * * @author LevelX2 */ - public class SpellCastTriggerTest extends CardTestPlayerBase { /** @@ -50,7 +49,7 @@ public class SpellCastTriggerTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Sunscorch Regent", 1); addCard(Zone.HAND, playerB, "Lightning Bolt"); - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); @@ -59,9 +58,9 @@ public class SpellCastTriggerTest extends CardTestPlayerBase { assertLife(playerA, 18); // 20 -3 +1 assertLife(playerB, 20); - + assertGraveyardCount(playerB, "Lightning Bolt", 1); - + assertPowerToughness(playerA, "Sunscorch Regent", 5, 4); } @@ -69,13 +68,13 @@ public class SpellCastTriggerTest extends CardTestPlayerBase { * Monastery Mentor triggers are causing a "rollback" error. */ @Test - public void testMonasteryMentor() { + public void testMonasteryMentor() { // Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.) // Whenever you cast a noncreature spell, put a 1/1 white Monk creature token with prowess onto the battlefield. addCard(Zone.BATTLEFIELD, playerA, "Monastery Mentor", 1); addCard(Zone.HAND, playerA, "Lightning Bolt", 2); - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); @@ -83,14 +82,67 @@ public class SpellCastTriggerTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertLife(playerA, 20); + assertLife(playerA, 20); assertLife(playerB, 14); - + assertGraveyardCount(playerA, "Lightning Bolt", 2); assertPermanentCount(playerA, "Monk", 2); assertPowerToughness(playerA, "Monk", 2, 2); assertPowerToughness(playerA, "Monk", 1, 1); - + assertPowerToughness(playerA, "Monastery Mentor", 4, 4); - } + } + + @Test + public void testHarnessTheStormFirstTurn() { + // Whenever you cast an instant or sorcery spell from your hand, you may cast target card with the same name as that spell from your graveyard. + addCard(Zone.BATTLEFIELD, playerA, "Harness the Storm", 1); + + // Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player." + addCard(Zone.HAND, playerA, "Dance with Devils", 1); // {3}{R} + addCard(Zone.GRAVEYARD, playerA, "Dance with Devils", 1); // {3}{R} + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dance with Devils"); + setChoice(playerA, "Yes"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Dance with Devils", 2); + assertPermanentCount(playerA, "Devil", 4); + } + + /** + * I had cast Dance with Devils the turn before. On this turn I was casting + * Read the Bones. The enchantment should not have triggered and if it did + * it should have asked me to cast read the bones. + */ + @Test + public void testHarnessTheStormThirdTurn() { + // Whenever you cast an instant or sorcery spell from your hand, you may cast target card with the same name as that spell from your graveyard. + addCard(Zone.BATTLEFIELD, playerA, "Harness the Storm", 1); + + // Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player." + addCard(Zone.HAND, playerA, "Dance with Devils", 1); // {3}{R} + // Scry 2, then draw two cards. You lose 2 life. + addCard(Zone.HAND, playerA, "Read the Bones", 1); // {2}{B} + addCard(Zone.GRAVEYARD, playerA, "Read the Bones", 1); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dance with Devils"); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Read the Bones"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Dance with Devils", 1); + assertPermanentCount(playerA, "Devil", 2); + assertGraveyardCount(playerA, "Read the Bones", 2); + assertHandCount(playerA, 5); // one normally drawn + 4 from Read the Bones + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java new file mode 100644 index 00000000000..6c508213d9d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/CreepingDreadTest.java @@ -0,0 +1,155 @@ +package org.mage.test.multiplayer; + +import java.io.FileNotFoundException; +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBase; + +/** + * Enchantment {3}{B} + * At the beginning of your upkeep, each player discards a card. + * Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life. + * (Players reveal the discarded cards simultaneously.) + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com + */ +public class CreepingDreadTest extends CardTestMultiPlayerBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } + + /** + * Discard creature and all opponents who discard creature lose 3 life + */ + @Test + public void basicTest() { + + addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); + addCard(Zone.HAND, playerA, "Merfolk Looter"); + addCard(Zone.HAND, playerB, "Hill Giant"); + addCard(Zone.HAND, playerC, "Elite Vanguard"); + addCard(Zone.HAND, playerD, "Bone Saw"); + + setChoice(playerA, "Merfolk Looter"); + setChoice(playerB, "Hill Giant"); + setChoice(playerC, "Elite Vanguard"); + setChoice(playerD, "Bone Saw"); + + setStopAt(1, PhaseStep.DRAW); + execute(); + + assertGraveyardCount(playerA, "Merfolk Looter", 1); + assertGraveyardCount(playerB, "Hill Giant", 1); + assertGraveyardCount(playerC, "Elite Vanguard", 1); + assertGraveyardCount(playerD, "Bone Saw", 1); + + assertLife(playerA, 40); // 4-player commander so 40 life starting + assertLife(playerB, 37); // matched creature type + assertLife(playerC, 37); + assertLife(playerD, 40); // no match + } + + /** + * Discard Artifact Creature and all opponents who discard either an Artifact or Creature lose 3 life + */ + @Test + public void twoTypesTest() { + + addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); + addCard(Zone.HAND, playerA, "Bronze Sable"); + addCard(Zone.HAND, playerB, "Hill Giant"); + addCard(Zone.HAND, playerC, "Swamp"); + addCard(Zone.HAND, playerD, "Bone Saw"); + + setChoice(playerA, "Bronze Sable"); + setChoice(playerB, "Hill Giant"); + setChoice(playerC, "Swamp"); + setChoice(playerD, "Bone Saw"); + + setStopAt(1, PhaseStep.DRAW); + execute(); + + assertGraveyardCount(playerA, "Bronze Sable", 1); + assertGraveyardCount(playerB, "Hill Giant", 1); + assertGraveyardCount(playerC, "Swamp", 1); + assertGraveyardCount(playerD, "Bone Saw", 1); + + assertLife(playerA, 40); // artifact-creature discarded + assertLife(playerB, 37); // creature + assertLife(playerC, 40); // neither + assertLife(playerD, 37); // artifact + } + + /** + * Discard enchantment and no opponents discard an enchantment, so no one loses life + */ + @Test + public void noMatchesTest() { + + addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); + addCard(Zone.HAND, playerA, "Moat"); // enchantment + addCard(Zone.HAND, playerB, "Hill Giant"); + addCard(Zone.HAND, playerC, "Swamp"); + addCard(Zone.HAND, playerD, "Bone Saw"); + + setChoice(playerA, "Moat"); + setChoice(playerB, "Hill Giant"); + setChoice(playerC, "Swamp"); + setChoice(playerD, "Bone Saw"); + + setStopAt(1, PhaseStep.DRAW); + execute(); + + assertGraveyardCount(playerA, "Moat", 1); + assertGraveyardCount(playerB, "Hill Giant", 1); + assertGraveyardCount(playerC, "Swamp", 1); + assertGraveyardCount(playerD, "Bone Saw", 1); + + assertLife(playerA, 40); // no matches + assertLife(playerB, 40); + assertLife(playerC, 40); + assertLife(playerD, 40); + } + + /** + * Upkeep player has no cards to discard, so no matches + */ + @Test + public void noDiscardNoMatches() { + + addCard(Zone.BATTLEFIELD, playerA, "Creeping Dread"); + addCard(Zone.HAND, playerB, "Hill Giant"); + addCard(Zone.HAND, playerC, "Swamp"); + addCard(Zone.HAND, playerD, "Bone Saw"); + + setChoice(playerB, "Hill Giant"); + setChoice(playerC, "Swamp"); + setChoice(playerD, "Bone Saw"); + + setStopAt(1, PhaseStep.DRAW); + execute(); + + assertGraveyardCount(playerB, "Hill Giant", 1); + assertGraveyardCount(playerC, "Swamp", 1); + assertGraveyardCount(playerD, "Bone Saw", 1); + + assertLife(playerA, 40); // no matches + assertLife(playerB, 40); + assertLife(playerC, 40); + assertLife(playerD, 40); + } +} diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 9dd6cc256ee..191264b0645 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1167,6 +1167,9 @@ public abstract class AbilityImpl implements Ability { public MageObject getSourceObjectIfItStillExists(Game game) { MageObject currentObject = game.getObject(getSourceId()); if (currentObject != null) { + if (sourceObject == null) { + setSourceObject(currentObject, game); + } MageObjectReference mor = new MageObjectReference(currentObject, game); if (mor.getZoneChangeCounter() == getSourceObjectZoneChangeCounter()) { // source object has meanwhile not changed zone diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java index b406ac4ebfb..f0e49882ae7 100644 --- a/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BeginningOfEndStepTriggeredAbility.java @@ -156,7 +156,7 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl { private String generateConditionString() { if (interveningIfClauseCondition != null) { - return new StringBuilder(interveningIfClauseCondition.toString()).append(", ").toString(); + return "if {this} is " + interveningIfClauseCondition.toString() + ", "; } switch (getZone()) { case GRAVEYARD: diff --git a/Mage/src/main/java/mage/abilities/condition/common/EnchantedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/EnchantedSourceCondition.java similarity index 89% rename from Mage/src/main/java/mage/abilities/condition/common/EnchantedCondition.java rename to Mage/src/main/java/mage/abilities/condition/common/EnchantedSourceCondition.java index 19d33148ede..133fb9c3b0c 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/EnchantedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/EnchantedSourceCondition.java @@ -28,9 +28,9 @@ package mage.abilities.condition.common; import java.util.UUID; -import mage.constants.CardType; import mage.abilities.Ability; import mage.abilities.condition.Condition; +import mage.constants.CardType; import mage.game.Game; import mage.game.permanent.Permanent; @@ -38,15 +38,15 @@ import mage.game.permanent.Permanent; * * @author North */ -public class EnchantedCondition implements Condition { +public class EnchantedSourceCondition implements Condition { private int numberOfEnchantments; - public EnchantedCondition() { + public EnchantedSourceCondition() { this(1); } - public EnchantedCondition(int numberOfEnchantments) { + public EnchantedSourceCondition(int numberOfEnchantments) { this.numberOfEnchantments = numberOfEnchantments; } @@ -58,7 +58,7 @@ public class EnchantedCondition implements Condition { for (UUID uuid : permanent.getAttachments()) { Permanent attached = game.getBattlefield().getPermanent(uuid); if (attached != null && attached.getCardType().contains(CardType.ENCHANTMENT)) { - if (++numberOfFoundEnchantments >= numberOfEnchantments) { + if (++numberOfFoundEnchantments >= numberOfEnchantments) { return true; } } @@ -66,4 +66,9 @@ public class EnchantedCondition implements Condition { } return (numberOfFoundEnchantments >= numberOfEnchantments); } + + @Override + public String toString() { + return "enchanted"; + } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/EquippedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java similarity index 89% rename from Mage/src/main/java/mage/abilities/condition/common/EquippedCondition.java rename to Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java index 53d25c429f7..20fa0bb2ab1 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/EquippedCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/EquippedSourceCondition.java @@ -38,9 +38,9 @@ import mage.game.permanent.Permanent; * * @author nantuko */ -public class EquippedCondition implements Condition { +public class EquippedSourceCondition implements Condition { - private static final EquippedCondition fInstance = new EquippedCondition(); + private static final EquippedSourceCondition fInstance = new EquippedSourceCondition(); public static Condition getInstance() { return fInstance; @@ -59,4 +59,10 @@ public class EquippedCondition implements Condition { } return false; } + + @Override + public String toString() { + return "equipped"; + } + } diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java index d34f2c49c7e..8f85ed82836 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java @@ -67,7 +67,7 @@ public class DiscardTargetCost extends CostImpl { @Override public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { this.cards.clear(); - this.targets.clearChosen();; + this.targets.clearChosen(); Player player = game.getPlayer(controllerId); if (player == null) { return false; diff --git a/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java index 6065eb3584c..f4dba03cb23 100644 --- a/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java @@ -32,6 +32,7 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.TransformAbility; import mage.cards.Card; import mage.constants.CardType; import mage.constants.Duration; @@ -90,6 +91,10 @@ public class AuraReplacementEffect extends ReplacementEffectImpl { UUID sourceId = event.getSourceId(); UUID controllerId = event.getPlayerId(); + if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()) != null) { + card = card.getSecondCardFace(); + } + // Aura cards that go to battlefield face down (Manifest) don't have to select targets if (card.isFaceDown(game)) { return false; @@ -167,6 +172,7 @@ public class AuraReplacementEffect extends ReplacementEffectImpl { } Player targetPlayer = game.getPlayer(targetId); if (targetCard != null || targetPermanent != null || targetPlayer != null) { + card = game.getCard(event.getTargetId()); card.removeFromZone(game, fromZone, sourceId); card.updateZoneChangeCounter(game); PermanentCard permanent = new PermanentCard(card, (controllingPlayer == null ? card.getOwnerId() : controllingPlayer.getId()), game); @@ -200,7 +206,12 @@ public class AuraReplacementEffect extends ReplacementEffectImpl { if (((ZoneChangeEvent) event).getToZone().equals(Zone.BATTLEFIELD) && !(((ZoneChangeEvent) event).getFromZone().equals(Zone.STACK))) { Card card = game.getCard(event.getTargetId()); - if (card != null && card.getCardType().contains(CardType.ENCHANTMENT) && card.hasSubtype("Aura")) { + if (card != null && (card.getCardType().contains(CardType.ENCHANTMENT) && card.hasSubtype("Aura") + || // in case of transformable enchantments + (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()) != null + && card.getSecondCardFace() != null + && card.getSecondCardFace().getCardType().contains(CardType.ENCHANTMENT) + && card.getSecondCardFace().hasSubtype("Aura")))) { return true; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java index e16c66de7bb..edc48080f57 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java @@ -90,6 +90,10 @@ public class DoIfCostPaid extends OneShotEffect { return game.getPlayer(source.getControllerId()); } + public Cost getCost() { + return cost; + } + @Override public String getText(Mode mode) { if (!staticText.isEmpty()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java index 4e3ee10d2c6..a3851a7c350 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java @@ -31,6 +31,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; +import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.Outcome; import mage.constants.Zone; @@ -78,20 +79,30 @@ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEff public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Card card = null; + Cards cardsToMove = new CardsImpl(); if (fromExileZone) { UUID exilZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); if (exilZoneId != null) { ExileZone exileZone = game.getExile().getExileZone(exilZoneId); - if (exileZone != null && getTargetPointer().getFirst(game, source) != null) { - card = exileZone.get(getTargetPointer().getFirst(game, source), game); + if (exileZone != null) { + for (UUID cardId : getTargetPointer().getTargets(game, source)) { + Card card = exileZone.get(cardId, game); + if (card != null) { + cardsToMove.add(card); + } + } } } } else { - card = game.getCard(getTargetPointer().getFirst(game, source)); + for (UUID cardId : getTargetPointer().getTargets(game, source)) { + Card card = game.getCard(cardId); + if (card != null) { + cardsToMove.add(card); + } + } } - if (card != null) { - controller.moveCards(new CardsImpl(card).getCards(game), + if (!cardsToMove.isEmpty()) { + controller.moveCards(cardsToMove.getCards(game), Zone.BATTLEFIELD, source, game, tapped, false, true, null); return true; } diff --git a/Mage/src/main/java/mage/constants/PlayerAction.java b/Mage/src/main/java/mage/constants/PlayerAction.java index 5568ac2f55d..5f920906f89 100644 --- a/Mage/src/main/java/mage/constants/PlayerAction.java +++ b/Mage/src/main/java/mage/constants/PlayerAction.java @@ -53,6 +53,8 @@ public enum PlayerAction { MANA_AUTO_PAYMENT_OFF, MANA_AUTO_PAYMENT_RESTRICTED_ON, MANA_AUTO_PAYMENT_RESTRICTED_OFF, + USE_FIRST_MANA_ABILITY_ON, + USE_FIRST_MANA_ABILITY_OFF, RESET_AUTO_SELECT_REPLACEMENT_EFFECTS, REVOKE_PERMISSIONS_TO_SEE_HAND_CARDS, REQUEST_PERMISSION_TO_SEE_HAND_CARDS, diff --git a/Mage/src/main/java/mage/constants/UseFirstManaAbilityMode.java b/Mage/src/main/java/mage/constants/UseFirstManaAbilityMode.java new file mode 100644 index 00000000000..2e56c7ba6c1 --- /dev/null +++ b/Mage/src/main/java/mage/constants/UseFirstManaAbilityMode.java @@ -0,0 +1,12 @@ +package mage.constants; + +/** + * Allows user to either tap a land for the first mode directly (shortcut) + * or have the normal method which pops up a menu + * + * @author spjspj + */ +public enum UseFirstManaAbilityMode { + + NORMAL, FIRST +} diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 3e44fb3657d..700865fd279 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -348,6 +348,8 @@ public interface Game extends MageItem, Serializable { void setManaPaymentMode(UUID playerId, boolean autoPayment); void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted); + + void setUseFirstManaAbility(UUID playerId, boolean useFirstManaAbility); void undo(UUID playerId); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index debe5347a16..e38d98857e6 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1238,6 +1238,14 @@ public abstract class GameImpl implements Game, Serializable { } } + @Override + public synchronized void setUseFirstManaAbility(UUID playerId, boolean useFirstManaAbility) { + Player player = state.getPlayer(playerId); + if (player != null) { + player.getUserData().setUseFirstManaAbility(useFirstManaAbility); + } + } + @Override public void playPriority(UUID activePlayerId, boolean resuming) { int errorContinueCounter = 0; diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 752c7ed53fb..acbcb8db002 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -32,8 +32,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import mage.MageObject; import mage.abilities.Abilities; @@ -59,6 +63,8 @@ import mage.game.combat.CombatGroup; import mage.game.command.Command; import mage.game.command.CommandObject; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.events.ZoneChangeGroupEvent; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.stack.SpellStack; @@ -655,7 +661,9 @@ public class GameState implements Serializable, Copyable { if (!simultaneousEvents.isEmpty() && !getTurn().isEndTurnRequested()) { // it can happen, that the events add new simultaneous events, so copy the list before List eventsToHandle = new ArrayList<>(); + List eventGroups = createEventGroups(simultaneousEvents, game); eventsToHandle.addAll(simultaneousEvents); + eventsToHandle.addAll(eventGroups); simultaneousEvents.clear(); for (GameEvent event : eventsToHandle) { this.handleEvent(event, game); @@ -684,6 +692,76 @@ public class GameState implements Serializable, Copyable { return effects.replaceEvent(event, game); } + public List createEventGroups(List events, Game game) { + + class ZoneChangeData { + + private final Zone fromZone; + private final Zone toZone; + private final UUID sourceId; + private final UUID playerId; + + public ZoneChangeData(UUID sourceId, UUID playerId, Zone fromZone, Zone toZone) { + this.sourceId = sourceId; + this.playerId = playerId; + this.fromZone = fromZone; + this.toZone = toZone; + } + + @Override + public int hashCode() { + return (this.fromZone.ordinal() + 1) * 1 + + (this.toZone.ordinal() + 1) * 10 + + (this.sourceId != null ? this.sourceId.hashCode() : 0) + + (this.playerId != null ? this.playerId.hashCode() : 0); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ZoneChangeData) { + ZoneChangeData data = (ZoneChangeData) obj; + return this.fromZone == data.fromZone + && this.toZone == data.toZone + && this.sourceId == data.sourceId + && this.playerId == data.playerId; + } + return false; + } + } + + Map> eventsByKey = new HashMap<>(); + List groupEvents = new LinkedList<>(); + for (GameEvent event : events) { + if (event instanceof ZoneChangeEvent) { + ZoneChangeEvent castEvent = (ZoneChangeEvent) event; + ZoneChangeData key = new ZoneChangeData(castEvent.getSourceId(), castEvent.getPlayerId(), castEvent.getFromZone(), castEvent.getToZone()); + if (eventsByKey.containsKey(key)) { + eventsByKey.get(key).add(event); + } else { + List list = new LinkedList<>(); + list.add(event); + eventsByKey.put(key, list); + } + } + } + for (Map.Entry> entry : eventsByKey.entrySet()) { + Set movedCards = new LinkedHashSet<>(); + for (Iterator it = entry.getValue().iterator(); it.hasNext();) { + GameEvent event = it.next(); + ZoneChangeEvent castEvent = (ZoneChangeEvent) event; + UUID targetId = castEvent.getTargetId(); + Card card = game.getCard(targetId); + movedCards.add(card); + } + ZoneChangeData eventData = entry.getKey(); + if (!movedCards.isEmpty()) { + ZoneChangeGroupEvent event = new ZoneChangeGroupEvent(movedCards, eventData.sourceId, eventData.playerId, eventData.fromZone, eventData.toZone); + groupEvents.add(event); + } + } + return groupEvents; + } + public void addCard(Card card) { setZone(card.getId(), Zone.OUTSIDE); for (Ability ability : card.getAbilities()) { diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 541d1caa842..fe35761b5bb 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -512,7 +512,8 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (canTransform) { if (!replaceEvent(EventType.TRANSFORM, game)) { setTransformed(!transformed); - fireEvent(EventType.TRANSFORMED, game); + game.applyEffects(); + game.addSimultaneousEvent(GameEvent.getEvent(EventType.TRANSFORMED, getId(), getControllerId())); return true; } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d2b86ab37ac..0039396a8ec 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -123,7 +123,6 @@ import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; -import mage.game.events.ZoneChangeGroupEvent; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -3249,9 +3248,6 @@ public abstract class PlayerImpl implements Player, Serializable { default: throw new UnsupportedOperationException("to Zone" + toZone.toString() + " not supported yet"); } - if (!successfulMovedCards.isEmpty()) { - game.fireEvent(new ZoneChangeGroupEvent(successfulMovedCards, source == null ? null : source.getSourceId(), this.getId(), fromZone, toZone)); - } return successfulMovedCards.size() > 0; } @@ -3267,7 +3263,6 @@ public abstract class PlayerImpl implements Player, Serializable { if (cards.isEmpty()) { return true; } - game.fireEvent(new ZoneChangeGroupEvent(cards, source == null ? null : source.getSourceId(), this.getId(), null, Zone.EXILED)); boolean result = false; for (Card card : cards) { Zone fromZone = game.getState().getZone(card.getId()); @@ -3368,7 +3363,6 @@ public abstract class PlayerImpl implements Player, Serializable { } } } - game.fireEvent(new ZoneChangeGroupEvent(movedCards, source == null ? null : source.getSourceId(), this.getId(), fromZone, Zone.GRAVEYARD)); return movedCards; } diff --git a/Mage/src/main/java/mage/players/net/UserData.java b/Mage/src/main/java/mage/players/net/UserData.java index 865ad606e98..f0e90229a96 100644 --- a/Mage/src/main/java/mage/players/net/UserData.java +++ b/Mage/src/main/java/mage/players/net/UserData.java @@ -22,6 +22,7 @@ public class UserData implements Serializable { protected boolean passPriorityCast; protected boolean passPriorityActivation; protected boolean autoOrderTrigger; + protected boolean useFirstManaAbility; protected String matchHistory; protected int matchQuitRatio; @@ -31,7 +32,7 @@ public class UserData implements Serializable { public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced, boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps, String flagName, boolean askMoveToGraveOrder, boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, - boolean passPriorityCast, boolean passPriorityActivation, boolean autoOrderTrigger) { + boolean passPriorityCast, boolean passPriorityActivation, boolean autoOrderTrigger, boolean useFirstManaAbility) { this.groupId = userGroup.getGroupId(); this.avatarId = avatarId; this.showAbilityPickerForced = showAbilityPickerForced; @@ -45,6 +46,7 @@ public class UserData implements Serializable { this.passPriorityCast = passPriorityCast; this.passPriorityActivation = passPriorityActivation; this.autoOrderTrigger = autoOrderTrigger; + this.useFirstManaAbility = useFirstManaAbility; this.matchHistory = ""; this.matchQuitRatio = 0; this.tourneyHistory = ""; @@ -65,10 +67,11 @@ public class UserData implements Serializable { this.passPriorityCast = userData.passPriorityCast; this.passPriorityActivation = userData.passPriorityActivation; this.autoOrderTrigger = userData.autoOrderTrigger; + this.useFirstManaAbility = userData.useFirstManaAbility; } public static UserData getDefaultUserDataView() { - return new UserData(UserGroup.DEFAULT, 0, false, false, true, null, getDefaultFlagName(), false, true, true, false, false, false); + return new UserData(UserGroup.DEFAULT, 0, false, false, true, null, getDefaultFlagName(), false, true, true, false, false, false, false); } public void setGroupId(int groupId) { @@ -175,6 +178,14 @@ public class UserData implements Serializable { this.autoOrderTrigger = autoOrderTrigger; } + public boolean isUseFirstManaAbility() { + return useFirstManaAbility; + } + + public void setUseFirstManaAbility(boolean useFirstManaAbility) { + this.useFirstManaAbility = useFirstManaAbility; + } + public String getHistory() { if (UserGroup.COMPUTER.equals(this.groupId)) { return ""; diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 0148c99eb7e..b2a3ef60c4c 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -78,11 +78,11 @@ public class CardUtil { // Enchantment subtypes "Aura", "Curse", "Shrine", // Artifact subtypes - "Equipment", "Fortification", "Contraption", + "Clue", "Equipment", "Fortification", "Contraption", // Land subtypes "Desert", "Gate", "Lair", "Locus", "Urza's", "Mine", "Power-Plant", "Tower", // Planeswalker subtypes - "Ajani", "Ashiok", "Bolas", "Chandra", "Dack", "Daretti", "Domri", "Elspeth", "Freyalise", "Garruk", "Gideon", "Jace", + "Ajani", "Arlinn", "Ashiok", "Bolas", "Chandra", "Dack", "Daretti", "Domri", "Elspeth", "Freyalise", "Garruk", "Gideon", "Jace", "Karn", "Kiora", "Koth", "Liliana", "Nahiri", "Nissa", "Narset", "Nixilis", "Ral", "Sarkhan", "Sorin", "Tamiyo", "Teferi", "Tezzeret", "Tibalt", "Ugin", "Venser", "Vraska", "Xenagos", // Instant sorcery subtypes diff --git a/Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java b/Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java new file mode 100644 index 00000000000..056950c5693 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/CastFromGraveyardWatcher.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.watchers.common; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author LevelX2 + */ +public class CastFromGraveyardWatcher extends Watcher { + + // holds which spell with witch zone change counter was cast from graveyard + private final Map> spellsCastFromGraveyard = new HashMap<>(); + + public CastFromGraveyardWatcher() { + super(CastFromGraveyardWatcher.class.getName(), WatcherScope.GAME); + } + + public CastFromGraveyardWatcher(final CastFromGraveyardWatcher watcher) { + super(watcher); + } + + @Override + public void watch(GameEvent event, Game game) { + /** + * This does still not handle if a spell is cast from hand and comes to + * play from other zones during the same step. But at least the state is + * reset if the game comes to a new step + */ + if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone().equals(Zone.GRAVEYARD)) { + Spell spell = (Spell) game.getObject(event.getTargetId()); + if (spell != null) { + HashSet zcc = spellsCastFromGraveyard.get(spell.getSourceId()); + if (zcc == null) { + zcc = new HashSet<>(); + spellsCastFromGraveyard.put(spell.getSourceId(), zcc); + } + zcc.add(spell.getZoneChangeCounter(game)); + } + + } + } + + public boolean spellWasCastFromGraveyard(UUID sourceId, int zcc) { + Set zccSet = spellsCastFromGraveyard.get(sourceId); + if (zccSet != null) { + return zccSet.contains(zcc); + } + return false; + + } + + @Override + public void reset() { + super.reset(); + spellsCastFromGraveyard.clear(); + } + + @Override + public CastFromGraveyardWatcher copy() { + return new CastFromGraveyardWatcher(this); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/FirstSpellCastThisTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/FirstSpellCastThisTurnWatcher.java new file mode 100644 index 00000000000..c85181d3f44 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/FirstSpellCastThisTurnWatcher.java @@ -0,0 +1,66 @@ +package mage.watchers.common; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import static mage.game.events.GameEvent.EventType.CAST_SPELL; +import static mage.game.events.GameEvent.EventType.SPELL_CAST; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * @author jeffwadsworth +**/ +public class FirstSpellCastThisTurnWatcher extends Watcher { + + private final Map playerFirstSpellCast = new HashMap<>(); + private final Map playerFirstCastSpell = new HashMap<>(); + + public FirstSpellCastThisTurnWatcher() { + super("FirstSpellCastThisTurn", WatcherScope.GAME); + } + + public FirstSpellCastThisTurnWatcher(final FirstSpellCastThisTurnWatcher watcher) { + super(watcher); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case SPELL_CAST: + case CAST_SPELL: + Spell spell = (Spell) game.getObject(event.getTargetId()); + if (spell != null && !playerFirstSpellCast.containsKey(spell.getControllerId())) { + if (event.getType().equals(EventType.SPELL_CAST)) { + playerFirstSpellCast.put(spell.getControllerId(), spell.getId()); + } else if (event.getType().equals(EventType.CAST_SPELL)) { + playerFirstCastSpell.put(spell.getControllerId(), spell.getId()); + } + } + } + } + + @Override + public FirstSpellCastThisTurnWatcher copy() { + return new FirstSpellCastThisTurnWatcher(this); + } + + @Override + public void reset() { + super.reset(); + playerFirstSpellCast.clear(); + playerFirstCastSpell.clear(); + } + + public UUID getIdOfFirstCastSpell(UUID playerId) { + if (playerFirstSpellCast.get(playerId) == null) { + return playerFirstCastSpell.get(playerId); + } else { + return playerFirstSpellCast.get(playerId); + } + } +} \ No newline at end of file diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index b792a7780ad..8dfc407a250 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -57051,7 +57051,7 @@ Behind the Scenes|Shadows over Innistrad|100|U|{2}{B}|Enchantment|||Creature you Behold the Beyond|Shadows over Innistrad|101|M|{5}{B}{B}|Sorcery|||Discard your hand. Search your library for three cards and put those cards into your hand. Then shuffle your library.| Biting Rain|Shadows over Innistrad|102|U|{2}{B}{B}|Sorcery|||All creatures get -2/-2 until end of turn.$Madness {2}{B} (If you discard this card, discard it into exile. WHen you do, cast it for its madness cost or put it into your graveyard.)| Call the Bloodline|Shadows over Innistrad|103|U|{1}{B}|Enchantment|||{1}, Discard a card: Put a 1/1 black Vampire Knight token with lifelink onto the battlefield. Activate this ability only once each turn.| -Creeping Death|Shadows over Innistrad|104|U|{3}{B}|Enchantment|||At the beginning of your upkeep, each player discards a card. Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life.| +Creeping Dread|Shadows over Innistrad|104|U|{3}{B}|Enchantment|||At the beginning of your upkeep, each player discards a card. Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life.| Crow of Dark Tidings|Shadows over Innistrad|105|C|{2}{B}|Creature - Zombie Bird|2|2|Flying$When Crow of Dark Tidings enters the battlefield or dies, put the top two cards of your library into your graveyard.| Dead Weight|Shadows over Innistrad|106|C|{B}|Enchantment - Aura|||Enchant creature$Enchanted creature gets -2/-2.| Diregraf Colossus|Shadows over Innistrad|107|R|{2}{B}|Creature - Zombie Giant|2|2|Diregraf Colossus enters the battlefield with a +1/+1 counter on it for each Zombie card in your graveyard.$Whenever you cast a Zombie spell, put a 2/2 black Zombie creature token onto the battlefield tapped.| @@ -57116,7 +57116,7 @@ Geier Reach Bandit|Shadows over Innistrad|159a|R|{2}{R}|Creature - Human Rogue W Vildin-Pack Alpha|Shadows over Innistrad|159b|R||Creature - Werewolf|4|3|Whenever a Werewolf enters the battlefield under your control, you may transform it.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Vildin-Pack Alpha.| Geistblast|Shadows over Innistrad|160|U|{2}{R}|Instant|||Geistblast deals 2 damage to target creature or player.${2}{U}, Exile Geist from your graveyard: Copy target instant or sorcery you control. You may choose new targets for the copy.| Gibbering Fiend|Shadows over Innistrad|161|U|{1}{R}|Creature - Devil|2|1|When Gibbering Fiend enters the battlefield, it deals 1 damage to each opponent.$Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, Gibbering Fiend deals 1 damage to that player.| -Goldknight Castigator|Shadows over Innistrad|162|M|{2}{R}{R}|Creature - Angel|4|9|Flying, haste$If a source would deal damage to you, it deals double that damage to you instead.$If a source would deal damage to Goldknight Castigator, it deals double that damage to Goldknight Castigator instead.| +Goldnight Castigator|Shadows over Innistrad|162|M|{2}{R}{R}|Creature - Angel|4|9|Flying, haste$If a source would deal damage to you, it deals double that damage to you instead.$If a source would deal damage to Goldnight Castigator, it deals double that damage to Goldnight Castigator instead.| Harness the Storm|Shadows over Innistrad|163|R|{2}{R}|Enchantment|||Whenever you cast an instant or sorcery spell from your hand, you may cast target card with the same name as that spell from your graveyard.| Howlpack Wolf|Shadows over Innistrad|164|C|{2}{R}|Creature - Wolf|3|3|Howlpack Wolf can't block unless you control another Wolf or Werewolf.| Hulking Devil|Shadows over Innistrad|165|C|{3}{R}|Creature - Devil|5|2||