diff --git a/Mage.Sets/src/mage/cards/a/AeonEngine.java b/Mage.Sets/src/mage/cards/a/AeonEngine.java new file mode 100644 index 0000000000..4bd58b111a --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AeonEngine.java @@ -0,0 +1,65 @@ + +package mage.cards.a; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.GameState; +import mage.players.PlayerList; + +/** + * + * @author azra1l + */ +public final class AeonEngine extends CardImpl { + public AeonEngine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{5}"); + + // Aeon Engine enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // {T}, Exile Aeon Engine: Reverse the game’s turn order. (For example, if play had proceeded clockwise around the table, it now goes counterclockwise.) + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AeonEngineEffect(), new TapSourceCost()); + ability.addCost(new ExileSourceCost()); + this.addAbility(ability); + } + + public AeonEngine(final AeonEngine card) { + super(card); + } + + @Override + public AeonEngine copy() { + return new AeonEngine(this); + } +} + +class AeonEngineEffect extends OneShotEffect { + public AeonEngineEffect() { + super(Outcome.Benefit); + this.staticText = "Reverse the game turn order."; + } + + public AeonEngineEffect(final AeonEngineEffect effect) { + super(effect); + } + + public AeonEngineEffect copy() { + return new AeonEngineEffect(this); + } + + public boolean apply(Game game, Ability source) { + game.getState().setReverseTurnOrder(true); + return true; + + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Commander2019Edition.java b/Mage.Sets/src/mage/sets/Commander2019Edition.java index 3b82f0ab89..c7d3ed50c7 100644 --- a/Mage.Sets/src/mage/sets/Commander2019Edition.java +++ b/Mage.Sets/src/mage/sets/Commander2019Edition.java @@ -20,6 +20,7 @@ public final class Commander2019Edition extends ExpansionSet { this.blockName = "Command Zone"; cards.add(new SetCardInfo("Ainok Survivalist", 156, Rarity.COMMON, mage.cards.a.AinokSurvivalist.class)); + cards.add(new SetCardInfo("Aeon Engine", 52, Rarity.RARE, mage.cards.a.AeonEngine.class)); cards.add(new SetCardInfo("Akoum Refuge", 226, Rarity.UNCOMMON, mage.cards.a.AkoumRefuge.class)); cards.add(new SetCardInfo("Alchemist's Greeting", 133, Rarity.COMMON, mage.cards.a.AlchemistsGreeting.class)); cards.add(new SetCardInfo("Angel of Sanctions", 61, Rarity.MYTHIC, mage.cards.a.AngelOfSanctions.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/AeonEngineTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/AeonEngineTest.java new file mode 100644 index 0000000000..72f333d47e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c19/AeonEngineTest.java @@ -0,0 +1,145 @@ +package org.mage.test.cards.single.c19; + +import java.io.FileNotFoundException; +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import mage.game.mulligan.MulliganType; +import mage.players.Player; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestMultiPlayerBase; + +/** + * + * @author azra1l + */ +public class AeonEngineTest extends CardTestMultiPlayerBase { + @Override + protected Game createNewGameAndPlayers() throws GameException { + Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } + + @Test + public void testEnterTappedNormalTurnOrder() { + // Aeon Engine - Artefact - {5} + // Aeon Engine enters the battlefield tapped. + // {T}, Exile Aeon Engine: Reverse the game’s turn order. (For example, if play had proceeded clockwise around the table, it now goes counterclockwise.) + addCard(Zone.HAND, playerA, "Aeon Engine", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.HAND, playerD, "Agonizing Syphon", 1); + addCard(Zone.BATTLEFIELD, playerD, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aeon Engine"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Agonizing Syphon", playerA); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + //check if aeon engine is tapped + assertTapped("Aeon Engine", true); + + //check if turn was passed to correct player - should be D + assertActivePlayer(playerD); + assertLife(playerA, 17); + assertLife(playerD, 23); + } + + @Test + public void testExileCostReversedTurnOrder() throws GameException, FileNotFoundException { + // Aeon Engine - Artefact - {5} + // Aeon Engine enters the battlefield tapped. + // {T}, Exile Aeon Engine: Reverse the game’s turn order. (For example, if play had proceeded clockwise around the table, it now goes counterclockwise.) + addCard(Zone.HAND, playerB, "Agonizing Syphon", 3); + addCard(Zone.HAND, playerA, "Aeon Engine", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.HAND, playerB, "Agonizing Syphon", 3); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); + addCard(Zone.HAND, playerC, "Agonizing Syphon", 3); + addCard(Zone.BATTLEFIELD, playerC, "Swamp", 5); + addCard(Zone.HAND, playerD, "Agonizing Syphon", 3); + addCard(Zone.BATTLEFIELD, playerD, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aeon Engine"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Agonizing Syphon", playerA); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerC, "Agonizing Syphon", playerA); + castSpell(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Agonizing Syphon", playerA); + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Exile {this}:"); + castSpell(6, PhaseStep.PRECOMBAT_MAIN, playerB, "Agonizing Syphon", playerA); + + setStrictChooseMode(true); + setStopAt(6, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + //check if aeon engine has been exiled + assertExileCount(playerA, "Aeon Engine", 1); + + //check if turn was passed to correct player each turn - should be B + assertActivePlayer(playerB); + assertLife(playerA, 8); + assertLife(playerB, 26); + assertLife(playerC, 23); + assertLife(playerD, 23); + assertGraveyardCount(playerB, "Agonizing Syphon", 2); + assertGraveyardCount(playerC, "Agonizing Syphon", 1); + assertGraveyardCount(playerD, "Agonizing Syphon", 1); + } + + @Test + public void testExileCostReversedTurnOrderDouble() throws GameException, FileNotFoundException { + // Aeon Engine - Artefact - {5} + // Aeon Engine enters the battlefield tapped. + // {T}, Exile Aeon Engine: Reverse the game’s turn order. (For example, if play had proceeded clockwise around the table, it now goes counterclockwise.) + addCard(Zone.HAND, playerA, "Agonizing Syphon", 3); + addCard(Zone.HAND, playerA, "Aeon Engine", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.HAND, playerB, "Agonizing Syphon", 3); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); + addCard(Zone.HAND, playerC, "Agonizing Syphon", 3); + addCard(Zone.BATTLEFIELD, playerC, "Swamp", 5); + addCard(Zone.HAND, playerD, "Agonizing Syphon", 3); + addCard(Zone.HAND, playerD, "Aeon Engine", 1); + addCard(Zone.BATTLEFIELD, playerD, "Swamp", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aeon Engine"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Aeon Engine"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerC, "Agonizing Syphon", playerA); + castSpell(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Agonizing Syphon", playerA); + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Exile {this}:"); + castSpell(6, PhaseStep.PRECOMBAT_MAIN, playerB, "Agonizing Syphon", playerA); + castSpell(7, PhaseStep.PRECOMBAT_MAIN, playerC, "Agonizing Syphon", playerA); + activateAbility(8, PhaseStep.PRECOMBAT_MAIN, playerD, "{T}, Exile {this}:"); + castSpell(9, PhaseStep.PRECOMBAT_MAIN, playerC, "Agonizing Syphon", playerA); + + setStrictChooseMode(true); + setStopAt(9, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + //check if aeon engine's have been exiled + assertExileCount(playerA, "Aeon Engine", 1); + assertExileCount(playerD, "Aeon Engine", 1); + + //check if turn was passed to correct player each turn - should be C + assertActivePlayer(playerC); + assertLife(playerA, 5); + assertLife(playerB, 26); + assertLife(playerC, 29); + assertLife(playerD, 20); + assertGraveyardCount(playerB, "Agonizing Syphon", 2); + assertGraveyardCount(playerC, "Agonizing Syphon", 3); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 178c31a8fd..ca7191e97a 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -647,4 +647,6 @@ public interface Game extends MageItem, Serializable, Copyable { void setGameStopped(boolean gameStopped); boolean isGameStopped(); + + boolean isTurnOrderReversed(); } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 13bd3f3ffd..52e4b6658e 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -3769,6 +3769,11 @@ public abstract class GameImpl implements Game { public boolean isGameStopped() { return gameStopped; } + + @Override + public boolean isTurnOrderReversed() { + return state.getReverseTurnOrder(); + } @Override public String toString() { diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 4da2e7772e..3f7067fcdb 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -108,6 +108,7 @@ public class GameState implements Serializable, Copyable { private boolean manaBurn = false; private boolean hasDayNight = false; private boolean isDaytime = true; + private boolean reverseTurnOrder = false; private int applyEffectsCounter; // Upcounting number of each applyEffects execution @@ -1444,9 +1445,21 @@ public class GameState implements Serializable, Copyable { boolean isDaytime() { return isDaytime; } - + @Override public String toString() { return CardUtil.getTurnInfo(this); } + + public boolean setReverseTurnOrder(boolean reverse){ + if(this.reverseTurnOrder&&reverse){ + this.reverseTurnOrder = false; + } else { + this.reverseTurnOrder = reverse; + } + return this.reverseTurnOrder; + } + public boolean getReverseTurnOrder(){ + return this.reverseTurnOrder; + } } diff --git a/Mage/src/main/java/mage/players/PlayerList.java b/Mage/src/main/java/mage/players/PlayerList.java index 3a884c16da..6316727b31 100644 --- a/Mage/src/main/java/mage/players/PlayerList.java +++ b/Mage/src/main/java/mage/players/PlayerList.java @@ -47,7 +47,11 @@ public class PlayerList extends CircularList { } Player player; while (true) { - player = game.getPlayer(super.getNext()); + if(game.isTurnOrderReversed()){ + player = game.getPlayer(super.getPrevious()); + } else{ + player = game.getPlayer(super.getNext()); + } if (player.isInGame()) { break; } @@ -89,4 +93,4 @@ public class PlayerList extends CircularList { return new PlayerList(this); } -} \ No newline at end of file +}