[OTJ] Implement Vengeful Townsfolk

This commit is contained in:
Susucre 2024-03-30 20:43:33 +01:00
parent 503368b54c
commit 559be38b00
4 changed files with 263 additions and 0 deletions

View file

@ -0,0 +1,52 @@
package mage.cards.v;
import mage.MageInt;
import mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import java.util.UUID;
/**
* @author Susucr
*/
public final class VengefulTownsfolk extends CardImpl {
public static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other creatures you control");
static {
filter.add(AnotherPredicate.instance);
filter.add(TargetController.YOU.getControllerPredicate());
}
public VengefulTownsfolk(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CITIZEN);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Whenever one or more other creatures you control die, put a +1/+1 counter on Vengeful Townsfolk.
this.addAbility(new DiesOneOrMoreCreatureTriggeredAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)),
filter
));
}
private VengefulTownsfolk(final VengefulTownsfolk card) {
super(card);
}
@Override
public VengefulTownsfolk copy() {
return new VengefulTownsfolk(this);
}
}

View file

@ -171,6 +171,7 @@ public final class OutlawsOfThunderJunction extends ExpansionSet {
cards.add(new SetCardInfo("Treasure Dredger", 110, Rarity.UNCOMMON, mage.cards.t.TreasureDredger.class));
cards.add(new SetCardInfo("Tumbleweed Rising", 187, Rarity.COMMON, mage.cards.t.TumbleweedRising.class));
cards.add(new SetCardInfo("Unscrupulous Contractor", 112, Rarity.UNCOMMON, mage.cards.u.UnscrupulousContractor.class));
cards.add(new SetCardInfo("Vengeful Townsfolk", 37, Rarity.COMMON, mage.cards.v.VengefulTownsfolk.class));
cards.add(new SetCardInfo("Vial Smasher, Gleeful Grenadier", 235, Rarity.UNCOMMON, mage.cards.v.VialSmasherGleefulGrenadier.class));
cards.add(new SetCardInfo("Visage Bandit", 76, Rarity.UNCOMMON, mage.cards.v.VisageBandit.class));
cards.add(new SetCardInfo("Voracious Varmint", 188, Rarity.COMMON, mage.cards.v.VoraciousVarmint.class));

View file

@ -0,0 +1,148 @@
package org.mage.test.cards.triggers.dies;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* Tests the {@link mage.abilities.common.DiesOneOrMoreCreatureTriggeredAbility} batching.
*
* @author Susucr
*/
public class VengefulTownsfolkTest extends CardTestPlayerBase {
/**
* {@link mage.cards.v.VengefulTownsfolk Vengeful Townsfolk}
* Creature Human Citizen
* Whenever one or more other creatures you control die, put a +1/+1 counter on Vengeful Townsfolk.
* 3/3
*/
private static final String townsfolk = "Vengeful Townsfolk";
// Choose up to one creature. Destroy the rest.
private static final String duneblast = "Duneblast";
@Test
public void testOnTokens() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, townsfolk);
addCard(Zone.HAND, playerA, "Raise the Alarm", 1); // 2 1/1 tokens
addCard(Zone.HAND, playerB, duneblast, 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 3);
addCard(Zone.BATTLEFIELD, playerB, "Forest", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raise the Alarm");
setChoice(playerB, townsfolk); // do not destroy townsfolk
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, duneblast);
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, duneblast, 1);
assertPermanentCount(playerA, 1 + 2);
assertPermanentCount(playerA, townsfolk, 1);
assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1);
}
@Test
public void testOnNonToken() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, townsfolk);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 3);
addCard(Zone.HAND, playerB, duneblast, 1);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 3);
addCard(Zone.BATTLEFIELD, playerB, "Forest", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
setChoice(playerB, townsfolk); // do not destroy townsfolk
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, duneblast);
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, duneblast, 1);
assertGraveyardCount(playerA, "Grizzly Bears", 3);
assertPermanentCount(playerA, 1);
assertPermanentCount(playerA, townsfolk, 1);
assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1);
}
@Test
public void testTwoSeparateDestroy() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, townsfolk);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1);
addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard", 1);
addCard(Zone.HAND, playerB, "Doom Blade", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Elite Vanguard");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Grizzly Bears");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, "Doom Blade", 2);
assertGraveyardCount(playerA, "Grizzly Bears", 1);
assertGraveyardCount(playerA, "Elite Vanguard", 1);
assertPermanentCount(playerA, 1);
assertPermanentCount(playerA, townsfolk, 1);
assertPowerToughness(playerA, townsfolk, 3 + 2, 3 + 2);
}
@Test
public void testControllerMatters() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, townsfolk);
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1);
addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard", 1);
addCard(Zone.HAND, playerB, "Doom Blade", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Elite Vanguard");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Grizzly Bears");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, "Doom Blade", 2);
assertGraveyardCount(playerB, "Grizzly Bears", 1);
assertGraveyardCount(playerA, "Elite Vanguard", 1);
assertPermanentCount(playerA, 1);
assertPermanentCount(playerA, townsfolk, 1);
assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1);
}
@Test
public void testDoubleSacrifice() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, townsfolk);
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears");
addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard");
addCard(Zone.HAND, playerB, "Barter in Blood", 1);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Barter in Blood");
// sacrificing both, those are moved to graveyard at same time
setChoice(playerA, "Grizzly Bears");
setChoice(playerA, "Elite Vanguard");
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, "Barter in Blood", 1);
assertGraveyardCount(playerA, "Grizzly Bears", 1);
assertGraveyardCount(playerA, "Elite Vanguard", 1);
assertPermanentCount(playerA, 1);
assertPermanentCount(playerA, townsfolk, 1);
assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1);
}
}

View file

@ -0,0 +1,62 @@
package mage.abilities.common;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeBatchEvent;
import mage.game.events.ZoneChangeEvent;
import java.util.Objects;
/**
* @author Susucr
*/
public class DiesOneOrMoreCreatureTriggeredAbility extends TriggeredAbilityImpl {
private final FilterCreaturePermanent filter;
public DiesOneOrMoreCreatureTriggeredAbility(Effect effect, FilterCreaturePermanent filter) {
super(Zone.BATTLEFIELD, effect, false);
this.filter = filter;
this.setTriggerPhrase("Whenever one or more " + filter.getMessage() + " die,");
}
private DiesOneOrMoreCreatureTriggeredAbility(final DiesOneOrMoreCreatureTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
}
@Override
public DiesOneOrMoreCreatureTriggeredAbility copy() {
return new DiesOneOrMoreCreatureTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return ((ZoneChangeBatchEvent) event)
.getEvents()
.stream()
.filter(ZoneChangeEvent::isDiesEvent)
.map(ZoneChangeEvent::getTargetId)
.map(game::getPermanentOrLKIBattlefield)
.filter(Objects::nonNull)
.anyMatch(p -> filter.match(p, getControllerId(), this, game));
}
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return ((ZoneChangeBatchEvent) event)
.getEvents()
.stream()
.allMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, e, game));
}
}