Refactor and test [AVR] Outwit & [OTJ] Ertha Jo, Frontier Mentor. (#12036)

This commit is contained in:
Susucre 2024-04-01 14:55:01 +02:00 committed by GitHub
parent 875b69933a
commit 569d693177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 232 additions and 163 deletions

View file

@ -1,7 +1,6 @@
package mage.cards.e;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.CopyStackObjectEffect;
@ -12,17 +11,18 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.FilterPlayer;
import mage.filter.FilterStackObject;
import mage.filter.StaticFilters;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.mageobject.TargetsPermanentOrPlayerPredicate;
import mage.filter.predicate.mageobject.TargetsPermanentPredicate;
import mage.filter.predicate.mageobject.TargetsPlayerPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.MercenaryToken;
import mage.game.stack.StackObject;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
@ -57,37 +57,16 @@ public final class ErthaJoFrontierMentor extends CardImpl {
}
}
// Some abstract type is confused there, so not using Predicates.or
enum ErthaJoFrontierMentorPredicate implements ObjectSourcePlayerPredicate<MageObject> {
instance;
private static final TargetsPermanentPredicate permanentPredicate =
new TargetsPermanentPredicate(StaticFilters.FILTER_PERMANENT_CREATURE);
private static final TargetsPlayerPredicate playerPredicate = new TargetsPlayerPredicate();
@Override
public boolean apply(ObjectSourcePlayer<MageObject> o, Game game) {
return permanentPredicate.apply(o, game) || playerPredicate.apply(o, game);
}
@Override
public String toString() {
return "Or(" + permanentPredicate + ", " + playerPredicate + ')';
}
}
class ErthaJoFrontierMentorTriggeredAbility extends TriggeredAbilityImpl {
private static final FilterStackObject filter = new FilterStackObject("ability that targets a creature or player");
static {
filter.add(ErthaJoFrontierMentorPredicate.instance);
filter.add(new TargetsPermanentOrPlayerPredicate(StaticFilters.FILTER_PERMANENT_CREATURE, new FilterPlayer()));
}
public ErthaJoFrontierMentorTriggeredAbility() {
super(Zone.BATTLEFIELD, new CopyStackObjectEffect(), false);
this.addTarget(new TargetAnyTarget());
setTriggerPhrase("Whenever you activate an ability that targets a creature or player, ");
}

View file

@ -1,42 +1,35 @@
package mage.cards.o;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.common.CounterTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.filter.Filter;
import mage.filter.FilterPlayer;
import mage.filter.FilterSpell;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetObject;
import mage.filter.predicate.mageobject.TargetsPlayerPredicate;
import mage.target.TargetSpell;
import java.util.UUID;
/**
*
* @author jeffwadsworth
* @author Susucr
*/
public final class Outwit extends CardImpl {
private static FilterSpell filter = new FilterSpell("spell that targets a player");
public Outwit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}");
static {
filter.add(new TargetsPlayerPredicate(new FilterPlayer()));
}
public Outwit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}");
// Counter target spell that targets a player.
this.getSpellAbility().addEffect(new CounterTargetEffect());
this.getSpellAbility().addTarget(new CustomTargetSpell(filter));
this.getSpellAbility().addTarget(new TargetSpell(filter));
}
private Outwit(final Outwit card) {
@ -47,111 +40,4 @@ public final class Outwit extends CardImpl {
public Outwit copy() {
return new Outwit(this);
}
private static class CustomTargetSpell extends TargetObject {
protected FilterSpell filter;
public CustomTargetSpell() {
this(1, 1, StaticFilters.FILTER_SPELL);
}
public CustomTargetSpell(FilterSpell filter) {
this(1, 1, filter);
}
public CustomTargetSpell(int numTargets, FilterSpell filter) {
this(numTargets, numTargets, filter);
}
public CustomTargetSpell(int minNumTargets, int maxNumTargets, FilterSpell filter) {
this.minNumberOfTargets = minNumTargets;
this.maxNumberOfTargets = maxNumTargets;
this.zone = Zone.STACK;
this.filter = filter;
this.targetName = filter.getMessage();
}
private CustomTargetSpell(final CustomTargetSpell target) {
super(target);
this.filter = target.filter.copy();
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
return canChoose(sourceControllerId, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
return possibleTargets(sourceControllerId, game);
}
@Override
public boolean canTarget(UUID id, Ability source, Game game) {
if (super.canTarget(id, source, game)) {
if (targetsPlayer(id, game)) {
return true;
}
}
return false;
}
@Override
public boolean canChoose(UUID sourceControllerId, Game game) {
int count = 0;
for (StackObject stackObject : game.getStack()) {
if (stackObject instanceof Spell && filter.match(stackObject, game)) {
if (targetsPlayer(stackObject.getId(), game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
}
return false;
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
for (StackObject stackObject : game.getStack()) {
if (stackObject instanceof Spell && filter.match(stackObject, game)) {
if (targetsPlayer(stackObject.getId(), game)) {
possibleTargets.add(stackObject.getId());
}
}
}
return possibleTargets;
}
@Override
public Filter getFilter() {
return filter;
}
private boolean targetsPlayer(UUID id, Game game) {
StackObject spell = game.getStack().getStackObject(id);
if (spell != null) {
Ability ability = spell.getStackAbility();
if (ability != null && !ability.getTargets().isEmpty()) {
for (Target target : ability.getTargets()) {
for (UUID playerId : target.getTargets()) {
Player player = game.getPlayer(playerId);
if (player != null) {
return true;
}
}
}
}
}
return false;
}
@Override
public CustomTargetSpell copy() {
return new CustomTargetSpell(this);
}
}
}

View file

@ -0,0 +1,60 @@
package org.mage.test.cards.single.avr;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class OutwitTest extends CardTestPlayerBase {
/**
* {@link mage.cards.o.Outwit Outwit} {U}
* Instant
* Counter target spell that targets a player.
*/
private static final String outwit = "Outwit";
@Test
public void test_BoltTargettingPlayer() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerB, "Island", 1);
addCard(Zone.HAND, playerB, outwit);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.HAND, playerA, "Lightning Bolt");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
checkPlayableAbility("Outwit castable", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Outwit", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, outwit, "Lightning Bolt");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertLife(playerB, 20);
assertGraveyardCount(playerA, "Lightning Bolt", 1);
assertGraveyardCount(playerB, outwit, 1);
}
@Test
public void test_BoltTargettingCreature_CantCastOutwit() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerB, "Island", 1);
addCard(Zone.HAND, playerB, outwit);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.HAND, playerA, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerA, "Memnite");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Memnite");
checkPlayableAbility("Outwit not castable without valid target", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Outwit", false);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Memnite", 1);
assertGraveyardCount(playerA, "Lightning Bolt", 1);
}
}

