From d4792e3665a4b8fd772253404084733fe196758c Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 14 Mar 2021 15:56:48 -0400 Subject: [PATCH] reworked/simplified/consolidated effects which exchange life totals, added test (fixes #7668) --- .../src/mage/cards/a/AxisOfMortality.java | 66 ++-------- .../src/mage/cards/m/MagusOfTheMirror.java | 4 +- .../src/mage/cards/m/MirrorUniverse.java | 4 +- .../src/mage/cards/p/PhyrexianRebirth.java | 27 ++-- .../src/mage/cards/p/ProfaneTransfusion.java | 36 ++---- .../src/mage/cards/p/PsychicTransfer.java | 50 +++----- Mage.Sets/src/mage/cards/s/SoulConduit.java | 62 +--------- .../single/cmr/ProfaneTransfusionTest.java | 117 ++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 5 + .../java/org/mage/test/stub/PlayerStub.java | 4 + .../ExchangeLifeControllerTargetEffect.java | 44 +++++++ .../common/ExchangeLifeTargetEffect.java | 65 ---------- .../common/ExchangeLifeTwoTargetEffect.java | 41 ++++++ .../token/PhyrexianRebirthHorrorToken.java | 12 +- Mage/src/main/java/mage/players/Player.java | 9 +- .../main/java/mage/players/PlayerImpl.java | 12 ++ 16 files changed, 284 insertions(+), 274 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ProfaneTransfusionTest.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeControllerTargetEffect.java delete mode 100644 Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTargetEffect.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTwoTargetEffect.java diff --git a/Mage.Sets/src/mage/cards/a/AxisOfMortality.java b/Mage.Sets/src/mage/cards/a/AxisOfMortality.java index 79df1aedac3..874f47c66f1 100644 --- a/Mage.Sets/src/mage/cards/a/AxisOfMortality.java +++ b/Mage.Sets/src/mage/cards/a/AxisOfMortality.java @@ -1,21 +1,17 @@ - package mage.cards.a; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExchangeLifeTwoTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class AxisOfMortality extends CardImpl { @@ -24,9 +20,10 @@ public final class AxisOfMortality extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{W}{W}"); // At the beginning of your upkeep, you may have two target players exchange life totals. - Ability ability = new BeginningOfUpkeepTriggeredAbility(new AxisOfMortalityEffect(), TargetController.YOU, true); - ability.addTarget(new TargetPlayer()); - ability.addTarget(new TargetPlayer()); + Ability ability = new BeginningOfUpkeepTriggeredAbility( + new ExchangeLifeTwoTargetEffect(), TargetController.YOU, true + ); + ability.addTarget(new TargetPlayer(2)); this.addAbility(ability); } @@ -39,52 +36,3 @@ public final class AxisOfMortality extends CardImpl { return new AxisOfMortality(this); } } - -class AxisOfMortalityEffect extends OneShotEffect { - - public AxisOfMortalityEffect() { - super(Outcome.Neutral); - this.staticText = "two target players exchange life totals"; - } - - public AxisOfMortalityEffect(final AxisOfMortalityEffect effect) { - super(effect); - } - - @Override - public AxisOfMortalityEffect copy() { - return new AxisOfMortalityEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player1 = game.getPlayer(source.getFirstTarget()); - Player player2 = game.getPlayer(source.getTargets().get(1).getFirstTarget()); - if (player1 != null && player2 != null) { - int lifePlayer1 = player1.getLife(); - int lifePlayer2 = player2.getLife(); - - if (lifePlayer1 == lifePlayer2) { - return false; - } - - if (!player1.isLifeTotalCanChange() || !player2.isLifeTotalCanChange()) { - return false; - } - - // 20110930 - 118.7, 118.8 - if (lifePlayer1 < lifePlayer2 && (!player1.isCanGainLife() || !player2.isCanLoseLife())) { - return false; - } - - if (lifePlayer1 > lifePlayer2 && (!player1.isCanLoseLife() || !player2.isCanGainLife())) { - return false; - } - - player1.setLife(lifePlayer2, game, source); - player2.setLife(lifePlayer1, game, source); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheMirror.java b/Mage.Sets/src/mage/cards/m/MagusOfTheMirror.java index a79dd4cec99..3b348e2cbcd 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheMirror.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheMirror.java @@ -8,7 +8,7 @@ import mage.abilities.condition.common.IsStepCondition; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalActivatedAbility; -import mage.abilities.effects.common.ExchangeLifeTargetEffect; +import mage.abilities.effects.common.ExchangeLifeControllerTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,7 +33,7 @@ public final class MagusOfTheMirror extends CardImpl { // {tap}, Sacrifice Magus of the Mirror: Exchange life totals with target opponent. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility( Zone.BATTLEFIELD, - new ExchangeLifeTargetEffect(), + new ExchangeLifeControllerTargetEffect(), new TapSourceCost(), new IsStepCondition(PhaseStep.UPKEEP), null); diff --git a/Mage.Sets/src/mage/cards/m/MirrorUniverse.java b/Mage.Sets/src/mage/cards/m/MirrorUniverse.java index 16ebdd6dff5..9766fb0b84a 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorUniverse.java +++ b/Mage.Sets/src/mage/cards/m/MirrorUniverse.java @@ -7,7 +7,7 @@ import mage.abilities.condition.common.IsStepCondition; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalActivatedAbility; -import mage.abilities.effects.common.ExchangeLifeTargetEffect; +import mage.abilities.effects.common.ExchangeLifeControllerTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -27,7 +27,7 @@ public final class MirrorUniverse extends CardImpl { // {tap}, Sacrifice Mirror Universe: Exchange life totals with target opponent. Activate this ability only during your upkeep. Ability ability = new ConditionalActivatedAbility( Zone.BATTLEFIELD, - new ExchangeLifeTargetEffect(), + new ExchangeLifeControllerTargetEffect(), new TapSourceCost(), new IsStepCondition(PhaseStep.UPKEEP), null); diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianRebirth.java b/Mage.Sets/src/mage/cards/p/PhyrexianRebirth.java index 35bea5d9633..22bd1af0a12 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianRebirth.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianRebirth.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -13,8 +11,9 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.PhyrexianRebirthHorrorToken; +import java.util.UUID; + /** - * * @author ayratn */ public final class PhyrexianRebirth extends CardImpl { @@ -35,27 +34,29 @@ public final class PhyrexianRebirth extends CardImpl { return new PhyrexianRebirth(this); } - class PhyrexianRebirthEffect extends OneShotEffect { + private static final class PhyrexianRebirthEffect extends OneShotEffect { - public PhyrexianRebirthEffect() { + private PhyrexianRebirthEffect() { super(Outcome.DestroyPermanent); - staticText = "Destroy all creatures, then create an X/X colorless Horror artifact creature token, where X is the number of creatures destroyed this way"; + staticText = "Destroy all creatures, then create an X/X colorless Horror artifact creature token, " + + "where X is the number of creatures destroyed this way"; } - public PhyrexianRebirthEffect(PhyrexianRebirthEffect ability) { + private PhyrexianRebirthEffect(PhyrexianRebirthEffect ability) { super(ability); } @Override public boolean apply(Game game, Ability source) { int count = 0; - for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), game + )) { count += permanent.destroy(source, game, false) ? 1 : 0; } - PhyrexianRebirthHorrorToken horrorToken = new PhyrexianRebirthHorrorToken(); - horrorToken.getPower().modifyBaseValue(count); - horrorToken.getToughness().modifyBaseValue(count); - horrorToken.putOntoBattlefield(1, game, source, source.getControllerId()); + new PhyrexianRebirthHorrorToken(count, count).putOntoBattlefield( + 1, game, source, source.getControllerId() + ); return true; } @@ -63,7 +64,5 @@ public final class PhyrexianRebirth extends CardImpl { public PhyrexianRebirthEffect copy() { return new PhyrexianRebirthEffect(this); } - } - } diff --git a/Mage.Sets/src/mage/cards/p/ProfaneTransfusion.java b/Mage.Sets/src/mage/cards/p/ProfaneTransfusion.java index 30261eef5fc..74134d9e0ef 100644 --- a/Mage.Sets/src/mage/cards/p/ProfaneTransfusion.java +++ b/Mage.Sets/src/mage/cards/p/ProfaneTransfusion.java @@ -2,13 +2,13 @@ package mage.cards.p; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExchangeLifeTwoTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.token.PhyrexianRebirthHorrorToken; -import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.TargetPlayer; @@ -23,6 +23,7 @@ public final class ProfaneTransfusion extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{6}{B}{B}{B}"); // Two target players exchange life totals. You create an X/X colorless Horror artifact creature token, where X is the difference between those players' life totals. + this.getSpellAbility().addEffect(new ExchangeLifeTwoTargetEffect()); this.getSpellAbility().addEffect(new ProfaneTransfusionEffect()); this.getSpellAbility().addTarget(new TargetPlayer(2)); } @@ -41,8 +42,7 @@ class ProfaneTransfusionEffect extends OneShotEffect { ProfaneTransfusionEffect() { super(Outcome.Benefit); - staticText = "two target players exchange life totals. " + - "You create an X/X colorless Horror artifact creature token, " + + staticText = "You create an X/X colorless Horror artifact creature token, " + "where X is the difference between those players' life totals"; } @@ -65,30 +65,8 @@ class ProfaneTransfusionEffect extends OneShotEffect { if (player1 == null || player2 == null) { return false; } - int lifePlayer1 = player1.getLife(); - int lifePlayer2 = player2.getLife(); - int lifeDifference = Math.abs(lifePlayer1 - lifePlayer2); - - Token token = new PhyrexianRebirthHorrorToken(); - token.setPower(lifeDifference); - token.setToughness(lifeDifference); - - if (lifeDifference == 0 - || !player1.isLifeTotalCanChange() - || !player2.isLifeTotalCanChange()) { - return token.putOntoBattlefield(1, game, source, source.getControllerId()); - } - - if (lifePlayer1 < lifePlayer2 && (!player1.isCanGainLife() || !player2.isCanLoseLife())) { - return token.putOntoBattlefield(1, game, source, source.getControllerId()); - } - - if (lifePlayer1 > lifePlayer2 && (!player1.isCanLoseLife() || !player2.isCanGainLife())) { - return token.putOntoBattlefield(1, game, source, source.getControllerId()); - } - - player1.setLife(lifePlayer2, game, source); - player2.setLife(lifePlayer1, game, source); - return token.putOntoBattlefield(1, game, source, source.getControllerId()); + int lifeDifference = Math.abs(player1.getLife() - player2.getLife()); + return new PhyrexianRebirthHorrorToken(lifeDifference, lifeDifference) + .putOntoBattlefield(1, game, source, source.getControllerId()); } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/p/PsychicTransfer.java b/Mage.Sets/src/mage/cards/p/PsychicTransfer.java index 5313f27d26d..af63bc2732b 100644 --- a/Mage.Sets/src/mage/cards/p/PsychicTransfer.java +++ b/Mage.Sets/src/mage/cards/p/PsychicTransfer.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -12,15 +10,15 @@ import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author Quercitron */ public final class PsychicTransfer extends CardImpl { public PsychicTransfer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{U}"); // If the difference between your life total and target player's life total is 5 or less, exchange life totals with that player. this.getSpellAbility().addEffect(new PsychicTransferEffect()); @@ -37,15 +35,15 @@ public final class PsychicTransfer extends CardImpl { } } -class PsychicTransferEffect extends OneShotEffect -{ +class PsychicTransferEffect extends OneShotEffect { - public PsychicTransferEffect() { + PsychicTransferEffect() { super(Outcome.Neutral); - this.staticText = "If the difference between your life total and target player's life total is 5 or less, exchange life totals with that player"; + this.staticText = "If the difference between your life total and target player's " + + "life total is 5 or less, exchange life totals with that player"; } - public PsychicTransferEffect(final PsychicTransferEffect effect) { + private PsychicTransferEffect(final PsychicTransferEffect effect) { super(effect); } @@ -57,32 +55,12 @@ class PsychicTransferEffect extends OneShotEffect @Override public boolean apply(Game game, Ability source) { Player sourcePlayer = game.getPlayer(source.getControllerId()); - Player targetPlayer = game.getPlayer(source.getTargets().getFirstTarget()); - if (sourcePlayer != null && targetPlayer != null) { - int lifePlayer1 = sourcePlayer.getLife(); - int lifePlayer2 = targetPlayer.getLife(); - - if (Math.abs(lifePlayer1 - lifePlayer2) > 5) { - return false; - } - - if (lifePlayer1 == lifePlayer2) { - return false; - } - - // 20110930 - 118.7, 118.8 - if (lifePlayer1 < lifePlayer2 && (!sourcePlayer.isCanGainLife() || !targetPlayer.isCanLoseLife())) { - return false; - } - - if (lifePlayer1 > lifePlayer2 && (!sourcePlayer.isCanLoseLife() || !targetPlayer.isCanGainLife())) { - return false; - } - - sourcePlayer.setLife(lifePlayer2, game, source); - targetPlayer.setLife(lifePlayer1, game, source); - return true; + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + if (sourcePlayer == null || targetPlayer == null + || Math.abs(sourcePlayer.getLife() - targetPlayer.getLife()) > 5) { + return false; } - return false; + sourcePlayer.exchangeLife(targetPlayer, source, game); + return true; } } diff --git a/Mage.Sets/src/mage/cards/s/SoulConduit.java b/Mage.Sets/src/mage/cards/s/SoulConduit.java index 06c16bc431c..606d83caae3 100644 --- a/Mage.Sets/src/mage/cards/s/SoulConduit.java +++ b/Mage.Sets/src/mage/cards/s/SoulConduit.java @@ -1,23 +1,18 @@ - package mage.cards.s; -import java.util.UUID; 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.OneShotEffect; +import mage.abilities.effects.common.ExchangeLifeTwoTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author North */ public final class SoulConduit extends CardImpl { @@ -26,7 +21,7 @@ public final class SoulConduit extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}"); // {6}, {tap}: Two target players exchange life totals. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new SoulConduitEffect(), new GenericManaCost(6)); + Ability ability = new SimpleActivatedAbility(new ExchangeLifeTwoTargetEffect(), new GenericManaCost(6)); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetPlayer(2)); this.addAbility(ability); @@ -41,52 +36,3 @@ public final class SoulConduit extends CardImpl { return new SoulConduit(this); } } - -class SoulConduitEffect extends OneShotEffect { - - public SoulConduitEffect() { - super(Outcome.Neutral); - this.staticText = "Two target players exchange life totals"; - } - - public SoulConduitEffect(final SoulConduitEffect effect) { - super(effect); - } - - @Override - public SoulConduitEffect copy() { - return new SoulConduitEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player1 = game.getPlayer(source.getTargets().get(0).getTargets().get(0)); - Player player2 = game.getPlayer(source.getTargets().get(0).getTargets().get(1)); - if (player1 != null && player2 != null) { - int lifePlayer1 = player1.getLife(); - int lifePlayer2 = player2.getLife(); - - if (lifePlayer1 == lifePlayer2) { - return false; - } - - if (!player1.isLifeTotalCanChange() || !player2.isLifeTotalCanChange()) { - return false; - } - - // 20110930 - 118.7, 118.8 - if (lifePlayer1 < lifePlayer2 && (!player1.isCanGainLife() || !player2.isCanLoseLife())) { - return false; - } - - if (lifePlayer1 > lifePlayer2 && (!player1.isCanLoseLife() || !player2.isCanGainLife())) { - return false; - } - - player1.setLife(lifePlayer2, game, source); - player2.setLife(lifePlayer1, game, source); - return true; - } - return false; - } -} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ProfaneTransfusionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ProfaneTransfusionTest.java new file mode 100644 index 00000000000..74bf1b3060c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/ProfaneTransfusionTest.java @@ -0,0 +1,117 @@ +package org.mage.test.cards.single.cmr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class ProfaneTransfusionTest extends CardTestPlayerBase { + + private static final String transfusion = "Profane Transfusion"; + private static final String emperion = "Platinum Emperion"; + private static final String reflection = "Boon Reflection"; + private static final String skullcrack = "Skullcrack"; + + @Test + public void testRegular() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 9); + addCard(Zone.HAND, playerA, transfusion); + + setLife(playerA, 24); + setLife(playerB, 16); + + addTarget(playerA, playerA); + addTarget(playerA, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, transfusion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 16); + assertLife(playerB, 24); + assertPermanentCount(playerA, "Horror", 1); + assertPowerToughness(playerA, "Horror", 24 - 16, 24 - 16); + assertGraveyardCount(playerA, transfusion, 1); + } + + @Test + public void testCantChange() { + // Platinum Emperion stops life totals from changing but token is still created + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 9); + addCard(Zone.BATTLEFIELD, playerA, emperion); + addCard(Zone.HAND, playerA, transfusion); + + setLife(playerA, 24); + setLife(playerB, 16); + + addTarget(playerA, playerA); + addTarget(playerA, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, transfusion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 24); + assertLife(playerB, 16); + assertPermanentCount(playerA, "Horror", 1); + assertPowerToughness(playerA, "Horror", 24 - 16, 24 - 16); + assertGraveyardCount(playerA, transfusion, 1); + } + + @Test + public void testDoubleLife() { + // Boon Reflection doubles life gain, which affects final difference + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 9); + addCard(Zone.BATTLEFIELD, playerB, reflection); + addCard(Zone.HAND, playerA, transfusion); + + setLife(playerA, 24); + setLife(playerB, 16); + + addTarget(playerA, playerA); + addTarget(playerA, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, transfusion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 16); + assertLife(playerB, 32); + assertPermanentCount(playerA, "Horror", 1); + assertPowerToughness(playerA, "Horror", 32 - 16, 32 - 16); + assertGraveyardCount(playerA, transfusion, 1); + } + + @Test + public void testCantGainLife() { + // Skullcrack prevents life gain, but final difference should still be 3 + addCard(Zone.BATTLEFIELD, playerA, "Badlands", 11); + addCard(Zone.HAND, playerA, skullcrack); + addCard(Zone.HAND, playerA, transfusion); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, skullcrack, playerB); + addTarget(playerA, playerA); + addTarget(playerA, playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, transfusion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20); + assertLife(playerB, 17); + assertPermanentCount(playerA, "Horror", 1); + assertPowerToughness(playerA, "Horror", 20 - 17, 20 - 17); + assertGraveyardCount(playerA, transfusion, 1); + } +} 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 e7e3e06d14d..cf72df155a8 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 @@ -3164,6 +3164,11 @@ public class TestPlayer implements Player { return computerPlayer.gainLife(amount, game, source); } + @Override + public void exchangeLife(Player player, Ability source, Game game) { + computerPlayer.exchangeLife(player, source, game); + } + @Override public int damage(int damage, UUID attackerId, Ability source, Game game) { return computerPlayer.damage(damage, attackerId, source, game); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index badd131e296..2bb39a3efc3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -137,6 +137,10 @@ public class PlayerStub implements Player { return 0; } + @Override + public void exchangeLife(Player player, Ability source, Game game) { + } + @Override public int damage(int damage, UUID attackerId, Ability source, Game game) { return 0; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeControllerTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeControllerTargetEffect.java new file mode 100644 index 00000000000..843a45dddb4 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeControllerTargetEffect.java @@ -0,0 +1,44 @@ + +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * @author Styxo + */ +public class ExchangeLifeControllerTargetEffect extends OneShotEffect { + + public ExchangeLifeControllerTargetEffect() { + super(Outcome.Neutral); + } + + private ExchangeLifeControllerTargetEffect(final ExchangeLifeControllerTargetEffect effect) { + super(effect); + } + + @Override + public ExchangeLifeControllerTargetEffect copy() { + return new ExchangeLifeControllerTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(source.getFirstTarget()); + if (controller == null || player == null) { + return false; + } + controller.exchangeLife(player, source, game); + return true; + } + + @Override + public String getText(Mode mode) { + return "Exchange life totals with target " + mode.getTargets().get(0).getTargetName(); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTargetEffect.java deleted file mode 100644 index 725072c7dc5..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTargetEffect.java +++ /dev/null @@ -1,65 +0,0 @@ - -package mage.abilities.effects.common; - -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.effects.OneShotEffect; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; - -/** - * - * @author Styxo - */ -public class ExchangeLifeTargetEffect extends OneShotEffect { - - public ExchangeLifeTargetEffect() { - super(Outcome.Neutral); - } - - public ExchangeLifeTargetEffect(final ExchangeLifeTargetEffect effect) { - super(effect); - } - - @Override - public ExchangeLifeTargetEffect copy() { - return new ExchangeLifeTargetEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Player player = game.getPlayer(source.getFirstTarget()); - if (controller != null && player != null) { - int lifeController = controller.getLife(); - int lifePlayer = player.getLife(); - - if (lifeController == lifePlayer) { - return false; - } - - if (!controller.isLifeTotalCanChange() || !player.isLifeTotalCanChange()) { - return false; - } - - if (lifeController < lifePlayer && (!controller.isCanGainLife() || !player.isCanLoseLife())) { - return false; - } - - if (lifeController > lifePlayer && (!controller.isCanLoseLife() || !player.isCanGainLife())) { - return false; - } - - controller.setLife(lifePlayer, game, source); - player.setLife(lifeController, game, source); - return true; - } - return false; - } - - @Override - public String getText(Mode mode) { - return "Exchange life totals with target " + mode.getTargets().get(0).getTargetName(); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTwoTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTwoTargetEffect.java new file mode 100644 index 00000000000..3e6d593fa97 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ExchangeLifeTwoTargetEffect.java @@ -0,0 +1,41 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class ExchangeLifeTwoTargetEffect extends OneShotEffect { + + public ExchangeLifeTwoTargetEffect() { + super(Outcome.Neutral); + staticText = "two target players exchange life totals"; + } + + private ExchangeLifeTwoTargetEffect(final ExchangeLifeTwoTargetEffect effect) { + super(effect); + } + + @Override + public ExchangeLifeTwoTargetEffect copy() { + return new ExchangeLifeTwoTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.getTargets().get(0).getTargets().size() < 2) { + return false; + } + Player player1 = game.getPlayer(source.getTargets().get(0).getTargets().get(0)); + Player player2 = game.getPlayer(source.getTargets().get(0).getTargets().get(1)); + if (player1 == null || player2 == null) { + return false; + } + player1.exchangeLife(player2, source, game); + return true; + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/PhyrexianRebirthHorrorToken.java b/Mage/src/main/java/mage/game/permanent/token/PhyrexianRebirthHorrorToken.java index ffafe4ca4e5..ef7fbf537d3 100644 --- a/Mage/src/main/java/mage/game/permanent/token/PhyrexianRebirthHorrorToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/PhyrexianRebirthHorrorToken.java @@ -11,13 +11,13 @@ import java.util.Arrays; */ public final class PhyrexianRebirthHorrorToken extends TokenImpl { - public PhyrexianRebirthHorrorToken() { + public PhyrexianRebirthHorrorToken(int power, int toughness) { super("Horror", "X/X colorless Horror artifact creature token"); - cardType.add(CardType.ARTIFACT); - cardType.add(CardType.CREATURE); - subtype.add(SubType.HORROR); - power = new MageInt(0); - toughness = new MageInt(0); + this.cardType.add(CardType.ARTIFACT); + this.cardType.add(CardType.CREATURE); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(power); + this.toughness = new MageInt(toughness); availableImageSetCodes = Arrays.asList("C18", "C19", "MBS", "CMR"); } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 50ec95bf201..1354544cbc6 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -50,9 +50,10 @@ public interface Player extends MageItem, Copyable { /** * Current player is real life player (human). Try to use in GUI and network engine only. - * + *

* WARNING, you must use isComputer instead isHuman in card's code (for good Human/AI logic testing in unit tests) * TODO: check combat code and other and replace isHuman to isComputer usage if possible (if AI support that actions) + * * @return */ boolean isHuman(); @@ -61,9 +62,9 @@ public interface Player extends MageItem, Copyable { /** * Current player is AI. Use it in card's code and all other places. - * + *

* It help to split Human/AI logic and test both by unit tests. - * + *

* Usage example: AI hint to skip or auto-calculate choices instead call of real choose dialogs * - unit tests for Human logic: call normal commands * - unit tests for AI logic: call aiXXX commands @@ -125,6 +126,8 @@ public interface Player extends MageItem, Copyable { */ int gainLife(int amount, Game game, Ability source); + void exchangeLife(Player player, Ability source, Game game); + int damage(int damage, UUID attackerId, Ability source, Game game); int damage(int damage, UUID attackerId, Ability source, Game game, boolean combatDamage, boolean preventable); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d91aa198a70..e10248f5df6 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2063,6 +2063,18 @@ public abstract class PlayerImpl implements Player, Serializable { return 0; } + @Override + public void exchangeLife(Player player, Ability source, Game game) { + int lifePlayer1 = getLife(); + int lifePlayer2 = player.getLife(); + if ((lifePlayer1 != lifePlayer2 && this.isLifeTotalCanChange() && player.isLifeTotalCanChange()) + && (lifePlayer1 >= lifePlayer2 || (this.isCanGainLife() && player.isCanLoseLife())) + && (lifePlayer1 <= lifePlayer2 || (this.isCanLoseLife() && player.isCanGainLife()))) { + this.setLife(lifePlayer2, game, source); + player.setLife(lifePlayer1, game, source); + } + } + @Override public int damage(int damage, UUID attackerId, Ability source, Game game) { return doDamage(damage, attackerId, source, game, false, true, null);