diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java index 22427aa16c8..1a0462e4ace 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/EndTurnEffectTest.java @@ -55,6 +55,9 @@ public class EndTurnEffectTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Sphinx's Tutelage"); // Each player shuffles his or her hand and graveyard into his or her library, then draws seven cards. If it's your turn, end the turn. + // (Exile all spells and abilities on the stack, including this card. + // Discard down to your maximum hand size. Damage wears off, and + // "this turn" and "until end of turn" effects end.) addCard(Zone.HAND, playerA, "Day's Undoing"); //Sorcery {2}{U} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Day's Undoing"); @@ -67,8 +70,42 @@ public class EndTurnEffectTest extends CardTestPlayerBase { assertHandCount(playerA, 7); assertHandCount(playerB, 7); - assertGraveyardCount(playerB, 0); // because the trigegrs of Sphinx's Tutelage cease to exist + assertGraveyardCount(playerB, 0); // because the triggers of Sphinx's Tutelage cease to exist } + @Test + public void testSpellSplitCard() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + // End the turn. + // (Exile all spells and abilities on the stack, including this card. + // Discard down to your maximum hand size. Damage wears off, and + // "this turn" and "until end of turn" effects end.) + addCard(Zone.HAND, playerA, "Time Stop"); //Instant {4}{U}{U} + + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // Fire + // Fire deals 2 damage divided as you choose among one or two target creatures and/or players. + // Ice + // Tap target permanent. Draw a card. + addCard(Zone.HAND, playerB, "Fire // Ice"); // Instant {1}{R} // {1}{U} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Ice", "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Time Stop", NO_TARGET, "Ice"); + + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertHandCount(playerB, "Fire // Ice", 0); + assertExileCount(playerA, "Time Stop", 1); + assertExileCount(playerB, "Fire // Ice", 1); + assertTapped("Silvercoat Lion", false); + assertHandCount(playerA, 0); + assertHandCount(playerB, 0); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java index b94797e5a02..4a5409b5547 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java @@ -29,7 +29,6 @@ package org.mage.test.cards.triggers; import mage.constants.PhaseStep; import mage.constants.Zone; -import mage.counters.CounterType; import mage.filter.Filter; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -313,14 +312,14 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { /* * playerA's Carnivorous Plant will get -1/-1 from Noxious Ghoul -> 3/4 * playerB's Carnivorous Plant will get -1/-1 from Noxious Ghoul -> 3/4 - */ + */ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Clone"); setChoice(playerA, "Noxious Ghoul"); /* * playerA's Carnivorous Plant will get -1/-1 from Clone -> 2/3 * playerB's Carnivorous Plant will get -1/-1 from Clone -> 2/3 - */ + */ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Ego Erasure", "targetPlayer=PlayerA", "Whenever"); /* * playerA' Noxious Ghoul will get -2/0 -> 1/3 @@ -329,7 +328,7 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { * playerA' Noxious Ghoul will get -1/-1 from itself -> -1/1 * playerA's Carnivorous Plant will get -1/-1 from Noxious Ghoul -> -1/2 * playerB's Carnivorous Plant will get -1/-1 from Noxious Ghoul -> 1/2 - */ + */ setStopAt(1, PhaseStep.END_TURN); execute(); @@ -342,72 +341,22 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertPowerToughness(playerB, "Carnivorous Plant", 1, 2); assertPowerToughness(playerA, "Carnivorous Plant", -1, 2); } - + @Test public void testHearthcageGiant() { // {6}{R}{R} Creature — Giant Warrior //When Hearthcage Giant enters the battlefield, put two 3/1 red Elemental Shaman creature tokens onto the battlefield. //Sacrifice an Elemental: Target Giant creature gets +3/+1 until end of turn. - addCard(Zone.HAND,playerA,"Hearthcage Giant"); + addCard(Zone.HAND, playerA, "Hearthcage Giant"); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hearthcage Giant"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - + assertPermanentCount(playerA, "Hearthcage Giant", 1); assertPermanentCount(playerA, "Elemental Shaman", 2); assertPowerToughness(playerA, "Elemental Shaman", 3, 1); } - /* - Reported bug: HAngarback interaciton with Hardened Scales and Metallic Mimic on board is incorrect. - */ - @Test - public void hangarBackHardenedScalesMetallicMimicTest() { - - /* - Hangarback Walker {X}{X} - Artifact Creature — Construct 0/0 - Hangarback Walker enters the battlefield with X +1/+1 counters on it. - When Hangarback Walker dies, create a 1/1 colorless Thopter artifact creature token with flying for each +1/+1 counter on Hangarback Walker. - {1}, {T}: Put a +1/+1 counter on Hangarback Walker. - */ - String hWalker = "Hangarback Walker"; - - /* - Hardened Scales {G} - Enchantment - If one or more +1/+1 counters would be placed on a creature you control, that many plus one +1/+1 counters are placed on it instead. - */ - String hScales = "Hardened Scales"; - - /* - Metallic Mimic {2} - Artifact Creature — Shapeshifter 2/1 - As Metallic Mimic enters the battlefield, choose a creature type. - Metallic Mimic is the chosen type in addition to its other types. - Each other creature you control of the chosen type enters the battlefield with an additional +1/+1 counter on it. - */ - String mMimic = "Metallic Mimic"; - - addCard(Zone.BATTLEFIELD, playerA, hScales); - addCard(Zone.HAND, playerA, mMimic); - addCard(Zone.HAND, playerA, hWalker); - addCard(Zone.BATTLEFIELD, playerA, "Wastes", 4); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, mMimic); - setChoice(playerA, "Construct"); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hWalker); - setChoice(playerA, "X=1"); - - setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - assertPermanentCount(playerA, mMimic, 1); - assertPermanentCount(playerA, hWalker, 1); - assertCounterCount(playerA, hWalker, CounterType.P1P1, 3); - assertPowerToughness(playerA, hWalker, 3, 3); - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/step/EndStepTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/step/EndStepTriggerTest.java index c2d1359615f..3aa461c4553 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/step/EndStepTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/step/EndStepTriggerTest.java @@ -70,4 +70,44 @@ public class EndStepTriggerTest extends CardTestPlayerBase { assertCounterCount("Bloodchief Ascension", CounterType.QUEST, 2); } + + /** + * Hey, I don't know how to submit bugs but in a game I played today I + * sacrificed Child of Alara by casting Bound at the end step of my previous + * opponent's turn, then chose Child as one of the cards to return to my + * hand. My graveyard was empty so that was the only card I chose. Child + * returned to my hand but it did NOT trigger for some reason. Nothing was + * destroyed + */ + @Test + public void testSacrificeChildOfAlara() { + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); //Creature + + // Trample + // When Child of Alara dies, destroy all nonland permanents. They can't be regenerated. + addCard(Zone.BATTLEFIELD, playerB, "Icy Manipulator", 1); //Creature + addCard(Zone.BATTLEFIELD, playerB, "Child of Alara", 1); //Creature + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 4); + // Bound + // Sacrifice a creature. Return up to X cards from your graveyard to your hand, where X is the number of colors that creature was. Exile this card. + // Determined + // Other spells you control can't be countered by spells or abilities this turn. + // Draw a card. + addCard(Zone.HAND, playerB, "Bound // Determined"); // Instant {3}{B}{G} // {G}{U} + + castSpell(1, PhaseStep.END_TURN, playerB, "Bound"); + addTarget(playerB, "Child of Alara"); + setChoice(playerB, "Child of Alara"); + + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertExileCount(playerB, "Bound // Determined", 1); + + assertHandCount(playerB, "Child of Alara", 1); + + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Icy Manipulator", 1); + } } diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 9d10f099ba4..6592de44e72 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -27,6 +27,9 @@ */ package mage.abilities; +import java.io.Serializable; +import java.util.List; +import java.util.UUID; import mage.MageObject; import mage.abilities.costs.Cost; import mage.abilities.costs.Costs; @@ -46,11 +49,6 @@ import mage.target.Target; import mage.target.Targets; import mage.watchers.Watcher; -import java.io.Serializable; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - /** * Practically everything in the game is started from an Ability. This interface * describes what an Ability is composed of at the highest level. diff --git a/Mage/src/main/java/mage/abilities/effects/common/EndTurnEffect.java b/Mage/src/main/java/mage/abilities/effects/common/EndTurnEffect.java index 5cb9237e914..5e5fb0b9389 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/EndTurnEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/EndTurnEffect.java @@ -48,7 +48,7 @@ public class EndTurnEffect extends OneShotEffect { if (!game.isSimulation()) { game.informPlayers("The current turn ends"); } - return game.endTurn(); + return game.endTurn(source); } @Override diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 2b838202683..87040399b57 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -27,6 +27,8 @@ */ package mage.game; +import java.io.Serializable; +import java.util.*; import mage.MageItem; import mage.MageObject; import mage.abilities.Ability; @@ -65,9 +67,6 @@ import mage.players.Players; import mage.util.MessageToClient; import mage.util.functions.ApplyToPermanent; -import java.io.Serializable; -import java.util.*; - public interface Game extends MageItem, Serializable { MatchType getGameType(); @@ -393,7 +392,7 @@ public interface Game extends MageItem, Serializable { void playPriority(UUID activePlayerId, boolean resuming); - boolean endTurn(); + boolean endTurn(Ability source); int doAction(MageAction action); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index eb74a20cee0..0fd3405f605 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -27,6 +27,10 @@ */ package mage.game; +import java.io.IOException; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; import mage.MageException; import mage.MageObject; import mage.abilities.*; @@ -92,11 +96,6 @@ import mage.watchers.Watchers; import mage.watchers.common.*; import org.apache.log4j.Logger; -import java.io.IOException; -import java.io.Serializable; -import java.util.*; -import java.util.Map.Entry; - public abstract class GameImpl implements Game, Serializable { private static final int ROLLBACK_TURNS_MAX = 4; @@ -2672,8 +2671,8 @@ public abstract class GameImpl implements Game, Serializable { } @Override - public boolean endTurn() { - getTurn().endTurn(this, getActivePlayerId()); + public boolean endTurn(Ability source) { + getTurn().endTurn(this, getActivePlayerId(), source); return true; } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index b85adf00c44..e71ea49ed98 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -27,6 +27,10 @@ */ package mage.game.stack; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.Mana; @@ -58,11 +62,6 @@ import mage.game.permanent.PermanentCard; import mage.players.Player; import mage.util.GameLog; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.UUID; - /** * * @author BetaSteward_at_googlemail.com @@ -334,8 +333,8 @@ public class Spell extends StackObjImpl implements Card { * determine whether card was kicked or not. E.g. Desolation Angel */ private void updateOptionalCosts(int index) { - spellCards.get(index).getAbilities().get(spellAbilities.get(index).getId()).ifPresent(abilityOrig -> - { + spellCards.get(index).getAbilities().get(spellAbilities.get(index).getId()).ifPresent(abilityOrig + -> { for (Object object : spellAbilities.get(index).getOptionalCosts()) { Cost cost = (Cost) object; for (Cost costOrig : abilityOrig.getOptionalCosts()) { @@ -749,24 +748,7 @@ public class Spell extends StackObjImpl implements Card { @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { - ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.STACK, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - game.getStack().remove(this); - game.rememberLKI(this.getId(), event.getFromZone(), this); - - if (!this.isCopiedSpell()) { - if (exileId == null) { - game.getExile().getPermanentExile().add(this.card); - } else { - game.getExile().createZone(exileId, name).add(this.card); - } - - game.setZone(this.card.getId(), event.getToZone()); - } - game.fireEvent(event); - return event.getToZone() == Zone.EXILED; - } - return false; + return this.card.moveToExile(exileId, name, sourceId, game, appliedEffects); } @Override diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index cd4f6301382..1de664ff84d 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; +import mage.abilities.Ability; import mage.constants.PhaseStep; import mage.constants.TurnPhase; import mage.counters.CounterType; @@ -268,8 +269,9 @@ public class Turn implements Serializable { * * @param game * @param activePlayerId + * @param source */ - public void endTurn(Game game, UUID activePlayerId) { + public void endTurn(Game game, UUID activePlayerId, Ability source) { // Ending the turn this way (Time Stop) means the following things happen in order: setEndTurnRequested(true); @@ -277,9 +279,9 @@ public class Turn implements Serializable { // 1) All spells and abilities on the stack are exiled. This includes Time Stop, though it will continue to resolve. // It also includes spells and abilities that can't be countered. while (!game.getStack().isEmpty()) { - StackObject stackObject = game.getStack().removeLast(); + StackObject stackObject = game.getStack().peekFirst(); if (stackObject instanceof Spell) { - ((Spell) stackObject).moveToExile(null, "", null, game); + ((Spell) stackObject).moveToExile(null, "", source.getSourceId(), game); } } // 2) All attacking and blocking creatures are removed from combat.