View file

@ -0,0 +1,76 @@
package org.mage.test.cards.single.otj;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class ErthaJoFrontierMentorTest extends CardTestPlayerBase {
/**
* {@link mage.cards.e.ErthaJoFrontierMentor Ertha Jo, Frontier Mentor} {2}{R}{W}
* Legendary Creature Kor Advisor
* When Ertha Jo, Frontier Mentor enters the battlefield, create a 1/1 red Mercenary creature token with {T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery.
* Whenever you activate an ability that targets a creature or player, copy that ability. You may choose new targets for the copy.
* 2/4
*/
private static final String ertha = "Ertha Jo, Frontier Mentor";
@Test
public void Test_TargetPlayer() {
setStrictChooseMode(true);
// Sacrifice Bile Urchin: Target player loses 1 life.
addCard(Zone.BATTLEFIELD, playerA, "Bile Urchin", 1);
addCard(Zone.BATTLEFIELD, playerA, ertha, 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice", playerB);
setChoice(playerA, true); // choose to change the targets for the copy
addTarget(playerA, playerA); // have the copy target playerA
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertLife(playerA, 19);
assertLife(playerB, 19);
}
@Test
public void Test_TargetCreature() {
setStrictChooseMode(true);
// {T}: Target creature gets +1/+1 until end of turn.
addCard(Zone.BATTLEFIELD, playerA, "Wyluli Wolf", 1);
addCard(Zone.BATTLEFIELD, playerA, ertha, 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}", ertha);
setChoice(playerA, false); // choose not to change the targets for the copy
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPowerToughness(playerA, ertha, 2 + 2, 4 + 2);
}
@Test
public void Test_TargetLand_NoCopy() {
setStrictChooseMode(true);
// {2}{R}, {T}: Destroy target nonbasic land.
addCard(Zone.BATTLEFIELD, playerA, "Dwarven Miner", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.BATTLEFIELD, playerB, "Plateau", 1);
addCard(Zone.BATTLEFIELD, playerB, "Tropical Island", 1);
addCard(Zone.BATTLEFIELD, playerA, ertha, 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}", "Plateau");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, "Plateau", 1);
}
}

View file

@ -0,0 +1,61 @@
package mage.filter.predicate.mageobject;
import mage.abilities.Mode;
import mage.filter.FilterPermanent;
import mage.filter.FilterPlayer;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.Target;
import java.util.UUID;
/**
* @author Susucr
*/
public class TargetsPermanentOrPlayerPredicate implements ObjectSourcePlayerPredicate<StackObject> {
private final FilterPermanent targetFilterPermanent;
private final FilterPlayer targetFilterPlayer;
public TargetsPermanentOrPlayerPredicate(FilterPermanent targetFilterPermanent, FilterPlayer targetFilterPlayer) {
this.targetFilterPermanent = targetFilterPermanent;
this.targetFilterPlayer = targetFilterPlayer;
}
@Override
public boolean apply(ObjectSourcePlayer<StackObject> input, Game game) {
StackObject object = game.getStack().getStackObject(input.getObject().getId());
if (object != null) {
for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) {
Mode mode = object.getStackAbility().getModes().get(modeId);
for (Target target : mode.getTargets()) {
if (target.isNotTarget()) {
continue;
}
for (UUID targetId : target.getTargets()) {
// Try for permanent
Permanent permanent = game.getPermanent(targetId);
if (targetFilterPermanent.match(permanent, input.getPlayerId(), input.getSource(), game)) {
return true;
}
// Try for player
Player player = game.getPlayer(targetId);
if (targetFilterPlayer.match(player, input.getPlayerId(), input.getSource(), game)) {
return true;
}
}
}
}
}
return false;
}
@Override
public String toString() {
return "that targets a " + targetFilterPermanent.getMessage() + " or " + targetFilterPlayer.getMessage();
}
}

