Refactor Devour ability; [LTC] Implement Feasting Hobbit (#10708)

* [LTC] Implement Feasting Hobbit

Refactor DevourEffect and its text generation, to be more permissive in the future.

* change DevourAbility to have the constructor parameters of DevourEffect

* only pluralize when there is more than 1 devoured permanent.

* added more unit tests than there are cards with devour

* public -> private filter of Caprichrome.
This commit is contained in:
Susucre 2023-08-01 15:49:32 +02:00 committed by GitHub
parent 3d3358cd05
commit 40e508ac19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 535 additions and 132 deletions

View file

@ -1,12 +1,10 @@
package mage.cards.b;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -17,6 +15,8 @@ import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author fireshoes
@ -30,7 +30,7 @@ public final class BloodsporeThrinax extends CardImpl {
this.toughness = new MageInt(2);
// Devour 1
this.addAbility(new DevourAbility(DevourEffect.DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
// Each other creature you control enters the battlefield with an additional X +1/+1 counters on it, where X is the number of +1/+1 counters on Bloodspire Thrinax.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BloodsporeThrinaxEntersBattlefieldEffect()));

View file

@ -1,11 +1,9 @@
package mage.cards.c;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.DamageAllEffect;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -13,6 +11,8 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import java.util.UUID;
/**
*
* @author LevelX2
@ -27,7 +27,7 @@ public final class CalderaHellion extends CardImpl {
this.toughness = new MageInt(3);
// Devour 1 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
// When Caldera Hellion enters the battlefield, it deals 3 damage to each creature.
this.addAbility(new EntersBattlefieldTriggeredAbility(new DamageAllEffect(3, "it", new FilterCreaturePermanent())));

View file

@ -1,7 +1,6 @@
package mage.cards.c;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlashAbility;
import mage.abilities.keyword.VigilanceAbility;
@ -9,6 +8,8 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.common.FilterControlledPermanent;
import java.util.UUID;
@ -17,6 +18,8 @@ import java.util.UUID;
*/
public final class Caprichrome extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledArtifactPermanent("artifact");
public Caprichrome(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{W}");
@ -31,7 +34,7 @@ public final class Caprichrome extends CardImpl {
this.addAbility(VigilanceAbility.getInstance());
// Devour artifact 1
this.addAbility(new DevourAbility(DevourEffect.DevourFactor.DevourArtifact1));
this.addAbility(new DevourAbility(1, filter));
}
private Caprichrome(final Caprichrome card) {

View file

@ -1,7 +1,6 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.keyword.ConvokeAbility;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlyingAbility;
@ -31,7 +30,7 @@ public final class FeasterOfFools extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// Devour 2
this.addAbility(new DevourAbility(DevourEffect.DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
}
private FeasterOfFools(final FeasterOfFools card) {

View file

@ -0,0 +1,45 @@
package mage.cards.f;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesWithLessPowerEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
*
* @author Susucr
*/
public final class FeastingHobbit extends CardImpl {
public FeastingHobbit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
this.subtype.add(SubType.HALFLING);
this.subtype.add(SubType.CITIZEN);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Devour Food 3
this.addAbility(new DevourAbility(3, StaticFilters.FILTER_CONTROLLED_FOOD));
// Creatures with power less than Feasting Hobbit's power can't block it.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeBlockedByCreaturesWithLessPowerEffect()));
}
private FeastingHobbit(final FeastingHobbit card) {
super(card);
}
@Override
public FeastingHobbit copy() {
return new FeastingHobbit(this);
}
}

View file

@ -1,9 +1,7 @@
package mage.cards.g;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlashAbility;
import mage.cards.CardImpl;
@ -11,6 +9,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -28,7 +28,7 @@ public final class GluttonousSlime extends CardImpl {
this.addAbility(FlashAbility.getInstance());
// Devour 1 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
}
private GluttonousSlime(final GluttonousSlime card) {

View file

@ -1,15 +1,15 @@
package mage.cards.g;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -25,7 +25,7 @@ public final class GorgerWurm extends CardImpl {
this.toughness = new MageInt(5);
// Devour 1 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourEffect.DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
}
private GorgerWurm(final GorgerWurm card) {

View file

@ -6,7 +6,6 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.DevouredCreaturesCondition;
import mage.abilities.decorator.ConditionalContinuousEffect;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlyingAbility;
@ -34,7 +33,7 @@ public final class HellkiteHatchling extends CardImpl {
this.toughness = new MageInt(2);
// Devour 1 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
// Hellkite Hatchling has flying and trample if it devoured a creature.
Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect(

View file

@ -1,11 +1,9 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.dynamicvalue.common.DevouredCreaturesCount;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
@ -13,6 +11,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -30,7 +30,7 @@ public final class MarrowChomper extends CardImpl {
this.toughness = new MageInt(3);
// Devour 2 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourEffect.DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
// When Marrow Chomper enters the battlefield, you gain 2 life for each creature it devoured.
this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(new DevouredCreaturesCount(2))));

View file

@ -1,12 +1,10 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -16,6 +14,8 @@ import mage.constants.TargetController;
import mage.counters.CounterType;
import mage.game.permanent.token.SaprolingToken;
import java.util.UUID;
/**
*
* @author LevelX2
@ -30,7 +30,7 @@ public final class Mycoloth extends CardImpl {
this.toughness = new MageInt(4);
// Devour 2 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
// At the beginning of your upkeep, create a 1/1 green Saproling creature token for each +1/+1 counter on Mycoloth.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(

View file

@ -1,9 +1,7 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
@ -12,6 +10,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -30,7 +30,7 @@ public final class PredatorDragon extends CardImpl {
this.addAbility(HasteAbility.getInstance());
// Devour 2 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
}
private PredatorDragon(final PredatorDragon card) {

View file

@ -1,13 +1,11 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
@ -17,6 +15,8 @@ import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
*
* @author LevelX2
@ -34,7 +34,7 @@ public final class PreyseizerDragon extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// Devour 2 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
// Whenever Preyseizer Dragon attacks, it deals damage to any target equal to the number of +1/+1 counters on Preyseizer Dragon.
Ability ability = new AttacksTriggeredAbility(new DamageTargetEffect(new CountersSourceCount(CounterType.P1P1)), false);

View file

@ -5,7 +5,6 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageMultiEffect;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -37,7 +36,7 @@ public final class RavenousGigantotherium extends CardImpl {
this.toughness = new MageInt(3);
// Devour 3
this.addAbility(new DevourAbility(DevourEffect.DevourFactor.Devour3));
this.addAbility(new DevourAbility(3));
// When Ravenous Gigantotherium enters the battlefield, it deals X damage divided as you choose among up to X target creatures, where X is its power. Each of those creatures deals damage equal to its power to Ravenous Gigantotherium.
this.addAbility(new RavenousGigantotheriumAbility());

View file

@ -1,11 +1,9 @@
package mage.cards.s;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.dynamicvalue.common.DevouredCreaturesCount;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
@ -13,6 +11,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -27,7 +27,7 @@ public final class Skullmulcher extends CardImpl {
this.toughness = new MageInt(3);
// Devour 1 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
// When Skullmulcher enters the battlefield, draw a card for each creature it devoured.
this.addAbility(new EntersBattlefieldTriggeredAbility(

View file

@ -1,12 +1,10 @@
package mage.cards.t;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.dynamicvalue.common.DevouredCreaturesCount;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.effects.common.discard.DiscardTargetEffect;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
@ -15,6 +13,8 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.target.TargetPlayer;
import java.util.UUID;
/**
*
* @author LevelX2
@ -29,7 +29,7 @@ public final class TarFiend extends CardImpl {
this.toughness = new MageInt(4);
// Devour 2 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
// When Tar Fiend enters the battlefield, target player discards a card for each creature it devoured.
Ability ability = new EntersBattlefieldTriggeredAbility(new DiscardTargetEffect(new DevouredCreaturesCount()));

View file

@ -1,21 +1,21 @@
package mage.cards.t;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import java.util.UUID;
/**
*
* @author LevelX2
@ -31,7 +31,7 @@ public final class ThornThrashViashino extends CardImpl {
this.toughness = new MageInt(2);
// Devour 2 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour2));
this.addAbility(new DevourAbility(2));
// {G}: Thorn-Thrash Viashino gains trample until end of turn.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(TrampleAbility.getInstance(), Duration.EndOfTurn),new ManaCostsImpl<>("{G}")));

View file

@ -1,9 +1,7 @@
package mage.cards.t;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -11,6 +9,8 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -18,7 +18,7 @@ import mage.constants.SuperType;
public final class ThromokTheInsatiable extends CardImpl {
public ThromokTheInsatiable(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}{G}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HELLION);
@ -26,7 +26,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(new DevourAbility(DevourFactor.DevourX));
this.addAbility(DevourAbility.DevourX());
}
private ThromokTheInsatiable(final ThromokTheInsatiable card) {

View file

@ -1,15 +1,15 @@
package mage.cards.t;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author LevelX2
@ -25,7 +25,7 @@ public final class ThunderThrashElder extends CardImpl {
this.toughness = new MageInt(1);
// Devour 3 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with twice that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour3));
this.addAbility(new DevourAbility(3));
}
private ThunderThrashElder(final ThunderThrashElder card) {

View file

@ -7,7 +7,6 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
@ -36,7 +35,7 @@ public final class VoraciousDragon extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// Devour 1 (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with that many +1/+1 counters on it.)
this.addAbility(new DevourAbility(DevourFactor.Devour1));
this.addAbility(new DevourAbility(1));
// When Voracious Dragon enters the battlefield, it deals damage to any target equal to twice the number of Goblins it devoured.
Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(TwiceDevouredGoblins.instance, "it"), false);

View file

@ -99,6 +99,7 @@ public final class TalesOfMiddleEarthCommander extends ExpansionSet {
cards.add(new SetCardInfo("Farmer Cotton", 55, Rarity.RARE, mage.cards.f.FarmerCotton.class));
cards.add(new SetCardInfo("Farseek", 244, Rarity.COMMON, mage.cards.f.Farseek.class));
cards.add(new SetCardInfo("Fealty to the Realm", 21, Rarity.RARE, mage.cards.f.FealtyToTheRealm.class));
cards.add(new SetCardInfo("Feasting Hobbit", 37, Rarity.RARE, mage.cards.f.FeastingHobbit.class));
cards.add(new SetCardInfo("Feed the Swarm", 200, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class));
cards.add(new SetCardInfo("Fell the Mighty", 167, Rarity.RARE, mage.cards.f.FellTheMighty.class));
cards.add(new SetCardInfo("Field of Ruin", 308, Rarity.UNCOMMON, mage.cards.f.FieldOfRuin.class));

View file

@ -0,0 +1,354 @@
package org.mage.test.cards.abilities.keywords;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* 702.82. Devour
* <p>
* 702.82a Devour is a static ability. Devour N means As this object enters the battlefield, you may sacrifice any number of creatures. This permanent enters the battlefield with N +1/+1 counters on it for each creature sacrificed this way.
* <p>
* 702.82b Some objects have abilities that refer to the number of creatures the permanent devoured. It devoured means sacrificed as a result of its devour ability as it entered the battlefield.
* <p>
* 702.82c Devour [quality] is a variant of devour. Devour [quality] N means As this object enters the battlefield, you may sacrifice any number of [quality] permanents. This permanent enters the battlefield with N +1/+1 counters on it for each permanent sacrificed this way.
*
* @author Susucr
*/
public class DevourTest extends CardTestPlayerBase {
private void expectedPossibleTest(
String devourer,
String devourTargets,
int assertCounter,
boolean assertLion,
boolean assertMyr,
boolean assertGinger,
boolean assertRelic
) {
setStrictChooseMode(true);
// Chromatic Orrery
// {7}
// Legendary Artifact
//
// You may spend mana as though it were mana of any color.
//
// {T}: Add {C}{C}{C}{C}{C}.
//
// {5}, {T}: Draw a card for each color among permanents you control.
addCard(Zone.BATTLEFIELD, playerA, "Chromatic Orrery", 1);
addCard(Zone.HAND, playerA, devourer);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Creature
addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); // Creature Artifact
addCard(Zone.BATTLEFIELD, playerA, "Gingerbrute"); // Artifact Creature Food Golem
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic"); // Artifact
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, devourer);
if (devourTargets == "") {
setChoice(playerA, false); // no to devour
} else {
setChoice(playerA, true); // yes to devour
addTarget(playerA, devourTargets); // devour targets.
}
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
if (devourer.equals(thromok) && assertCounter == 0) {
// Thromok is a 0/0, devouring nothing, it just dies due to SBA.
assertGraveyardCount(playerA, devourer, 1);
} else {
assertCounterCount(playerA, devourer, CounterType.P1P1, assertCounter);
}
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, "Gingerbrute", assertGinger ? 1 : 0);
assertGraveyardCount(playerA, "Gingerbrute", assertGinger ? 0 : 1);
assertPermanentCount(playerA, "Darksteel Relic", assertRelic ? 1 : 0);
assertGraveyardCount(playerA, "Darksteel Relic", assertRelic ? 0 : 1);
}
private void expectedIllegalTest(
String devourer,
String devourTargets
) {
setStrictChooseMode(true);
// Chromatic Orrery
// {7}
// Legendary Artifact
//
// You may spend mana as though it were mana of any color.
//
// {T}: Add {C}{C}{C}{C}{C}.
//
// {5}, {T}: Draw a card for each color among permanents you control.
addCard(Zone.BATTLEFIELD, playerA, "Chromatic Orrery", 1);
addCard(Zone.HAND, playerA, devourer);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Creature
addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); // Creature Artifact
addCard(Zone.BATTLEFIELD, playerA, "Gingerbrute"); // Artifact Creature Food Golem
addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic"); // Artifact
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, devourer);
setChoice(playerA, true); // yes to devour
addTarget(playerA, devourTargets); // devour targets.
boolean legal = true;
try {
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
} catch (AssertionError e) {
if (e.getMessage().startsWith("PlayerA - Targets list was setup by addTarget with [" + devourTargets + "], but not used")) {
legal = false;
}
} finally {
assert !legal;
}
}
// Gorger Wurm
// {3}{R}{G}
// Creature Wurm
//
// Devour 1
//
// 5/5
private static final String gorgerWurm = "Gorger Wurm";
@Test
public void Wurm_NoDevour() {
expectedPossibleTest(gorgerWurm, "",
1 * 0, true, true, true, true);
}
@Test
public void Wurm_OneDevour() {
expectedPossibleTest(gorgerWurm, "Alpha Myr",
1 * 1, true, false, true, true);
}
@Test
public void Wurm_TwoDevour() {
expectedPossibleTest(gorgerWurm, "Alpha Myr^Gingerbrute",
1 * 2, true, false, false, true);
}
@Test
public void Wurm_ThreeDevour() {
expectedPossibleTest(gorgerWurm, "Alpha Myr^Gingerbrute^Silvercoat Lion",
1 * 3, false, false, false, true);
}
@Test
public void Wurm_IllegalDevour() {
expectedIllegalTest(gorgerWurm, "Darksteel Relic");
}
// Thromok the Insatiable
// {3}{R}{G}
// Legendary Creature Hellion
//
// Devour X, where X is the number of creatures devoured this way
//
// 0/0
private static final String thromok = "Thromok the Insatiable";
@Test
public void Thromok_NoDevour() {
expectedPossibleTest(thromok, "",
0 * 0, true, true, true, true);
}
@Test
public void Thromok_OneDevour() {
expectedPossibleTest(thromok, "Alpha Myr",
1 * 1, true, false, true, true);
}
@Test
public void Thromok_TwoDevour() {
expectedPossibleTest(thromok, "Alpha Myr^Gingerbrute",
2 * 2, true, false, false, true);
}
@Test
public void Thromok_ThreeDevour() {
expectedPossibleTest(thromok, "Alpha Myr^Gingerbrute^Silvercoat Lion",
3 * 3, false, false, false, true);
}
@Test
public void Thromok_IllegalDevour() {
expectedIllegalTest(thromok, "Darksteel Relic");
}
// Feasting Hobbit
// {1}{G}
// Creature Halfling Citizen
//
// Devour Food 3
//
// Creatures with power less than Feasting Hobbits power cant block it.
private static final String hobbit = "Feasting Hobbit";
@Test
public void Hobbit_NoDevour() {
expectedPossibleTest(hobbit, "",
3 * 0, true, true, true, true);
}
@Test
public void Hobbit_OneDevour() {
expectedPossibleTest(hobbit, "Gingerbrute",
3 * 1, true, true, false, true);
}
@Test
public void Hobbit_IllegalDevour() {
expectedIllegalTest(hobbit, "Alpha Myr");
}
// Caprichrome
// {3}{W}
// Artifact Creature Goat
//
// Flash
//
// Vigilance
//
// Devour artifact 1
//
// 2/2
private static final String caprichrome = "Caprichrome";
@Test
public void Caprichrome_NoDevour() {
expectedPossibleTest(caprichrome, "",
1 * 0, true, true, true, true);
}
@Test
public void Caprichrome_OneDevour() {
expectedPossibleTest(caprichrome, "Alpha Myr",
1 * 1, true, false, true, true);
}
@Test
public void Caprichrome_TwoDevour() {
expectedPossibleTest(caprichrome, "Alpha Myr^Gingerbrute",
1 * 2, true, false, false, true);
}
@Test
public void Caprichrome_ThreeDevour() {
expectedPossibleTest(caprichrome, "Alpha Myr^Gingerbrute^Darksteel Relic",
1 * 3, true, false, false, false);
}
@Test
public void Caprichrome_IllegalDevour() {
expectedIllegalTest(caprichrome, "Silvercoat Lion");
}
// Hellkite Hatchling
// {2}{R}{G}
// Creature Dragon
//
// Devour 1
//
// Hellkite Hatchling has flying and trample if it devoured a creature.
//
// 2/2
private static final String hatchling = "Hellkite Hatchling";
@Test
public void Hatchling_NoDevour() {
expectedPossibleTest(hatchling, "",
1 * 0, true, true, true, true);
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);
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);
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);
assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true);
assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true);
}
@Test
public void Hatchling_IllegalDevour() {
expectedIllegalTest(hatchling, "Darksteel Relic");
}
// Marrow Chomper
// {3}{B}{G}
// Creature Zombie Lizard
//
// Devour 2
//
// When Marrow Chomper enters the battlefield, you gain 2 life for each creature it devoured.
private static final String chomper = "Marrow Chomper";
@Test
public void Chomper_NoDevour() {
expectedPossibleTest(chomper, "",
2 * 0, true, true, true, true);
assertLife(playerA, 20 + 2 * 0);
}
@Test
public void Chomper_OneDevour() {
expectedPossibleTest(chomper, "Alpha Myr",
2 * 1, true, false, true, true);
assertLife(playerA, 20 + 2 * 1);
}
@Test
public void Chomper_TwoDevour() {
expectedPossibleTest(chomper, "Alpha Myr^Gingerbrute",
2 * 2, true, false, false, true);
assertLife(playerA, 20 + 2 * 2);
}
@Test
public void Chomper_ThreeDevour() {
expectedPossibleTest(chomper, "Alpha Myr^Gingerbrute^Silvercoat Lion",
2 * 3, false, false, false, true);
assertLife(playerA, 20 + 2 * 3);
}
@Test
public void Chomper_IllegalDevour() {
expectedIllegalTest(chomper, "Darksteel Relic");
}
}

