diff --git a/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java b/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java new file mode 100644 index 00000000000..cad0acb0dfa --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VengefulTownsfolk.java @@ -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); + } +} diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java index 91b6f0febd3..e02c1a4bd17 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunction.java @@ -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)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java new file mode 100644 index 00000000000..182f315048c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java @@ -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); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java new file mode 100644 index 00000000000..b22eb0802e7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreCreatureTriggeredAbility.java @@ -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)); + } +} \ No newline at end of file