diff --git a/Mage.Sets/src/mage/cards/a/AshPartyCrasher.java b/Mage.Sets/src/mage/cards/a/AshPartyCrasher.java new file mode 100644 index 00000000000..5e2d9a24d09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AshPartyCrasher.java @@ -0,0 +1,56 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.common.CelebrationCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.watchers.common.CelebrationWatcher; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class AshPartyCrasher extends CardImpl { + + public AshPartyCrasher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.PEASANT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Celebration -- Whenever Ash, Party Crasher attacks, if two or more nonland permanents entered the battlefield under your control this turn, put a +1/+1 counter on Ash. + Ability ability = new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)), false), + CelebrationCondition.instance, + "Whenever {this} attacks, if two or more nonland permanents entered the battlefield under your control this turn, put a +1/+1 counter on {this}." + ); + ability.setAbilityWord(AbilityWord.CELEBRATION); + this.addAbility(ability, new CelebrationWatcher()); + } + + private AshPartyCrasher(final AshPartyCrasher card) { + super(card); + } + + @Override + public AshPartyCrasher copy() { + return new AshPartyCrasher(this); + } +} diff --git a/Mage.Sets/src/mage/sets/WildsOfEldraine.java b/Mage.Sets/src/mage/sets/WildsOfEldraine.java index f8bd6a9b377..5010c3be79a 100644 --- a/Mage.Sets/src/mage/sets/WildsOfEldraine.java +++ b/Mage.Sets/src/mage/sets/WildsOfEldraine.java @@ -20,6 +20,7 @@ public final class WildsOfEldraine extends ExpansionSet { this.blockName = "Wilds of Eldraine"; this.hasBoosters = false; // temporary + cards.add(new SetCardInfo("Ash, Party Crasher", 201, Rarity.UNCOMMON, mage.cards.a.AshPartyCrasher.class)); cards.add(new SetCardInfo("Cruel Somnophage", 222, Rarity.RARE, mage.cards.c.CruelSomnophage.class)); cards.add(new SetCardInfo("Evolving Wilds", 256, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Faerie Dreamthief", 89, Rarity.UNCOMMON, mage.cards.f.FaerieDreamthief.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/woe/AshPartyCrasherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/woe/AshPartyCrasherTest.java new file mode 100644 index 00000000000..0e4291867dd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/woe/AshPartyCrasherTest.java @@ -0,0 +1,151 @@ +package org.mage.test.cards.single.woe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class AshPartyCrasherTest extends CardTestPlayerBase { + + /** + * Ash, Party Crasher + * {R}{W} + * Legendary Creature — Human Peasant + *

+ * Haste + * Celebration — Whenever Ash, Party Crasher attacks, if two or more nonland permanents entered the battlefield under your co + */ + private static final String ash = "Ash, Party Crasher"; + + // {1}{G} 2/2 + private static final String bears = "Grizzly Bears"; + + /** + * Instant {1}{W} + * Create two 1/1 white Soldier creature tokens. + */ + private static final String raiseTheAlarm = "Raise the Alarm"; + + @Test + public void noCelebration() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, ash); + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 2); + } + + @Test + public void celebrationItselfAndBear() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, ash); + addCard(Zone.HAND, playerA, bears); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ash, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bears); + + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 3); + } + + @Test + public void noCelebrationOnLand() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, ash); + addCard(Zone.HAND, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ash, true); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); + + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 2); + } + + @Test + public void celebrationBearThenItself() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, ash); + addCard(Zone.HAND, playerA, bears); + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bears, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ash); + + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 3); + } + + @Test + public void celebrationDoubleBear() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, ash); + addCard(Zone.HAND, playerA, bears, 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bears, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bears); + + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 3); + } + + @Test + public void celebrationRaiseTheAlarm() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, ash); + addCard(Zone.HAND, playerA, raiseTheAlarm); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, raiseTheAlarm); + + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 3); + } + + @Test + public void noCelebrationOpponentRaiseTheAlarm() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, ash); + addCard(Zone.HAND, playerB, raiseTheAlarm); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, raiseTheAlarm); + + attack(1, playerA, ash); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerB, 20 - 2); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/condition/common/CelebrationCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CelebrationCondition.java new file mode 100644 index 00000000000..1e7c9390dfe --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/CelebrationCondition.java @@ -0,0 +1,25 @@ + +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.watchers.common.CelebrationWatcher; + +/** + * @author Susucr + */ +public enum CelebrationCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + CelebrationWatcher watcher = game.getState().getWatcher(CelebrationWatcher.class); + return watcher != null && watcher.celebrationActive(source.getControllerId()); + } + + @Override + public String toString() { + return "two or more nonland permanents entered the battlefield under your control this turn"; + } +} diff --git a/Mage/src/main/java/mage/constants/AbilityWord.java b/Mage/src/main/java/mage/constants/AbilityWord.java index 467171211f4..9f3f197b921 100644 --- a/Mage/src/main/java/mage/constants/AbilityWord.java +++ b/Mage/src/main/java/mage/constants/AbilityWord.java @@ -12,6 +12,7 @@ public enum AbilityWord { ALLIANCE("Alliance"), BATTALION("Battalion"), BLOODRUSH("Bloodrush"), + CELEBRATION("Celebration"), CHANNEL("Channel"), CHROMA("Chroma"), COHORT("Cohort"), diff --git a/Mage/src/main/java/mage/watchers/common/CelebrationWatcher.java b/Mage/src/main/java/mage/watchers/common/CelebrationWatcher.java new file mode 100644 index 00000000000..d191b94f947 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/CelebrationWatcher.java @@ -0,0 +1,47 @@ +package mage.watchers.common; + +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Susucr + */ +public class CelebrationWatcher extends Watcher { + + // playerId -> number of nonland permanents entered the battlefield this turn under that player's control. + private final Map celebrationCounts = new HashMap<>(); + + public CelebrationWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + return; + } + + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null && !permanent.isLand(game)) { + celebrationCounts.compute(permanent.getControllerId(), CardUtil::setOrIncrementValue); + } + } + + public boolean celebrationActive(UUID playerId) { + return celebrationCounts.getOrDefault(playerId, 0) >= 2; + } + + @Override + public void reset() { + super.reset(); + celebrationCounts.clear(); + } +}