View file

@ -3,11 +3,11 @@ package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.CardType;
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.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
@ -16,6 +16,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetControlledPermanent;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Collections;
@ -32,20 +33,30 @@ import java.util.UUID;
* to the number of creatures the permanent devoured. "It devoured" means
* "sacrificed as a result of its devour ability as it entered the battlefield."
*
* @author LevelX2
* @author LevelX2, Susucr
*/
public class DevourEffect extends ReplacementEffectImpl {
private final DevourFactor devourFactor;
// how many counters per devoured permanent.
// Integer.MAX_VALUE is a special value that means "X, where X is the number of devoured permanent"
private final int devourFactor;
// For text generation, the filter's message is expected to be the singular
// type word in the devour ability. e.g. "Food" "artifact" "creature".
// "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;
public DevourEffect(DevourFactor devourFactor) {
public DevourEffect(int devourFactor, FilterControlledPermanent filterDevoured) {
super(Duration.EndOfGame, Outcome.Detriment);
this.devourFactor = devourFactor;
this.filterDevoured = filterDevoured;
}
public DevourEffect(final DevourEffect effect) {
private DevourEffect(final DevourEffect effect) {
super(effect);
this.devourFactor = effect.devourFactor;
this.filterDevoured = effect.filterDevoured;
}
@Override
@ -75,12 +86,19 @@ public class DevourEffect extends ReplacementEffectImpl {
if (creature == null || controller == null) {
return false;
}
Target target = new TargetControlledPermanent(1, Integer.MAX_VALUE, devourFactor.getFilter(), true);
FilterControlledPermanent filter = new FilterControlledPermanent(filterDevoured.getMessage() + "s to devour");
for (Predicate predicate : filterDevoured.getPredicates()) {
filter.add(predicate);
}
filter.add(AnotherPredicate.instance);
Target target = new TargetControlledPermanent(1, Integer.MAX_VALUE, filter, true);
target.setRequired(false);
if (!target.canChoose(source.getControllerId(), source, game)) {
return false;
}
if (!controller.chooseUse(Outcome.Detriment, "Devour " + devourFactor.getCardType().toString().toLowerCase() + "s?", source, game)) {
if (!controller.chooseUse(Outcome.Detriment, "Devour " + filterDevoured.getMessage() + "s?", source, game)) {
return false;
}
controller.chooseTarget(Outcome.Detriment, target, source, game);
@ -96,16 +114,19 @@ public class DevourEffect extends ReplacementEffectImpl {
devouredCreatures++;
}
}
if (!game.isSimulation()) {
game.informPlayers(creature.getLogName() + " devours " + devouredCreatures + " " + devourFactor.getCardType().toString().toLowerCase() + "s");
}
game.informPlayers(creature.getLogName()
+ " devours " + devouredCreatures + " "
+ filterDevoured.getMessage() + (devouredCreatures > 1 ? "s" : "")
);
game.getState().processAction(game); // need for multistep effects
int amountCounters;
if (devourFactor == DevourFactor.DevourX) {
if (devourFactor == Integer.MAX_VALUE) {
amountCounters = devouredCreatures * devouredCreatures;
} else {
amountCounters = devouredCreatures * devourFactor.getFactor();
amountCounters = devouredCreatures * devourFactor;
}
creature.addCounters(CounterType.P1P1.createInstance(amountCounters), source.getControllerId(), source, game);
game.getState().setValue(creature.getId().toString() + "devoured", creaturesDevoured);
@ -114,13 +135,38 @@ public class DevourEffect extends ReplacementEffectImpl {
@Override
public String getText(Mode mode) {
StringBuilder sb = new StringBuilder(devourFactor.toString());
sb.append(" <i>(As this enters the battlefield, you may sacrifice any number of ");
sb.append(devourFactor.getCardType());
sb.append("s. This creature enters the battlefield with ");
sb.append(devourFactor.getRuleText());
sb.append(")</i>");
return sb.toString();
String text = "Devour ";
String filterMessage = filterDevoured.getMessage();
if (!filterMessage.equals("creature")) {
text += filterMessage + " ";
}
if (devourFactor == Integer.MAX_VALUE) {
text += "X, where X is the number of " + filterMessage + "s devoured this way";
} else {
text += devourFactor;
}
text += " <i>(As this enters the battlefield, you may sacrifice any number of "
+ filterMessage + "s. "
+ "This creature enters the battlefield with ";
if (devourFactor == Integer.MAX_VALUE) {
text += "X +1/+1 counters on it for each of those creatures";
} else {
if (devourFactor == 2) {
text += "twice ";
} else if (devourFactor > 2) {
text += CardUtil.numberToText(devourFactor) + " times ";
}
text += "that many +1/+1 counters on it";
}
text += ".)</i>";
return text;
}
public List<Permanent> getDevouredCreatures(Game game, UUID permanentId) {
@ -143,59 +189,4 @@ public class DevourEffect extends ReplacementEffectImpl {
public DevourEffect copy() {
return new DevourEffect(this);
}
public enum DevourFactor {
Devour1("Devour 1", "that many +1/+1 counters on it", 1),
Devour2("Devour 2", "twice that many +1/+1 counters on it", 2),
Devour3("Devour 3", "three times that many +1/+1 counters on it", 3),
DevourArtifact1("Devour artifact 1", "that many +1/+1 counters on it", 1, CardType.ARTIFACT),
DevourX("Devour X, where X is the number of creatures devoured this way", "X +1/+1 counters on it for each of those creatures", Integer.MAX_VALUE);
private final String text;
private final String ruleText;
private final int factor;
private final CardType cardType;
private final FilterControlledPermanent filter;
DevourFactor(String text, String ruleText, int factor) {
this(text, ruleText, factor, CardType.CREATURE);
}
DevourFactor(String text, String ruleText, int factor, CardType cardType) {
this.text = text;
this.ruleText = ruleText;
this.factor = factor;
this.cardType = cardType;
this.filter = makeFilter(cardType);
}
@Override
public String toString() {
return text;
}
public String getRuleText() {
return ruleText;
}
public int getFactor() {
return factor;
}
public CardType getCardType() {
return cardType;
}
public FilterControlledPermanent getFilter() {
return filter;
}
private static final FilterControlledPermanent makeFilter(CardType cardType) {
FilterControlledPermanent filter = new FilterControlledPermanent(cardType.toString().toLowerCase() + "s to devour");
filter.add(cardType.getPredicate());
filter.add(AnotherPredicate.instance);
return filter;
}
}
}

View file

@ -3,8 +3,9 @@ package mage.abilities.keyword;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.effects.common.DevourEffect.DevourFactor;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterControlledPermanent;
/**
* 502.82. Devour
@ -40,15 +41,28 @@ import mage.constants.Zone;
* can't sacrifice the same creature to satisfy multiple devour abilities.) All
* creatures devoured this way are sacrificed at the same time.
*
* @author LevelX2
* @author LevelX2, Susucr
*/
public class DevourAbility extends SimpleStaticAbility {
public DevourAbility(DevourFactor devourFactor) {
super(Zone.ALL, new DevourEffect(devourFactor));
private static final FilterControlledPermanent 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() {
return new DevourAbility(Integer.MAX_VALUE);
}
public DevourAbility(final DevourAbility ability) {
public DevourAbility(int devourFactor) {
this(devourFactor, filterCreature);
}
public DevourAbility(int devourFactor, FilterControlledPermanent filterDevoured) {
super(Zone.ALL, new DevourEffect(devourFactor, filterDevoured));
}
private DevourAbility(final DevourAbility ability) {
super(ability);
}

View file

@ -1,7 +1,6 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.effects.common.DevourEffect;
import mage.abilities.keyword.DevourAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.constants.CardType;
@ -21,7 +20,7 @@ public final class DragonBroodmotherDragonToken extends TokenImpl {
power = new MageInt(1);
toughness = new MageInt(1);
addAbility(FlyingAbility.getInstance());
addAbility(new DevourAbility(DevourEffect.DevourFactor.Devour2));
addAbility(new DevourAbility(2));
}
public DragonBroodmotherDragonToken(final DragonBroodmotherDragonToken token) {