mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
Refactor and test [AVR] Outwit & [OTJ] Ertha Jo, Frontier Mentor. (#12036)
This commit is contained in:
parent
875b69933a
commit
569d693177
7 changed files with 232 additions and 163 deletions
|
|
@ -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, ");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue