fix #12867 (Devouring Hellion)

by refactoring to use DevourEffect

test added
This commit is contained in:
xenohedron 2024-09-15 20:58:43 -04:00
parent d052ab45c6
commit 842fa90e7e
7 changed files with 116 additions and 134 deletions

View file

@ -8,8 +8,8 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.common.FilterControlledPermanent;
import java.util.UUID;
@ -18,7 +18,7 @@ import java.util.UUID;
*/
public final class Caprichrome extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledArtifactPermanent("artifact");
private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifact");
public Caprichrome(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{W}");

View file

@ -1,24 +1,14 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.DevourEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetSacrifice;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import java.util.UUID;
@ -35,7 +25,11 @@ public final class DevouringHellion extends CardImpl {
this.toughness = new MageInt(2);
// As Devouring Hellion enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers. If you do, it enters with twice that many +1/+1 counters on it.
this.addAbility(new AsEntersBattlefieldAbility(new DevouringHellionEffect()));
this.addAbility(new SimpleStaticAbility(Zone.ALL,
new DevourEffect(2, StaticFilters.FILTER_CONTROLLED_PERMANENT_CREATURE_OR_PLANESWALKER)
.setText("As {this} enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers."
+ " If you do, it enters with twice that many +1/+1 counters on it")
));
}
private DevouringHellion(final DevouringHellion card) {
@ -47,50 +41,3 @@ public final class DevouringHellion extends CardImpl {
return new DevouringHellion(this);
}
}
class DevouringHellionEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterControlledPermanent("creatures and/or planeswalkers");
static {
filter.add(Predicates.or(
CardType.CREATURE.getPredicate(),
CardType.PLANESWALKER.getPredicate()
));
}
DevouringHellionEffect() {
super(Outcome.Benefit);
staticText = "you may sacrifice any number of creatures and/or planeswalkers. " +
"If you do, it enters with twice that many +1/+1 counters on it.";
}
private DevouringHellionEffect(final DevouringHellionEffect effect) {
super(effect);
}
@Override
public DevouringHellionEffect copy() {
return new DevouringHellionEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
Target target = new TargetSacrifice(0, Integer.MAX_VALUE, filter);
if (!player.choose(Outcome.Sacrifice, target, source, game)) {
return false;
}
int xValue = 0;
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null && permanent.sacrifice(source, game)) {
xValue++;
}
}
return new AddCountersSourceEffect(CounterType.P1P1.createInstance(2 * xValue)).apply(game, source);
}
}

View file

@ -14,7 +14,6 @@ import mage.filter.StaticFilters;
import java.util.UUID;
/**
*
* @author Susucr
*/
public final class FeastingHobbit extends CardImpl {

View file

@ -1,4 +1,3 @@
package mage.cards.t;
import mage.MageInt;
@ -26,7 +25,7 @@ public final class ThromokTheInsatiable extends CardImpl {
this.toughness = new MageInt(0);
// Devour X, where X is the number of creatures devoured this way (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with X +1/+1 counters on it for each of those creatures.)
this.addAbility(DevourAbility.DevourX());
this.addAbility(DevourAbility.devourX());
}
private ThromokTheInsatiable(final ThromokTheInsatiable card) {

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.keywords;
import mage.abilities.keyword.FlyingAbility;
@ -9,6 +8,8 @@ import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import java.util.Objects;
/**
* 702.82. Devour
* <p>
@ -27,9 +28,11 @@ public class DevourTest extends CardTestPlayerBase {
String devourTargets,
int assertCounter,
boolean assertLion,
boolean assertMyr,
boolean assertGolem,
boolean assertGinger,
boolean assertRelic
boolean assertRelic,
boolean assertAngrath,
int life
) {
setStrictChooseMode(true);
@ -46,17 +49,21 @@ public class DevourTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, devourer);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Creature
addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); // Creature Artifact
addCard(Zone.BATTLEFIELD, playerA, "Enatu Golem"); // Artifact Creature - gain 4 life on death
addCard(Zone.BATTLEFIELD, playerA, "Gingerbrute"); // Artifact Creature Food Golem
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic"); // Artifact
addCard(Zone.BATTLEFIELD, playerA, "Angrath, Captain of Chaos"); // Planeswalker
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, devourer);
if (devourTargets == "") {
if (Objects.equals(devourTargets, "")) {
setChoice(playerA, false); // no to devour
} else {
setChoice(playerA, true); // yes to devour
addTarget(playerA, devourTargets); // devour targets.
}
if (!assertGolem && devourer.equals("Marrow Chomper")) {
setChoice(playerA, "When {this} dies, you gain 4 life"); // order triggers
}
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
@ -70,12 +77,15 @@ public class DevourTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Silvercoat Lion", assertLion ? 1 : 0);
assertGraveyardCount(playerA, "Silvercoat Lion", assertLion ? 0 : 1);
assertPermanentCount(playerA, "Alpha Myr", assertMyr ? 1 : 0);
assertGraveyardCount(playerA, "Alpha Myr", assertMyr ? 0 : 1);
assertPermanentCount(playerA, "Enatu Golem", assertGolem ? 1 : 0);
assertGraveyardCount(playerA, "Enatu Golem", assertGolem ? 0 : 1);
assertPermanentCount(playerA, "Gingerbrute", assertGinger ? 1 : 0);
assertGraveyardCount(playerA, "Gingerbrute", assertGinger ? 0 : 1);
assertPermanentCount(playerA, "Darksteel Relic", assertRelic ? 1 : 0);
assertGraveyardCount(playerA, "Darksteel Relic", assertRelic ? 0 : 1);
assertPermanentCount(playerA, "Angrath, Captain of Chaos", assertAngrath ? 1 : 0);
assertGraveyardCount(playerA, "Angrath, Captain of Chaos", assertAngrath ? 0 : 1);
assertLife(playerA, life);
}
private void expectedIllegalTest(
@ -97,7 +107,7 @@ public class DevourTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, devourer);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Creature
addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); // Creature Artifact
addCard(Zone.BATTLEFIELD, playerA, "Enatu Golem"); // Creature Artifact
addCard(Zone.BATTLEFIELD, playerA, "Gingerbrute"); // Artifact Creature Food Golem
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic"); // Artifact
@ -130,25 +140,25 @@ public class DevourTest extends CardTestPlayerBase {
@Test
public void Wurm_NoDevour() {
expectedPossibleTest(gorgerWurm, "",
1 * 0, true, true, true, true);
1 * 0, true, true, true, true, true, 20);
}
@Test
public void Wurm_OneDevour() {
expectedPossibleTest(gorgerWurm, "Alpha Myr",
1 * 1, true, false, true, true);
expectedPossibleTest(gorgerWurm, "Enatu Golem",
1 * 1, true, false, true, true, true, 24);
}
@Test
public void Wurm_TwoDevour() {
expectedPossibleTest(gorgerWurm, "Alpha Myr^Gingerbrute",
1 * 2, true, false, false, true);
expectedPossibleTest(gorgerWurm, "Enatu Golem^Gingerbrute",
1 * 2, true, false, false, true, true, 24);
}
@Test
public void Wurm_ThreeDevour() {
expectedPossibleTest(gorgerWurm, "Alpha Myr^Gingerbrute^Silvercoat Lion",
1 * 3, false, false, false, true);
expectedPossibleTest(gorgerWurm, "Enatu Golem^Gingerbrute^Silvercoat Lion",
1 * 3, false, false, false, true, true, 24);
}
@Test
@ -168,25 +178,25 @@ public class DevourTest extends CardTestPlayerBase {
@Test
public void Thromok_NoDevour() {
expectedPossibleTest(thromok, "",
0 * 0, true, true, true, true);
0 * 0, true, true, true, true, true, 20);
}
@Test
public void Thromok_OneDevour() {
expectedPossibleTest(thromok, "Alpha Myr",
1 * 1, true, false, true, true);
expectedPossibleTest(thromok, "Enatu Golem",
1 * 1, true, false, true, true, true, 24);
}
@Test
public void Thromok_TwoDevour() {
expectedPossibleTest(thromok, "Alpha Myr^Gingerbrute",
2 * 2, true, false, false, true);
expectedPossibleTest(thromok, "Enatu Golem^Gingerbrute",
2 * 2, true, false, false, true, true, 24);
}
@Test
public void Thromok_ThreeDevour() {
expectedPossibleTest(thromok, "Alpha Myr^Gingerbrute^Silvercoat Lion",
3 * 3, false, false, false, true);
expectedPossibleTest(thromok, "Enatu Golem^Gingerbrute^Silvercoat Lion",
3 * 3, false, false, false, true, true, 24);
}
@Test
@ -206,18 +216,18 @@ public class DevourTest extends CardTestPlayerBase {
@Test
public void Hobbit_NoDevour() {
expectedPossibleTest(hobbit, "",
3 * 0, true, true, true, true);
3 * 0, true, true, true, true, true, 20);
}
@Test
public void Hobbit_OneDevour() {
expectedPossibleTest(hobbit, "Gingerbrute",
3 * 1, true, true, false, true);
3 * 1, true, true, false, true, true, 20);
}
@Test
public void Hobbit_IllegalDevour() {
expectedIllegalTest(hobbit, "Alpha Myr");
expectedIllegalTest(hobbit, "Enatu Golem");
}
// Caprichrome
@ -236,25 +246,25 @@ public class DevourTest extends CardTestPlayerBase {
@Test
public void Caprichrome_NoDevour() {
expectedPossibleTest(caprichrome, "",
1 * 0, true, true, true, true);
1 * 0, true, true, true, true, true, 20);
}
@Test
public void Caprichrome_OneDevour() {
expectedPossibleTest(caprichrome, "Alpha Myr",
1 * 1, true, false, true, true);
expectedPossibleTest(caprichrome, "Enatu Golem",
1 * 1, true, false, true, true, true, 24);
}
@Test
public void Caprichrome_TwoDevour() {
expectedPossibleTest(caprichrome, "Alpha Myr^Gingerbrute",
1 * 2, true, false, false, true);
expectedPossibleTest(caprichrome, "Enatu Golem^Gingerbrute",
1 * 2, true, false, false, true, true, 24);
}
@Test
public void Caprichrome_ThreeDevour() {
expectedPossibleTest(caprichrome, "Alpha Myr^Gingerbrute^Darksteel Relic",
1 * 3, true, false, false, false);
expectedPossibleTest(caprichrome, "Enatu Golem^Gingerbrute^Darksteel Relic",
1 * 3, true, false, false, false, true, 24);
}
@Test
@ -276,31 +286,31 @@ public class DevourTest extends CardTestPlayerBase {
@Test
public void Hatchling_NoDevour() {
expectedPossibleTest(hatchling, "",
1 * 0, true, true, true, true);
1 * 0, true, true, true, true, true, 20);
assertAbility(playerA, hatchling, FlyingAbility.getInstance(), false);
assertAbility(playerA, hatchling, TrampleAbility.getInstance(), false);
}
@Test
public void Hatchling_OneDevour() {
expectedPossibleTest(hatchling, "Alpha Myr",
1 * 1, true, false, true, true);
expectedPossibleTest(hatchling, "Enatu Golem",
1 * 1, true, false, true, true, true, 24);
assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true);
assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true);
}
@Test
public void Hatchling_TwoDevour() {
expectedPossibleTest(hatchling, "Alpha Myr^Gingerbrute",
1 * 2, true, false, false, true);
expectedPossibleTest(hatchling, "Enatu Golem^Gingerbrute",
1 * 2, true, false, false, true, true, 24);
assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true);
assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true);
}
@Test
public void Hatchling_ThreeDevour() {
expectedPossibleTest(hatchling, "Alpha Myr^Gingerbrute^Silvercoat Lion",
1 * 3, false, false, false, true);
expectedPossibleTest(hatchling, "Enatu Golem^Gingerbrute^Silvercoat Lion",
1 * 3, false, false, false, true, true, 24);
assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true);
assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true);
}
@ -322,33 +332,65 @@ public class DevourTest extends CardTestPlayerBase {
@Test
public void Chomper_NoDevour() {
expectedPossibleTest(chomper, "",
2 * 0, true, true, true, true);
assertLife(playerA, 20 + 2 * 0);
2 * 0, true, true, true, true, true, 20);
}
@Test
public void Chomper_OneDevour() {
expectedPossibleTest(chomper, "Alpha Myr",
2 * 1, true, false, true, true);
assertLife(playerA, 20 + 2 * 1);
expectedPossibleTest(chomper, "Enatu Golem",
2 * 1, true, false, true, true, true, 26);
}
@Test
public void Chomper_TwoDevour() {
expectedPossibleTest(chomper, "Alpha Myr^Gingerbrute",
2 * 2, true, false, false, true);
assertLife(playerA, 20 + 2 * 2);
expectedPossibleTest(chomper, "Enatu Golem^Gingerbrute",
2 * 2, true, false, false, true, true, 28);
}
@Test
public void Chomper_ThreeDevour() {
expectedPossibleTest(chomper, "Alpha Myr^Gingerbrute^Silvercoat Lion",
2 * 3, false, false, false, true);
assertLife(playerA, 20 + 2 * 3);
expectedPossibleTest(chomper, "Enatu Golem^Gingerbrute^Silvercoat Lion",
2 * 3, false, false, false, true, true, 30);
}
@Test
public void Chomper_IllegalDevour() {
expectedIllegalTest(chomper, "Darksteel Relic");
}
}
// Devouring Hellion {2}{R}
// Creature Hellion
// As Devouring Hellion enters, you may sacrifice any number of creatures and/or planeswalkers.
// If you do, it enters with twice that many +1/+1 counters on it.
private static final String hellion = "Devouring Hellion";
@Test
public void hellionNoDevour() {
expectedPossibleTest(hellion, "",
0, true, true, true, true, true, 20);
}
@Test
public void hellionOneDevour() {
expectedPossibleTest(hellion, "Angrath, Captain of Chaos",
2, true, true, true, true, false, 20);
}
@Test
public void hellionTwoDevour() {
expectedPossibleTest(hellion, "Enatu Golem^Angrath, Captain of Chaos",
4, true, false, true, true, false, 24);
}
@Test
public void hellionThreeDevour() {
expectedPossibleTest(hellion, "Enatu Golem^Gingerbrute^Angrath, Captain of Chaos",
6, true, false, false, true, false, 24);
}
@Test
public void hellionIllegalDevour() {
expectedIllegalTest(hellion, "Darksteel Relic");
}
}

View file

@ -6,8 +6,7 @@ import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.counters.CounterType;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicate;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
@ -45,9 +44,9 @@ public class DevourEffect extends ReplacementEffectImpl {
// "creature" is a special case as the rule will not mention it.
//
// 's' will be added to pluralize, so far so good with the current text generation.
private final FilterControlledPermanent filterDevoured;
private final FilterPermanent filterDevoured;
public DevourEffect(int devourFactor, FilterControlledPermanent filterDevoured) {
public DevourEffect(int devourFactor, FilterPermanent filterDevoured) {
super(Duration.EndOfGame, Outcome.Detriment);
this.devourFactor = devourFactor;
this.filterDevoured = filterDevoured;
@ -81,11 +80,8 @@ public class DevourEffect extends ReplacementEffectImpl {
if (creature == null || controller == null) {
return false;
}
FilterControlledPermanent filter = new FilterControlledPermanent(filterDevoured.getMessage() + "s to devour");
for (Predicate predicate : filterDevoured.getPredicates()) {
filter.add(predicate);
}
FilterPermanent filter = filterDevoured.copy();
filter.setMessage(filterDevoured.getMessage() + "s (to devour)");
filter.add(AnotherPredicate.instance);
Target target = new TargetSacrifice(1, Integer.MAX_VALUE, filter);
@ -142,9 +138,9 @@ public class DevourEffect extends ReplacementEffectImpl {
text += devourFactor;
}
text += " <i>(As this enters the battlefield, you may sacrifice any number of "
text += " <i>(As this enters, you may sacrifice any number of "
+ filterMessage + "s. "
+ "This creature enters the battlefield with ";
+ "This creature enters with ";
if (devourFactor == Integer.MAX_VALUE) {
text += "X +1/+1 counters on it for each of those creatures";

View file

@ -1,11 +1,10 @@
package mage.abilities.keyword;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.DevourEffect;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterControlledPermanent;
/**
* 502.82. Devour
@ -45,12 +44,12 @@ import mage.filter.common.FilterControlledPermanent;
*/
public class DevourAbility extends SimpleStaticAbility {
private static final FilterControlledPermanent filterCreature = new FilterControlledCreaturePermanent("creature");
private static final FilterPermanent filterCreature = new FilterControlledCreaturePermanent("creature");
// Integer.MAX_VALUE is a special value
// for "devour X, where X is the number of devored permanents"
// see DevourEffect for the full details.
public static DevourAbility DevourX() {
public static DevourAbility devourX() {
return new DevourAbility(Integer.MAX_VALUE);
}
@ -58,7 +57,7 @@ public class DevourAbility extends SimpleStaticAbility {
this(devourFactor, filterCreature);
}
public DevourAbility(int devourFactor, FilterControlledPermanent filterDevoured) {
public DevourAbility(int devourFactor, FilterPermanent filterDevoured) {
super(Zone.ALL, new DevourEffect(devourFactor, filterDevoured));
}