diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 575af11abd1..54c8faf01a8 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -2512,16 +2512,10 @@ public class ComputerPlayer extends PlayerImpl implements Player { goodList.sort(comparator); badList.sort(comparator); - // real sort - if (outcome.isGood()) { - // good effect -- most valueable goes first - Collections.reverse(goodList); - // Collections.reverse(badList); - } else { - // bad effect - most weakest goes first, no need in reverse - // Collections.reverse(goodList); - Collections.reverse(badList); - } + // most valueable goes first in good list + Collections.reverse(goodList); + // most weakest goes first in bad list (no need to reverse) + //Collections.reverse(badList); allList.addAll(goodList); allList.addAll(badList); diff --git a/Mage.Sets/src/mage/cards/a/Arena.java b/Mage.Sets/src/mage/cards/a/Arena.java index 4e5cf769f93..dd81e0ed59a 100644 --- a/Mage.Sets/src/mage/cards/a/Arena.java +++ b/Mage.Sets/src/mage/cards/a/Arena.java @@ -1,7 +1,5 @@ - package mage.cards.a; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -19,20 +17,21 @@ import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponentsChoicePermanent; +import java.util.UUID; + /** - * * @author emerald000 */ public final class Arena extends CardImpl { public Arena(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {3}, {tap}: Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ArenaEffect(), new GenericManaCost(3)); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetControlledCreaturePermanent()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false)); this.addAbility(ability); } @@ -47,21 +46,21 @@ public final class Arena extends CardImpl { } class ArenaEffect extends OneShotEffect { - + ArenaEffect() { super(Outcome.Benefit); this.staticText = "Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other."; } - + ArenaEffect(final ArenaEffect effect) { super(effect); } - + @Override public ArenaEffect copy() { return new ArenaEffect(this); } - + @Override public boolean apply(Game game, Ability source) { Permanent creature = game.getPermanent(source.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/d/DiaochanArtfulBeauty.java b/Mage.Sets/src/mage/cards/d/DiaochanArtfulBeauty.java index 0262d9111f9..cc3b7dea5f6 100644 --- a/Mage.Sets/src/mage/cards/d/DiaochanArtfulBeauty.java +++ b/Mage.Sets/src/mage/cards/d/DiaochanArtfulBeauty.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ActivateIfConditionActivatedAbility; @@ -10,11 +8,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; @@ -22,8 +16,9 @@ import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetOpponentsChoicePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class DiaochanArtfulBeauty extends CardImpl { @@ -40,7 +35,7 @@ public final class DiaochanArtfulBeauty extends CardImpl { // {tap}: Destroy target creature of your choice, then destroy target creature of an opponent's choice. Activate this ability only during your turn, before attackers are declared. Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD, new DiaochanArtfulBeautyDestroyEffect(), new TapSourceCost(), MyTurnBeforeAttackersDeclaredCondition.instance); ability.addTarget(new TargetCreaturePermanent()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/EchoChamber.java b/Mage.Sets/src/mage/cards/e/EchoChamber.java index 49e226f2e2c..eeaf1e8d1f2 100644 --- a/Mage.Sets/src/mage/cards/e/EchoChamber.java +++ b/Mage.Sets/src/mage/cards/e/EchoChamber.java @@ -1,8 +1,5 @@ - package mage.cards.e; -import java.util.UUID; -import mage.MageInt; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.ActivateAsSorceryActivatedAbility; @@ -10,21 +7,22 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.filter.common.FilterControlledCreaturePermanent; import mage.target.common.TargetOpponentsChoicePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author L_J */ public final class EchoChamber extends CardImpl { @@ -37,7 +35,7 @@ public final class EchoChamber extends CardImpl { // {4}, {tap}: An opponent chooses target creature they control. Create a token that's a copy of that creature. That token gains haste until end of turn. Exile the token at the beginning of the next end step. Activate this ability only any time you could cast a sorcery. Ability ability = new ActivateAsSorceryActivatedAbility(Zone.BATTLEFIELD, new EchoChamberCreateTokenEffect(), new GenericManaCost(4)); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filter, false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filter, false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/Evangelize.java b/Mage.Sets/src/mage/cards/e/Evangelize.java index ce1f0dff68f..af9b9fcbf38 100644 --- a/Mage.Sets/src/mage/cards/e/Evangelize.java +++ b/Mage.Sets/src/mage/cards/e/Evangelize.java @@ -1,7 +1,5 @@ - package mage.cards.e; -import java.util.UUID; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.keyword.BuybackAbility; import mage.cards.CardImpl; @@ -11,8 +9,9 @@ import mage.constants.Duration; import mage.filter.common.FilterControlledCreaturePermanent; import mage.target.common.TargetOpponentsChoicePermanent; +import java.util.UUID; + /** - * * @author spjspj */ public final class Evangelize extends CardImpl { @@ -29,7 +28,7 @@ public final class Evangelize extends CardImpl { GainControlTargetEffect effect = new GainControlTargetEffect(Duration.EndOfGame); effect.setText("Gain control of target creature of an opponent's choice they control"); this.getSpellAbility().addEffect(effect); - this.getSpellAbility().addTarget(new TargetOpponentsChoicePermanent(1, 1, filter, false, true)); + this.getSpellAbility().addTarget(new TargetOpponentsChoicePermanent(1, 1, filter, false)); } public Evangelize(final Evangelize card) { diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java b/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java index 77ffc45fc38..f15ba6fcbbd 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheArena.java @@ -1,7 +1,5 @@ - package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -12,8 +10,8 @@ import mage.abilities.effects.common.FightTargetsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; @@ -21,14 +19,15 @@ import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetOpponentsChoicePermanent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class MagusOfTheArena extends CardImpl { public MagusOfTheArena(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.WIZARD); @@ -39,7 +38,7 @@ public final class MagusOfTheArena extends CardImpl { Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new MagusOfTheArenaEffect(), new GenericManaCost(3)); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetControlledCreaturePermanent()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/n/NovaPentacle.java b/Mage.Sets/src/mage/cards/n/NovaPentacle.java index 78475c9f3c2..2023a8679b5 100644 --- a/Mage.Sets/src/mage/cards/n/NovaPentacle.java +++ b/Mage.Sets/src/mage/cards/n/NovaPentacle.java @@ -1,7 +1,5 @@ - package mage.cards.n; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -22,8 +20,9 @@ import mage.players.Player; import mage.target.TargetSource; import mage.target.common.TargetOpponentsChoicePermanent; +import java.util.UUID; + /** - * * @author L_J */ public final class NovaPentacle extends CardImpl { @@ -34,7 +33,7 @@ public final class NovaPentacle extends CardImpl { // {3}, {tap}: The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new NovaPentacleEffect(), new GenericManaCost(3)); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterCreaturePermanent(), false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterCreaturePermanent(), false)); this.addAbility(ability); } @@ -92,9 +91,7 @@ class NovaPentacleEffect extends RedirectionEffect { // check player Player player = game.getPlayer(event.getTargetId()); if (player != null) { - if (player.getId().equals(source.getControllerId())) { - return true; - } + return player.getId().equals(source.getControllerId()); } return false; } diff --git a/Mage.Sets/src/mage/cards/p/Preacher.java b/Mage.Sets/src/mage/cards/p/Preacher.java index 02bb23ec7dc..155d71719ed 100644 --- a/Mage.Sets/src/mage/cards/p/Preacher.java +++ b/Mage.Sets/src/mage/cards/p/Preacher.java @@ -1,6 +1,5 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -14,11 +13,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; @@ -26,8 +21,9 @@ import mage.players.Player; import mage.target.common.TargetOpponentsChoicePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author spjspj */ public final class Preacher extends CardImpl { @@ -44,7 +40,7 @@ public final class Preacher extends CardImpl { // {tap}: Gain control of target creature of an opponent's choice that they control for as long as Preacher remains tapped. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PreacherEffect(), new TapSourceCost()); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, new FilterControlledCreaturePermanent(), false)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VolcanicOffering.java b/Mage.Sets/src/mage/cards/v/VolcanicOffering.java index 3c9d41e2ee5..cbfaeeaeed4 100644 --- a/Mage.Sets/src/mage/cards/v/VolcanicOffering.java +++ b/Mage.Sets/src/mage/cards/v/VolcanicOffering.java @@ -1,4 +1,3 @@ - package mage.cards.v; import mage.abilities.Ability; @@ -70,12 +69,12 @@ enum VolcanicOfferingAdjuster implements TargetAdjuster { FilterLandPermanent filterLandForOpponent = new FilterLandPermanent("nonbasic land not controlled by " + controller.getLogName()); filterLandForOpponent.add(Predicates.not(new SupertypePredicate(SuperType.BASIC))); filterLandForOpponent.add(Predicates.not(new ControllerIdPredicate(controller.getId()))); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filterLandForOpponent, false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filterLandForOpponent, false)); ability.addTarget(new TargetPermanent(filterCreature)); FilterCreaturePermanent filterCreatureForOpponent = new FilterCreaturePermanent("creature not controlled by " + controller.getLogName()); filterCreatureForOpponent.add(Predicates.not(new ControllerIdPredicate(controller.getId()))); - ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filterCreatureForOpponent, false, true)); + ability.addTarget(new TargetOpponentsChoicePermanent(1, 1, filterCreatureForOpponent, false)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetControllerChangeTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetControllerChangeTest.java new file mode 100644 index 00000000000..cbdb6ef2e3f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetControllerChangeTest.java @@ -0,0 +1,60 @@ +package org.mage.test.AI.basic; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class TargetControllerChangeTest extends CardTestPlayerBase { + + @Test + public void test_OpponentMakeChooseInsteadPlayer_User() { + // Gain control of target creature of an opponent’s choice they control. + addCard(Zone.HAND, playerA, "Evangelize", 1); // {4}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Evangelize"); // do not call direct target setup + addTarget(playerA, playerB); // choose target opponent + setChoice(playerA, "No"); // no buyback + // + addTarget(playerB, "Balduvian Bears"); // give small bear to A + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Evangelize", 1); + assertPermanentCount(playerA, "Balduvian Bears", 1); + } + + @Test + public void test_OpponentMakeChooseInsteadPlayer_AI() { + // Gain control of target creature of an opponent’s choice they control. + addCard(Zone.HAND, playerA, "Evangelize", 1); // {4}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears", 1); // 3/3 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Evangelize"); // do not call direct target setup + //addTarget(playerA, playerB); // choose target opponent - AI must choose itself + //setChoice(playerA, "No"); // no buyback - AI must choose itself + // + //addTarget(playerB, "Balduvian Bears"); // give small bear to A - AI must choose itself + + //setStrictChooseMode(true); // AI must choose + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Evangelize", 1); + assertPermanentCount(playerA, "Balduvian Bears", 1); // AI give smallest permanent to A as bad effect for B + } +} diff --git a/Mage/src/main/java/mage/constants/Outcome.java b/Mage/src/main/java/mage/constants/Outcome.java index 8470f43b384..0aa1235d248 100644 --- a/Mage/src/main/java/mage/constants/Outcome.java +++ b/Mage/src/main/java/mage/constants/Outcome.java @@ -17,7 +17,7 @@ public enum Outcome { PutCreatureInPlay(true), PutCardInPlay(true), PutLandInPlay(true), - GainControl(false), + GainControl(true), DrawCard(true), Discard(false), Sacrifice(false), @@ -40,7 +40,7 @@ public enum Outcome { Removal(false), AIDontUseIt(false), Vote(true); - private final boolean good; // good or bad for targets in current effect + private final boolean good; // good or bad effect for targeting player (for AI usage) private boolean canTargetAll; Outcome(boolean good) { @@ -59,4 +59,13 @@ public enum Outcome { public boolean isCanTargetAll() { return canTargetAll; } + + public static Outcome inverse(Outcome outcome) { + // inverse bad/good effect (as example, after controlling player change) + if (outcome.isGood()) { + return Outcome.Detriment; + } else { + return Outcome.Benefit; + } + } } diff --git a/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java b/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java index 5dce07ed4c2..766be3b681d 100644 --- a/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java +++ b/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java @@ -1,11 +1,5 @@ -/* - * 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 mage.target.common; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.constants.Outcome; @@ -15,28 +9,22 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author Mael */ public class TargetOpponentsChoicePermanent extends TargetPermanent { protected UUID opponentId = null; - private boolean dontTargetPlayer = false; - public TargetOpponentsChoicePermanent(FilterPermanent filter) { - super(1, 1, filter, false); - } - - public TargetOpponentsChoicePermanent(int minNumTargets, int maxNumTargets, FilterPermanent filter, boolean notTarget, boolean dontTargetPlayer) { + public TargetOpponentsChoicePermanent(int minNumTargets, int maxNumTargets, FilterPermanent filter, boolean notTarget) { super(minNumTargets, maxNumTargets, filter, notTarget); - this.dontTargetPlayer = dontTargetPlayer; } public TargetOpponentsChoicePermanent(final TargetOpponentsChoicePermanent target) { super(target); this.opponentId = target.opponentId; - this.dontTargetPlayer = target.dontTargetPlayer; } @Override @@ -68,7 +56,22 @@ public class TargetOpponentsChoicePermanent extends TargetPermanent { @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - return super.chooseTarget(outcome, getOpponentId(playerId, source, game), source, game); + // choose opponent + if (opponentId == null) { + TargetOpponent target = new TargetOpponent(true); // notTarget true = can't cancel + Player player = game.getPlayer(playerId); + if (player != null) { + if (player.chooseTarget(Outcome.Detriment, target, source, game)) { + opponentId = target.getFirstTarget(); + } + } + } + if (opponentId == null) { + return false; + } + + // opponent choose real targets (outcome must be inversed) + return super.chooseTarget(Outcome.inverse(outcome), opponentId, source, game); } @Override @@ -103,22 +106,18 @@ public class TargetOpponentsChoicePermanent extends TargetPermanent { return new TargetOpponentsChoicePermanent(this); } - private UUID getOpponentId(UUID playerId, Ability source, Game game) { - if (opponentId == null) { - TargetOpponent target = new TargetOpponent(dontTargetPlayer); - Player player = game.getPlayer(playerId); - if (player != null) { - if (player.chooseTarget(Outcome.Detriment, target, source, game)) { - opponentId = target.getFirstTarget(); - } - } - } - return opponentId; + @Override + public boolean isRequired() { + return true; // opponent can't cancel the spell + } + + @Override + public boolean isRequired(UUID sourceId, Game game) { + return true; // opponent can't cancel the spell } @Override public boolean isRequired(Ability ability) { return true; // opponent can't cancel the spell } - }