[PIP] Implement Glowing One & Ingesting Radroach

This commit is contained in:
Susucre 2024-04-18 19:28:15 +02:00
parent 0da12c4bc2
commit 9a21f24f2c
8 changed files with 374 additions and 11 deletions

View file

@ -3,7 +3,7 @@ package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.SourceInGraveyardCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
@ -34,7 +34,7 @@ public final class ArdenAngel extends CardImpl {
// At the beginning of your upkeep, if Arden Angel is in your graveyard, roll a four-sided die. If the result is 1, return Arden Angel from your graveyard to the battlefield.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new BeginningOfUpkeepTriggeredAbility(new ArdenAngelEffect(), TargetController.YOU, false),
ArdenAngelCondition.instance, "At the beginning of your upkeep, if {this} is in your graveyard, " +
SourceInGraveyardCondition.instance, "At the beginning of your upkeep, if {this} is in your graveyard, " +
"roll a four-sided die. If the result is 1, return {this} from your graveyard to the battlefield."
));
}
@ -49,15 +49,6 @@ public final class ArdenAngel extends CardImpl {
}
}
enum ArdenAngelCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD;
}
}
class ArdenAngelEffect extends OneShotEffect {
ArdenAngelEffect() {

View file

@ -0,0 +1,59 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.MillTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.DeathtouchAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.filter.common.FilterNonlandCard;
import java.util.UUID;
/**
* @author Susucr
*/
public final class GlowingOne extends CardImpl {
private static final FilterCard filter = new FilterNonlandCard();
public GlowingOne(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.ZOMBIE);
this.subtype.add(SubType.MUTANT);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Deathtouch
this.addAbility(DeathtouchAbility.getInstance());
// Whenever Glowing One deals combat damage to a player, they get four rad counters.
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(
new AddCountersTargetEffect(CounterType.RAD.createInstance(4)), false, true)
);
// Whenever a player mills a nonland card, you gain 1 life.
this.addAbility(new MillTriggeredAbility(
Zone.BATTLEFIELD, new GainLifeEffect(1),
TargetController.ANY, filter, false
));
}
private GlowingOne(final GlowingOne card) {
super(card);
}
@Override
public GlowingOne copy() {
return new GlowingOne(this);
}
}

View file

@ -0,0 +1,68 @@
package mage.cards.i;
import mage.MageInt;
import mage.abilities.common.CantBlockAbility;
import mage.abilities.common.DealsDamageToAPlayerTriggeredAbility;
import mage.abilities.common.MillTriggeredAbility;
import mage.abilities.condition.common.SourceInGraveyardCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.dynamicvalue.common.SavedDamageValue;
import mage.abilities.effects.common.ReturnToHandSourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.filter.common.FilterNonlandCard;
import java.util.UUID;
/**
* @author Susucr
*/
public final class InfestingRadroach extends CardImpl {
private static final FilterCard filter = new FilterNonlandCard();
public InfestingRadroach(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
this.subtype.add(SubType.INSECT);
this.subtype.add(SubType.MUTANT);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Infesting Radroach can't block.
this.addAbility(new CantBlockAbility());
// Whenever Infesting Radroach deals combat damage to a player, they get that many rad counters.
this.addAbility(new DealsDamageToAPlayerTriggeredAbility(
new AddCountersTargetEffect(CounterType.RAD.createInstance(), SavedDamageValue.MANY),
false, true
));
// Whenever an opponent mills a nonland card, if Infesting Radroach is in your graveyard, you may return it to your hand.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new MillTriggeredAbility(Zone.GRAVEYARD, new ReturnToHandSourceEffect(), TargetController.OPPONENT, filter, true),
SourceInGraveyardCondition.instance, "Whenever an opponent mills a nonland card, "
+ "if {this} is in your graveyard, you may return it to your hand"
));
}
private InfestingRadroach(final InfestingRadroach card) {
super(card);
}
@Override
public InfestingRadroach copy() {
return new InfestingRadroach(this);
}
}

View file

