AI fixes:

* Gain control abilities - fixed wrong target chooses by AI (selects weakest permanent instead most powerful);
* Target of an opponent’s choice abilities - fixed that AI was able to cancel card cast, fixed wrong target chooses (Evangelize, Echo Chamber, Arena, Preacher, etc);
This commit is contained in:
Oleg Agafonov 2020-01-04 22:37:16 +04:00
parent bcb37992cc
commit bb59cedbd9
12 changed files with 137 additions and 93 deletions

View file

@ -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);

View file

@ -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());

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -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 opponents 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 opponents 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
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}