View file

@ -1,6 +1,5 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.abilities.Mode;
import mage.filter.FilterPermanent;
import mage.filter.predicate.ObjectSourcePlayer;
@ -15,7 +14,7 @@ import java.util.UUID;
/**
* @author LoneFox
*/
public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate<MageObject> {
public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate<StackObject> {
private final FilterPermanent targetFilter;
@ -24,7 +23,7 @@ public class TargetsPermanentPredicate implements ObjectSourcePlayerPredicate<Ma
}
@Override
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
public boolean apply(ObjectSourcePlayer<StackObject> input, Game game) {
StackObject object = game.getStack().getStackObject(input.getObject().getId());
if (object != null) {
for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) {

View file

@ -1,8 +1,7 @@
package mage.filter.predicate.mageobject;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Mode;
import mage.filter.FilterPlayer;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
@ -10,25 +9,34 @@ import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.Target;
/**
*
* @author jeffwadsworth
*/
public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate<MageObject> {
import java.util.UUID;
public TargetsPlayerPredicate() {
/**
* @author jeffwadsworth, Susucr
*/
public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate<StackObject> {
private final FilterPlayer targetFilter;
public TargetsPlayerPredicate(FilterPlayer targetFilter) {
this.targetFilter = targetFilter;
}
@Override
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
public boolean apply(ObjectSourcePlayer<StackObject> input, Game game) {
StackObject object = game.getStack().getStackObject(input.getObject().getId());
if (object != null) {
for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) {
Mode mode = object.getStackAbility().getModes().get(modeId);
for (Target target : mode.getTargets()) {
if (target.isNotTarget()) {
continue;
}
for (UUID targetId : target.getTargets()) {
Player player = game.getPlayer(targetId);
return player != null;
if (targetFilter.match(player, input.getPlayerId(), input.getSource(), game)) {
return true;
}
}
}
}
@ -38,6 +46,6 @@ public class TargetsPlayerPredicate implements ObjectSourcePlayerPredicate<MageO
@Override
public String toString() {
return "that targets a player";
return "that targets a " + targetFilter.getMessage();
}
}