diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index e30923010bd..c8d95300c5c 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -122,20 +122,28 @@ public class ChatManager { } } + private static final String COMMANDS_LIST = + "
List of commands:" + + "
\\history or \\h [username] - shows the history of a player" + + "
\\me - shows the history of the current player" + + "
\\list or \\l - Show a list of commands" + + "
\\whisper or \\w [player name] [text] - whisper to the player with the given name"; + private boolean performUserCommand(User user, String message, UUID chatId, boolean doError) { String command = message.substring(1).trim().toUpperCase(Locale.ENGLISH); if (doError) { - message += new StringBuilder("
Invalid User Command '" + message + "'.") - .append("
List of commands:") - .append("
\\history or \\h [username] - shows the history of a player") - .append("
\\list or \\l - Show a list of commands") - .append("
\\whisper or \\w [player name] [text] - whisper to the player with the given name").toString(); + message += new StringBuilder("
Invalid User Command '" + message + "'.").append(COMMANDS_LIST).toString(); chatSessions.get(chatId).broadcastInfoToUser(user, message); return true; } if (command.startsWith("H ") || command.startsWith("HISTORY ")) { - message = UserManager.getInstance().getUserHistory(message.substring(command.startsWith("H ") ? 3 : 9)); + message += "
" + UserManager.getInstance().getUserHistory(message.substring(command.startsWith("H ") ? 3 : 9)); + chatSessions.get(chatId).broadcastInfoToUser(user, message); + return true; + } + if (command.equals("ME")) { + message += "
" + UserManager.getInstance().getUserHistory(user.getName()); chatSessions.get(chatId).broadcastInfoToUser(user, message); return true; } @@ -159,10 +167,7 @@ public class ChatManager { } } if (command.equals("L") || command.equals("LIST")) { - message += new StringBuilder("
List of commands:") - .append("
\\history or \\h [username] - shows the history of a player") - .append("
\\list or \\l - Show a list of commands") - .append("
\\whisper or \\w [player name] [text] - whisper to the player with the given name").toString(); + message += COMMANDS_LIST; chatSessions.get(chatId).broadcastInfoToUser(user, message); return true; } diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/CropSigil.java b/Mage.Sets/src/mage/sets/eldritchmoon/CropSigil.java index d6e8eb05eb4..66e8358a08b 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/CropSigil.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/CropSigil.java @@ -34,7 +34,6 @@ import mage.abilities.condition.common.DeliriumCondition; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.decorator.ConditionalActivatedAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.PutTopCardOfLibraryIntoGraveControllerEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardImpl; @@ -44,7 +43,7 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.events.GameEvent; -import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetCardInYourGraveyard; /** * @@ -69,12 +68,13 @@ public class CropSigil extends CardImpl { // Delirium — {2}{G}, Sacrifice Crop Sigil: Return up to one target creature card and up to one target land card from your graveyard to your hand. // Activate this ability only if there are four or more card types among cards in your graveyard. - Effect effect = new ReturnToHandTargetEffect(true, true); - effect.setText("Return up to one target creature card and up to one target land card from your graveyard to your hand"); - Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{2}{G}"), DeliriumCondition.getInstance()); + Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(true, true), new ManaCostsImpl<>("{2}{G}"), + DeliriumCondition.getInstance(), + "Delirium — {2}{G}, Sacrifice {this}: Return up to one target creature card and up to one target land card from your graveyard to your hand. " + + "Activate this ability only if there are four or more card types among cards in your graveyard"); ability.addCost(new SacrificeSourceCost()); - ability.addTarget(new TargetCardInGraveyard(0, 1, filterCreature)); - ability.addTarget(new TargetCardInGraveyard(0, 1, filterLand)); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filterCreature)); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filterLand)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/EmrakulsEvangel.java b/Mage.Sets/src/mage/sets/eldritchmoon/EmrakulsEvangel.java index 75243bc5060..515d0160b6d 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/EmrakulsEvangel.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/EmrakulsEvangel.java @@ -33,6 +33,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; +import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.constants.CardType; @@ -54,7 +55,7 @@ import mage.target.common.TargetControlledCreaturePermanent; * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public class EmrakulsEvangel extends CardImpl { - + public EmrakulsEvangel(UUID ownerId) { super(ownerId, 156, "Emrakul's Evangel", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.expansionSetCode = "EMN"; @@ -63,9 +64,11 @@ public class EmrakulsEvangel extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(2); - // {T}, Sacrifice Emrakul's Evangel and any number of other non-Eldrazi creatures: + // {T}, Sacrifice Emrakul's Evangel and any number of other non-Eldrazi creatures: // Put a 3/2 colorless Eldrazi Horror creature token onto the battlefield for each creature sacrificed this way. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new EmrakulsEvangelEffect(), new EmrakulsEvangelCost())); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new EmrakulsEvangelEffect(), new TapSourceCost()); + ability.addCost(new EmrakulsEvangelCost()); + this.addAbility(ability); } public EmrakulsEvangel(final EmrakulsEvangel card) { @@ -79,14 +82,14 @@ public class EmrakulsEvangel extends CardImpl { } class EmrakulsEvangelCost extends CostImpl { - + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("non-Eldrazi creatures you control"); - + static { filter.add(new AnotherPredicate()); filter.add(Predicates.not(new SubtypePredicate("Eldrazi"))); - } - + } + private int numSacrificed = 1; // always sacrifices self at least public EmrakulsEvangelCost() { @@ -117,7 +120,7 @@ class EmrakulsEvangelCost extends CostImpl { } return paid; } - + public int getNumSacrificed() { return numSacrificed; } @@ -125,7 +128,7 @@ class EmrakulsEvangelCost extends CostImpl { @Override public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { Permanent permanent = game.getPermanent(sourceId); - + return permanent != null && game.getPlayer(controllerId).canPaySacrificeCost(permanent, sourceId, controllerId, game); } @@ -136,25 +139,25 @@ class EmrakulsEvangelCost extends CostImpl { } class EmrakulsEvangelEffect extends OneShotEffect { - + EmrakulsEvangelEffect() { super(Outcome.Sacrifice); - this.staticText = "Sacrifice {this} and any number of other non-Eldrazi creatures: Put a 3/2 colorless Eldrazi Horror creature token onto the battlefield for each creature sacrificed this way."; + this.staticText = "Put a 3/2 colorless Eldrazi Horror creature token onto the battlefield for each creature sacrificed this way."; } - + EmrakulsEvangelEffect(final EmrakulsEvangelEffect effect) { super(effect); } - + @Override public EmrakulsEvangelEffect copy() { return new EmrakulsEvangelEffect(this); } - + @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { + if (player != null) { int tokensToCreate = 0; for (Cost cost : source.getCosts()) { if (cost instanceof EmrakulsEvangelCost) { diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/GeierReachSanitarium.java b/Mage.Sets/src/mage/sets/eldritchmoon/GeierReachSanitarium.java index 84e493f7388..f0bdff485f2 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/GeierReachSanitarium.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/GeierReachSanitarium.java @@ -32,6 +32,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.DrawCardAllEffect; import mage.abilities.effects.common.discard.DiscardEachPlayerEffect; import mage.abilities.mana.ColorlessManaAbility; @@ -56,7 +57,9 @@ public class GeierReachSanitarium extends CardImpl { // {2}, {T}: Each player draws a card, then discards a card. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardAllEffect(1), new GenericManaCost(2)); - ability.addEffect(new DiscardEachPlayerEffect()); + Effect effect = new DiscardEachPlayerEffect(); + effect.setText(", then discards a card"); + ability.addEffect(effect); ability.addCost(new TapSourceCost()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/GrimFlayer.java b/Mage.Sets/src/mage/sets/eldritchmoon/GrimFlayer.java index f9776940cc2..1cd7057e746 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/GrimFlayer.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/GrimFlayer.java @@ -66,7 +66,7 @@ public class GrimFlayer extends CardImpl { // and the rest back on top of your library in any order. Effect effect = new LookLibraryAndPickControllerEffect( new StaticValue(3), false, new StaticValue(3), new FilterCard(), Zone.LIBRARY, true, false, true, Zone.GRAVEYARD, false); - effect.setText("look at the top three cards of your library. Put any number of them into your graveyard and the rest on top of your library in any order"); + effect.setText("look at the top three cards of your library. Put any number of them into your graveyard and the rest back on top of your library in any order"); this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(effect, false)); // Delirium — Grim Flayer gets +2/+2 as long as there are four or more card types among cards in your graveyard. diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/IshkanahGrafwidow.java b/Mage.Sets/src/mage/sets/eldritchmoon/IshkanahGrafwidow.java index 82d5d9d19f2..bc9cdc5cd9f 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/IshkanahGrafwidow.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/IshkanahGrafwidow.java @@ -54,7 +54,7 @@ import mage.target.common.TargetOpponent; */ public class IshkanahGrafwidow extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("each Spider you control"); + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Spider you control"); static { filter.add(new SubtypePredicate("Spider")); diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/MindsDilation.java b/Mage.Sets/src/mage/sets/eldritchmoon/MindsDilation.java index ca777a1c688..06354f26ebe 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/MindsDilation.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/MindsDilation.java @@ -32,15 +32,11 @@ import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SpellCastOpponentTriggeredAbility; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; -import mage.constants.AsThoughEffectType; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; @@ -64,8 +60,7 @@ public class MindsDilation extends CardImpl { // Whenever an opponent casts his or her first spell each turn, that player exiles the top card of his or her library. If it's a nonland card, // you may cast it without paying its mana cost. - Ability ability = new MindsDilationTriggeredAbility(new MindsDilationEffect(), false); - this.addAbility(ability, new SpellsCastWatcher()); + this.addAbility(new MindsDilationTriggeredAbility(new MindsDilationEffect(), false), new SpellsCastWatcher()); } public MindsDilation(final MindsDilation card) { @@ -113,7 +108,7 @@ class MindsDilationTriggeredAbility extends SpellCastOpponentTriggeredAbility { @Override public String getRule() { return "Whenever an opponent casts his or her first spell each turn, that player exiles the top card of his or her library." - + " If it's a nonland card, you may cast it without paying its mana cost"; + + " If it's a nonland card, you may cast it without paying its mana cost."; } } @@ -121,7 +116,7 @@ class MindsDilationEffect extends OneShotEffect { MindsDilationEffect() { super(Outcome.Benefit); - this.staticText = "that player exiles the top card of his or her library. If it's a nonland card, you may cast it without paying its mana cost."; + this.staticText = "that player exiles the top card of his or her library. If it's a nonland card, you may cast it without paying its mana cost"; } MindsDilationEffect(final MindsDilationEffect effect) { @@ -140,12 +135,13 @@ class MindsDilationEffect extends OneShotEffect { Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && sourceObject != null && opponent != null) { if (opponent.getLibrary().size() > 0) { - Card card = opponent.getLibrary().removeFromTop(game); - if (card != null) { - opponent.moveCardToExileWithInfo(card, source.getSourceId(), sourceObject.getIdName(), source.getSourceId(), game, Zone.LIBRARY, true); - ContinuousEffect effect = new MindsDilationCastFromExileEffect(); - effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); + Card card = opponent.getLibrary().getFromTop(game); + if (card != null && opponent.moveCards(card, Zone.EXILED, source, game)) { + if (!card.getCardType().contains(CardType.LAND)) { + if (controller.chooseUse(outcome, "Cast " + card.getLogName() + " without paying its mana cost from exile?", source, game)) { + controller.cast(card.getSpellAbility(), game, true); + } + } } } return true; @@ -153,41 +149,3 @@ class MindsDilationEffect extends OneShotEffect { return false; } } - -class MindsDilationCastFromExileEffect extends AsThoughEffectImpl { - - MindsDilationCastFromExileEffect() { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); - staticText = "If it's a nonland card, you may cast it without paying its mana cost"; - } - - MindsDilationCastFromExileEffect(final MindsDilationCastFromExileEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public MindsDilationCastFromExileEffect copy() { - return new MindsDilationCastFromExileEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - UUID targetId = getTargetPointer().getFirst(game, source); - Player controller = game.getPlayer(source.getControllerId()); - if (targetId != null && controller != null) { - Card card = game.getCard(targetId); - if (card != null && !card.getCardType().contains(CardType.LAND) && game.getState().getZone(targetId) == Zone.EXILED) { - if (controller.chooseUse(outcome, "Cast " + card.getLogName() + "?", source, game)) { - controller.cast(card.getSpellAbility(), game, true); - } - return true; - } - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/PrimalDruid.java b/Mage.Sets/src/mage/sets/eldritchmoon/PrimalDruid.java index 48e367f5ab1..3792e57a92c 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/PrimalDruid.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/PrimalDruid.java @@ -30,6 +30,7 @@ package mage.sets.eldritchmoon; import java.util.UUID; import mage.MageInt; import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.constants.CardType; @@ -52,7 +53,9 @@ public class PrimalDruid extends CardImpl { this.toughness = new MageInt(3); // When Primal Druid dies, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle your library. - this.addAbility(new DiesTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(new FilterBasicLandCard()), true), true)); + Effect effect = new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(new FilterBasicLandCard()), true); + effect.setText("you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle your library"); + this.addAbility(new DiesTriggeredAbility(effect, true)); } diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/SpiritOfTheHunt.java b/Mage.Sets/src/mage/sets/eldritchmoon/SpiritOfTheHunt.java index 7a9a50b9d27..04dc445bd5e 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/SpiritOfTheHunt.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/SpiritOfTheHunt.java @@ -30,6 +30,7 @@ package mage.sets.eldritchmoon; import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.keyword.FlashAbility; import mage.cards.CardImpl; @@ -65,7 +66,9 @@ public class SpiritOfTheHunt extends CardImpl { this.addAbility(FlashAbility.getInstance()); // When Spirit of the Hunt enters the battlefield, each other creature you control that's a Wolf or a Werewolf gets +0/+3 until end of turn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new BoostControlledEffect(0, 3, Duration.EndOfTurn, filter, true), false)); + Effect effect = new BoostControlledEffect(0, 3, Duration.EndOfTurn, filter, true); + effect.setText("each other creature you control that's a Wolf or a Werewolf gets +0/+3 until end of turn"); + this.addAbility(new EntersBattlefieldTriggeredAbility(effect, false)); } public SpiritOfTheHunt(final SpiritOfTheHunt card) { diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/StitchersGraft.java b/Mage.Sets/src/mage/sets/eldritchmoon/StitchersGraft.java index 65876b2e08f..33f3f77342d 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/StitchersGraft.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/StitchersGraft.java @@ -45,8 +45,8 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.GameEvent.EventType; import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; /** * @@ -68,7 +68,9 @@ public class StitchersGraft extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); // Whenever Stitcher's Graft becomes unattached from a permanent, sacrifice that permanent. - this.addAbility(new UnattachedTriggeredAbility(new SacrificeEquippedEffect(), false)); + effect = new SacrificeEquippedEffect(); + effect.setText("sacrifice that permanent"); + this.addAbility(new UnattachedTriggeredAbility(effect, false)); // Equip {2} this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(2))); diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/TamiyoFieldResearcher.java b/Mage.Sets/src/mage/sets/eldritchmoon/TamiyoFieldResearcher.java index 2e8464303c2..c79567d3a14 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/TamiyoFieldResearcher.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/TamiyoFieldResearcher.java @@ -157,7 +157,7 @@ class TamiyoFieldResearcherDelayedTriggeredAbility extends DelayedTriggeredAbili private List creatures; public TamiyoFieldResearcherDelayedTriggeredAbility(List creatures) { - super(new DrawCardSourceControllerEffect(1), Duration.UntilYourNextTurn); + super(new DrawCardSourceControllerEffect(1), Duration.UntilYourNextTurn, false); this.creatures = creatures; } diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/WaxingMoon.java b/Mage.Sets/src/mage/sets/eldritchmoon/WaxingMoon.java index 480cb9fa307..3a7e5d9f774 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/WaxingMoon.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/WaxingMoon.java @@ -28,6 +28,7 @@ package mage.sets.eldritchmoon; import java.util.UUID; +import mage.abilities.effects.Effect; import mage.abilities.effects.common.TransformTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.keyword.TrampleAbility; @@ -60,8 +61,11 @@ public class WaxingMoon extends CardImpl { this.expansionSetCode = "EMN"; // Transform up to one target Werewolf you control. - this.getSpellAbility().addEffect(new TransformTargetEffect(false)); + Effect effect = new TransformTargetEffect(false); + effect.setText("Transform up to one target Werewolf you control"); + this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1, filter, false)); + // Creatures you control gain trample until end of turn. this.getSpellAbility().addEffect(new GainAbilityAllEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, new FilterControlledCreaturePermanent(), "Creatures you control gain trample until end of turn")); } diff --git a/Mage.Sets/src/mage/sets/gatecrash/GideonChampionOfJustice.java b/Mage.Sets/src/mage/sets/gatecrash/GideonChampionOfJustice.java index debb217f509..ca21e65092f 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/GideonChampionOfJustice.java +++ b/Mage.Sets/src/mage/sets/gatecrash/GideonChampionOfJustice.java @@ -32,13 +32,12 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; -import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.LockedInDynamicValue; import mage.abilities.dynamicvalue.common.CountersCount; import mage.abilities.dynamicvalue.common.PermanentsTargetOpponentControlsCount; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.PreventAllDamageToSourceEffect; import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; -import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.IndestructibleAbility; import mage.cards.CardImpl; @@ -46,7 +45,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; -import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; @@ -74,7 +72,9 @@ public class GideonChampionOfJustice extends CardImpl { this.addAbility(ability1); // 0: Until end of turn, Gideon becomes an indestructible Human Soldier creature with power and toughness each equal to the number of loyalty counters on him. He's still a planeswalker. Prevent all damage that would be dealt to him this turn. - LoyaltyAbility ability2 = new LoyaltyAbility(new BecomesCreatureSourceEffect(new GideonChampionOfJusticeToken(), "planeswalker", Duration.EndOfTurn), 0); + LockedInDynamicValue loyaltyCount = new LockedInDynamicValue(new CountersCount(CounterType.LOYALTY)); + LoyaltyAbility ability2 = new LoyaltyAbility(new BecomesCreatureSourceEffect( + new GideonChampionOfJusticeToken(), "planeswalker", Duration.EndOfTurn, false, false, loyaltyCount, loyaltyCount), 0); ability2.addEffect(new PreventAllDamageToSourceEffect(Duration.EndOfTurn)); this.addAbility(ability2); @@ -131,7 +131,7 @@ class GideonChampionOfJusticeToken extends Token { toughness = new MageInt(0); this.addAbility(IndestructibleAbility.getInstance()); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(new CountersCount(CounterType.LOYALTY), Duration.WhileOnBattlefield))); + } } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DevilsPlayground.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DevilsPlayground.java index 6f5bee35e38..6c04fd98126 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DevilsPlayground.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DevilsPlayground.java @@ -42,7 +42,7 @@ import mage.game.permanent.token.DevilToken; public class DevilsPlayground extends CardImpl { public DevilsPlayground(UUID ownerId) { - super(ownerId, 151, "Devil's Playground", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); + super(ownerId, 151, "Devils' Playground", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); this.expansionSetCode = "SOI"; // Put four 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player." diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java new file mode 100644 index 00000000000..22626a912a5 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.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 org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class EscalateTest extends CardTestPlayerBase { + + @Test + public void testUseOneMode() { + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // Escalate {1} (Pay this cost for each mode chosen beyond the first.) + // Choose one or more — + // Creatures target player controls gain trample until end of turn. + // Savage Alliance deals 2 damage to target creature; + // Savage Alliance deals 1 damage to each creature target opponent controls. + addCard(Zone.HAND, playerA, "Savage Alliance"); // Instant {2}{R} + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Savage Alliance", "mode=2Silvercoat Lion"); + setModeChoice(playerA, "2"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + assertGraveyardCount(playerA, "Savage Alliance", 1); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/MindsDilationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/MindsDilationTest.java new file mode 100644 index 00000000000..5088a35ffdd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/MindsDilationTest.java @@ -0,0 +1,72 @@ +/* + * 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.enchantments; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class MindsDilationTest extends CardTestPlayerBase { + + @Test + public void testExileNonLandCardAndCastIt() { + + /** + * Mind's Dilation {5}{U}{U} Enchantment Whenever an opponent casts his + * or her first spell each turn, that player exiles the top card of his + * or her library. If it's a nonland card, you may cast it without + * paying its mana cost. + */ + addCard(Zone.BATTLEFIELD, playerB, "Mind's Dilation", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.LIBRARY, playerA, "Divination", 1); // draw 2 cards + + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + setChoice(playerB, "Yes"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 17); + assertExileCount("Divination", 0); + assertHandCount(playerB, 2); // free divination! + } + + @Test + public void testExileNonLandCardDontCastIt() { + + removeAllCardsFromLibrary(playerA); + + /** + * Mind's Dilation {5}{U}{U} Enchantment Whenever an opponent casts his + * or her first spell each turn, that player exiles the top card of his + * or her library. If it's a nonland card, you may cast it without + * paying its mana cost. + */ + addCard(Zone.BATTLEFIELD, playerB, "Mind's Dilation", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.LIBRARY, playerA, "Divination", 1); // draw 2 cards + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + setChoice(playerB, "No"); // no, I don't want my free 2 cards + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 17); + assertExileCount("Divination", 1); + assertHandCount(playerB, 0); // Divination never cast + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/GideonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/GideonTest.java index 850f102ffb5..ef3fe0da492 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/GideonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/GideonTest.java @@ -76,4 +76,43 @@ public class GideonTest extends CardTestPlayerBase { assertAbility(playerB, "Silvercoat Lion", IndestructibleAbility.getInstance(), false); } + /* + * Reported bug: When Gideon, Champion of Justice uses his +0 ability to become a creature, + * he is immediately sent to the grave instead. + */ + @Test + public void testGideonChampionOfJusticeSecondAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + /* + Gideon, Champion of Justice {2}{W}{W} - 4 Loyalty + +1: Put a loyalty counter on Gideon, Champion of Justice for each creature target opponent controls. + + 0: Until end of turn, Gideon, Champion of Justice becomes a Human Soldier creature with power and toughness + each equal to the number of loyalty counters on him and gains indestructible. He's still a planeswalker. + Prevent all damage that would be dealt to him this turn. + LoyaltyAbility ability1 = new LoyaltyAbility( + + -15: Exile all other permanents. + */ + addCard(Zone.HAND, playerA, "Gideon, Champion of Justice", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gideon, Champion of Justice"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+0: Until end of turn"); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Put a loyalty counter on", playerB); + + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "+0: Until end of turn"); + + setStopAt(5, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Gideon, Champion of Justice", 0); + assertPermanentCount(playerA, "Gideon, Champion of Justice", 1); + assertCounterCount(playerA, "Gideon, Champion of Justice", CounterType.LOYALTY, 7); + assertPowerToughness(playerA, "Gideon, Champion of Justice", 7, 7); + + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java index a22a34506d8..bc7b5689323 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/TamiyoTest.java @@ -29,7 +29,6 @@ package org.mage.test.cards.planeswalker; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -40,120 +39,120 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class TamiyoTest extends CardTestPlayerBase { /* - * Reported bug: I activated Tamiyo's +1 ability on a 5/5 Gideon and his 2/2 Knight Ally, but when they both attacked + * Reported bug: I activated Tamiyo's +1 ability on a 5/5 Gideon and his 2/2 Knight Ally, but when they both attacked * and dealt damage I only drew one card when I'm pretty sure I was supposed to draw for each of the two. */ @Test public void testFieldResearcherFirstEffectOnGideon() { - + // Tamiyo, Field Researcher {1}{G}{W}{U} - 4 loyalty - // +1: Choose up to two target creatures. Until your next turn, + // +1: Choose up to two target creatures. Until your next turn, // whenever either of those creatures deals combat damage, you draw a card. addCard(Zone.BATTLEFIELD, playerA, "Tamiyo, Field Researcher", 1); - + /* Gideon, Ally of Zendikar {2}{W}{W} - 4 loyalty - * +1: Until end of turn, Gideon, Ally of Zendikar becomes a 5/5 Human Soldier Ally creature with indestructible + * +1: Until end of turn, Gideon, Ally of Zendikar becomes a 5/5 Human Soldier Ally creature with indestructible * that's still a planeswalker. Prevent all damage that would be dealt to him this turn. * 0: Put a 2/2 white Knight Ally creature token onto the battlefield. **/ addCard(Zone.BATTLEFIELD, playerA, "Gideon, Ally of Zendikar", 1); - + // put 2/2 knight ally token on battlefield activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+0: Put a"); - + // next, activate Gideon to make him a 5/5 human soldier ally creature activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Until end of turn"); // finally, use Tamiyo +1 on both creatures activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Knight Ally^Gideon, Ally of Zendikar"); // both token and Gideon as creature - + // attack with both unblocked - attack(3, playerA, "Knight Ally"); + attack(3, playerA, "Knight Ally"); attack(3, playerA, "Gideon, Ally of Zendikar"); - + setStopAt(3, PhaseStep.END_COMBAT); - execute(); - + execute(); + assertLife(playerB, 13); // 5 + 2 damage, 20 - 7 = 13 assertPermanentCount(playerA, "Tamiyo, Field Researcher", 1); assertPermanentCount(playerA, "Gideon, Ally of Zendikar", 1); assertPermanentCount(playerA, "Knight Ally", 1); assertHandCount(playerA, 3); // two cards drawn from each creature dealing damage + 1 card drawn on turn } - + /* * Testing more basic scenario with Tamiyo, Field of Researcher +1 effect */ @Test public void testFieldResearcherFirstEffectSimpleCreatureAttacks() { - + // Tamiyo, Field Researcher {1}{G}{W}{U} - 4 loyalty - // +1: Choose up to two target creatures. Until your next turn, + // +1: Choose up to two target creatures. Until your next turn, // whenever either of those creatures deals combat damage, you draw a card. addCard(Zone.BATTLEFIELD, playerA, "Tamiyo, Field Researcher", 1); - + addCard(Zone.BATTLEFIELD, playerA, "Bronze Sable", 1); // 2/1 activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); - + attack(1, playerA, "Bronze Sable"); - + setStopAt(1, PhaseStep.END_COMBAT); execute(); - + assertLife(playerB, 18); assertHandCount(playerA, 1); } - + /* * Testing more basic scenario with Tamiyo, Field of Researcher +1 effect */ @Test public void testFieldResearcherFirstEffectSimpleCreaturesAttacks() { - + // Tamiyo, Field Researcher {1}{G}{W}{U} - 4 loyalty - // +1: Choose up to two target creatures. Until your next turn, + // +1: Choose up to two target creatures. Until your next turn, // whenever either of those creatures deals combat damage, you draw a card. addCard(Zone.BATTLEFIELD, playerA, "Tamiyo, Field Researcher", 1); - + addCard(Zone.BATTLEFIELD, playerA, "Bronze Sable", 1); // 2/1 addCard(Zone.BATTLEFIELD, playerA, "Sylvan Advocate", 1); // 2/3 activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable^Sylvan Advocate"); - + attack(1, playerA, "Bronze Sable"); attack(1, playerA, "Sylvan Advocate"); - + setStopAt(1, PhaseStep.END_COMBAT); execute(); - + assertLife(playerB, 16); assertHandCount(playerA, 2); } - + /* * Testing more basic scenarios with Tamiyo, Field of Researcher +1 effect */ @Test public void testFieldResearcherFirstEffectAttackAndBlock() { - + // Tamiyo, Field Researcher {1}{G}{W}{U} - 4 loyalty - // +1: Choose up to two target creatures. Until your next turn, + // +1: Choose up to two target creatures. Until your next turn, // whenever either of those creatures deals combat damage, you draw a card. addCard(Zone.BATTLEFIELD, playerA, "Tamiyo, Field Researcher", 1); - + addCard(Zone.BATTLEFIELD, playerA, "Sylvan Advocate", 1); // 2/3 addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Sylvan Advocate"); - + attack(1, playerA, "Sylvan Advocate"); attack(2, playerB, "Memnite"); block(2, playerA, "Sylvan Advocate", "Memnite"); - + setStopAt(2, PhaseStep.END_COMBAT); execute(); - + assertLife(playerB, 18); assertHandCount(playerA, 2); // Sylvan Advocate dealt combat damage twice } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/KalitasTraitorOfGhetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/KalitasTraitorOfGhetTest.java new file mode 100644 index 00000000000..5191c1ae612 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/KalitasTraitorOfGhetTest.java @@ -0,0 +1,55 @@ +/* + * 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.replacement; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class KalitasTraitorOfGhetTest extends CardTestPlayerBase { + + /* + * Reported bug: Damnation with Kalitas, Traitor of Ghet on my side and 3 opponent creatures, it only exiled 1 creature giving me only 1 zombie instead of 3. + */ + @Test + public void testDamnation() { + + /* + Kalitas, Traitor of Ghet {2}{B}{B} 3/4 lifelink - Legendary Vampire + If a nontoken creature an opponent controls would die, instead exile that card and put a 2/2 black Zombie creature token onto the battlefield. + */ + addCard(Zone.BATTLEFIELD, playerA, "Kalitas, Traitor of Ghet", 1); + /* + Damnation {2}{B}{B} - Sorcery + Destroy all creatures. They can't be regenerated. + */ + addCard(Zone.HAND, playerA, "Damnation", 1); + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); + addCard(Zone.BATTLEFIELD, playerB, "Wall of Roots", 1); + addCard(Zone.BATTLEFIELD, playerB, "Sigiled Starfish", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Damnation"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Kalitas, Traitor of Ghet", 1); + assertGraveyardCount(playerA, "Damnation", 1); + assertExileCount("Bronze Sable", 1); + assertExileCount("Wall of Roots", 1); + assertExileCount("Sigiled Starfish", 1); + assertGraveyardCount(playerB, 0); // all 3 creatures of playerB should be exiled not in graveyard + assertExileCount("Kalitas, Traitor of Ghet", 0); // player controlled, not opponent so not exiled + assertPermanentCount(playerA, "Zombie", 3); // 3 tokens generated from exiling 3 opponent's creatures + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/UrzasIncubatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/UrzasIncubatorTest.java new file mode 100644 index 00000000000..86daec971b9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/UrzasIncubatorTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.single; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class UrzasIncubatorTest extends CardTestPlayerBase { + + /* + * Reported bug: Urza's Incubator does not reduce the cost of Eldrazi creatures + */ + @Test + public void testEldraziCostReduction() { + + /* + Urza's Incubator (3) Artifact + As Urza's Incubator enters the battlefield, choose a creature type. + Creature spells of the chosen type cost 2 less to cast. + */ + addCard(Zone.HAND, playerA, "Urza's Incubator", 1); + addCard(Zone.HAND, playerA, "Eldrazi Displacer", 1); // {2}{W} eldrazi 3/3 + addCard(Zone.HAND, playerA, "Eldrazi Mimic", 2); // {2} eldrazi 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Urza's Incubator"); // taps 3 plains + setChoice(playerA, "Eldrazi"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Eldrazi Displacer"); // taps last plains + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Eldrazi Mimic"); // both mimics should be free + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Eldrazi Mimic"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Urza's Incubator", 1); + assertPermanentCount(playerA, "Eldrazi Displacer", 1); + assertPermanentCount(playerA, "Eldrazi Mimic", 2); + } + + /* + * Test to make sure incubator only reduces generic cost. Cards with <> requirement + * still require specific colorless mana to cast. + */ + @Test + public void testEldraziCostReductionWastesRequirement() { + + /* + Urza's Incubator (3) Artifact + As Urza's Incubator enters the battlefield, choose a creature type. + Creature spells of the chosen type cost 2 less to cast. + */ + addCard(Zone.HAND, playerA, "Urza's Incubator", 1); + addCard(Zone.HAND, playerA, "Thought-Knot Seer", 1); // {3}{<>} eldrazi 4/4 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Urza's Incubator"); // taps 3 plains + setChoice(playerA, "Eldrazi"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Thought-Knot Seer"); // 2 plains remaining, but <> required + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Urza's Incubator", 1); + assertPermanentCount(playerA, "Thought-Knot Seer", 0); // should not be able to cast + } +} 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 44ce17cfbb8..97c83236776 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 @@ -574,6 +574,9 @@ public class TestPlayer implements Player { i++; } } + if (modes.getMinModes() <= modes.getSelectedModes().size()) { + return null; + } return computerPlayer.chooseMode(modes, source, game); //To change body of generated methods, choose Tools | Templates. } @@ -1734,7 +1737,7 @@ public class TestPlayer implements Player { public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId, UUID controllerId, Game game) { return computerPlayer.canPaySacrificeCost(permanent, sourceId, controllerId, game); } - + @Override public FilterPermanent getSacrificeCostFilter() { return computerPlayer.getSacrificeCostFilter(); diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/LockedInDynamicValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/LockedInDynamicValue.java new file mode 100644 index 00000000000..81e2a9a1b51 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/LockedInDynamicValue.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.abilities.dynamicvalue; + +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.game.Game; + +/** + * The first calculated value is used as long as the class instance is in use + * + * IMPORTANT: If used the ability / effect that uses a locked in dynamic value + * has to really copy the dnamic value in its copy method (not reference) + * + * @author LevelX2 + */ +public class LockedInDynamicValue implements DynamicValue { + + private boolean valueChecked = false; + private int lockedInValue; + private final DynamicValue basicDynamicValue; + + public LockedInDynamicValue(DynamicValue dynamicValue) { + this.basicDynamicValue = dynamicValue; + } + + public LockedInDynamicValue(LockedInDynamicValue dynamicValue, final boolean copy) { + this.basicDynamicValue = dynamicValue.basicDynamicValue; + this.lockedInValue = dynamicValue.lockedInValue; + this.valueChecked = dynamicValue.valueChecked; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + if (!valueChecked) { + lockedInValue = basicDynamicValue.calculate(game, sourceAbility, effect); + valueChecked = true; + } + return lockedInValue; + } + + @Override + public LockedInDynamicValue copy() { + return new LockedInDynamicValue(this, true); + } + + @Override + public String getMessage() { + return basicDynamicValue.getMessage(); + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java index 54df18dd219..f59773e1e4e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java @@ -159,7 +159,7 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR return staticText; } if (targetName != null && targetName.length() > 0) { - if (targetName.equals("Those creatures")) { + if (targetName.equals("Those creatures") || targetName.equals("They")) { return targetName + " don't untap during their controller's next untap step"; } else return targetName + " doesn't untap during its controller's next untap step"; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java index c8606d07b8b..99399d7c278 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java @@ -27,9 +27,9 @@ */ package mage.abilities.effects.common.continuous; -import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.repository.CardRepository; import mage.constants.CardType; @@ -50,17 +50,25 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl implements protected Token token; protected String type; protected boolean losePreviousTypes; + protected DynamicValue power = null; + protected DynamicValue toughness = null; public BecomesCreatureSourceEffect(Token token, String type, Duration duration) { this(token, type, duration, false, false); } public BecomesCreatureSourceEffect(Token token, String type, Duration duration, boolean losePreviousTypes, boolean characterDefining) { + this(token, type, duration, losePreviousTypes, characterDefining, null, null); + } + + public BecomesCreatureSourceEffect(Token token, String type, Duration duration, boolean losePreviousTypes, boolean characterDefining, DynamicValue power, DynamicValue toughness) { super(duration, Outcome.BecomeCreature); this.characterDefining = characterDefining; this.token = token; this.type = type; this.losePreviousTypes = losePreviousTypes; + this.power = power; + this.toughness = toughness; setText(); } @@ -69,6 +77,12 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl implements this.token = effect.token.copy(); this.type = effect.type; this.losePreviousTypes = effect.losePreviousTypes; + if (effect.power != null) { + this.power = effect.power.copy(); + } + if (effect.toughness != null) { + this.toughness = effect.toughness.copy(); + } } @Override @@ -133,19 +147,21 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl implements case PTChangingEffects_7: if ((sublayer == SubLayer.CharacteristicDefining_7a && isCharacterDefining()) || (sublayer == SubLayer.SetPT_7b && !isCharacterDefining())) { - MageInt power = token.getPower(); - MageInt toughness = token.getToughness(); - if (power != null && toughness != null) { - permanent.getPower().setValue(power.getValue()); - permanent.getToughness().setValue(toughness.getValue()); + if (power != null) { + permanent.getPower().setValue(power.calculate(game, source, this)); + } else if (token.getPower() != null) { + permanent.getPower().setValue(token.getPower().getValue()); + } + if (toughness != null) { + permanent.getToughness().setValue(toughness.calculate(game, source, this)); + } else if (token.getToughness() != null) { + permanent.getToughness().setValue(token.getToughness().getValue()); } } } return true; - } else { - if (duration.equals(Duration.Custom)) { - this.discard(); - } + } else if (duration.equals(Duration.Custom)) { + this.discard(); } return false; } diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index 92d00489275..04885dd42e5 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -24,21 +24,19 @@ * 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.target; -import mage.constants.Zone; -import mage.cards.Card; -import mage.cards.Cards; -import mage.filter.FilterCard; -import mage.game.Game; -import mage.players.Player; - import java.util.HashSet; import java.util.Set; import java.util.UUID; +import mage.cards.Card; +import mage.cards.Cards; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; import mage.game.events.GameEvent; +import mage.players.Player; /** * @@ -87,9 +85,12 @@ public class TargetCard extends TargetObject { @Override public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { int possibleTargets = 0; - for (UUID playerId: game.getState().getPlayersInRange(sourceControllerId, game)) { + for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); if (player != null) { + if (this.minNumberOfTargets == 0) { + return true; + } switch (zone) { case HAND: for (Card card : player.getHand().getCards(filter, sourceId, sourceControllerId, game)) { @@ -200,7 +201,7 @@ public class TargetCard extends TargetObject { public Set possibleTargets(UUID sourceControllerId, Cards cards, Game game) { Set possibleTargets = new HashSet<>(); - for (Card card: cards.getCards(filter, game)) { + for (Card card : cards.getCards(filter, game)) { possibleTargets.add(card.getId()); } return possibleTargets; diff --git a/Mage/src/main/java/mage/target/Targets.java b/Mage/src/main/java/mage/target/Targets.java index c29f1932d90..be2bbe1d6e8 100644 --- a/Mage/src/main/java/mage/target/Targets.java +++ b/Mage/src/main/java/mage/target/Targets.java @@ -121,7 +121,7 @@ public class Targets extends ArrayList { } } // it is legal when either there is no target or not all targets are illegal - return this.size() == 0 || this.size() != illegalCount; + return this.isEmpty() || this.size() != illegalCount; } /** diff --git a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java index eba50fb268d..de6b3863873 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java @@ -30,10 +30,10 @@ package mage.target.common; import java.util.HashSet; import java.util.Set; import java.util.UUID; -import mage.constants.Zone; import mage.abilities.Ability; import mage.cards.Card; import mage.cards.Cards; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.game.events.GameEvent; @@ -122,10 +122,7 @@ public class TargetCardInYourGraveyard extends TargetCard { */ @Override public boolean canChoose(UUID sourceControllerId, Game game) { - if (game.getPlayer(sourceControllerId).getGraveyard().count(filter, game) >= this.minNumberOfTargets) { - return true; - } - return false; + return game.getPlayer(sourceControllerId).getGraveyard().count(filter, game) >= this.minNumberOfTargets; } @Override diff --git a/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java b/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java index a175971151e..f0309d0ff89 100644 --- a/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java @@ -32,6 +32,7 @@ public class SpellsCastWatcher extends Watcher { public SpellsCastWatcher(final SpellsCastWatcher watcher) { super(watcher); + this.spellsCast.putAll(watcher.spellsCast); } @Override