diff --git a/Mage.Sets/src/mage/sets/bornofthegods/VortexElemental.java b/Mage.Sets/src/mage/sets/bornofthegods/VortexElemental.java index 67a0add64ff..be1995a6390 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/VortexElemental.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/VortexElemental.java @@ -64,7 +64,7 @@ public class VortexElemental extends CardImpl { this.toughness = new MageInt(1); // {U}: Put Vortex Elemental and each creature blocking or blocked by it on top of their owners' libraries, then those players shuffle their libraries. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new VortexElementaöEffect(), new ManaCostsImpl("{U}"))); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new VortexElementalEffect(), new ManaCostsImpl("{U}"))); // {3}{U}{U}: Target creature blocks Vortex Elemental this turn if able. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MustBeBlockedByTargetSourceEffect(), new ManaCostsImpl("{3}{U}{U}")); @@ -82,20 +82,20 @@ public class VortexElemental extends CardImpl { } } -class VortexElementaöEffect extends OneShotEffect { +class VortexElementalEffect extends OneShotEffect { - public VortexElementaöEffect() { + public VortexElementalEffect() { super(Outcome.Benefit); this.staticText = "Put {this} and each creature blocking or blocked by it on top of their owners' libraries, then those players shuffle their libraries"; } - public VortexElementaöEffect(final VortexElementaöEffect effect) { + public VortexElementalEffect(final VortexElementalEffect effect) { super(effect); } @Override - public VortexElementaöEffect copy() { - return new VortexElementaöEffect(this); + public VortexElementalEffect copy() { + return new VortexElementalEffect(this); } @Override @@ -103,8 +103,8 @@ class VortexElementaöEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { Combat combat = game.getState().getCombat(); - Set creaturesToReturn = new HashSet(); - Set playersToShuffle = new HashSet(); + Set creaturesToReturn = new HashSet<>(); + Set playersToShuffle = new HashSet<>(); creaturesToReturn.add(source.getSourceId()); if (combat != null) { for(CombatGroup combatGroup: combat.getGroups()) { diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java b/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java index f6050c8ffd5..520cb44c282 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/SiftThroughSands.java @@ -1,145 +1,146 @@ -/* - * 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.championsofkamigawa; - -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalOneShotEffect; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.discard.DiscardControllerEffect; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; -import mage.cards.CardImpl; -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.constants.WatcherScope; -import mage.filter.common.FilterCreatureCard; -import mage.filter.predicate.mageobject.NamePredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.stack.Spell; -import mage.target.common.TargetCardInLibrary; -import mage.watchers.Watcher; - -/** - * - * @author LevelX2 - */ -public class SiftThroughSands extends CardImpl { - - private static final String rule = "If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library"; - private static final FilterCreatureCard filter = new FilterCreatureCard("a card named The Unspeakable"); - static { - filter.add(new NamePredicate("The Unspeakable")); - } - - public SiftThroughSands(UUID ownerId) { - super(ownerId, 84, "Sift Through Sands", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); - this.expansionSetCode = "CHK"; - this.subtype.add("Arcane"); - - this.color.setBlue(true); - - // Draw two cards, then discard a card. - this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); - Effect effect = new DiscardControllerEffect(1); - effect.setText(", then discard a card"); - this.getSpellAbility().addEffect(effect); - // If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library. - this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), false, true), new SiftThroughSandsCondition(), rule)); - this.getSpellAbility().addWatcher(new SiftThroughSandsWatcher()); - } - - public SiftThroughSands(final SiftThroughSands card) { - super(card); - } - - @Override - public SiftThroughSands copy() { - return new SiftThroughSands(this); - } -} - -class SiftThroughSandsCondition implements Condition { - - @Override - public boolean apply(Game game, Ability source) { - SiftThroughSandsWatcher watcher = (SiftThroughSandsWatcher) game.getState().getWatchers().get("SiftThroughSandsWatcher", source.getControllerId()); - if (watcher != null) { - return watcher.conditionMet(); - } - return false; - } -} - -class SiftThroughSandsWatcher extends Watcher { - - boolean castPeerThroughDepths = false; - boolean castReachThroughMists = false; - - public SiftThroughSandsWatcher() { - super("SiftThroughSandsWatcher", WatcherScope.PLAYER); - } - - public SiftThroughSandsWatcher(final SiftThroughSandsWatcher watcher) { - super(watcher); - this.castPeerThroughDepths = watcher.castPeerThroughDepths; - this.castReachThroughMists = watcher.castReachThroughMists; - } - - @Override - public SiftThroughSandsWatcher copy() { - return new SiftThroughSandsWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) { //no need to check - condition has already occured - return; - } - if (event.getType() == EventType.SPELL_CAST - && controllerId == event.getPlayerId()) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell.getCard().getName().equals("Peer Through Depths")) { - castPeerThroughDepths = true; - } else if (spell.getCard().getName().equals("Reach Through Mists")) { - castReachThroughMists = true; - } - condition = castPeerThroughDepths && castReachThroughMists; - } - } - - @Override - public void reset() { - super.reset(); - this.castPeerThroughDepths = false; - this.castReachThroughMists = false; - } -} +/* + * 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.championsofkamigawa; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.WatcherScope; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.stack.Spell; +import mage.target.common.TargetCardInLibrary; +import mage.watchers.Watcher; + +/** + * + * @author LevelX2 + */ +public class SiftThroughSands extends CardImpl { + + private static final String rule = "If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library"; + private static final FilterCreatureCard filter = new FilterCreatureCard("a card named The Unspeakable"); + static { + filter.add(new NamePredicate("The Unspeakable")); + } + + public SiftThroughSands(UUID ownerId) { + super(ownerId, 84, "Sift Through Sands", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}{U}"); + this.expansionSetCode = "CHK"; + this.subtype.add("Arcane"); + + this.color.setBlue(true); + + // Draw two cards, then discard a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + Effect effect = new DiscardControllerEffect(1); + effect.setText(", then discard a card"); + this.getSpellAbility().addEffect(effect); + + // If you've cast a spell named Peer Through Depths and a spell named Reach Through Mists this turn, you may search your library for a card named The Unspeakable, put it onto the battlefield, then shuffle your library. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), false, true), new SiftThroughSandsCondition(), rule)); + this.getSpellAbility().addWatcher(new SiftThroughSandsWatcher()); + } + + public SiftThroughSands(final SiftThroughSands card) { + super(card); + } + + @Override + public SiftThroughSands copy() { + return new SiftThroughSands(this); + } +} + +class SiftThroughSandsCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + SiftThroughSandsWatcher watcher = (SiftThroughSandsWatcher) game.getState().getWatchers().get("SiftThroughSandsWatcher", source.getControllerId()); + if (watcher != null) { + return watcher.conditionMet(); + } + return false; + } +} + +class SiftThroughSandsWatcher extends Watcher { + + boolean castPeerThroughDepths = false; + boolean castReachThroughMists = false; + + public SiftThroughSandsWatcher() { + super("SiftThroughSandsWatcher", WatcherScope.PLAYER); + } + + public SiftThroughSandsWatcher(final SiftThroughSandsWatcher watcher) { + super(watcher); + this.castPeerThroughDepths = watcher.castPeerThroughDepths; + this.castReachThroughMists = watcher.castReachThroughMists; + } + + @Override + public SiftThroughSandsWatcher copy() { + return new SiftThroughSandsWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (condition == true) { //no need to check - condition has already occured + return; + } + if (event.getType() == EventType.SPELL_CAST + && controllerId == event.getPlayerId()) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell.getCard().getName().equals("Peer Through Depths")) { + castPeerThroughDepths = true; + } else if (spell.getCard().getName().equals("Reach Through Mists")) { + castReachThroughMists = true; + } + condition = castPeerThroughDepths && castReachThroughMists; + } + } + + @Override + public void reset() { + super.reset(); + this.castPeerThroughDepths = false; + this.castReachThroughMists = false; + } +} diff --git a/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java b/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java index a6e719e370f..4d1b1c471ed 100644 --- a/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java +++ b/Mage.Sets/src/mage/sets/coldsnap/Commandeer.java @@ -107,7 +107,7 @@ class CommandeerEffect extends OneShotEffect { Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); if (controller != null && spell != null) { spell.setControllerId(controller.getId()); - spell.chooseNewTargets(game, controller.getId(), false, false); + spell.chooseNewTargets(game, controller.getId(), false, false, null); return true; } return false; diff --git a/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java b/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java index 601693ea440..a7ad11f4e5c 100644 --- a/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java +++ b/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java @@ -142,7 +142,7 @@ class LightningStormAddCounterEffect extends OneShotEffect { Spell spell = game.getStack().getSpell(source.getSourceId()); if (spell != null) { spell.addCounters(CounterType.CHARGE.createInstance(2), game); - return spell.chooseNewTargets(game, source.getControllerId(), false, false); + return spell.chooseNewTargets(game, source.getControllerId(), false, false, null); } return false; } diff --git a/Mage.Sets/src/mage/sets/commander2013/CurseOfInertia.java b/Mage.Sets/src/mage/sets/commander2013/CurseOfInertia.java index da6f61eaf5b..69d24545cfc 100644 --- a/Mage.Sets/src/mage/sets/commander2013/CurseOfInertia.java +++ b/Mage.Sets/src/mage/sets/commander2013/CurseOfInertia.java @@ -89,27 +89,26 @@ class CurseOfInertiaTriggeredAbility extends TriggeredAbilityImpl { public CurseOfInertiaTriggeredAbility() { super(Zone.BATTLEFIELD, new CurseOfInertiaTapOrUntapTargetEffect(), false); } - - public CurseOfInertiaTriggeredAbility(Effect effect, boolean optional, String text) { - super(Zone.BATTLEFIELD, effect, optional); - } - + public CurseOfInertiaTriggeredAbility(final CurseOfInertiaTriggeredAbility ability) { super(ability); } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType().equals(EventType.DECLARED_ATTACKERS); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType().equals(EventType.DECLARED_ATTACKERS)) { - Permanent enchantment = game.getPermanent(this.getSourceId()); - if (enchantment != null - && enchantment.getAttachedTo() != null - && game.getCombat().getPlayerDefenders(game).contains(enchantment.getAttachedTo())) { - for (Effect effect: this.getEffects()) { - effect.setTargetPointer(new FixedTarget(game.getCombat().getAttackerId())); - } - return true; - } + Permanent enchantment = game.getPermanent(this.getSourceId()); + if (enchantment != null + && enchantment.getAttachedTo() != null + && game.getCombat().getPlayerDefenders(game).contains(enchantment.getAttachedTo())) { + TargetPermanent target = new TargetPermanent(); + target.setTargetController(game.getCombat().getAttackerId()); + addTarget(target); + return true; } return false; } @@ -138,24 +137,20 @@ class CurseOfInertiaTapOrUntapTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(this.getTargetPointer().getFirst(game, source)); + Player player = game.getPlayer(source.getTargets().get(0).getTargetController()); if (player != null) { - Target target = new TargetPermanent(); - if (target.canChoose(source.getSourceId(), player.getId(), game) - && player.choose(outcome, target, source.getSourceId(), game)) { - Permanent targetPermanent = game.getPermanent(target.getFirstTarget()); - if (targetPermanent != null) { - if (targetPermanent.isTapped()) { - if (player.chooseUse(Outcome.Untap, "Untap that permanent?", game)) { - targetPermanent.untap(game); - } - } else { - if (player.chooseUse(Outcome.Tap, "Tap that permanent?", game)) { - targetPermanent.tap(game); - } + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (targetPermanent != null) { + if (targetPermanent.isTapped()) { + if (player.chooseUse(Outcome.Untap, "Untap that permanent?", game)) { + targetPermanent.untap(game); + } + } else { + if (player.chooseUse(Outcome.Tap, "Tap that permanent?", game)) { + targetPermanent.tap(game); } - return true; } + return true; } } return false; diff --git a/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java b/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java index 730543b46f2..e7578f97727 100644 --- a/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java +++ b/Mage.Sets/src/mage/sets/darkascension/DungeonGeists.java @@ -70,7 +70,6 @@ public class DungeonGeists extends CardImpl { this.expansionSetCode = "DKA"; this.subtype.add("Spirit"); - this.color.setBlue(true); this.power = new MageInt(3); this.toughness = new MageInt(3); @@ -112,40 +111,52 @@ class DungeonGeistsEffect extends ContinuousRuleModifyingEffectImpl { } @Override - public boolean apply(Game game, Ability source) { - return false; + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.UNTAP || event.getType() == GameEvent.EventType.ZONE_CHANGE || event.getType() == GameEvent.EventType.LOST_CONTROL; } + @Override public boolean applies(GameEvent event, Ability source, Game game) { // Source must be on the battlefield (it's neccessary to check here because if as response to the enter // the battlefield triggered ability the source dies (or will be exiled), then the ZONE_CHANGE or LOST_CONTROL // event will happen before this effect is applied ever) - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + Permanent sourcePermanent = (Permanent) source.getSourceObjectIfItStillExists(game); if (sourcePermanent == null || !sourcePermanent.getControllerId().equals(source.getControllerId())) { - this.used = true; + discard(); return false; } - if (event.getType() == GameEvent.EventType.LOST_CONTROL) { - if (event.getTargetId().equals(source.getSourceId())) { - discard(); - return false; - } + switch(event.getType()) { + case ZONE_CHANGE: + // end effect if source does a zone move + if (event.getTargetId().equals(source.getSourceId())) { + ZoneChangeEvent zEvent = (ZoneChangeEvent)event; + if (zEvent.getFromZone() == Zone.BATTLEFIELD) { + discard(); + return false; + } + } + break; + case UNTAP: + // prevent to untap the target creature + if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getTargetId().equals(targetPointer.getFirst(game, source))) { + Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + if (targetCreature != null) { + return targetCreature.getControllerId().equals(game.getActivePlayerId()); + } else { + discard(); + return false; + } + } + break; + case LOST_CONTROL: + // end effect if source control is changed + if (event.getTargetId().equals(source.getSourceId())) { + discard(); + return false; + } + break; } - if (event.getType() == GameEvent.EventType.ZONE_CHANGE && event.getTargetId().equals(source.getSourceId())) { - ZoneChangeEvent zEvent = (ZoneChangeEvent)event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD) { - discard(); - return false; - } - } - - if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == GameEvent.EventType.UNTAP) { - if (event.getTargetId().equals(targetPointer.getFirst(game, source))) { - return true; - } - } - return false; } } diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/CloneLegion.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/CloneLegion.java index 1ab5b09da0b..6225c113df0 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/CloneLegion.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/CloneLegion.java @@ -86,13 +86,14 @@ class CloneLegionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); - if (targetPlayer != null) { + if (controller != null && targetPlayer != null) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), targetPlayer.getId(), game)) { if (permanent != null) { EmptyToken token = new EmptyToken(); CardUtil.copyTo(token).from(permanent); - token.putOntoBattlefield(1, game, source.getSourceId(), targetPlayer.getId()); + token.putOntoBattlefield(1, game, source.getSourceId(), controller.getId()); } } return true; diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java index fd8fce1274b..050adf87c48 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/IcefallRegent.java @@ -48,7 +48,6 @@ import mage.constants.TargetController; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; -import static mage.filter.predicate.permanent.ControllerControlsIslandPredicate.filter; import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; import mage.game.events.GameEvent; @@ -157,7 +156,8 @@ class IcefallRegentEffect extends ContinuousRuleModifyingEffectImpl { if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == GameEvent.EventType.UNTAP) { if (event.getTargetId().equals(targetPointer.getFirst(game, source))) { - return true; + Permanent targetCreature = game.getPermanent(targetPointer.getFirst(game, source)); + return targetCreature != null && game.getActivePlayerId().equals(targetCreature.getControllerId()); } } diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/MirrorMockery.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/MirrorMockery.java index 89b177f0c80..84029470b98 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/MirrorMockery.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/MirrorMockery.java @@ -36,7 +36,6 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.keyword.EnchantAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.constants.AttachmentType; import mage.constants.CardType; @@ -106,22 +105,19 @@ class MirrorMockeryEffect extends OneShotEffect { } Permanent enchanted = game.getPermanent(enchantment.getAttachedTo()); if (enchanted != null) { - Card card = game.getCard(enchanted.getId()); - if (card != null) { - EmptyToken token = new EmptyToken(); - CardUtil.copyTo(token).from(card); + EmptyToken token = new EmptyToken(); + CardUtil.copyTo(token).from(enchanted); - token.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); + token.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); - ExileTargetEffect exileEffect = new ExileTargetEffect(); - exileEffect.setTargetPointer(new FixedTarget(token.getLastAddedToken())); - DelayedTriggeredAbility delayedAbility = new AtTheEndOfCombatDelayedTriggeredAbility(exileEffect); - delayedAbility.setSourceId(source.getSourceId()); - delayedAbility.setControllerId(source.getControllerId()); - delayedAbility.setSourceObject(source.getSourceObject(game), game); - game.addDelayedTriggeredAbility(delayedAbility); - return true; - } + ExileTargetEffect exileEffect = new ExileTargetEffect(); + exileEffect.setTargetPointer(new FixedTarget(token.getLastAddedToken())); + DelayedTriggeredAbility delayedAbility = new AtTheEndOfCombatDelayedTriggeredAbility(exileEffect); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.setSourceObject(source.getSourceObject(game), game); + game.addDelayedTriggeredAbility(delayedAbility); + return true; } return false; } diff --git a/Mage.Sets/src/mage/sets/exodus/Pandemonium.java b/Mage.Sets/src/mage/sets/exodus/Pandemonium.java new file mode 100644 index 00000000000..199f1c97fc4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/exodus/Pandemonium.java @@ -0,0 +1,54 @@ +/* + * 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.exodus; + +import java.util.UUID; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Pandemonium extends mage.sets.timeshifted.Pandemonium { + + public Pandemonium(UUID ownerId) { + super(ownerId); + this.cardNumber = 93; + this.expansionSetCode = "EXO"; + this.rarity = Rarity.RARE; + } + + public Pandemonium(final Pandemonium card) { + super(card); + } + + @Override + public Pandemonium copy() { + return new Pandemonium(this); + } +} diff --git a/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java b/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java index 03c29db4dfd..7ae39c928db 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java +++ b/Mage.Sets/src/mage/sets/fatereforged/SoulfireGrandMaster.java @@ -48,14 +48,12 @@ import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.SubLayer; -import mage.constants.TargetController; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.FilterObject; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -230,7 +228,10 @@ class SoulfireGrandMasterCastFromHandReplacementEffect extends ReplacementEffect if (zEvent.getFromZone() == Zone.STACK && zEvent.getToZone() == Zone.GRAVEYARD && event.getTargetId().equals(spellId)) { - return true; + Spell spell = game.getStack().getSpell(spellId); + if (spell != null && !spell.isCountered()) { + return true; + } } } return false; diff --git a/Mage.Sets/src/mage/sets/fifthedition/TheWretched.java b/Mage.Sets/src/mage/sets/fifthedition/TheWretched.java index 954925a745b..9554817d948 100644 --- a/Mage.Sets/src/mage/sets/fifthedition/TheWretched.java +++ b/Mage.Sets/src/mage/sets/fifthedition/TheWretched.java @@ -42,7 +42,6 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; -import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.events.GameEvent; @@ -71,7 +70,7 @@ import mage.watchers.common.BlockedAttackerWatcher; public class TheWretched extends CardImpl { public TheWretched(UUID ownerId) { - super(ownerId, 239, "The Wretched", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); + super(ownerId, 59, "The Wretched", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{B}{B}"); this.expansionSetCode = "5ED"; this.subtype.add("Demon"); this.power = new MageInt(2); @@ -160,19 +159,6 @@ class TheWretchedEffect extends OneShotEffect { } } return true; -// BlockedAttackerWatcher watcher = (BlockedAttackerWatcher) game.getState().getWatchers().get("BlockedAttackerWatcher"); -// if (watcher != null) { -// for (Permanent creature : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game)) { -// if (watcher.creatureHasBlockedAttacker(theWretched, creature, game) -// && !creature.isRemovedFromCombat()) { -// ContinuousEffect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom, source.getControllerId()), new SourceOnBattlefieldControlUnchangedCondition(), "test"); -// effect.setTargetPointer(new FixedTarget(creature.getId())); -// game.addEffect(effect, source); -// } -// } -// return true; -// } -// return false; } @Override diff --git a/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java b/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java index 1b209d61865..a6c1216c766 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java +++ b/Mage.Sets/src/mage/sets/gatecrash/MimingSlime.java @@ -1,119 +1,117 @@ -/* - * 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.gatecrash; - -import java.util.List; -import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.CardImpl; -import mage.filter.common.FilterCreaturePermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.game.permanent.token.Token; -import mage.players.Player; - -/** - * - * @author LevelX2 - */ -public class MimingSlime extends CardImpl { - - public MimingSlime(UUID ownerId) { - super(ownerId, 126, "Miming Slime", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{2}{G}"); - this.expansionSetCode = "GTC"; - - this.color.setGreen(true); - - // Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control. - this.getSpellAbility().addEffect(new MimingSlimeEffect()); - } - - public MimingSlime(final MimingSlime card) { - super(card); - } - - @Override - public MimingSlime copy() { - return new MimingSlime(this); - } -} - -class MimingSlimeEffect extends OneShotEffect { - - public MimingSlimeEffect() { - super(Outcome.PutCreatureInPlay); - staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control"; - } - - public MimingSlimeEffect(final MimingSlimeEffect effect) { - super(effect); - } - - @Override - public MimingSlimeEffect copy() { - return new MimingSlimeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - List creatures = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game); - int amount = 0; - for (Permanent creature : creatures) { - int power = creature.getPower().getValue(); - if (amount < power) { - amount = power; - } - } - OozeToken oozeToken = new OozeToken(); - oozeToken.getPower().initValue(amount); - oozeToken.getToughness().initValue(amount); - oozeToken.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); - return true; - } - return false; - } -} - -class OozeToken extends Token { - public OozeToken() { - super("Ooze", "X/X green Ooze creature token"); - cardType.add(CardType.CREATURE); - subtype.add("Ooze"); - color.setGreen(true); - power = new MageInt(0); - toughness = new MageInt(0); - setOriginalExpansionSetCode("RTR"); - } -} +/* + * 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.gatecrash; + +import java.util.List; +import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.Token; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class MimingSlime extends CardImpl { + + public MimingSlime(UUID ownerId) { + super(ownerId, 126, "Miming Slime", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{2}{G}"); + this.expansionSetCode = "GTC"; + + // Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control. + this.getSpellAbility().addEffect(new MimingSlimeEffect()); + } + + public MimingSlime(final MimingSlime card) { + super(card); + } + + @Override + public MimingSlime copy() { + return new MimingSlime(this); + } +} + +class MimingSlimeEffect extends OneShotEffect { + + public MimingSlimeEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the greatest power among creatures you control"; + } + + public MimingSlimeEffect(final MimingSlimeEffect effect) { + super(effect); + } + + @Override + public MimingSlimeEffect copy() { + return new MimingSlimeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + List creatures = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game); + int amount = 0; + for (Permanent creature : creatures) { + int power = creature.getPower().getValue(); + if (amount < power) { + amount = power; + } + } + OozeToken oozeToken = new OozeToken(); + oozeToken.getPower().initValue(amount); + oozeToken.getToughness().initValue(amount); + oozeToken.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); + return true; + } + return false; + } +} + +class OozeToken extends Token { + public OozeToken() { + super("Ooze", "X/X green Ooze creature token"); + cardType.add(CardType.CREATURE); + subtype.add("Ooze"); + color.setGreen(true); + power = new MageInt(0); + toughness = new MageInt(0); + setOriginalExpansionSetCode("RTR"); + } +} diff --git a/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java b/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java index 99deed43895..8fdb455bce9 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java +++ b/Mage.Sets/src/mage/sets/gatecrash/OozeFlux.java @@ -1,111 +1,109 @@ -/* - * 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.gatecrash; - -import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.constants.Zone; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.RemoveVariableCountersTargetCost; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; -import mage.cards.CardImpl; -import mage.counters.CounterType; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.game.Game; -import mage.game.permanent.token.Token; - -/** - * - * @author LevelX2 - */ -public class OozeFlux extends CardImpl { - - public OozeFlux(UUID ownerId) { - super(ownerId, 128, "Ooze Flux", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); - this.expansionSetCode = "GTC"; - - this.color.setGreen(true); - - // {1}{G}, Remove one or more +1/+1 counters from among creatures you control: Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new OozeFluxCreateTokenEffect(new OozeToken()),new ManaCostsImpl("{1}{G}")); - ability.addCost(new RemoveVariableCountersTargetCost(new FilterControlledCreaturePermanent("creatures you control"), CounterType.P1P1, "one or more", 1)); - this.addAbility(ability); - } - - public OozeFlux(final OozeFlux card) { - super(card); - } - - @Override - public OozeFlux copy() { - return new OozeFlux(this); - } -} - -class OozeFluxCreateTokenEffect extends OneShotEffect { - - private Token token; - - public OozeFluxCreateTokenEffect(Token token) { - super(Outcome.PutCreatureInPlay); - this.token = token; - staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way"; - } - - public OozeFluxCreateTokenEffect(final OozeFluxCreateTokenEffect effect) { - super(effect); - this.token = effect.token.copy(); - } - - @Override - public OozeFluxCreateTokenEffect copy() { - return new OozeFluxCreateTokenEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - int xValue = 0; - for (Cost cost : source.getCosts()) { - if (cost instanceof RemoveVariableCountersTargetCost) { - xValue = ((RemoveVariableCountersTargetCost) cost).getAmount(); - break; - } - } - Token tokenCopy = token.copy(); - tokenCopy.getAbilities().newId(); - tokenCopy.getPower().initValue(xValue); - tokenCopy.getToughness().initValue(xValue); - tokenCopy.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); - return true; - } -} +/* + * 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.gatecrash; + +import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.RemoveVariableCountersTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.token.Token; + +/** + * + * @author LevelX2 + */ +public class OozeFlux extends CardImpl { + + public OozeFlux(UUID ownerId) { + super(ownerId, 128, "Ooze Flux", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); + this.expansionSetCode = "GTC"; + + // {1}{G}, Remove one or more +1/+1 counters from among creatures you control: Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new OozeFluxCreateTokenEffect(new OozeToken()),new ManaCostsImpl("{1}{G}")); + ability.addCost(new RemoveVariableCountersTargetCost(new FilterControlledCreaturePermanent("creatures you control"), CounterType.P1P1, "one or more", 1)); + this.addAbility(ability); + } + + public OozeFlux(final OozeFlux card) { + super(card); + } + + @Override + public OozeFlux copy() { + return new OozeFlux(this); + } +} + +class OozeFluxCreateTokenEffect extends OneShotEffect { + + private final Token token; + + public OozeFluxCreateTokenEffect(Token token) { + super(Outcome.PutCreatureInPlay); + this.token = token; + staticText = "Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way"; + } + + public OozeFluxCreateTokenEffect(final OozeFluxCreateTokenEffect effect) { + super(effect); + this.token = effect.token.copy(); + } + + @Override + public OozeFluxCreateTokenEffect copy() { + return new OozeFluxCreateTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + int xValue = 0; + for (Cost cost : source.getCosts()) { + if (cost instanceof RemoveVariableCountersTargetCost) { + xValue = ((RemoveVariableCountersTargetCost) cost).getAmount(); + break; + } + } + Token tokenCopy = token.copy(); + tokenCopy.getAbilities().newId(); + tokenCopy.getPower().initValue(xValue); + tokenCopy.getToughness().initValue(xValue); + tokenCopy.putOntoBattlefield(1, game, source.getSourceId(), source.getControllerId()); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java b/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java index 61ed7b2cd7e..2ff987f20dc 100644 --- a/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java +++ b/Mage.Sets/src/mage/sets/jacevschandra/QuicksilverDragon.java @@ -107,7 +107,7 @@ class QuicksilverDragonEffect extends OneShotEffect { numTargets += target.getTargets().size(); } if (numTargets == 1 && spell.getSpellAbility().getTargets().getFirstTarget().equals(source.getSourceId())) { - spell.chooseNewTargets(game, source.getControllerId(), true, false); + spell.chooseNewTargets(game, source.getControllerId(), true, false, null); } return true; } diff --git a/Mage.Sets/src/mage/sets/judgment/WorldgorgerDragon.java b/Mage.Sets/src/mage/sets/judgment/WorldgorgerDragon.java index ee241e0634c..7944d126748 100644 --- a/Mage.Sets/src/mage/sets/judgment/WorldgorgerDragon.java +++ b/Mage.Sets/src/mage/sets/judgment/WorldgorgerDragon.java @@ -48,6 +48,7 @@ import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.ExileZone; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; import mage.players.Player; import mage.util.CardUtil; @@ -143,8 +144,10 @@ class WorldgorgerDragonLeavesEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter() -1)); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + int zoneChangeCounter = (sourceObject instanceof PermanentToken) ? source.getSourceObjectZoneChangeCounter() : source.getSourceObjectZoneChangeCounter() -1; + ExileZone exile = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source.getSourceId(), zoneChangeCounter)); if (exile != null) { exile = exile.copy(); for (UUID cardId : exile) { diff --git a/Mage.Sets/src/mage/sets/legends/TheAbyss.java b/Mage.Sets/src/mage/sets/legends/TheAbyss.java index 8957b130f4a..3664e65ad01 100644 --- a/Mage.Sets/src/mage/sets/legends/TheAbyss.java +++ b/Mage.Sets/src/mage/sets/legends/TheAbyss.java @@ -86,24 +86,27 @@ class TheAbyssTriggeredAbility extends TriggeredAbilityImpl { public TheAbyssTriggeredAbility copy() { return new TheAbyssTriggeredAbility(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) { - if (event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE) { - Player player = game.getPlayer(event.getPlayerId()); - if (player != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control"); - filter.add(Predicates.not(new CardTypePredicate(CardType.ARTIFACT))); - filter.add(new ControllerIdPredicate(player.getId())); - Target target = new TargetCreaturePermanent(filter); - if (target.canChoose(this.getSourceId(), this.getControllerId(), game) && player.chooseTarget(Outcome.DestroyPermanent, target, this, game)) { - for (Effect effect: this.getEffects()) { - if (effect instanceof DestroyTargetEffect) { - effect.setTargetPointer(new FixedTarget(target.getFirstTarget())); - } + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control"); + filter.add(Predicates.not(new CardTypePredicate(CardType.ARTIFACT))); + filter.add(new ControllerIdPredicate(player.getId())); + Target target = new TargetCreaturePermanent(filter); + if (target.canChoose(this.getSourceId(), this.getControllerId(), game) && player.chooseTarget(Outcome.DestroyPermanent, target, this, game)) { + for (Effect effect: this.getEffects()) { + if (effect instanceof DestroyTargetEffect) { + effect.setTargetPointer(new FixedTarget(target.getFirstTarget())); } - return true; } + return true; } } return false; diff --git a/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java b/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java index 2496e3e9871..015dc2f6c23 100644 --- a/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java +++ b/Mage.Sets/src/mage/sets/limitedalpha/Timetwister.java @@ -30,10 +30,12 @@ package mage.sets.limitedalpha; import java.util.UUID; import mage.abilities.Ability; 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.Zone; import mage.game.Game; import mage.players.Player; @@ -81,14 +83,23 @@ class TimetwisterEffect extends OneShotEffect { for (UUID playerId: sourcePlayer.getInRange()) { Player player = game.getPlayer(playerId); if (player != null) { - player.getLibrary().addAll(player.getHand().getCards(game), game); - player.getLibrary().addAll(player.getGraveyard().getCards(game), game); + for (Card card: player.getHand().getCards(game)) { + card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); + } + for (Card card: player.getGraveyard().getCards(game)) { + card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); + } player.shuffleLibrary(game); - player.getHand().clear(); - player.getGraveyard().clear(); - player.drawCards(7, game); + } } + game.getState().handleSimultaneousEvent(game); // needed here so state based triggered effects + for (UUID playerId: sourcePlayer.getInRange()) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.drawCards(7, game); + } + } return true; } diff --git a/Mage.Sets/src/mage/sets/lorwyn/JudgeOfCurrents.java b/Mage.Sets/src/mage/sets/lorwyn/JudgeOfCurrents.java new file mode 100644 index 00000000000..09bd019a922 --- /dev/null +++ b/Mage.Sets/src/mage/sets/lorwyn/JudgeOfCurrents.java @@ -0,0 +1,65 @@ +/* + * 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.lorwyn; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.BecomesTappedCreatureControlledTriggeredAbility; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterControlledCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class JudgeOfCurrents extends CardImpl { + + public JudgeOfCurrents(UUID ownerId) { + super(ownerId, 22, "Judge of Currents", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{1}{W}"); + this.expansionSetCode = "LRW"; + this.subtype.add("Merfolk"); + this.subtype.add("Wizard"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Whenever a Merfolk you control becomes tapped, you may gain 1 life. + this.addAbility(new BecomesTappedCreatureControlledTriggeredAbility(new GainLifeEffect(1), true, new FilterControlledCreaturePermanent("Merfolk", "a Merfolk you control"))); + } + + public JudgeOfCurrents(final JudgeOfCurrents card) { + super(card); + } + + @Override + public JudgeOfCurrents copy() { + return new JudgeOfCurrents(this); + } +} diff --git a/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java b/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java index 714eac76eaf..e08b5280a8d 100644 --- a/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java +++ b/Mage.Sets/src/mage/sets/magic2012/GrandAbolisher.java @@ -106,30 +106,24 @@ class GrandAbolisherEffect extends ContinuousRuleModifyingEffectImpl { } @Override - public boolean applies(GameEvent event, Ability source, Game game) { - boolean spell = event.getType() == GameEvent.EventType.CAST_SPELL; - boolean activated = event.getType() == GameEvent.EventType.ACTIVATE_ABILITY; - if ((spell || activated) && game.getActivePlayerId().equals(source.getControllerId()) && game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) { - - if (spell) { - return true; - } - - // check source of activated ability - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - return permanent.getCardType().contains(CardType.ARTIFACT) || permanent.getCardType().contains(CardType.CREATURE) - || permanent.getCardType().contains(CardType.ENCHANTMENT); - } else { - MageObject object = game.getObject(event.getSourceId()); - if (object != null) { - return object.getCardType().contains(CardType.ARTIFACT) || object.getCardType().contains(CardType.CREATURE) - || object.getCardType().contains(CardType.ENCHANTMENT); - } - } - } - - return false; + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL || event.getType() == GameEvent.EventType.ACTIVATE_ABILITY; } + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (game.getActivePlayerId().equals(source.getControllerId()) && game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) { + switch(event.getType()) { + case CAST_SPELL: + return true; + case ACTIVATE_ABILITY: + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null) { + return permanent.getCardType().contains(CardType.ARTIFACT) || permanent.getCardType().contains(CardType.CREATURE) + || permanent.getCardType().contains(CardType.ENCHANTMENT); + } + } + } + return false; + } } diff --git a/Mage.Sets/src/mage/sets/odyssey/Divert.java b/Mage.Sets/src/mage/sets/odyssey/Divert.java index 91af687c71c..f0755b8e183 100644 --- a/Mage.Sets/src/mage/sets/odyssey/Divert.java +++ b/Mage.Sets/src/mage/sets/odyssey/Divert.java @@ -94,7 +94,7 @@ class DivertEffect extends OneShotEffect { cost.clearPaid(); if (!cost.pay(source, game, spell.getControllerId(), spell.getControllerId(), false)) { - return spell.chooseNewTargets(game, source.getControllerId(), true, true); + return spell.chooseNewTargets(game, source.getControllerId(), true, true, null); } } } diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java index 5444eecf2a0..7fb923d0381 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/PainfulQuandary.java @@ -37,6 +37,9 @@ import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterSpell; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInHand; @@ -52,7 +55,7 @@ public class PainfulQuandary extends CardImpl { this.expansionSetCode = "SOM"; // Whenever an opponent casts a spell, that player loses 5 life unless he or she discards a card. - this.addAbility(new SpellCastOpponentTriggeredAbility(new PainfulQuandryEffect(), false)); + this.addAbility(new SpellCastOpponentTriggeredAbility(Zone.BATTLEFIELD, new PainfulQuandryEffect(), new FilterSpell(), false, SetTargetPointer.PLAYER)); } public PainfulQuandary(final PainfulQuandary card) { diff --git a/Mage.Sets/src/mage/sets/stronghold/Cannibalize.java b/Mage.Sets/src/mage/sets/stronghold/Cannibalize.java new file mode 100644 index 00000000000..8a4516af6c1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/stronghold/Cannibalize.java @@ -0,0 +1,54 @@ +/* + * 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.stronghold; + +import java.util.UUID; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Cannibalize extends mage.sets.tempestremastered.Cannibalize { + + public Cannibalize(UUID ownerId) { + super(ownerId); + this.cardNumber = 3; + this.expansionSetCode = "STH"; + this.rarity = Rarity.COMMON; + } + + public Cannibalize(final Cannibalize card) { + super(card); + } + + @Override + public Cannibalize copy() { + return new Cannibalize(this); + } +} diff --git a/Mage.Sets/src/mage/sets/stronghold/SilverWyvern.java b/Mage.Sets/src/mage/sets/stronghold/SilverWyvern.java new file mode 100644 index 00000000000..28096c464f9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/stronghold/SilverWyvern.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.stronghold; + +import java.util.UUID; + +/** + * + * @author LevelX2 + */ +public class SilverWyvern extends mage.sets.tempestremastered.SilverWyvern { + + public SilverWyvern(UUID ownerId) { + super(ownerId); + this.cardNumber = 43; + this.expansionSetCode = "STH"; + } + + public SilverWyvern(final SilverWyvern card) { + super(card); + } + + @Override + public SilverWyvern copy() { + return new SilverWyvern(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempest/CoffinQueen.java b/Mage.Sets/src/mage/sets/tempest/CoffinQueen.java new file mode 100644 index 00000000000..b030b93e2af --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempest/CoffinQueen.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.tempest; + +import java.util.UUID; + +/** + * + * @author anonymous + */ +public class CoffinQueen extends mage.sets.tempestremastered.CoffinQueen { + + public CoffinQueen(UUID ownerId) { + super(ownerId); + this.cardNumber = 8; + this.expansionSetCode = "TMP"; + } + + public CoffinQueen(final CoffinQueen card) { + super(card); + } + + @Override + public CoffinQueen copy() { + return new CoffinQueen(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempest/TrumpetingArmodon.java b/Mage.Sets/src/mage/sets/tempest/TrumpetingArmodon.java new file mode 100644 index 00000000000..e329e297cee --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempest/TrumpetingArmodon.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.tempest; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.combat.MustBeBlockedByTargetSourceEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Sir-Speshkitty + */ +public class TrumpetingArmodon extends CardImpl { + + public TrumpetingArmodon(UUID ownerId) { + super(ownerId, 156, "Trumpeting Armodon", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{G}"); + this.expansionSetCode = "TMP"; + this.subtype.add("Elephant"); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // {1}{G}: Target creature blocks Trumpeting Armodon this turn if able. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MustBeBlockedByTargetSourceEffect(), new ManaCostsImpl("{1}{G}")); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public TrumpetingArmodon(final TrumpetingArmodon card) { + super(card); + } + + @Override + public TrumpetingArmodon copy() { + return new TrumpetingArmodon(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/Cannibalize.java b/Mage.Sets/src/mage/sets/tempestremastered/Cannibalize.java new file mode 100644 index 00000000000..6a4df481c13 --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/Cannibalize.java @@ -0,0 +1,152 @@ +/* + * 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.tempestremastered; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class Cannibalize extends CardImpl { + + public Cannibalize(UUID ownerId) { + super(ownerId, 83, "Cannibalize", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{1}{B}"); + this.expansionSetCode = "TPR"; + + // Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other. + this.getSpellAbility().addEffect(new CannibalizeEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanentSameController(2,2,new FilterCreaturePermanent(),false)); + } + + public Cannibalize(final Cannibalize card) { + super(card); + } + + @Override + public Cannibalize copy() { + return new Cannibalize(this); + } +} + + + +class CannibalizeEffect extends OneShotEffect { + + public CannibalizeEffect() { + super(Outcome.Benefit); + this.staticText = "Choose two target creatures controlled by the same player. Exile one of the creatures and put two +1/+1 counters on the other"; + } + + public CannibalizeEffect(final CannibalizeEffect effect) { + super(effect); + } + + @Override + public CannibalizeEffect copy() { + return new CannibalizeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + boolean exileDone = false; + int count = 0; + for(UUID targetId: getTargetPointer().getTargets(game, source)) { + Permanent creature = game.getPermanent(targetId); + if (creature != null) { + if ((count == 0 && controller.chooseUse(Outcome.Exile, "Exile " + creature.getLogName() +"?", game)) + || (count == 1 && !exileDone)) { + controller.moveCardToExileWithInfo(creature, null, "", source.getSourceId(), game, Zone.BATTLEFIELD, true); + exileDone = true; + } else { + creature.addCounters(CounterType.P1P1.createInstance(2), game); + game.informPlayers("Added two +1/+1 counters on " + creature.getLogName()); + } + count++; + } + } + return true; + } + return false; + } +} + +class TargetCreaturePermanentSameController extends TargetCreaturePermanent { + + public TargetCreaturePermanentSameController(int minNumTargets, int maxNumTargets, FilterCreaturePermanent filter, boolean notTarget) { + super(minNumTargets, maxNumTargets, filter, notTarget); + this.targetName = filter.getMessage(); + } + + public TargetCreaturePermanentSameController(final TargetCreaturePermanentSameController target) { + super(target); + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + UUID firstTarget = this.getFirstTarget(); + if (firstTarget != null) { + Permanent permanent = game.getPermanent(firstTarget); + Permanent targetPermanent = game.getPermanent(id); + if (permanent == null || targetPermanent == null + || !permanent.getControllerId().equals(targetPermanent.getOwnerId())) { + return false; + } + } + return super.canTarget(id, source, game); + } + + @Override + public TargetCreaturePermanentSameController copy() { + return new TargetCreaturePermanentSameController(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/tempestremastered/CoffinQueen.java b/Mage.Sets/src/mage/sets/tempestremastered/CoffinQueen.java new file mode 100644 index 00000000000..eedb00ce94d --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/CoffinQueen.java @@ -0,0 +1,153 @@ +/* + * 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.tempestremastered; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SkipUntapOptionalAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author anonymous + */ +public class CoffinQueen extends CardImpl { + + public CoffinQueen(UUID ownerId) { + super(ownerId, 87, "Coffin Queen", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{B}"); + this.expansionSetCode = "TPR"; + this.subtype.add("Zombie"); + this.subtype.add("Wizard"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // You may choose not to untap Coffin Queen during your untap step. + this.addAbility(new SkipUntapOptionalAbility()); + + // {2}{B}, {tap}: Put target creature card from a graveyard onto the battlefield under your control. When Coffin Queen becomes untapped or you lose control of Coffin Queen, exile that creature. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl("{2}{B}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard"))); + ability.addEffect(new CoffinQueenCreateDelayedTriggerEffect()); + this.addAbility(ability); + + } + + public CoffinQueen(final CoffinQueen card) { + super(card); + } + + @Override + public CoffinQueen copy() { + return new CoffinQueen(this); + } +} +class CoffinQueenCreateDelayedTriggerEffect extends OneShotEffect { + + public CoffinQueenCreateDelayedTriggerEffect() { + super(Outcome.Detriment); + this.staticText = "When Coffin Queen becomes untapped or you lose control of Coffin Queen, exile that creature"; + } + + public CoffinQueenCreateDelayedTriggerEffect(final CoffinQueenCreateDelayedTriggerEffect effect) { + super(effect); + } + + @Override + public CoffinQueenCreateDelayedTriggerEffect copy() { + return new CoffinQueenCreateDelayedTriggerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent controlledCreature = game.getPermanent(source.getFirstTarget()); + if (controlledCreature != null) { + DelayedTriggeredAbility delayedAbility = new CoffinQueenDelayedTriggeredAbility(); + delayedAbility.getEffects().get(0).setTargetPointer(new FixedTarget(controlledCreature.getId())); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.setSourceObject(source.getSourceObject(game), game); + delayedAbility.init(game); + game.addDelayedTriggeredAbility(delayedAbility); + return true; + } + return false; + } +} + +class CoffinQueenDelayedTriggeredAbility extends DelayedTriggeredAbility { + + CoffinQueenDelayedTriggeredAbility() { + super(new ExileTargetEffect(), Duration.EndOfGame, true); + } + + CoffinQueenDelayedTriggeredAbility(CoffinQueenDelayedTriggeredAbility ability) { + super(ability); + } + + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (GameEvent.EventType.LOST_CONTROL.equals(event.getType()) + && event.getSourceId().equals(getSourceId())) { + return true; + } + return GameEvent.EventType.UNTAPPED.equals(event.getType()) + && event.getTargetId() != null && event.getTargetId().equals(getSourceId()); + } + + @Override + public CoffinQueenDelayedTriggeredAbility copy() { + return new CoffinQueenDelayedTriggeredAbility(this); + } + + @Override + public String getRule() { + return "When {this} becomes untapped or you lose control of {this}, exile that creature"; + } +} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/Pandemonium.java b/Mage.Sets/src/mage/sets/tempestremastered/Pandemonium.java new file mode 100644 index 00000000000..f458fbe40fc --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/Pandemonium.java @@ -0,0 +1,54 @@ +/* + * 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.tempestremastered; + +import java.util.UUID; +import mage.constants.Rarity; + +/** + * + * @author LevelX2 + */ +public class Pandemonium extends mage.sets.timeshifted.Pandemonium { + + public Pandemonium(UUID ownerId) { + super(ownerId); + this.cardNumber = 149; + this.expansionSetCode = "TPR"; + this.rarity = Rarity.RARE; + } + + public Pandemonium(final Pandemonium card) { + super(card); + } + + @Override + public Pandemonium copy() { + return new Pandemonium(this); + } +} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/SilverWyvern.java b/Mage.Sets/src/mage/sets/tempestremastered/SilverWyvern.java new file mode 100644 index 00000000000..204897befb8 --- /dev/null +++ b/Mage.Sets/src/mage/sets/tempestremastered/SilverWyvern.java @@ -0,0 +1,118 @@ +/* + * 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.tempestremastered; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterStackObject; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.target.Target; +import mage.target.TargetStackObject; +import mage.target.Targets; + +/** + * + * @author LevelX2 + */ +public class SilverWyvern extends CardImpl { + + public SilverWyvern(UUID ownerId) { + super(ownerId, 68, "Silver Wyvern", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + this.expansionSetCode = "TPR"; + this.subtype.add("Drake"); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // {U}: Change the target of target spell or ability that targets only Silver Wyvern. The new target must be a creature. + Effect effect = new ChooseNewTargetsTargetEffect(true, true); + effect.setText("Change the target of target spell or ability that targets only {this}. The new target must be a creature"); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{U}")); + FilterStackObject filter = new FilterStackObject(); + filter.add(new TargetsOnlySourcePredicate(getId())); + ability.addTarget(new TargetStackObject(filter)); + this.addAbility(ability); + + } + + public SilverWyvern(final SilverWyvern card) { + super(card); + } + + @Override + public SilverWyvern copy() { + return new SilverWyvern(this); + } +} + +class TargetsOnlySourcePredicate implements Predicate { + + private final UUID sourceId; + + public TargetsOnlySourcePredicate(UUID sourceId) { + this.sourceId = sourceId; + } + + @Override + public boolean apply(MageObject input, Game game) { + StackObject stackObject = game.getStack().getStackObject(input.getId()); + if (stackObject != null) { + Targets spellTargets = stackObject.getStackAbility().getTargets(); + int numberOfTargets = 0; + for (Target target : spellTargets) { + if (target.getFirstTarget() == null || !target.getFirstTarget().toString().equals(sourceId.toString())) { // UUID != UUID does not work - it's always false + return false; + } + numberOfTargets += target.getTargets().size(); + } + if (numberOfTargets == 1) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "target spell or ability that targets only source"; + } +} diff --git a/Mage.Sets/src/mage/sets/timeshifted/Pandemonium.java b/Mage.Sets/src/mage/sets/timeshifted/Pandemonium.java new file mode 100644 index 00000000000..588ee3e077f --- /dev/null +++ b/Mage.Sets/src/mage/sets/timeshifted/Pandemonium.java @@ -0,0 +1,119 @@ +/* + * 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.timeshifted; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreatureOrPlayer; + +/** + * + * @author LevelX2 + */ +public class Pandemonium extends CardImpl { + + public Pandemonium(UUID ownerId) { + super(ownerId, 68, "Pandemonium", Rarity.SPECIAL, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); + this.expansionSetCode = "TSB"; + + // Whenever a creature enters the battlefield, that creature's controller may have it deal damage equal to its power to target creature or player of his or her choice. + Ability ability = new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new PandemoniumEffect(), new FilterCreaturePermanent(), false, SetTargetPointer.PERMANENT, ""); + ability.addTarget(new TargetCreatureOrPlayer()); + this.addAbility(ability); + } + + public Pandemonium(final Pandemonium card) { + super(card); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability instanceof EntersBattlefieldAllTriggeredAbility) { + UUID creatureId = ability.getEffects().get(0).getTargetPointer().getFirst(game, ability); + Permanent creature = game.getPermanent(creatureId); + if (creature != null) { + ability.getTargets().get(0).setTargetController(creature.getControllerId()); + } + } + } + + @Override + public Pandemonium copy() { + return new Pandemonium(this); + } +} + +class PandemoniumEffect extends OneShotEffect { + + public PandemoniumEffect() { + super(Outcome.Benefit); + this.staticText = "that creature's controller may have it deal damage equal to its power to target creature or player of his or her choice"; + } + + public PandemoniumEffect(final PandemoniumEffect effect) { + super(effect); + } + + @Override + public PandemoniumEffect copy() { + return new PandemoniumEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent enteringCreature = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + if (enteringCreature != null) { + Permanent targetPermanent = game.getPermanent(source.getTargets().getFirstTarget()); + if (targetPermanent != null) { + targetPermanent.damage(enteringCreature.getPower().getValue(), source.getSourceId(), game, false, true); + } else { + Player targetPlayer = game.getPlayer(source.getTargets().getFirstTarget()); + if (targetPlayer != null) { + targetPlayer.damage(enteringCreature.getPower().getValue(), source.getSourceId(), game, false, true); + } + } + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/timespiral/BonesplitterSliver.java b/Mage.Sets/src/mage/sets/timespiral/BonesplitterSliver.java new file mode 100644 index 00000000000..6c745369dd9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/timespiral/BonesplitterSliver.java @@ -0,0 +1,71 @@ +/* + * 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.timespiral; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; + +/** + * + * @author Sir-Speshkitty + */ +public class BonesplitterSliver extends CardImpl { + + public BonesplitterSliver(UUID ownerId) { + super(ownerId, 149, "Bonesplitter Sliver", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{3}{R}"); + this.expansionSetCode = "TSP"; + this.subtype.add("Sliver"); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // All Sliver creatures get +2/+0. + this.addAbility( + new SimpleStaticAbility( + Zone.BATTLEFIELD, + new BoostAllEffect(2, 0, Duration.WhileOnBattlefield, new FilterCreaturePermanent("Sliver","Sliver creatures"), false) + ) + ); + } + + public BonesplitterSliver(final BonesplitterSliver card) { + super(card); + } + + @Override + public BonesplitterSliver copy() { + return new BonesplitterSliver(this); + } +} diff --git a/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java b/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java index 90816ead691..8e141aa20d7 100644 --- a/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java +++ b/Mage.Sets/src/mage/sets/urzassaga/SpinedFluke.java @@ -36,6 +36,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ColoredManaCost; import mage.abilities.effects.common.RegenerateSourceEffect; +import mage.abilities.effects.common.SacrificeControllerEffect; import mage.abilities.effects.common.SacrificeEffect; import mage.cards.CardImpl; import mage.constants.ColoredManaSymbol; @@ -54,7 +55,6 @@ public class SpinedFluke extends CardImpl { this.subtype.add("Worm"); this.subtype.add("Horror"); - this.color.setBlack(true); this.power = new MageInt(5); this.toughness = new MageInt(1); } @@ -62,7 +62,7 @@ public class SpinedFluke extends CardImpl { @Override public void build() { // When Spined Fluke enters the battlefield, sacrifice a creature. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeEffect(new FilterCreaturePermanent("a creature"), 1, ""))); + this.addAbility(new EntersBattlefieldTriggeredAbility(new SacrificeControllerEffect(new FilterCreaturePermanent("a creature"), 1, ""))); // {B}: Regenerate Spined Fluke. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new RegenerateSourceEffect(), new ColoredManaCost(ColoredManaSymbol.B))); } diff --git a/Mage.Sets/src/mage/sets/zendikar/VinesOfVastwood.java b/Mage.Sets/src/mage/sets/zendikar/VinesOfVastwood.java index 0dee86bb13d..d67de8aa3f2 100644 --- a/Mage.Sets/src/mage/sets/zendikar/VinesOfVastwood.java +++ b/Mage.Sets/src/mage/sets/zendikar/VinesOfVastwood.java @@ -60,7 +60,6 @@ public class VinesOfVastwood extends CardImpl { public VinesOfVastwood(UUID ownerId) { super(ownerId, 193, "Vines of Vastwood", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{G}"); this.expansionSetCode = "ZEN"; - this.color.setGreen(true); // Kicker {G} (You may pay an additional {G} as you cast this spell.) this.addAbility(new KickerAbility("{G}")); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java new file mode 100644 index 00000000000..1ab753078be --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/PutOntoBattlefieldTest.java @@ -0,0 +1,51 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.abilities.activated; + +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; + +/** + * + * @author LevelX2 + */ + +public class PutOntoBattlefieldTest extends CardTestPlayerBase { + + /** + * Tests to put a token onto the battlefield + */ + @Test + public void testOozeFlux() { + // Enchantment + // {1}{G}, Remove one or more +1/+1 counters from among creatures you control: Put an X/X green Ooze creature token onto the battlefield, where X is the number of +1/+1 counters removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Ooze Flux"); + // Trample + // Kalonian Hydra enters the battlefield with four +1/+1 counters on it. + // Whenever Kalonian Hydra attacks, double the number of +1/+1 counters on each creature you control. + addCard(Zone.BATTLEFIELD, playerA, "Kalonian Hydra"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{G},"); + setChoice(playerA, "X=2"); // Remove how many + setChoice(playerA,"Kalonian Hydra"); + setChoice(playerA, "X=2"); // Remove from Hydra + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Kalonian Hydra", 2, 2); + assertPermanentCount(playerA, "Ooze", 1); + assertPowerToughness(playerA, "Ooze", 2, 2); + + } + + +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java index fd8bed2ff1c..5d7dff07e3b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SoulfireGrandMasterTest.java @@ -30,7 +30,6 @@ package org.mage.test.cards.abilities.other; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -42,7 +41,7 @@ public class SoulfireGrandMasterTest extends CardTestPlayerBase { /** * Soulfire Grand Master - * Creature — Human Monk 2/2, 1W (2) + * Creature - Human Monk 2/2, 1W (2) * Lifelink * Instant and sorcery spells you control have lifelink. * {2}{U/R}{U/R}: The next time you cast an instant or sorcery spell from @@ -233,5 +232,67 @@ public class SoulfireGrandMasterTest extends CardTestPlayerBase { assertLife(playerA, 20); } + /** + * I activated the ability of Soulfire grand master, it resolved, then i cast Stoke the Flames + * on Whisperwood Elemental, my opponenet sacrificed the elemental, so stoke didnt resolve, + * but i still got the life from lifelink. + */ + @Test + public void testSoulfireStokeTheFlames() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); + + addCard(Zone.HAND, playerA, "Stoke the Flames"); + addCard(Zone.BATTLEFIELD, playerA, "Soulfire Grand Master", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Whisperwood Elemental", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U/R}{U/R}:"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Stoke the Flames", "Whisperwood Elemental"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Sacrifice {this}", null ,"{this} deals 4 damage"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Stoke the Flames", 1); // no legal target left so the spell is countered and goes to graveyard + assertGraveyardCount(playerB, "Whisperwood Elemental", 1); + + assertLife(playerB, 20); + assertLife(playerA, 20); + + } + + /** + * Check if second ability resolved, the next spell that is counterer + * won't go to hand back because it did not resolve + * + */ + + @Test + public void testSoulfireCounteredSpellDontGoesBack() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); + + addCard(Zone.HAND, playerA, "Stoke the Flames"); + addCard(Zone.BATTLEFIELD, playerA, "Soulfire Grand Master", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.HAND, playerB, "Counterspell", 1); + addCard(Zone.BATTLEFIELD, playerB, "Whisperwood Elemental", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U/R}{U/R}:"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Stoke the Flames", "Whisperwood Elemental"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Counterspell", "Stoke the Flames"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Counterspell", 1); + assertGraveyardCount(playerA, "Stoke the Flames", 1); // no legal target left so the spell is countered and goes to graveyard + + assertLife(playerB, 20); + assertLife(playerA, 20); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java index 6e53c7e1d13..07b27ebbda4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/DungeonGeistsTest.java @@ -74,16 +74,20 @@ public class DungeonGeistsTest extends CardTestPlayerBase { public void testWithBlink() { addCard(Zone.BATTLEFIELD, playerA, "Island", 4); addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + // When Dungeon Geists enters the battlefield, tap target creature an opponent controls. + // That creature doesn't untap during its controller's untap step for as long as you control Dungeon Geists. addCard(Zone.HAND, playerA, "Dungeon Geists"); addCard(Zone.HAND, playerA, "Cloudshift"); addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm"); addCard(Zone.BATTLEFIELD, playerB, "Elite Vanguard"); - addTarget(playerA, "Craw Wurm"); // first target Craw Wurm - addTarget(playerA, "Elite Vanguard"); // after Cloudshift effect (return back to battlefield) target Elite Vanguard castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dungeon Geists"); + addTarget(playerA, "Craw Wurm"); // first target Craw Wurm + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cloudshift", "Dungeon Geists"); + addTarget(playerA, "Elite Vanguard"); // after Cloudshift effect (return back to battlefield) target Elite Vanguard + setStopAt(2, PhaseStep.DRAW); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java new file mode 100644 index 00000000000..fee51299c4c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/state/SynodCenturionTest.java @@ -0,0 +1,66 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.triggers.state; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ + +public class SynodCenturionTest extends CardTestPlayerBase { + + + /** + * Check that Synod Centurion gets sacrificed if no other artifacts are on the battlefield + * + */ + @Test + public void testAlone() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + addCard(Zone.BATTLEFIELD, playerA, "Demon's Horn"); + addCard(Zone.HAND, playerA, "Shatter"); + addCard(Zone.HAND, playerA, "Synod Centurion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Synod Centurion"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Shatter", "Demon's Horn"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Demon's Horn", 1); + assertGraveyardCount(playerA, "Shatter", 1); + assertGraveyardCount(playerA, "Synod Centurion", 1); + } + + /** + * Check that Synod Centurion gets sacrificed if the only other + * artifact left the battlefiled for a short time + * + */ + @Test + public void testWithFlicker() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.BATTLEFIELD, playerA, "Bottle Gnomes"); + addCard(Zone.HAND, playerA, "Cloudshift"); + addCard(Zone.HAND, playerA, "Synod Centurion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Synod Centurion"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cloudshift", "Bottle Gnomes"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Bottle Gnomes", 1); + assertGraveyardCount(playerA, "Cloudshift", 1); + assertGraveyardCount(playerA, "Synod Centurion", 1); + } +} \ No newline at end of file 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 1e9ba488b0a..a3811e1bfdb 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 @@ -502,6 +502,18 @@ public class TestPlayer extends ComputerPlayer { return super.announceXCost(min, max, message, game, ability, null); } + @Override + public int getAmount(int min, int max, String message, Game game) { + if (!choices.isEmpty()) { + if (choices.get(0).startsWith("X=")) { + int xValue = Integer.parseInt(choices.get(0).substring(2)); + choices.remove(0); + return xValue; + } + } + return super.getAmount(min, max, message, game); + } + protected Permanent findPermanent(FilterPermanent filter, UUID controllerId, Game game) { List permanents = game.getBattlefield().getAllActivePermanents(filter, controllerId, game); if (permanents.size() > 0) { diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index ccef8142206..3ad6958cd7b 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -215,14 +215,20 @@ public abstract class AbilityImpl implements Ability { else { game.addEffect((ContinuousEffect) effect, this); } + /** + * All restrained trigger events are fired now. + * To restrain the events is mainly neccessary because of the movement of multiple object at once. + * If the event is fired directly as one object moved, other objects are not already in the correct zone + * to check for their effects. (e.g. Valakut, the Molten Pinnacle) + */ + game.getState().handleSimultaneousEvent(game); + game.resetShortLivingLKI(); /** * game.applyEffects() has to be done at least for every effect that moves cards/permanent between zones, * so Static effects work as intened if dependant from the moved objects zone it is in * Otherwise for example were static abilities with replacement effects deactivated to late * Example: {@link org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy} */ -// game.applyEffects(); - // some effects must be applied before next effect is resolved, because effect is dependend. if (effect.applyEffectsAfter()) { game.applyEffects(); } diff --git a/Mage/src/mage/abilities/common/BecomesTappedCreatureControlledTriggeredAbility.java b/Mage/src/mage/abilities/common/BecomesTappedCreatureControlledTriggeredAbility.java index e8086954118..4961f8af297 100644 --- a/Mage/src/mage/abilities/common/BecomesTappedCreatureControlledTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/BecomesTappedCreatureControlledTriggeredAbility.java @@ -4,10 +4,10 @@ */ package mage.abilities.common; -import mage.constants.CardType; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -18,12 +18,19 @@ import mage.game.permanent.Permanent; */ public class BecomesTappedCreatureControlledTriggeredAbility extends TriggeredAbilityImpl{ + FilterControlledCreaturePermanent filter; + public BecomesTappedCreatureControlledTriggeredAbility(Effect effect, boolean optional) { + this(effect, optional, new FilterControlledCreaturePermanent("a creature you control")); + } + public BecomesTappedCreatureControlledTriggeredAbility(Effect effect, boolean optional, FilterControlledCreaturePermanent filter) { super(Zone.BATTLEFIELD, effect, optional); + this.filter = filter; } public BecomesTappedCreatureControlledTriggeredAbility(final BecomesTappedCreatureControlledTriggeredAbility ability) { super(ability); + this.filter = ability.filter; } @Override @@ -39,15 +46,11 @@ public class BecomesTappedCreatureControlledTriggeredAbility extends TriggeredAb @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null && permanent.getControllerId().equals(this.controllerId) - && permanent.getCardType().contains(CardType.CREATURE)) { - return true; - } - return false; + return permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game); } @Override public String getRule() { - return "When a creature you control becomes tapped, " + super.getRule(); + return "When " + filter.getMessage() + " becomes tapped, " + super.getRule(); } } diff --git a/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java b/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java index 39a317cd59d..ee08b2d7417 100644 --- a/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java @@ -32,8 +32,10 @@ import mage.constants.Outcome; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; +import mage.filter.Filter; +import mage.filter.FilterPermanent; import mage.game.Game; -import mage.game.stack.Spell; +import mage.game.stack.StackObject; /** * @author BetaSteward_at_googlemail.com @@ -43,21 +45,27 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { private boolean forceChange; private boolean onlyOneTarget; - + private FilterPermanent filterNewTarget; + public ChooseNewTargetsTargetEffect() { this(false, false); } + public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) { + this(forceChange, onlyOneTarget, null); + } /** * * @param forceChange forces the user to choose another target (only targets with maxtargets = 1 supported) * @param onlyOneTarget only one target can be selected for the change + * @param filterNewTarget restriction to the new target */ - public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget) { + public ChooseNewTargetsTargetEffect(boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) { super(Outcome.Benefit); this.forceChange = forceChange; this.onlyOneTarget = onlyOneTarget; + this.filterNewTarget = filterNewTarget; } public ChooseNewTargetsTargetEffect(final ChooseNewTargetsTargetEffect effect) { @@ -68,9 +76,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Spell spell = game.getStack().getSpell(source.getFirstTarget()); - if (spell != null) { - return spell.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget); + StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget()); + if (stackObject != null) { + return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, filterNewTarget); } return false; } @@ -82,6 +90,9 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { @Override public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } StringBuilder sb = new StringBuilder(); if (forceChange) { sb.append("change the target of target "); diff --git a/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java b/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java index a0be206131f..5ae4cd6e314 100644 --- a/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/counter/AddCountersTargetEffect.java @@ -95,9 +95,10 @@ public class AddCountersTargetEffect extends OneShotEffect { permanent.addCounters(newCounter, game); int numberAdded = permanent.getCounters().getCount(counter.getName()) - before; affectedTargets ++; - if (!game.isSimulation()) + if (!game.isSimulation()) { game.informPlayers(sourceObject.getLogName() +": "+ controller.getName()+ " puts " + numberAdded + " " + counter.getName().toLowerCase() + " counter on " + permanent.getLogName()); + } } } else { Player player = game.getPlayer(uuid); diff --git a/Mage/src/mage/abilities/keyword/ShadowAbility.java b/Mage/src/mage/abilities/keyword/ShadowAbility.java index ca95603cf34..57b41e1b348 100644 --- a/Mage/src/mage/abilities/keyword/ShadowAbility.java +++ b/Mage/src/mage/abilities/keyword/ShadowAbility.java @@ -31,7 +31,7 @@ public class ShadowAbility extends EvasionAbility implements MageSingleton { @Override public String getRule() { - return "Shadow"; + return "Shadow (This creature can block or be blocked by only creatures with shadow.)"; } @Override @@ -53,10 +53,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - if (permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId())) { - return true; - } - return false; + return permanent.getAbilities().containsKey(ShadowAbility.getInstance().getId()); } @Override @@ -66,11 +63,8 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton { @Override public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) { - if (blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId()) - || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game)) { - return true; - } - return false; + return blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId()) + || game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game); } @Override diff --git a/Mage/src/mage/cards/CardImpl.java b/Mage/src/mage/cards/CardImpl.java index 71fc93c6cd2..d8a295896f7 100644 --- a/Mage/src/mage/cards/CardImpl.java +++ b/Mage/src/mage/cards/CardImpl.java @@ -1,759 +1,759 @@ -/* -* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, are -* permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this list of -* conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this list -* of conditions and the following disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* The views and conclusions contained in the software and documentation are those of the -* authors and should not be interpreted as representing official policies, either expressed -* or implied, of BetaSteward_at_googlemail.com. -*/ - -package mage.cards; - -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import mage.MageObject; -import mage.MageObjectImpl; -import mage.Mana; -import mage.abilities.Abilities; -import mage.abilities.AbilitiesImpl; -import mage.abilities.Ability; -import mage.abilities.PlayLandAbility; -import mage.abilities.SpellAbility; -import mage.abilities.mana.ManaAbility; -import mage.constants.CardType; -import mage.constants.ColoredManaSymbol; -import mage.constants.Rarity; -import mage.constants.SpellAbilityType; -import mage.constants.TimingRule; -import mage.constants.Zone; -import static mage.constants.Zone.BATTLEFIELD; -import static mage.constants.Zone.COMMAND; -import static mage.constants.Zone.EXILED; -import static mage.constants.Zone.GRAVEYARD; -import static mage.constants.Zone.HAND; -import static mage.constants.Zone.LIBRARY; -import static mage.constants.Zone.OUTSIDE; -import static mage.constants.Zone.PICK; -import static mage.constants.Zone.STACK; -import mage.counters.Counter; -import mage.counters.Counters; -import mage.game.CardState; -import mage.game.Game; -import mage.game.command.Commander; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.PermanentCard; -import mage.game.stack.Spell; -import mage.game.stack.StackObject; -import mage.watchers.Watcher; -import org.apache.log4j.Logger; - -public abstract class CardImpl extends MageObjectImpl implements Card { - private static final long serialVersionUID = 1L; - - private static final Logger logger = Logger.getLogger(CardImpl.class); - - protected UUID ownerId; - protected int cardNumber; - protected String expansionSetCode; - protected String tokenSetCode; - protected Rarity rarity; - protected boolean canTransform; - protected Card secondSideCard; - protected boolean nightCard; - protected SpellAbility spellAbility; - protected boolean flipCard; - protected String flipCardName; - protected boolean usesVariousArt = false; - protected boolean splitCard; - protected boolean morphCard; - - public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) { - this(ownerId, cardNumber, name, rarity, cardTypes, costs, SpellAbilityType.BASE); - } - - public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) { - this(ownerId, name); - this.rarity = rarity; - this.cardNumber = cardNumber; - this.cardType.addAll(Arrays.asList(cardTypes)); - this.manaCost.load(costs); - setDefaultColor(); - if (cardType.contains(CardType.LAND)) { - Ability ability = new PlayLandAbility(name); - ability.setSourceId(this.getId()); - abilities.add(ability); - } - else { - SpellAbility ability = new SpellAbility(manaCost, name, Zone.HAND, spellAbilityType); - if (!cardType.contains(CardType.INSTANT)) { - ability.setTiming(TimingRule.SORCERY); - } - ability.setSourceId(this.getId()); - abilities.add(ability); - } - this.usesVariousArt = Character.isDigit(this.getClass().getName().charAt(this.getClass().getName().length()-1)); - this.morphCard = false; - } - - private void setDefaultColor() { - this.color.setWhite(this.manaCost.containsColor(ColoredManaSymbol.W)); - this.color.setBlue(this.manaCost.containsColor(ColoredManaSymbol.U)); - this.color.setBlack(this.manaCost.containsColor(ColoredManaSymbol.B)); - this.color.setRed(this.manaCost.containsColor(ColoredManaSymbol.R)); - this.color.setGreen(this.manaCost.containsColor(ColoredManaSymbol.G)); - } - - protected CardImpl(UUID ownerId, String name) { - this.ownerId = ownerId; - this.name = name; - } - - protected CardImpl(UUID id, UUID ownerId, String name) { - super(id); - this.ownerId = ownerId; - this.name = name; - } - - public CardImpl(final CardImpl card) { - super(card); - ownerId = card.ownerId; - cardNumber = card.cardNumber; - expansionSetCode = card.expansionSetCode; - rarity = card.rarity; - - canTransform = card.canTransform; - if (canTransform) { - secondSideCard = card.secondSideCard; - nightCard = card.nightCard; - } - flipCard = card.flipCard; - flipCardName = card.flipCardName; - splitCard = card.splitCard; - usesVariousArt = card.usesVariousArt; - } - - @Override - public void assignNewId() { - this.objectId = UUID.randomUUID(); - this.abilities.newOriginalId(); - this.abilities.setSourceId(objectId); - } - - public static Card createCard(String name) { - try { - return createCard(Class.forName(name)); - } catch (ClassNotFoundException ex) { - logger.fatal("Error loading card: " + name, ex); - return null; - } - } - - public static Card createCard(Class clazz) { - try { - Constructor con = clazz.getConstructor(new Class[]{UUID.class}); - Card card = (Card) con.newInstance(new Object[]{null}); - card.build(); - return card; - } catch (Exception e) { - logger.fatal("Error loading card: " + clazz.getCanonicalName(), e); - return null; - } - } - - @Override - public UUID getOwnerId() { - return ownerId; - } - - @Override - public int getCardNumber() { - return cardNumber; - } - - @Override - public Rarity getRarity() { - return rarity; - } - - @Override - public void addInfo(String key, String value, Game game) { - game.getState().getCardState(objectId).addInfo(key, value); - } - - protected static final ArrayList rulesError = new ArrayList() {{add("Exception occured in rules generation");}}; - - @Override - public List getRules() { - try { - return abilities.getRules(this.getLogName()); - } catch (Exception e) { - logger.info("Exception in rules generation for card: " + this.getName(), e); - } - return rulesError; - } - - @Override - public List getRules(Game game) { - try { - List rules = getRules(); - if (game != null) { - CardState cardState = game.getState().getCardState(objectId); - if (cardState != null) { - for (String data : cardState.getInfo().values()) { - rules.add(data); - } - for (Ability ability: cardState.getAbilities()) { - rules.add(ability.getRule()); - } - } - } - return rules; - } catch (Exception e) { - logger.error("Exception in rules generation for card: " + this.getName(), e); - } - return rulesError; - } - - /** - * Gets all base abilities - does not include additional abilities added by - * other cards or effects - * @return A list of {@link Ability} - this collection is modifiable - */ - @Override - public Abilities getAbilities() { - return super.getAbilities(); - } - - /** - * Gets all current abilities - includes additional abilities added by - * other cards or effects - * @param game - * @return A list of {@link Ability} - this collection is not modifiable - */ - @Override - public Abilities getAbilities(Game game) { - Abilities otherAbilities = game.getState().getAllOtherAbilities(objectId); - if (otherAbilities == null) { - return abilities; - } - Abilities all = new AbilitiesImpl<>(); - all.addAll(abilities); - all.addAll(otherAbilities); - return all; - } - - protected void addAbility(Ability ability) { - ability.setSourceId(this.getId()); - abilities.add(ability); - for (Ability subAbility: ability.getSubAbilities()) { - abilities.add(subAbility); - } - } - - protected void addAbilities(List abilities) { - for (Ability ability: abilities) { - addAbility(ability); - } - } - - protected void addAbility(Ability ability, Watcher watcher) { - addAbility(ability); - ability.addWatcher(watcher); - } - - @Override - public SpellAbility getSpellAbility() { - if (spellAbility == null) { - for (Ability ability : abilities.getActivatedAbilities(Zone.HAND)) { - // name check prevents that alternate casting methods (like "cast [card name] using bestow") are returned here - if (ability instanceof SpellAbility && ability.toString().endsWith(getName())) { - spellAbility = (SpellAbility) ability; - } - } - } - return spellAbility; - } - - @Override - public void setOwnerId(UUID ownerId) { - this.ownerId = ownerId; - abilities.setControllerId(ownerId); - } - - @Override - public String getExpansionSetCode() { - return expansionSetCode; - } - - @Override - public String getTokenSetCode() { - return tokenSetCode; - } - - @Override - public List getMana() { - List mana = new ArrayList<>(); - for (ManaAbility ability : this.abilities.getManaAbilities(Zone.BATTLEFIELD)) { - for (Mana netMana: ability.getNetMana(null)) { - mana.add(netMana); - } - } - return mana; - } - - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { - return this.moveToZone(toZone, sourceId, game, flag, null); - } - - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { - Zone fromZone = game.getState().getZone(objectId); - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone, appliedEffects); - if (!game.replaceEvent(event)) { - if (event.getFromZone() != null) { - switch (event.getFromZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).removeFromGraveyard(this, game); - break; - case HAND: - game.getPlayer(ownerId).removeFromHand(this, game); - break; - case LIBRARY: - game.getPlayer(ownerId).removeFromLibrary(this, game); - break; - case EXILED: - game.getExile().removeCard(this, game); - break; - case OUTSIDE: - game.getPlayer(ownerId).getSideboard().remove(this); - break; - case COMMAND: - game.getState().getCommand().remove((Commander)game.getObject(objectId)); - break; - case STACK: - StackObject stackObject = game.getStack().getSpell(getId()); - if (stackObject != null) { - game.getStack().remove(stackObject); - } - break; - case PICK: - case BATTLEFIELD: // for sacrificing permanents or putting to library - break; - default: - Card sourceCard = game.getCard(sourceId); - logger.fatal(new StringBuilder("Invalid from zone [").append(fromZone) - .append("] for card [").append(this.getName()) - .append("] to zone [").append(toZone) - .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); - break; - } - game.rememberLKI(objectId, event.getFromZone(), this); - } - - setFaceDown(false, game); - updateZoneChangeCounter(game); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).putInGraveyard(this, game, !flag); - break; - case HAND: - game.getPlayer(ownerId).getHand().add(this); - break; - case STACK: - game.getStack().push(new Spell(this, this.getSpellAbility().copy(), ownerId, event.getFromZone())); - break; - case EXILED: - game.getExile().getPermanentExile().add(this); - break; - case COMMAND: - game.addCommander(new Commander(this)); - break; - case LIBRARY: - if (flag) { - game.getPlayer(ownerId).getLibrary().putOnTop(this, game); - } - else { - game.getPlayer(ownerId).getLibrary().putOnBottom(this, game); - } - break; - case BATTLEFIELD: - PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) - game.addPermanent(permanent); - game.setZone(objectId, Zone.BATTLEFIELD); - game.setScopeRelevant(true); - game.applyEffects(); - permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - if (flag) { - permanent.setTapped(true); - } - event.setTarget(permanent); - break; - default: - Card sourceCard = game.getCard(sourceId); - logger.fatal(new StringBuilder("Invalid to zone [").append(toZone) - .append("] for card [").append(this.getName()) - .append("] to zone [").append(toZone) - .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); - return false; - } - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return game.getState().getZone(objectId) == toZone; - } - return false; - } - - @Override - public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { - Card mainCard = getMainCard(); - ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); - if (!game.replaceEvent(event)) { - if (event.getFromZone() != null) { - switch (event.getFromZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).removeFromGraveyard(mainCard, game); - break; - case HAND: - game.getPlayer(ownerId).removeFromHand(mainCard, game); - break; - case LIBRARY: - game.getPlayer(ownerId).removeFromLibrary(mainCard, game); - break; - case EXILED: - game.getExile().removeCard(mainCard, game); - break; - case OUTSIDE: - game.getPlayer(ownerId).getSideboard().remove(mainCard); - break; - - case COMMAND: - game.getState().getCommand().remove((Commander)game.getObject(mainCard.getId())); - break; - default: - //logger.warning("moveToZone, not fully implemented: from="+event.getFromZone() + ", to="+event.getToZone()); - } - game.rememberLKI(mainCard.getId(), event.getFromZone(), this); - } - game.getStack().push(new Spell(this, ability.copy(), controllerId, event.getFromZone())); - setZone(event.getToZone(), game); - game.fireEvent(event); - return game.getState().getZone(mainCard.getId()) == Zone.STACK; - } - return false; - } - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { - return moveToExile(exileId, name, sourceId, game, null); - } - - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { - Zone fromZone = game.getState().getZone(objectId); - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - if (fromZone != null) { - switch (fromZone) { - case GRAVEYARD: - game.getPlayer(ownerId).removeFromGraveyard(this, game); - break; - case HAND: - game.getPlayer(ownerId).removeFromHand(this, game); - break; - case LIBRARY: - game.getPlayer(ownerId).removeFromLibrary(this, game); - break; - case EXILED: - game.getExile().removeCard(this, game); - break; - case STACK: - case PICK: - // nothing to do - break; - default: - MageObject object = game.getObject(sourceId); - logger.warn(new StringBuilder("moveToExile, not fully implemented: from = ").append(fromZone).append(" - ").append(object != null ? object.getName():"null")); - } - game.rememberLKI(objectId, event.getFromZone(), this); - } - - if (exileId == null) { - game.getExile().getPermanentExile().add(this); - } else { - game.getExile().createZone(exileId, name).add(this); - } - setFaceDown(false, game); - updateZoneChangeCounter(game); - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return true; - } - return false; - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId) { - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, false, false, null); - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped){ - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, false, null); - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown){ - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, facedown, null); - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects){ - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); - if (facedown) { - this.setFaceDown(true, game); - } - if (!game.replaceEvent(event)) { - if (facedown) { - this.setFaceDown(false, game); - } - if (fromZone != null) { - boolean removed = false; - switch (fromZone) { - case GRAVEYARD: - removed = game.getPlayer(ownerId).removeFromGraveyard(this, game); - break; - case HAND: - removed = game.getPlayer(ownerId).removeFromHand(this, game); - break; - case LIBRARY: - removed = game.getPlayer(ownerId).removeFromLibrary(this, game); - break; - case EXILED: - game.getExile().removeCard(this, game); - removed = true; - break; - case COMMAND: - // command object (commander) is only on the stack, so no removing neccessary here - removed = true; - break; - case PICK: - removed = true; - break; - default: - logger.warn("putOntoBattlefield, not fully implemented: fromZone="+fromZone); - } - game.rememberLKI(objectId, event.getFromZone(), this); - if (!removed) { - logger.warn("Couldn't find card in fromZone, card=" + getName() + ", fromZone=" + fromZone); - } - } - updateZoneChangeCounter(game); - PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); - // make sure the controller of all continuous effects of this card are switched to the current controller - game.getContinuousEffects().setController(objectId, event.getPlayerId()); - game.addPermanent(permanent); - setZone(Zone.BATTLEFIELD, game); - game.setScopeRelevant(true); - permanent.setTapped(tapped); - permanent.setFaceDown(facedown, game); - permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - game.fireEvent(new ZoneChangeEvent(permanent, event.getPlayerId(), fromZone, Zone.BATTLEFIELD)); - return true; - } - if (facedown) { - this.setFaceDown(false, game); - } - return false; - } - - @Override - public void setFaceDown(boolean value, Game game) { - game.getState().getCardState(objectId).setFaceDown(value); - } - - @Override - public boolean isFaceDown(Game game) { - return game.getState().getCardState(objectId).isFaceDown(); - } - - @Override - public boolean turnFaceUp(Game game, UUID playerId) { - GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEUP, getId(), playerId); - if (!game.replaceEvent(event)) { - setFaceDown(false, game); - for (Ability ability :abilities) { // abilities that were set to not visible face down must be set to visible again - if (ability.getWorksFaceDown() && !ability.getRuleVisible()) { - ability.setRuleVisible(true); - } - } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEUP, getId(), playerId)); - return true; - } - return false; - } - - @Override - public boolean turnFaceDown(Game game, UUID playerId) { - GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEDOWN, getId(), playerId); - if (!game.replaceEvent(event)) { - setFaceDown(true, game); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEDOWN, getId(), playerId)); - return true; - } - return false; - } - - @Override - public boolean canTransform() { - return this.canTransform; - } - - @Override - public Card getSecondCardFace() { - return this.secondSideCard; - } - - @Override - public boolean isNightCard() { - return this.nightCard; - } - - @Override - public boolean isFlipCard() { - return flipCard; - } - - @Override - public String getFlipCardName() { - return flipCardName; - } - - @Override - public boolean isSplitCard() { - return splitCard; - } - - @Override - public void build() {} - - @Override - public boolean getUsesVariousArt() { - return usesVariousArt; - } - - @Override - public Counters getCounters(Game game) { - return game.getState().getCardState(this.objectId).getCounters(); - } - - @Override - public void addCounters(String name, int amount, Game game) { - addCounters(name, amount, game, null); - } - - @Override - public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { - GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, name, amount); - countersEvent.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(countersEvent)) { - for (int i = 0; i < countersEvent.getAmount(); i++) { - GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, name, 1); - event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { - game.getState().getCardState(this.objectId).getCounters().addCounter(name, 1); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, name, 1)); - } - } - } - } - - @Override - public void addCounters(Counter counter, Game game) { - addCounters(counter, game, null); - } - - @Override - public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { - GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, counter.getName(), counter.getCount()); - countersEvent.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(countersEvent)) { - int amount = countersEvent.getAmount(); - for (int i = 0; i < amount; i++) { - Counter eventCounter = counter.copy(); - eventCounter.remove(amount - 1); - GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, counter.getName(), 1); - event.setAppliedEffects(appliedEffects); - if (!game.replaceEvent(event)) { - game.getState().getCardState(this.objectId).getCounters().addCounter(eventCounter); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, counter.getName(), 1)); - } - } - } - } - - @Override - public void removeCounters(String name, int amount, Game game) { - for (int i = 0; i < amount; i++) { - game.getState().getCardState(this.objectId).getCounters().removeCounter(name, 1); - GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, ownerId); - event.setData(name); - game.fireEvent(event); - } - } - - @Override - public void removeCounters(Counter counter, Game game) { - removeCounters(counter.getName(), counter.getCount(), game); - } - - @Override - public String getLogName() { -// if (this.isFaceDown()) { -// return "facedown card"; -// } - return name; - } - - @Override - public Card getMainCard() { - return this; - } - - @Override - public void setZone(Zone zone, Game game) { - game.setZone(getId(), zone); - } - - @Override - public void setSpellAbility(SpellAbility ability) { - spellAbility = ability; - } - -} +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. +*/ + +package mage.cards; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import mage.MageObject; +import mage.MageObjectImpl; +import mage.Mana; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; +import mage.abilities.PlayLandAbility; +import mage.abilities.SpellAbility; +import mage.abilities.mana.ManaAbility; +import mage.constants.CardType; +import mage.constants.ColoredManaSymbol; +import mage.constants.Rarity; +import mage.constants.SpellAbilityType; +import mage.constants.TimingRule; +import mage.constants.Zone; +import static mage.constants.Zone.BATTLEFIELD; +import static mage.constants.Zone.COMMAND; +import static mage.constants.Zone.EXILED; +import static mage.constants.Zone.GRAVEYARD; +import static mage.constants.Zone.HAND; +import static mage.constants.Zone.LIBRARY; +import static mage.constants.Zone.OUTSIDE; +import static mage.constants.Zone.PICK; +import static mage.constants.Zone.STACK; +import mage.counters.Counter; +import mage.counters.Counters; +import mage.game.CardState; +import mage.game.Game; +import mage.game.command.Commander; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.PermanentCard; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.watchers.Watcher; +import org.apache.log4j.Logger; + +public abstract class CardImpl extends MageObjectImpl implements Card { + private static final long serialVersionUID = 1L; + + private static final Logger logger = Logger.getLogger(CardImpl.class); + + protected UUID ownerId; + protected int cardNumber; + protected String expansionSetCode; + protected String tokenSetCode; + protected Rarity rarity; + protected boolean canTransform; + protected Card secondSideCard; + protected boolean nightCard; + protected SpellAbility spellAbility; + protected boolean flipCard; + protected String flipCardName; + protected boolean usesVariousArt = false; + protected boolean splitCard; + protected boolean morphCard; + + public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) { + this(ownerId, cardNumber, name, rarity, cardTypes, costs, SpellAbilityType.BASE); + } + + public CardImpl(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) { + this(ownerId, name); + this.rarity = rarity; + this.cardNumber = cardNumber; + this.cardType.addAll(Arrays.asList(cardTypes)); + this.manaCost.load(costs); + setDefaultColor(); + if (cardType.contains(CardType.LAND)) { + Ability ability = new PlayLandAbility(name); + ability.setSourceId(this.getId()); + abilities.add(ability); + } + else { + SpellAbility ability = new SpellAbility(manaCost, name, Zone.HAND, spellAbilityType); + if (!cardType.contains(CardType.INSTANT)) { + ability.setTiming(TimingRule.SORCERY); + } + ability.setSourceId(this.getId()); + abilities.add(ability); + } + this.usesVariousArt = Character.isDigit(this.getClass().getName().charAt(this.getClass().getName().length()-1)); + this.morphCard = false; + } + + private void setDefaultColor() { + this.color.setWhite(this.manaCost.containsColor(ColoredManaSymbol.W)); + this.color.setBlue(this.manaCost.containsColor(ColoredManaSymbol.U)); + this.color.setBlack(this.manaCost.containsColor(ColoredManaSymbol.B)); + this.color.setRed(this.manaCost.containsColor(ColoredManaSymbol.R)); + this.color.setGreen(this.manaCost.containsColor(ColoredManaSymbol.G)); + } + + protected CardImpl(UUID ownerId, String name) { + this.ownerId = ownerId; + this.name = name; + } + + protected CardImpl(UUID id, UUID ownerId, String name) { + super(id); + this.ownerId = ownerId; + this.name = name; + } + + public CardImpl(final CardImpl card) { + super(card); + ownerId = card.ownerId; + cardNumber = card.cardNumber; + expansionSetCode = card.expansionSetCode; + rarity = card.rarity; + + canTransform = card.canTransform; + if (canTransform) { + secondSideCard = card.secondSideCard; + nightCard = card.nightCard; + } + flipCard = card.flipCard; + flipCardName = card.flipCardName; + splitCard = card.splitCard; + usesVariousArt = card.usesVariousArt; + } + + @Override + public void assignNewId() { + this.objectId = UUID.randomUUID(); + this.abilities.newOriginalId(); + this.abilities.setSourceId(objectId); + } + + public static Card createCard(String name) { + try { + return createCard(Class.forName(name)); + } catch (ClassNotFoundException ex) { + logger.fatal("Error loading card: " + name, ex); + return null; + } + } + + public static Card createCard(Class clazz) { + try { + Constructor con = clazz.getConstructor(new Class[]{UUID.class}); + Card card = (Card) con.newInstance(new Object[]{null}); + card.build(); + return card; + } catch (Exception e) { + logger.fatal("Error loading card: " + clazz.getCanonicalName(), e); + return null; + } + } + + @Override + public UUID getOwnerId() { + return ownerId; + } + + @Override + public int getCardNumber() { + return cardNumber; + } + + @Override + public Rarity getRarity() { + return rarity; + } + + @Override + public void addInfo(String key, String value, Game game) { + game.getState().getCardState(objectId).addInfo(key, value); + } + + protected static final ArrayList rulesError = new ArrayList() {{add("Exception occured in rules generation");}}; + + @Override + public List getRules() { + try { + return abilities.getRules(this.getLogName()); + } catch (Exception e) { + logger.info("Exception in rules generation for card: " + this.getName(), e); + } + return rulesError; + } + + @Override + public List getRules(Game game) { + try { + List rules = getRules(); + if (game != null) { + CardState cardState = game.getState().getCardState(objectId); + if (cardState != null) { + for (String data : cardState.getInfo().values()) { + rules.add(data); + } + for (Ability ability: cardState.getAbilities()) { + rules.add(ability.getRule()); + } + } + } + return rules; + } catch (Exception e) { + logger.error("Exception in rules generation for card: " + this.getName(), e); + } + return rulesError; + } + + /** + * Gets all base abilities - does not include additional abilities added by + * other cards or effects + * @return A list of {@link Ability} - this collection is modifiable + */ + @Override + public Abilities getAbilities() { + return super.getAbilities(); + } + + /** + * Gets all current abilities - includes additional abilities added by + * other cards or effects + * @param game + * @return A list of {@link Ability} - this collection is not modifiable + */ + @Override + public Abilities getAbilities(Game game) { + Abilities otherAbilities = game.getState().getAllOtherAbilities(objectId); + if (otherAbilities == null) { + return abilities; + } + Abilities all = new AbilitiesImpl<>(); + all.addAll(abilities); + all.addAll(otherAbilities); + return all; + } + + protected void addAbility(Ability ability) { + ability.setSourceId(this.getId()); + abilities.add(ability); + for (Ability subAbility: ability.getSubAbilities()) { + abilities.add(subAbility); + } + } + + protected void addAbilities(List abilities) { + for (Ability ability: abilities) { + addAbility(ability); + } + } + + protected void addAbility(Ability ability, Watcher watcher) { + addAbility(ability); + ability.addWatcher(watcher); + } + + @Override + public SpellAbility getSpellAbility() { + if (spellAbility == null) { + for (Ability ability : abilities.getActivatedAbilities(Zone.HAND)) { + // name check prevents that alternate casting methods (like "cast [card name] using bestow") are returned here + if (ability instanceof SpellAbility && ability.toString().endsWith(getName())) { + spellAbility = (SpellAbility) ability; + } + } + } + return spellAbility; + } + + @Override + public void setOwnerId(UUID ownerId) { + this.ownerId = ownerId; + abilities.setControllerId(ownerId); + } + + @Override + public String getExpansionSetCode() { + return expansionSetCode; + } + + @Override + public String getTokenSetCode() { + return tokenSetCode; + } + + @Override + public List getMana() { + List mana = new ArrayList<>(); + for (ManaAbility ability : this.abilities.getManaAbilities(Zone.BATTLEFIELD)) { + for (Mana netMana: ability.getNetMana(null)) { + mana.add(netMana); + } + } + return mana; + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { + return this.moveToZone(toZone, sourceId, game, flag, null); + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { + Zone fromZone = game.getState().getZone(objectId); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone, appliedEffects); + if (!game.replaceEvent(event)) { + if (event.getFromZone() != null) { + switch (event.getFromZone()) { + case GRAVEYARD: + game.getPlayer(ownerId).removeFromGraveyard(this, game); + break; + case HAND: + game.getPlayer(ownerId).removeFromHand(this, game); + break; + case LIBRARY: + game.getPlayer(ownerId).removeFromLibrary(this, game); + break; + case EXILED: + game.getExile().removeCard(this, game); + break; + case OUTSIDE: + game.getPlayer(ownerId).getSideboard().remove(this); + break; + case COMMAND: + game.getState().getCommand().remove((Commander)game.getObject(objectId)); + break; + case STACK: + StackObject stackObject = game.getStack().getSpell(getId()); + if (stackObject != null) { + game.getStack().remove(stackObject); + } + break; + case PICK: + case BATTLEFIELD: // for sacrificing permanents or putting to library + break; + default: + Card sourceCard = game.getCard(sourceId); + logger.fatal(new StringBuilder("Invalid from zone [").append(fromZone) + .append("] for card [").append(this.getName()) + .append("] to zone [").append(toZone) + .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); + break; + } + game.rememberLKI(objectId, event.getFromZone(), this); + } + + setFaceDown(false, game); + updateZoneChangeCounter(game); + switch (event.getToZone()) { + case GRAVEYARD: + game.getPlayer(ownerId).putInGraveyard(this, game, !flag); + break; + case HAND: + game.getPlayer(ownerId).getHand().add(this); + break; + case STACK: + game.getStack().push(new Spell(this, this.getSpellAbility().copy(), ownerId, event.getFromZone())); + break; + case EXILED: + game.getExile().getPermanentExile().add(this); + break; + case COMMAND: + game.addCommander(new Commander(this)); + break; + case LIBRARY: + if (flag) { + game.getPlayer(ownerId).getLibrary().putOnTop(this, game); + } + else { + game.getPlayer(ownerId).getLibrary().putOnBottom(this, game); + } + break; + case BATTLEFIELD: + PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) + game.addPermanent(permanent); + game.setZone(objectId, Zone.BATTLEFIELD); + game.setScopeRelevant(true); + game.applyEffects(); + permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); + game.setScopeRelevant(false); + game.applyEffects(); + if (flag) { + permanent.setTapped(true); + } + event.setTarget(permanent); + break; + default: + Card sourceCard = game.getCard(sourceId); + logger.fatal(new StringBuilder("Invalid to zone [").append(toZone) + .append("] for card [").append(this.getName()) + .append("] to zone [").append(toZone) + .append("] source [").append(sourceCard != null ? sourceCard.getName():"null").append("]").toString()); + return false; + } + game.setZone(objectId, event.getToZone()); + game.addSimultaneousEvent(event); + return game.getState().getZone(objectId) == toZone; + } + return false; + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + Card mainCard = getMainCard(); + ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); + if (!game.replaceEvent(event)) { + if (event.getFromZone() != null) { + switch (event.getFromZone()) { + case GRAVEYARD: + game.getPlayer(ownerId).removeFromGraveyard(mainCard, game); + break; + case HAND: + game.getPlayer(ownerId).removeFromHand(mainCard, game); + break; + case LIBRARY: + game.getPlayer(ownerId).removeFromLibrary(mainCard, game); + break; + case EXILED: + game.getExile().removeCard(mainCard, game); + break; + case OUTSIDE: + game.getPlayer(ownerId).getSideboard().remove(mainCard); + break; + + case COMMAND: + game.getState().getCommand().remove((Commander)game.getObject(mainCard.getId())); + break; + default: + //logger.warning("moveToZone, not fully implemented: from="+event.getFromZone() + ", to="+event.getToZone()); + } + game.rememberLKI(mainCard.getId(), event.getFromZone(), this); + } + game.getStack().push(new Spell(this, ability.copy(), controllerId, event.getFromZone())); + setZone(event.getToZone(), game); + game.fireEvent(event); + return game.getState().getZone(mainCard.getId()) == Zone.STACK; + } + return false; + } + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { + return moveToExile(exileId, name, sourceId, game, null); + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { + Zone fromZone = game.getState().getZone(objectId); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); + if (!game.replaceEvent(event)) { + if (fromZone != null) { + switch (fromZone) { + case GRAVEYARD: + game.getPlayer(ownerId).removeFromGraveyard(this, game); + break; + case HAND: + game.getPlayer(ownerId).removeFromHand(this, game); + break; + case LIBRARY: + game.getPlayer(ownerId).removeFromLibrary(this, game); + break; + case EXILED: + game.getExile().removeCard(this, game); + break; + case STACK: + case PICK: + // nothing to do + break; + default: + MageObject object = game.getObject(sourceId); + logger.warn(new StringBuilder("moveToExile, not fully implemented: from = ").append(fromZone).append(" - ").append(object != null ? object.getName():"null")); + } + game.rememberLKI(objectId, event.getFromZone(), this); + } + + if (exileId == null) { + game.getExile().getPermanentExile().add(this); + } else { + game.getExile().createZone(exileId, name).add(this); + } + setFaceDown(false, game); + updateZoneChangeCounter(game); + game.setZone(objectId, event.getToZone()); + game.addSimultaneousEvent(event); + return true; + } + return false; + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId) { + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, false, false, null); + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped){ + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, false, null); + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown){ + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, facedown, null); + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects){ + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); + if (facedown) { + this.setFaceDown(true, game); + } + if (!game.replaceEvent(event)) { + if (facedown) { + this.setFaceDown(false, game); + } + if (fromZone != null) { + boolean removed = false; + switch (fromZone) { + case GRAVEYARD: + removed = game.getPlayer(ownerId).removeFromGraveyard(this, game); + break; + case HAND: + removed = game.getPlayer(ownerId).removeFromHand(this, game); + break; + case LIBRARY: + removed = game.getPlayer(ownerId).removeFromLibrary(this, game); + break; + case EXILED: + game.getExile().removeCard(this, game); + removed = true; + break; + case COMMAND: + // command object (commander) is only on the stack, so no removing neccessary here + removed = true; + break; + case PICK: + removed = true; + break; + default: + logger.warn("putOntoBattlefield, not fully implemented: fromZone="+fromZone); + } + game.rememberLKI(objectId, event.getFromZone(), this); + if (!removed) { + logger.warn("Couldn't find card in fromZone, card=" + getName() + ", fromZone=" + fromZone); + } + } + updateZoneChangeCounter(game); + PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); + // make sure the controller of all continuous effects of this card are switched to the current controller + game.getContinuousEffects().setController(objectId, event.getPlayerId()); + game.addPermanent(permanent); + setZone(Zone.BATTLEFIELD, game); + game.setScopeRelevant(true); + permanent.setTapped(tapped); + permanent.setFaceDown(facedown, game); + permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); + game.setScopeRelevant(false); + game.applyEffects(); + game.addSimultaneousEvent(new ZoneChangeEvent(permanent, event.getPlayerId(), fromZone, Zone.BATTLEFIELD)); + return true; + } + if (facedown) { + this.setFaceDown(false, game); + } + return false; + } + + @Override + public void setFaceDown(boolean value, Game game) { + game.getState().getCardState(objectId).setFaceDown(value); + } + + @Override + public boolean isFaceDown(Game game) { + return game.getState().getCardState(objectId).isFaceDown(); + } + + @Override + public boolean turnFaceUp(Game game, UUID playerId) { + GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEUP, getId(), playerId); + if (!game.replaceEvent(event)) { + setFaceDown(false, game); + for (Ability ability :abilities) { // abilities that were set to not visible face down must be set to visible again + if (ability.getWorksFaceDown() && !ability.getRuleVisible()) { + ability.setRuleVisible(true); + } + } + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEUP, getId(), playerId)); + return true; + } + return false; + } + + @Override + public boolean turnFaceDown(Game game, UUID playerId) { + GameEvent event = GameEvent.getEvent(GameEvent.EventType.TURNFACEDOWN, getId(), playerId); + if (!game.replaceEvent(event)) { + setFaceDown(true, game); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.TURNEDFACEDOWN, getId(), playerId)); + return true; + } + return false; + } + + @Override + public boolean canTransform() { + return this.canTransform; + } + + @Override + public Card getSecondCardFace() { + return this.secondSideCard; + } + + @Override + public boolean isNightCard() { + return this.nightCard; + } + + @Override + public boolean isFlipCard() { + return flipCard; + } + + @Override + public String getFlipCardName() { + return flipCardName; + } + + @Override + public boolean isSplitCard() { + return splitCard; + } + + @Override + public void build() {} + + @Override + public boolean getUsesVariousArt() { + return usesVariousArt; + } + + @Override + public Counters getCounters(Game game) { + return game.getState().getCardState(this.objectId).getCounters(); + } + + @Override + public void addCounters(String name, int amount, Game game) { + addCounters(name, amount, game, null); + } + + @Override + public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { + GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, name, amount); + countersEvent.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(countersEvent)) { + for (int i = 0; i < countersEvent.getAmount(); i++) { + GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, name, 1); + event.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(event)) { + game.getState().getCardState(this.objectId).getCounters().addCounter(name, 1); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, name, 1)); + } + } + } + } + + @Override + public void addCounters(Counter counter, Game game) { + addCounters(counter, game, null); + } + + @Override + public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { + GameEvent countersEvent = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTERS, objectId, ownerId, counter.getName(), counter.getCount()); + countersEvent.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(countersEvent)) { + int amount = countersEvent.getAmount(); + for (int i = 0; i < amount; i++) { + Counter eventCounter = counter.copy(); + eventCounter.remove(amount - 1); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.ADD_COUNTER, objectId, ownerId, counter.getName(), 1); + event.setAppliedEffects(appliedEffects); + if (!game.replaceEvent(event)) { + game.getState().getCardState(this.objectId).getCounters().addCounter(eventCounter); + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER_ADDED, objectId, ownerId, counter.getName(), 1)); + } + } + } + } + + @Override + public void removeCounters(String name, int amount, Game game) { + for (int i = 0; i < amount; i++) { + game.getState().getCardState(this.objectId).getCounters().removeCounter(name, 1); + GameEvent event = GameEvent.getEvent(GameEvent.EventType.COUNTER_REMOVED, objectId, ownerId); + event.setData(name); + game.fireEvent(event); + } + } + + @Override + public void removeCounters(Counter counter, Game game) { + removeCounters(counter.getName(), counter.getCount(), game); + } + + @Override + public String getLogName() { +// if (this.isFaceDown()) { +// return "facedown card"; +// } + return name; + } + + @Override + public Card getMainCard() { + return this; + } + + @Override + public void setZone(Zone zone, Game game) { + game.setZone(getId(), zone); + } + + @Override + public void setSpellAbility(SpellAbility ability) { + spellAbility = ability; + } + +} diff --git a/Mage/src/mage/cards/repository/CardRepository.java b/Mage/src/mage/cards/repository/CardRepository.java index 2732389cbc0..451a4dfacfe 100644 --- a/Mage/src/mage/cards/repository/CardRepository.java +++ b/Mage/src/mage/cards/repository/CardRepository.java @@ -60,7 +60,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 37; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 12; + private static final long CARD_CONTENT_VERSION = 13; private final Random random = new Random(); private Dao cardDao; diff --git a/Mage/src/mage/game/events/GameEvent.java b/Mage/src/mage/game/events/GameEvent.java index 65948f54e37..0f515ccc77a 100644 --- a/Mage/src/mage/game/events/GameEvent.java +++ b/Mage/src/mage/game/events/GameEvent.java @@ -177,7 +177,16 @@ public class GameEvent { ADD_COUNTER, COUNTER_ADDED, ADD_COUNTERS, COUNTERS_ADDED, COUNTER_REMOVED, - LOSE_CONTROL, LOST_CONTROL, + LOSE_CONTROL, + + /* LOST_CONTROL + targetId id of the creature that lost control + sourceId id of the creature that lost control + playerId player that controlles the creature before + amount not used for this event + flag not used for this event + */ + LOST_CONTROL, GAIN_CONTROL, GAINED_CONTROL, CREATE_TOKEN, diff --git a/Mage/src/mage/game/permanent/PermanentToken.java b/Mage/src/mage/game/permanent/PermanentToken.java index 75dcbb9e9bf..69d2aa37442 100644 --- a/Mage/src/mage/game/permanent/PermanentToken.java +++ b/Mage/src/mage/game/permanent/PermanentToken.java @@ -108,7 +108,7 @@ public class PermanentToken extends PermanentImpl { if (!game.replaceEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED))) { game.rememberLKI(objectId, Zone.BATTLEFIELD, this); if (game.getPlayer(controllerId).removeFromBattlefield(this, game)) { - game.fireEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED)); + game.addSimultaneousEvent(new ZoneChangeEvent(this, sourceId, this.getControllerId(), Zone.BATTLEFIELD, Zone.EXILED)); return true; } } diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 8b0536318d1..18de6258c90 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -55,6 +55,7 @@ import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.counters.Counter; import mage.counters.Counters; +import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; @@ -81,6 +82,7 @@ public class Spell implements StackObject, Card { private UUID controllerId; private boolean copiedSpell; private boolean faceDown; + private boolean countered; public Spell(Card card, SpellAbility ability, UUID controllerId, Zone fromZone) { this.card = card; @@ -99,6 +101,7 @@ public class Spell implements StackObject, Card { } this.controllerId = controllerId; this.fromZone = fromZone; + this.countered = false; } public Spell(final Spell spell) { @@ -198,8 +201,8 @@ public class Spell implements StackObject, Card { result |= spellAbility.resolve(game); } } - game.getState().handleSimultaneousEvent(game); - game.resetShortLivingLKI(); +// game.getState().handleSimultaneousEvent(game); +// game.resetShortLivingLKI(); index++; } } @@ -319,7 +322,7 @@ public class Spell implements StackObject, Card { * @return */ public boolean chooseNewTargets(Game game, UUID playerId) { - return chooseNewTargets(game, playerId, false, false); + return chooseNewTargets(game, playerId, false, false, null); } /** @@ -377,13 +380,13 @@ public class Spell implements StackObject, Card { * * @param game * @param playerId - player that can/has to change the taregt of the spell - * @param forceChange - does only work for targets with maximum of one - * targetId - * @param onlyOneTarget - 114.6b one target must be changed to another - * target + * @param forceChange - does only work for targets with maximum of one targetId + * @param onlyOneTarget - 114.6b one target must be changed to another target + * @param filterNewTarget restriction for the new target, if null nothing is cheched * @return */ - public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget) { + @Override + public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) { Player player = game.getPlayer(playerId); if (player != null) { StringBuilder newTargetDescription = new StringBuilder(); @@ -393,7 +396,7 @@ public class Spell implements StackObject, Card { for (UUID modeId : spellAbility.getModes().getSelectedModes()) { Mode mode = spellAbility.getModes().get(modeId); for (Target target : mode.getTargets()) { - Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, game); + Target newTarget = chooseNewTarget(player, spellAbility, mode, target, forceChange, filterNewTarget, game); // clear the old target and copy all targets from new target target.clearChosen(); for (UUID targetId : newTarget.getTargets()) { @@ -424,7 +427,7 @@ public class Spell implements StackObject, Card { * @param game * @return */ - private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, Game game) { + private Target chooseNewTarget(Player player, SpellAbility spellAbility, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) { Target newTarget = target.copy(); newTarget.clearChosen(); for (UUID targetId : target.getTargets()) { @@ -443,6 +446,14 @@ public class Spell implements StackObject, Card { newTarget.clearChosen(); // TODO: Distinction between "spell controller" and "player that can change the target" - here player is used for both newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), spellAbility, game); + // check target restriction + if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + newTarget.clearChosen(); + } + } } while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); // choose a new target } else { @@ -473,6 +484,12 @@ public class Spell implements StackObject, Card { } else { newTarget.addTarget(targetId, target.getTargetAmount(targetId), spellAbility, game, false); } + } else if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + again = true; + } } else { // valid target was selected, add it to the new target definition newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), spellAbility, game, false); @@ -506,6 +523,7 @@ public class Spell implements StackObject, Card { @Override public void counter(UUID sourceId, Game game) { + this.countered = true; if (!isCopiedSpell()) { card.moveToZone(Zone.GRAVEYARD, sourceId, game, false); } @@ -987,4 +1005,8 @@ public class Spell implements StackObject, Card { throw new UnsupportedOperationException("Not supported."); } + public boolean isCountered() { + return countered; + } + } diff --git a/Mage/src/mage/game/stack/SpellStack.java b/Mage/src/mage/game/stack/SpellStack.java index 50caf946515..7f2179d4d77 100644 --- a/Mage/src/mage/game/stack/SpellStack.java +++ b/Mage/src/mage/game/stack/SpellStack.java @@ -97,9 +97,10 @@ public class SpellStack extends ArrayDeque { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, sourceId, stackObject.getControllerId()))) { if ( stackObject instanceof Spell ) { game.rememberLKI(objectId, Zone.STACK, (Spell)stackObject); + } else { + this.remove(stackObject); } - this.remove(stackObject); - stackObject.counter(sourceId, game); // tries to move to graveyard + stackObject.counter(sourceId, game); if (!game.isSimulation()) game.informPlayers(counteredObjectName + " is countered by " + sourceObject.getLogName()); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.COUNTERED, objectId, sourceId, stackObject.getControllerId())); diff --git a/Mage/src/mage/game/stack/StackAbility.java b/Mage/src/mage/game/stack/StackAbility.java index b7413d9d6e2..b9826bd2a33 100644 --- a/Mage/src/mage/game/stack/StackAbility.java +++ b/Mage/src/mage/game/stack/StackAbility.java @@ -54,10 +54,15 @@ import mage.target.Targets; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.cards.Card; import mage.constants.AbilityWord; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetAmount; import mage.watchers.Watcher; /** @@ -535,4 +540,197 @@ public class StackAbility implements StackObject, Ability { throw new UnsupportedOperationException("Not supported."); } + /** + * 114.6. Some effects allow a player to change the target(s) of a spell or + * ability, and other effects allow a player to choose new targets for a + * spell or ability. + * + * 114.6a If an effect allows a player to "change the + * target(s)" of a spell or ability, each target can be changed only to + * another legal target. If a target can't be changed to another legal + * target, the original target is unchanged, even if the original target is + * itself illegal by then. If all the targets aren't changed to other legal + * targets, none of them are changed. + * + * 114.6b If an effect allows a player to "change a target" of a + * spell or ability, the process described in rule 114.6a + * is followed, except that only one of those targets may be changed + * (rather than all of them or none of them). + * + * 114.6c If an effect allows a + * player to "change any targets" of a spell or ability, the process + * described in rule 114.6a is followed, except that any number of those + * targets may be changed (rather than all of them or none of them). + * + * 114.6d If an effect allows a player to "choose new targets" for a spell or + * ability, the player may leave any number of the targets unchanged, even + * if those targets would be illegal. If the player chooses to change some + * or all of the targets, the new targets must be legal and must not cause + * any unchanged targets to become illegal. + * + * 114.6e When changing targets or + * choosing new targets for a spell or ability, only the final set of + * targets is evaluated to determine whether the change is legal. + * + * Example: Arc Trail is a sorcery that reads "Arc Trail deals 2 damage to + * target creature or player and 1 damage to another target creature or + * player." The current targets of Arc Trail are Runeclaw Bear and Llanowar + * Elves, in that order. You cast Redirect, an instant that reads "You may + * choose new targets for target spell," targeting Arc Trail. You can change + * the first target to Llanowar Elves and change the second target to + * Runeclaw Bear. + * + * 114.7. Modal spells and abilities may have different targeting + * requirements for each mode. An effect that allows a player to change the + * target(s) of a modal spell or ability, or to choose new targets for a + * modal spell or ability, doesn't allow that player to change its mode. + * (See rule 700.2.) + * + * 706.10c Some effects copy a spell or ability and state that its + * controller may choose new targets for the copy. The player may leave any + * number of the targets unchanged, even if those targets would be illegal. + * If the player chooses to change some or all of the targets, the new + * targets must be legal. Once the player has decided what the copy's + * targets will be, the copy is put onto the stack with those targets. + * + * @param game + * @param playerId - player that can/has to change the target of the ability + * @param forceChange - does only work for targets with maximum of one targetId + * @param onlyOneTarget - 114.6b one target must be changed to another target + * @param filterNewTarget restriction for the new target, if null nothing is cheched + * @return + */ + @Override + public boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget) { + Player player = game.getPlayer(playerId); + if (player != null) { + StringBuilder newTargetDescription = new StringBuilder(); + // Some abilities can have more than one mode + for (UUID modeId : ability.getModes().getSelectedModes()) { + Mode mode = ability.getModes().get(modeId); + for (Target target : mode.getTargets()) { + Target newTarget = chooseNewTarget(player, getStackAbility(), mode, target, forceChange, filterNewTarget, game); + // clear the old target and copy all targets from new target + target.clearChosen(); + for (UUID targetId : newTarget.getTargets()) { + target.addTarget(targetId, newTarget.getTargetAmount(targetId), ability, game, false); + } + + } + newTargetDescription.append(((AbilityImpl)ability).getTargetDescription(mode.getTargets(), game)); + } + + if (newTargetDescription.length() > 0 && !game.isSimulation()) { + game.informPlayers(this.getName() + " is now " + newTargetDescription.toString()); + } + return true; + } + return false; + } + + /** + * Handles the change of one target instance of a mode + * + * @param player - player that can choose the new target + * @param ability + * @param mode + * @param target + * @param forceChange + * @param game + * @return + */ + private Target chooseNewTarget(Player player, Ability ability, Mode mode, Target target, boolean forceChange, FilterPermanent filterNewTarget, Game game) { + Target newTarget = target.copy(); + newTarget.clearChosen(); + for (UUID targetId : target.getTargets()) { + String targetNames = getNamesOfTargets(targetId, game); + // change the target? + if (targetNames != null + && (forceChange || player.chooseUse(mode.getEffects().get(0).getOutcome(), "Change this target: " + targetNames + "?", game))) { + // choose exactly one other target + if (forceChange && target.possibleTargets(this.getSourceId(), getControllerId(), game).size() > 1) { // controller of ability must be used (e.g. TargetOpponent) + int iteration = 0; + do { + if (iteration > 0 && !game.isSimulation()) { + game.informPlayer(player, "You may only select exactly one target that must be different from the origin target!"); + } + iteration++; + newTarget.clearChosen(); + // TODO: Distinction between "ability controller" and "player that can change the target" - here player is used for both + newTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game); + // check target restriction + if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "Target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + newTarget.clearChosen(); + } + } + } while (player.isInGame() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1)); + // choose a new target + } else { + // build a target definition with exactly one possible target to select that replaces old target + Target tempTarget = target.copy(); + if (target instanceof TargetAmount) { + ((TargetAmount)tempTarget).setAmountDefinition(new StaticValue(target.getTargetAmount(targetId))); + } + tempTarget.setMinNumberOfTargets(1); + tempTarget.setMaxNumberOfTargets(1); + boolean again; + do { + again = false; + tempTarget.clearChosen(); + if (!tempTarget.chooseTarget(mode.getEffects().get(0).getOutcome(), player.getId(), ability, game)) { + if (player.chooseUse(Outcome.Benefit, "No target object selected. Reset to original target?", game)) { + // use previous target no target was selected + newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false); + } else { + again = true; + } + } else { + // if possible add the alternate Target - it may not be included in the old definition nor in the already selected targets of the new definition + if (newTarget.getTargets().contains(tempTarget.getFirstTarget()) || target.getTargets().contains(tempTarget.getFirstTarget())) { + if (player.isHuman()) { + game.informPlayer(player, "This target was already selected from origin ability. You can only keep this target!"); + again = true; + } else { + newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false); + } + } else if (newTarget.getFirstTarget() != null && filterNewTarget != null) { + Permanent newTargetPermanent = game.getPermanent(newTarget.getFirstTarget()); + if (newTargetPermanent == null || !filterNewTarget.match(newTargetPermanent, game)) { + game.informPlayer(player, "This target does not fullfil the target requirements (" + filterNewTarget.getMessage() +")"); + again = true; + } + } else { + // valid target was selected, add it to the new target definition + newTarget.addTarget(tempTarget.getFirstTarget(), target.getTargetAmount(targetId), ability, game, false); + } + } + } while (again && player.isInGame()); + } + } + // keep the target + else { + newTarget.addTarget(targetId, target.getTargetAmount(targetId), ability, game, false); + } + } + return newTarget; + } + + + private String getNamesOfTargets(UUID targetId, Game game) { + MageObject object = game.getObject(targetId); + String targetNames = null; + if (object == null) { + Player targetPlayer = game.getPlayer(targetId); + if (targetPlayer != null) { + targetNames = targetPlayer.getName(); + } + } else { + targetNames = object.getName(); + } + return targetNames; + } + } diff --git a/Mage/src/mage/game/stack/StackObject.java b/Mage/src/mage/game/stack/StackObject.java index 791cc6caaf4..ce408387aa5 100644 --- a/Mage/src/mage/game/stack/StackObject.java +++ b/Mage/src/mage/game/stack/StackObject.java @@ -31,6 +31,7 @@ package mage.game.stack; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; +import mage.filter.FilterPermanent; import mage.game.Controllable; import mage.game.Game; @@ -41,6 +42,7 @@ public interface StackObject extends MageObject, Controllable { void counter(UUID sourceId, Game game); Ability getStackAbility(); int getConvertedManaCost(); + boolean chooseNewTargets(Game game, UUID playerId, boolean forceChange, boolean onlyOneTarget, FilterPermanent filterNewTarget); @Override StackObject copy(); } diff --git a/Mage/src/mage/target/Target.java b/Mage/src/mage/target/Target.java index 4639f7702fd..c1c5ea41846 100644 --- a/Mage/src/mage/target/Target.java +++ b/Mage/src/mage/target/Target.java @@ -104,4 +104,8 @@ public interface Target extends Serializable { UUID getFirstTarget(); Target copy(); -} + + // some targets are choosen from players that are not the controller of the ability (e.g. Pandemonium) + void setTargetController(UUID playerId); + UUID getTargetController(); +} diff --git a/Mage/src/mage/target/TargetImpl.java b/Mage/src/mage/target/TargetImpl.java index f2c01dc5637..c6d46a94c66 100644 --- a/Mage/src/mage/target/TargetImpl.java +++ b/Mage/src/mage/target/TargetImpl.java @@ -60,6 +60,7 @@ public abstract class TargetImpl implements Target { // is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice) protected boolean notTarget = false; protected boolean atRandom = false; + protected UUID targetController = null; // if null the ability controller is the targetController @Override public abstract TargetImpl copy(); @@ -84,6 +85,7 @@ public abstract class TargetImpl implements Target { this.zoneChangeCounters.putAll(target.zoneChangeCounters); this.atRandom = target.atRandom; this.notTarget = target.notTarget; + this.targetController = target.targetController; } @Override @@ -421,5 +423,15 @@ public abstract class TargetImpl implements Target { this.atRandom = atRandom; } + @Override + public void setTargetController(UUID playerId) { + this.targetController = playerId; + } + + @Override + public UUID getTargetController() { + return targetController; + } + } diff --git a/Mage/src/mage/target/Targets.java b/Mage/src/mage/target/Targets.java index 8544fa944a2..ad1f1c37fb5 100644 --- a/Mage/src/mage/target/Targets.java +++ b/Mage/src/mage/target/Targets.java @@ -97,7 +97,11 @@ public class Targets extends ArrayList { } while (!isChosen()) { Target target = this.getUnchosen().get(0); - if (!target.chooseTarget(outcome, playerId, source, game)) { + UUID targetController = playerId; + if (target.getTargetController() != null) { // some targets can have controller different than ability controller + targetController = target.getTargetController(); + } + if (!target.chooseTarget(outcome, targetController, source, game)) { return false; } } diff --git a/Mage/src/mage/util/CardUtil.java b/Mage/src/mage/util/CardUtil.java index c9261ede99b..88116bd390e 100644 --- a/Mage/src/mage/util/CardUtil.java +++ b/Mage/src/mage/util/CardUtil.java @@ -132,7 +132,7 @@ public class CardUtil { * @param increaseCount */ public static void increaseCost(Ability ability, int increaseCount) { - adjustCost(ability, -increaseCount); + adjustAbilityCost(ability, -increaseCount); adjustAlternativeCosts(ability, -increaseCount); } @@ -143,7 +143,7 @@ public class CardUtil { * @param reduceCount */ public static void reduceCost(Ability ability, int reduceCount) { - adjustCost(ability, reduceCount); + adjustAbilityCost(ability, reduceCount); adjustAlternativeCosts(ability, reduceCount); } @@ -154,7 +154,7 @@ public class CardUtil { * @param reduceCount */ public static void adjustCost(SpellAbility spellAbility, int reduceCount) { - CardUtil.adjustCost((Ability) spellAbility, reduceCount); + CardUtil.adjustAbilityCost((Ability) spellAbility, reduceCount); adjustAlternativeCosts(spellAbility, reduceCount); } @@ -208,7 +208,7 @@ public class CardUtil { * @param ability * @param reduceCount */ - private static void adjustCost(Ability ability, int reduceCount) { + public static void adjustAbilityCost(Ability ability, int reduceCount) { ManaCosts adjustedCost = adjustCost(ability.getManaCostsToPay(), reduceCount); ability.getManaCostsToPay().clear(); ability.getManaCostsToPay().addAll(adjustedCost); diff --git a/Utils/release/getting_implemented_cards.txt b/Utils/release/getting_implemented_cards.txt index f67dd98e14f..60f0ef7fe8d 100644 --- a/Utils/release/getting_implemented_cards.txt +++ b/Utils/release/getting_implemented_cards.txt @@ -117,6 +117,9 @@ git log 65f731557bb55d0c85723e382001bdf9701f0a7f..HEAD --diff-filter=A --name-st since 1.3.0-2015-03-14v6 git log b79d6e64cff01726be93cbbfffca8a6f18188a3c..HEAD --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt +since 1.3.0-2015-03-14v8 +git log 47b17535194c6aa5397a966463c8b17d37f8bd44..HEAD --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt + 3. Copy added_cards.txt to trunk\Utils folder 4. Run script: > perl extract_in_wiki_format.perl