@ -131,6 +131,7 @@ public final class Fallout extends ExpansionSet {
cards.add(new SetCardInfo("General's Enforcer", 217, Rarity.UNCOMMON, mage.cards.g.GeneralsEnforcer.class));
cards.add(new SetCardInfo("Glacial Fortress", 266, Rarity.RARE, mage.cards.g.GlacialFortress.class));
cards.add(new SetCardInfo("Glimmer of Genius", 176, Rarity.UNCOMMON, mage.cards.g.GlimmerOfGenius.class));
cards.add(new SetCardInfo("Glowing One", 76, Rarity.UNCOMMON, mage.cards.g.GlowingOne.class));
cards.add(new SetCardInfo("Grave Titan", 346, Rarity.MYTHIC, mage.cards.g.GraveTitan.class));
cards.add(new SetCardInfo("Guardian Project", 199, Rarity.RARE, mage.cards.g.GuardianProject.class));
cards.add(new SetCardInfo("Gunner Conscript", 77, Rarity.UNCOMMON, mage.cards.g.GunnerConscript.class));
@ -146,6 +147,7 @@ public final class Fallout extends ExpansionSet {
cards.add(new SetCardInfo("Ian the Reckless", 59, Rarity.UNCOMMON, mage.cards.i.IanTheReckless.class));
cards.add(new SetCardInfo("Impassioned Orator", 162, Rarity.COMMON, mage.cards.i.ImpassionedOrator.class));
cards.add(new SetCardInfo("Inexorable Tide", 177, Rarity.RARE, mage.cards.i.InexorableTide.class));
cards.add(new SetCardInfo("Infesting Radroach", 46, Rarity.UNCOMMON, mage.cards.i.InfestingRadroach.class));
cards.add(new SetCardInfo("Inspiring Call", 203, Rarity.UNCOMMON, mage.cards.i.InspiringCall.class));
cards.add(new SetCardInfo("Intangible Virtue", 163, Rarity.UNCOMMON, mage.cards.i.IntangibleVirtue.class));
cards.add(new SetCardInfo("Intelligence Bobblehead", 134, Rarity.UNCOMMON, mage.cards.i.IntelligenceBobblehead.class));

View file

@ -0,0 +1,63 @@
package org.mage.test.cards.single.pip;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class GlowingOneTest extends CardTestPlayerBase {
/**
* {@link mage.cards.g.GlowingOne Glowing One} {2}{G}
* Creature Zombie Mutant
* Deathtouch
* Whenever Glowing One deals combat damage to a player, they get four rad counters.
* Whenever a player mills a nonland card, you gain 1 life.
* 2/2
*/
private static final String glowing = "Glowing One";
@Test
public void test_MillSelf() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.BATTLEFIELD, playerA, glowing);
addCard(Zone.HAND, playerA, "Stitcher's Supplier"); // etb, mill 3
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.LIBRARY, playerA, "Goblin Piker", 2);
addCard(Zone.LIBRARY, playerA, "Taiga", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stitcher's Supplier");
setChoice(playerA, "Whenever"); // 2 triggers to stack.
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, 3);
assertLife(playerA, 20 + 2);
}
@Test
public void test_MillOpponent() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.BATTLEFIELD, playerA, glowing);
addCard(Zone.BATTLEFIELD, playerB, "Swamp");
addCard(Zone.LIBRARY, playerB, "Goblin Piker", 1);
addCard(Zone.LIBRARY, playerB, "Taiga", 2);
addCard(Zone.LIBRARY, playerB, "Stitcher's Supplier"); // etb, mill 3
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Stitcher's Supplier");
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, 3);
assertLife(playerA, 20 + 1);
}
}

View file

@ -0,0 +1,80 @@
package org.mage.test.cards.single.pip;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class InfestingRadroachTest extends CardTestPlayerBase {
/**
* {@link mage.cards.i.InfestingRadroach Infesting Radroach} {2}{B}
* Creature Insect Mutant
* Flying
* Infesting Radroach cant block.
* Whenever Infesting Radroach deals combat damage to a player, they get that many rad counters.
* Whenever an opponent mills a nonland card, if Infesting Radroach is in your graveyard, you may return it to your hand.
* 2/2
*/
private static final String radroach = "Infesting Radroach";
@Test
public void test_MillSelf() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.GRAVEYARD, playerA, radroach);
addCard(Zone.HAND, playerA, "Stitcher's Supplier"); // etb, mill 3
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.LIBRARY, playerA, "Goblin Piker", 2);
addCard(Zone.LIBRARY, playerA, "Taiga", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stitcher's Supplier");
// no trigger
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, 4);
}
@Test
public void test_MillOpponent() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.GRAVEYARD, playerA, radroach);
addCard(Zone.BATTLEFIELD, playerB, "Swamp");
addCard(Zone.LIBRARY, playerB, "Goblin Piker", 2);
addCard(Zone.LIBRARY, playerB, "Taiga", 1);
addCard(Zone.LIBRARY, playerB, "Stitcher's Supplier"); // etb, mill 3
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Stitcher's Supplier");
setChoice(playerA, "Whenever"); // 2 triggers
setChoice(playerA, true); // yes to first, second trigger fizzles
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, 3);
assertHandCount(playerA, radroach, 1);
}
@Test
public void test_Damage() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, radroach);
attack(1, playerA, radroach, playerB);
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertCounterCount(playerB, CounterType.RAD, 2);
}
}

View file

@ -0,0 +1,79 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.cards.Card;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
* @author Susucr
*/
public class MillTriggeredAbility extends TriggeredAbilityImpl {
private final TargetController targetController;
private final FilterCard filter;
public MillTriggeredAbility(Zone zone, Effect effect, TargetController targetController, FilterCard filter, boolean optional) {
super(zone, effect, optional);
this.targetController = targetController;
this.filter = filter;
setTriggerPhrase(generateTriggerPhrase());
}
private MillTriggeredAbility(final MillTriggeredAbility ability) {
super(ability);
this.targetController = ability.targetController;
this.filter = ability.filter;
}
@Override
public MillTriggeredAbility copy() {
return new MillTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.MILLED_CARD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
UUID playerId = event.getPlayerId();
switch (targetController) {
case ANY:
// no check.
break;
case OPPONENT:
if (!game.getOpponents(getControllerId()).contains(playerId)) {
return false;
}
break;
default:
throw new IllegalArgumentException("Wrong code usage. targetController not yet supported: " + targetController);
}
Card card = game.getCard(event.getTargetId());
return card != null && filter.match(card, getControllerId(), this, game);
}
private String generateTriggerPhrase() {
String text = "Whenever ";
switch (targetController) {
case ANY:
text += "a player ";
break;
case OPPONENT:
text += "an opponent ";
break;
default:
throw new IllegalArgumentException("Wrong code usage. targetController not yet supported: " + targetController);
}
return text + "mills a " + filter.getMessage() + ", ";
}
}

View file

@ -0,0 +1,21 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.constants.Zone;
import mage.game.Game;
/**
* If {this} is in your graveyard
*
* @author Susucr
*/
public enum SourceInGraveyardCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD;
}
}