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 a9c4cb6c6b..ee62a58f33 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 @@ -799,6 +799,7 @@ public class HumanPlayer extends PlayerImpl { if (unpaid instanceof ManaCostsImpl) { specialManaAction(unpaid, game); // TODO: delve or convoke cards with PhyrexianManaCost won't work together (this combinaton does not exist yet) + @SuppressWarnings("unchecked") ManaCostsImpl costs = (ManaCostsImpl) unpaid; for (ManaCost cost : costs.getUnpaid()) { if (cost instanceof PhyrexianManaCost) { diff --git a/Mage.Sets/src/mage/sets/avacynrestored/StolenGoods.java b/Mage.Sets/src/mage/sets/avacynrestored/StolenGoods.java index f6550ca84e..591adb8e6a 100644 --- a/Mage.Sets/src/mage/sets/avacynrestored/StolenGoods.java +++ b/Mage.Sets/src/mage/sets/avacynrestored/StolenGoods.java @@ -141,7 +141,7 @@ class StolenGoodsCastFromExileEffect extends AsThoughEffectImpl { Card card = game.getCard(sourceId); if (card != null && game.getState().getZone(sourceId) == Zone.EXILED) { Player player = game.getPlayer(affectedControllerId); - player.setCastSourceIdWithAlternateMana(sourceId, null); + player.setCastSourceIdWithAlternateMana(sourceId, null, null); return true; } } diff --git a/Mage.Sets/src/mage/sets/commander2015/DeadlyTempest.java b/Mage.Sets/src/mage/sets/commander2015/DeadlyTempest.java new file mode 100644 index 0000000000..7af864bd0f --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/DeadlyTempest.java @@ -0,0 +1,107 @@ +/* + * 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.commander2015; + +import java.util.HashMap; +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.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class DeadlyTempest extends CardImpl { + + public DeadlyTempest(UUID ownerId) { + super(ownerId, 19, "Deadly Tempest", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{B}{B}"); + this.expansionSetCode = "C15"; + + // Destroy all creatures. Each player loses life equal to the number of creatures he or she controlled that were destroyed this way. + getSpellAbility().addEffect(new DeadlyTempestEffect()); + } + + public DeadlyTempest(final DeadlyTempest card) { + super(card); + } + + @Override + public DeadlyTempest copy() { + return new DeadlyTempest(this); + } +} + +class DeadlyTempestEffect extends OneShotEffect { + + public DeadlyTempestEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Destroy all creatures. Each player loses life equal to the number of creatures he or she controlled that were destroyed this way"; + } + + public DeadlyTempestEffect(final DeadlyTempestEffect effect) { + super(effect); + } + + @Override + public DeadlyTempestEffect copy() { + return new DeadlyTempestEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + HashMap destroyedCreatures = new HashMap<>(); + for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { + if (permanent.destroy(source.getSourceId(), game, false)) { + int count = destroyedCreatures.containsKey(permanent.getControllerId()) ? destroyedCreatures.get(permanent.getControllerId()) : 0; + destroyedCreatures.put(permanent.getControllerId(), count + 1); + } + } + for (UUID playerId : game.getState().getPlayerList(source.getControllerId())) { + int count = destroyedCreatures.containsKey(playerId) ? destroyedCreatures.get(playerId) : 0; + if (count > 0) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.damage(count, playerId, game, false, true); + } + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/commander2015/DreadSummons.java b/Mage.Sets/src/mage/sets/commander2015/DreadSummons.java new file mode 100644 index 0000000000..4f2b645a37 --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/DreadSummons.java @@ -0,0 +1,111 @@ +/* + * 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.commander2015; + +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +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.game.Game; +import mage.game.permanent.token.ZombieToken; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class DreadSummons extends CardImpl { + + public DreadSummons(UUID ownerId) { + super(ownerId, 20, "Dread Summons", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{B}{B}"); + this.expansionSetCode = "C15"; + + // Each player puts the top X cards of his or her library into his or her graveyard. For each creature card put into a graveyard this way, you put a 2/2 black Zombie creature token onto the battlefield tapped. + getSpellAbility().addEffect(new DreadSummonsEffect()); + } + + public DreadSummons(final DreadSummons card) { + super(card); + } + + @Override + public DreadSummons copy() { + return new DreadSummons(this); + } +} + +class DreadSummonsEffect extends OneShotEffect { + + public DreadSummonsEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "Each player puts the top X cards of his or her library into his or her graveyard. For each creature card put into a graveyard this way, you put a 2/2 black Zombie creature token onto the battlefield tapped"; + } + + public DreadSummonsEffect(final DreadSummonsEffect effect) { + super(effect); + } + + @Override + public DreadSummonsEffect copy() { + return new DreadSummonsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int numberOfCards = source.getManaCostsToPay().getX(); + if (numberOfCards > 0) { + int numberOfCreatureCards = 0; + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + Set movedCards = player.moveCardsToGraveyardWithInfo(player.getLibrary().getTopCards(game, numberOfCards), source, game, Zone.LIBRARY); + for (Card card : movedCards) { + if (card.getCardType().contains(CardType.CREATURE)) { + numberOfCreatureCards++; + } + } + } + } + if (numberOfCreatureCards > 0) { + return new CreateTokenEffect(new ZombieToken(), numberOfCreatureCards, true, false).apply(game, source); + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/commander2015/ScourgeOfNelToth.java b/Mage.Sets/src/mage/sets/commander2015/ScourgeOfNelToth.java new file mode 100644 index 0000000000..38eaaf4a7e --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/ScourgeOfNelToth.java @@ -0,0 +1,120 @@ +/* + * 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.commander2015; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class ScourgeOfNelToth extends CardImpl { + + public ScourgeOfNelToth(UUID ownerId) { + super(ownerId, 21, "Scourge of Nel Toth", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{B}{B}"); + this.expansionSetCode = "C15"; + this.subtype.add("Zombie"); + this.subtype.add("Dragon"); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // You may cast Scourge of Nel Toth from your graveyard by paying {B}{B} and sacrificing two creatures rather than paying its mana cost. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new ScourgeOfNelTothPlayEffect())); + } + + public ScourgeOfNelToth(final ScourgeOfNelToth card) { + super(card); + } + + @Override + public ScourgeOfNelToth copy() { + return new ScourgeOfNelToth(this); + } +} + +class ScourgeOfNelTothPlayEffect extends AsThoughEffectImpl { + + public ScourgeOfNelTothPlayEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.Benefit); + staticText = "You may cast {this} from your graveyard by paying {B}{B} and sacrificing two creatures rather than paying its mana cost"; + } + + public ScourgeOfNelTothPlayEffect(final ScourgeOfNelTothPlayEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ScourgeOfNelTothPlayEffect copy() { + return new ScourgeOfNelTothPlayEffect(this); + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (sourceId.equals(source.getSourceId()) && source.getControllerId().equals(affectedControllerId)) { + if (game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { + Player player = game.getPlayer(affectedControllerId); + if (player != null) { + // can sometimes be cast with base mana cost from grave???? + Costs costs = new CostsImpl<>(); + costs.add(new SacrificeTargetCost(new TargetControlledCreaturePermanent(2))); + player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{B}{B}"), costs); + return true; + } + } + } + return false; + } + +} diff --git a/Mage.Sets/src/mage/sets/darkascension/Seance.java b/Mage.Sets/src/mage/sets/darkascension/Seance.java index 5f848ebe8a..1b28fafa42 100644 --- a/Mage.Sets/src/mage/sets/darkascension/Seance.java +++ b/Mage.Sets/src/mage/sets/darkascension/Seance.java @@ -97,7 +97,7 @@ class SeanceEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && card != null) { if (controller.moveCards(card, null, Zone.EXILED, source, game)) { - PutTokenOntoBattlefieldCopyTargetEffect effect = new PutTokenOntoBattlefieldCopyTargetEffect(source.getControllerId(), null, true); + PutTokenOntoBattlefieldCopyTargetEffect effect = new PutTokenOntoBattlefieldCopyTargetEffect(source.getControllerId(), null, false); effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game))); effect.setAdditionalSubType("Spirit"); effect.apply(game, source); diff --git a/Mage.Sets/src/mage/sets/guildpact/BurningTreeShaman.java b/Mage.Sets/src/mage/sets/guildpact/BurningTreeShaman.java index 0405ecd60a..b4c5d10a83 100644 --- a/Mage.Sets/src/mage/sets/guildpact/BurningTreeShaman.java +++ b/Mage.Sets/src/mage/sets/guildpact/BurningTreeShaman.java @@ -30,6 +30,7 @@ package mage.sets.guildpact; import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; @@ -41,7 +42,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.stack.StackAbility; -import mage.target.TargetPlayer; import mage.target.targetpointer.FixedTarget; /** @@ -76,8 +76,7 @@ public class BurningTreeShaman extends CardImpl { class BurningTreeShamanTriggeredAbility extends TriggeredAbilityImpl { BurningTreeShamanTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(1, false, "that player")); - this.addTarget(new TargetPlayer()); + super(Zone.BATTLEFIELD, new DamageTargetEffect(new StaticValue(1), false, "that player", true)); } BurningTreeShamanTriggeredAbility(final BurningTreeShamanTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/sets/judgment/Spelljack.java b/Mage.Sets/src/mage/sets/judgment/Spelljack.java index 13152d6939..f6961e9683 100644 --- a/Mage.Sets/src/mage/sets/judgment/Spelljack.java +++ b/Mage.Sets/src/mage/sets/judgment/Spelljack.java @@ -141,7 +141,7 @@ class SpelljackCastFromExileEffect extends AsThoughEffectImpl { if (card != null) { if (game.getState().getZone(sourceId) == Zone.EXILED) { Player player = game.getPlayer(affectedControllerId); - player.setCastSourceIdWithAlternateMana(sourceId, null); + player.setCastSourceIdWithAlternateMana(sourceId, null, null); return true; } else { this.discard(); diff --git a/Mage.Sets/src/mage/sets/khansoftarkir/KheruSpellsnatcher.java b/Mage.Sets/src/mage/sets/khansoftarkir/KheruSpellsnatcher.java index df16a3a0fb..2ce29a65aa 100644 --- a/Mage.Sets/src/mage/sets/khansoftarkir/KheruSpellsnatcher.java +++ b/Mage.Sets/src/mage/sets/khansoftarkir/KheruSpellsnatcher.java @@ -158,7 +158,7 @@ class KheruSpellsnatcherCastFromExileEffect extends AsThoughEffectImpl { if (card != null) { if (game.getState().getZone(sourceId) == Zone.EXILED) { Player player = game.getPlayer(affectedControllerId); - player.setCastSourceIdWithAlternateMana(sourceId, null); + player.setCastSourceIdWithAlternateMana(sourceId, null, null); return true; } else { this.discard(); diff --git a/Mage.Sets/src/mage/sets/khansoftarkir/NarsetEnlightenedMaster.java b/Mage.Sets/src/mage/sets/khansoftarkir/NarsetEnlightenedMaster.java index a5093dcec4..f97744e57b 100644 --- a/Mage.Sets/src/mage/sets/khansoftarkir/NarsetEnlightenedMaster.java +++ b/Mage.Sets/src/mage/sets/khansoftarkir/NarsetEnlightenedMaster.java @@ -151,7 +151,7 @@ class NarsetEnlightenedMasterCastFromExileEffect extends AsThoughEffectImpl { if (card != null) { Player player = game.getPlayer(affectedControllerId); if (player != null) { - player.setCastSourceIdWithAlternateMana(objectId, null); + player.setCastSourceIdWithAlternateMana(objectId, null, null); return true; } } diff --git a/Mage.Sets/src/mage/sets/magic2010/LurkingPredators.java b/Mage.Sets/src/mage/sets/magic2010/LurkingPredators.java index 9a1a079556..2e53c0c49e 100644 --- a/Mage.Sets/src/mage/sets/magic2010/LurkingPredators.java +++ b/Mage.Sets/src/mage/sets/magic2010/LurkingPredators.java @@ -28,10 +28,7 @@ package mage.sets.magic2010; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.constants.Zone; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SpellCastOpponentTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -39,6 +36,10 @@ 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; +import mage.constants.Zone; import mage.game.Game; import mage.players.Player; @@ -52,7 +53,6 @@ public class LurkingPredators extends CardImpl { super(ownerId, 190, "Lurking Predators", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); this.expansionSetCode = "M10"; - // Whenever an opponent casts a spell, reveal the top card of your library. If it's a creature card, put it onto the battlefield. Otherwise, you may put that card on the bottom of your library. this.addAbility(new SpellCastOpponentTriggeredAbility(new LurkingPredatorsEffect(), false)); } @@ -85,26 +85,25 @@ class LurkingPredatorsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = game.getObject(source.getSourceId()); + if (controller == null || sourceObject == null) { return false; } - if (player.getLibrary().size() > 0) { - Card card = player.getLibrary().getFromTop(game); - Cards cards = new CardsImpl(); - cards.add(card); - player.revealCards("Lurking Predators", cards, game); + if (controller.getLibrary().size() > 0) { + Card card = controller.getLibrary().getFromTop(game); + Cards cards = new CardsImpl(card); + controller.revealCards(sourceObject.getIdName(), cards, game); if (card != null) { if (card.getCardType().contains(CardType.CREATURE)) { - card = player.getLibrary().removeFromTop(game); - card.putOntoBattlefield(game, Zone.HAND, source.getSourceId(), source.getControllerId()); - } else if (player.chooseUse(Outcome.Neutral, "Put " + card.getName() + " on the bottom of your library?", source, game)) { - card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, false); + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + } else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) { + controller.putCardsOnBottomOfLibrary(cards, game, source, false); } } } - return false; + return true; } } diff --git a/Mage.Sets/src/mage/sets/modernmasters2015/WorldheartPhoenix.java b/Mage.Sets/src/mage/sets/modernmasters2015/WorldheartPhoenix.java index d790518da1..06051fdcd9 100644 --- a/Mage.Sets/src/mage/sets/modernmasters2015/WorldheartPhoenix.java +++ b/Mage.Sets/src/mage/sets/modernmasters2015/WorldheartPhoenix.java @@ -111,7 +111,7 @@ public class WorldheartPhoenix extends CardImpl { Player player = game.getPlayer(affectedControllerId); if (player != null) { // can sometimes be cast with base mana cost from grave???? - player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{W}{U}{B}{R}{G}")); + player.setCastSourceIdWithAlternateMana(sourceId, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), null); return true; } } diff --git a/Mage.Sets/src/mage/sets/ninthedition/AnabaShaman.java b/Mage.Sets/src/mage/sets/ninthedition/AnabaShaman.java index 40ad710034..f1615de339 100644 --- a/Mage.Sets/src/mage/sets/ninthedition/AnabaShaman.java +++ b/Mage.Sets/src/mage/sets/ninthedition/AnabaShaman.java @@ -28,16 +28,15 @@ package mage.sets.ninthedition; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.constants.Zone; import mage.target.common.TargetCreatureOrPlayer; @@ -57,7 +56,7 @@ public class AnabaShaman extends CardImpl { this.toughness = new MageInt(2); // {R}, {tap}: Anaba Shaman deals 1 damage to target creature or player. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamagePlayersEffect(1), new ManaCostsImpl("{R}")); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(1), new ManaCostsImpl("{R}")); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetCreatureOrPlayer()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/sets/planarchaos/IntetTheDreamer.java b/Mage.Sets/src/mage/sets/planarchaos/IntetTheDreamer.java index d3107a9bfb..4e45cd7b79 100644 --- a/Mage.Sets/src/mage/sets/planarchaos/IntetTheDreamer.java +++ b/Mage.Sets/src/mage/sets/planarchaos/IntetTheDreamer.java @@ -166,7 +166,7 @@ class IntetTheDreamerCastEffect extends AsThoughEffectImpl { return controller.chooseUse(outcome, "Play " + card.getName() + "?", source, game); } } else { - controller.setCastSourceIdWithAlternateMana(objectId, null); + controller.setCastSourceIdWithAlternateMana(objectId, null, null); return true; } } diff --git a/Mage.Sets/src/mage/sets/ravnica/SinsOfThePast.java b/Mage.Sets/src/mage/sets/ravnica/SinsOfThePast.java index a199e0cfe0..ba8dd13ec2 100644 --- a/Mage.Sets/src/mage/sets/ravnica/SinsOfThePast.java +++ b/Mage.Sets/src/mage/sets/ravnica/SinsOfThePast.java @@ -131,7 +131,7 @@ class SinsOfThePastCastFromGraveyardEffect extends AsThoughEffectImpl { public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { if (sourceId.equals(this.getTargetPointer().getFirst(game, source)) && affectedControllerId.equals(source.getControllerId())) { Player player = game.getPlayer(affectedControllerId); - player.setCastSourceIdWithAlternateMana(sourceId, null); + player.setCastSourceIdWithAlternateMana(sourceId, null, null); return true; } return false; diff --git a/Mage.Sets/src/mage/sets/thedark/SkullOfOrm.java b/Mage.Sets/src/mage/sets/thedark/SkullOfOrm.java index dfc74fba39..d6ca4b86d3 100644 --- a/Mage.Sets/src/mage/sets/thedark/SkullOfOrm.java +++ b/Mage.Sets/src/mage/sets/thedark/SkullOfOrm.java @@ -40,7 +40,7 @@ import mage.cards.CardImpl; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetCardInYourGraveyard; /** * @@ -48,7 +48,7 @@ import mage.target.common.TargetCardInGraveyard; */ public class SkullOfOrm extends CardImpl { - private static final FilterCard filter = new FilterCard("enchantment cards"); + private static final FilterCard filter = new FilterCard("enchantment card from your graveyard"); static { filter.add(new CardTypePredicate(CardType.ENCHANTMENT)); @@ -60,7 +60,7 @@ public class SkullOfOrm extends CardImpl { // {5}, {tap}: Return target enchantment card from your graveyard to your hand. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new ManaCostsImpl("{5}")); - ability.addTarget(new TargetCardInGraveyard(filter)); + ability.addTarget(new TargetCardInYourGraveyard(filter)); ability.addCost(new TapSourceCost()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/vintagemasters/MindsDesire.java b/Mage.Sets/src/mage/sets/vintagemasters/MindsDesire.java index 0533cb5b64..184e2b898b 100644 --- a/Mage.Sets/src/mage/sets/vintagemasters/MindsDesire.java +++ b/Mage.Sets/src/mage/sets/vintagemasters/MindsDesire.java @@ -140,7 +140,7 @@ class MindsDesireCastFromExileEffect extends AsThoughEffectImpl { Card card = game.getCard(sourceId); if (card != null && game.getState().getZone(sourceId) == Zone.EXILED) { Player player = game.getPlayer(affectedControllerId); - player.setCastSourceIdWithAlternateMana(sourceId, null); + player.setCastSourceIdWithAlternateMana(sourceId, null, null); return true; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 80199a68a9..1f59b9387a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -43,6 +43,8 @@ import mage.abilities.Modes; import mage.abilities.SpellAbility; import mage.abilities.TriggeredAbility; import mage.abilities.costs.AlternativeSourceCosts; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; @@ -1122,8 +1124,8 @@ public class TestPlayer implements Player { } @Override - public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts) { - computerPlayer.setCastSourceIdWithAlternateMana(sourceId, manaCosts); + public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs) { + computerPlayer.setCastSourceIdWithAlternateMana(sourceId, manaCosts, costs); } @Override @@ -1136,6 +1138,11 @@ public class TestPlayer implements Player { return computerPlayer.getCastSourceIdManaCosts(); } + @Override + public Costs getCastSourceIdCosts() { + return computerPlayer.getCastSourceIdCosts(); + } + @Override public boolean isInPayManaMode() { return computerPlayer.isInPayManaMode(); diff --git a/Mage/src/mage/abilities/effects/PlaneswalkerRedirectionEffect.java b/Mage/src/mage/abilities/effects/PlaneswalkerRedirectionEffect.java index 7f2ee3c504..e435a88f4c 100644 --- a/Mage/src/mage/abilities/effects/PlaneswalkerRedirectionEffect.java +++ b/Mage/src/mage/abilities/effects/PlaneswalkerRedirectionEffect.java @@ -109,6 +109,7 @@ public class PlaneswalkerRedirectionEffect extends RedirectionEffect { if (permanent != null) { return permanent.getControllerId(); } - return null; + // for effects like Deflecting Palm (could be wrong if card was played multiple times by different players) + return game.getContinuousEffects().getControllerOfSourceId(sourceId); } } diff --git a/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java b/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java index 5995832873..61f433d298 100644 --- a/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java @@ -164,10 +164,8 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { if (gainsHaste) { token.addAbility(HasteAbility.getInstance()); } - if (additionalSubType != null) { - if (token.getSubtype().contains(additionalSubType)) { - token.getSubtype().add(additionalSubType); - } + if (additionalSubType != null && !token.getSubtype().contains(additionalSubType)) { + token.getSubtype().add(additionalSubType); } token.putOntoBattlefield(number, game, source.getSourceId(), playerId == null ? source.getControllerId() : playerId, tapped, attacking, attackedPlayer); for (UUID tokenId : token.getLastAddedTokenIds()) { // by cards like Doubling Season multiple tokens can be added to the battlefield diff --git a/Mage/src/mage/abilities/effects/common/PutTopCardOfLibraryIntoGraveEachPlayerEffect.java b/Mage/src/mage/abilities/effects/common/PutTopCardOfLibraryIntoGraveEachPlayerEffect.java index c52b88fff0..421a97c768 100644 --- a/Mage/src/mage/abilities/effects/common/PutTopCardOfLibraryIntoGraveEachPlayerEffect.java +++ b/Mage/src/mage/abilities/effects/common/PutTopCardOfLibraryIntoGraveEachPlayerEffect.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.effects.common; import java.util.UUID; @@ -44,7 +43,6 @@ import mage.util.CardUtil; * * @author LevelX2 */ - public class PutTopCardOfLibraryIntoGraveEachPlayerEffect extends OneShotEffect { private final DynamicValue numberCards; @@ -53,7 +51,7 @@ public class PutTopCardOfLibraryIntoGraveEachPlayerEffect extends OneShotEffect public PutTopCardOfLibraryIntoGraveEachPlayerEffect(int numberCards, TargetController targetController) { this(new StaticValue(numberCards), targetController); } - + public PutTopCardOfLibraryIntoGraveEachPlayerEffect(DynamicValue numberCards, TargetController targetController) { super(Outcome.Discard); this.numberCards = numberCards; @@ -76,42 +74,42 @@ public class PutTopCardOfLibraryIntoGraveEachPlayerEffect extends OneShotEffect public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - switch(targetController) { + switch (targetController) { case OPPONENT: - for(UUID playerId: game.getOpponents(source.getControllerId()) ) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { putCardsToGravecard(playerId, source, game); } break; case ANY: - for(UUID playerId: player.getInRange() ) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { putCardsToGravecard(playerId, source, game); } break; case NOT_YOU: - for(UUID playerId: player.getInRange() ) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { if (!playerId.equals(source.getSourceId())) { putCardsToGravecard(playerId, source, game); } } break; default: - throw new UnsupportedOperationException("TargetController type not supported."); - } + throw new UnsupportedOperationException("TargetController type not supported."); + } return true; } return false; } - + private void putCardsToGravecard(UUID playerId, Ability source, Game game) { Player player = game.getPlayer(playerId); if (player != null) { - player.moveCards(player.getLibrary().getTopCards(game, numberCards.calculate(game, source, this)), Zone.LIBRARY, Zone.GRAVEYARD, source, game); + player.moveCards(player.getLibrary().getTopCards(game, numberCards.calculate(game, source, this)), Zone.GRAVEYARD, source, game); } } private String setText() { StringBuilder sb = new StringBuilder(); - switch(targetController) { + switch (targetController) { case OPPONENT: sb.append("Each opponent "); break; @@ -120,14 +118,14 @@ public class PutTopCardOfLibraryIntoGraveEachPlayerEffect extends OneShotEffect break; case NOT_YOU: sb.append("Each other player "); - break; + break; default: - throw new UnsupportedOperationException("TargetController type not supported."); + throw new UnsupportedOperationException("TargetController type not supported."); } sb.append("puts the top "); - sb.append(CardUtil.numberToText(numberCards.toString(),"a")); + sb.append(CardUtil.numberToText(numberCards.toString(), "a")); sb.append(" card"); - sb.append(numberCards.toString().equals("1")?"":"s"); + sb.append(numberCards.toString().equals("1") ? "" : "s"); sb.append(" of his or her library into his or her graveyard"); return sb.toString(); } diff --git a/Mage/src/mage/game/permanent/token/ZombieToken.java b/Mage/src/mage/game/permanent/token/ZombieToken.java index 90b9381004..28d1e5b4ce 100644 --- a/Mage/src/mage/game/permanent/token/ZombieToken.java +++ b/Mage/src/mage/game/permanent/token/ZombieToken.java @@ -43,7 +43,7 @@ public class ZombieToken extends Token { final static private List tokenImageSets = new ArrayList<>(); static { - tokenImageSets.addAll(Arrays.asList("10E", "M10", "M11", "M12", "M13", "M14", "M15", "MBS", "ALA", "ISD", "C14", "CNS", "MMA", "BNG", "KTK", "DTK", "ORI")); + tokenImageSets.addAll(Arrays.asList("10E", "M10", "M11", "M12", "M13", "M14", "M15", "MBS", "ALA", "ISD", "C14", "C15", "CNS", "MMA", "BNG", "KTK", "DTK", "ORI")); } public ZombieToken() { diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 3dae03761b..dce42de2d6 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -44,6 +44,8 @@ import mage.abilities.Modes; import mage.abilities.SpellAbility; import mage.abilities.TriggeredAbility; import mage.abilities.costs.AlternativeSourceCosts; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; @@ -756,19 +758,23 @@ public interface Player extends MageItem, Copyable { void cleanUpOnMatchEnd(); /** - * If the next cast spell has the set sourceId, the spell will be cast - * without mana. + * If the next spell cast has the set sourceId, the spell will be cast + * without mana (null) or the mana set to manaCosts instead of its normal + * mana costs. * * @param sourceId the source that can be cast without mana * @param manaCosts alternate ManaCost, null if it can be cast without mana * cost + * @param costs alternate other costs you need to pay */ - void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts); + void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, mage.abilities.costs.Costs costs); UUID getCastSourceIdWithAlternateMana(); ManaCosts getCastSourceIdManaCosts(); + Costs getCastSourceIdCosts(); + // permission handling to show hand cards void addPermissionToShowHandCards(UUID watcherUserId); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 7f2e1424af..55ce77993b 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -61,6 +61,7 @@ import mage.abilities.costs.AlternativeCost; import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; import mage.abilities.costs.OptionalAdditionalSourceCosts; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; @@ -222,7 +223,8 @@ public abstract class PlayerImpl implements Player, Serializable { // indicates that the spell with the set sourceId can be cast with an alternate mana costs (can also be no mana costs) protected UUID castSourceIdWithAlternateMana; - protected ManaCosts castSourceIdManaCosts; + protected ManaCosts castSourceIdManaCosts; + protected Costs castSourceIdCosts; // indicates that the player is in mana payment phase protected boolean payManaMode = false; @@ -326,6 +328,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.castSourceIdWithAlternateMana = player.castSourceIdWithAlternateMana; this.castSourceIdManaCosts = player.castSourceIdManaCosts; + this.castSourceIdCosts = player.castSourceIdCosts; this.payManaMode = player.payManaMode; } @@ -388,6 +391,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving(); this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana(); this.castSourceIdManaCosts = player.getCastSourceIdManaCosts(); + this.castSourceIdCosts = player.getCastSourceIdCosts(); // Don't restore! // this.storedBookmark @@ -453,6 +457,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.castSourceIdWithAlternateMana = null; this.castSourceIdManaCosts = null; + this.castSourceIdCosts = null; } /** @@ -476,6 +481,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.alternativeSourceCosts.clear(); this.castSourceIdWithAlternateMana = null; this.castSourceIdManaCosts = null; + this.castSourceIdCosts = null; this.getManaPool().clearEmptyManaPoolRules(); } @@ -684,6 +690,12 @@ public abstract class PlayerImpl implements Player, Serializable { return true; } + /** + * + * @param amount + * @param source + * @param game + */ @Override public void discard(int amount, Ability source, Game game) { discard(amount, false, source, game); @@ -917,9 +929,10 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts) { + public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, mage.abilities.costs.Costs costs) { castSourceIdWithAlternateMana = sourceId; castSourceIdManaCosts = manaCosts; + castSourceIdCosts = costs; } @Override @@ -927,6 +940,11 @@ public abstract class PlayerImpl implements Player, Serializable { return castSourceIdWithAlternateMana; } + @Override + public Costs getCastSourceIdCosts() { + return castSourceIdCosts; + } + @Override public ManaCosts getCastSourceIdManaCosts() { return castSourceIdManaCosts; @@ -950,20 +968,25 @@ public abstract class PlayerImpl implements Player, Serializable { Zone fromZone = game.getState().getZone(card.getMainCard().getId()); card.cast(game, fromZone, ability, playerId); Spell spell = game.getStack().getSpell(ability.getId()); - // some effects set sourceId to cast without paying mana costs + // some effects set sourceId to cast without paying mana costs or other costs if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) { - ManaCosts alternateCosts = getCastSourceIdManaCosts(); Ability spellAbility = spell.getSpellAbility(); + ManaCosts alternateCosts = getCastSourceIdManaCosts(); + Costs costs = getCastSourceIdCosts(); if (alternateCosts == null) { noMana = true; } else { spellAbility.getManaCosts().clear(); - spellAbility.getManaCosts().add(alternateCosts.copy()); spellAbility.getManaCostsToPay().clear(); + spellAbility.getManaCosts().add(alternateCosts.copy()); spellAbility.getManaCostsToPay().add(alternateCosts.copy()); } + spellAbility.getCosts().clear(); + if (costs != null) { + spellAbility.getCosts().addAll(costs); + } } - setCastSourceIdWithAlternateMana(null, null); + setCastSourceIdWithAlternateMana(null, null, null); GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId); game.fireEvent(event); if (spell.activate(game, noMana)) { @@ -984,12 +1007,14 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana) { + public SpellAbility chooseSpellAbilityForCast(SpellAbility ability, Game game, boolean noMana + ) { return ability; } @Override - public boolean playLand(Card card, Game game) { + public boolean playLand(Card card, Game game + ) { // Check for alternate casting possibilities: e.g. land with Morph ActivatedAbility playLandAbility = null; boolean found = false; @@ -3050,7 +3075,7 @@ public abstract class PlayerImpl implements Player, Serializable { case GRAVEYARD: fromZone = game.getState().getZone(cards.iterator().next().getId()); successfulMovedCards = moveCardsToGraveyardWithInfo(cards, source, game, fromZone); - break; + return successfulMovedCards.size() > 0; case BATTLEFIELD: // new logic that does not yet add the permanents to battlefield while replacement effects are handled List permanents = new ArrayList<>(); List permanentsEntered = new ArrayList<>(); @@ -3260,6 +3285,7 @@ public abstract class PlayerImpl implements Player, Serializable { } } } + game.fireEvent(new ZoneChangeGroupEvent(movedCards, source == null ? null : source.getSourceId(), this.getId(), fromZone, Zone.GRAVEYARD)); return movedCards; }