From 24687eb4afdcdb8ea893b6be84bed16f8e5696a1 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 8 Dec 2024 20:25:51 -0500 Subject: [PATCH] implement [CLB] Hezrou --- Mage.Sets/src/mage/cards/h/Hezrou.java | 121 ++++++++++++++++++ .../src/mage/cards/s/SizzlingBarrage.java | 52 +------- .../CommanderLegendsBattleForBaldursGate.java | 1 + .../test/cards/single/clb/HezrouTest.java | 84 ++++++++++++ .../permanent/BlockedThisTurnPredicate.java | 26 ++++ .../common/BlockedThisTurnWatcher.java | 8 +- 6 files changed, 237 insertions(+), 55 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/h/Hezrou.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/clb/HezrouTest.java create mode 100644 Mage/src/main/java/mage/filter/predicate/permanent/BlockedThisTurnPredicate.java diff --git a/Mage.Sets/src/mage/cards/h/Hezrou.java b/Mage.Sets/src/mage/cards/h/Hezrou.java new file mode 100644 index 00000000000..97033f959ce --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/Hezrou.java @@ -0,0 +1,121 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.AdventureCard; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.BlockedThisTurnPredicate; +import mage.filter.predicate.permanent.BlockingPredicate; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.events.GameEvent; +import mage.watchers.common.BlockedThisTurnWatcher; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class Hezrou extends AdventureCard { + + private static final FilterCreaturePermanent filterBlocked = new FilterCreaturePermanent("each creature that blocked this turn"); + private static final FilterCreaturePermanent filterBlocking = new FilterCreaturePermanent("each blocking creature"); + static { + filterBlocked.add(BlockedThisTurnPredicate.instance); + filterBlocking.add(BlockingPredicate.instance); + } + + + public Hezrou(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{5}{B}{B}", "Demonic Stench", "{B}"); + + this.subtype.add(SubType.FROG); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Whenever one or more creatures you control become blocked, each blocking creature gets -1/-1 until end of turn. + this.addAbility(new HezrouTriggeredAbility(new BoostAllEffect(-1, -1, Duration.EndOfTurn, filterBlocking, false))); + + // Demonic Stench + // Each creature that blocked this turn gets -1/-1 until end of turn. + this.getSpellCard().getSpellAbility().addEffect(new BoostAllEffect(-1, -1, Duration.EndOfTurn, filterBlocked, false)); + this.getSpellCard().getSpellAbility().addWatcher(new BlockedThisTurnWatcher()); + + this.finalizeAdventure(); + } + + private Hezrou(final Hezrou card) { + super(card); + } + + @Override + public Hezrou copy() { + return new Hezrou(this); + } +} + +class HezrouTriggeredAbility extends TriggeredAbilityImpl { + + HezrouTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); + setTriggerPhrase("Whenever one or more creatures you control become blocked, "); + } + + private HezrouTriggeredAbility(final HezrouTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARE_BLOCKERS_STEP + || event.getType() == GameEvent.EventType.BATCH_BLOCK_NONCOMBAT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (event.getType()) { + case DECLARE_BLOCKERS_STEP: + return game.getCombat() + .getGroups() + .stream() + .filter(CombatGroup::getBlocked) + .map(CombatGroup::getAttackers) + .flatMap(Collection::stream) + .map(game::getControllerId) + .anyMatch(this.getControllerId()::equals); + case BATCH_BLOCK_NONCOMBAT: + Object value = game.getState().getValue("becameBlocked_" + event.getData()); + if (!(value instanceof Set)) { + return false; + } + Set permanents = (Set) value; + return permanents + .stream() + .map(mor -> mor.getPermanentOrLKIBattlefield(game)) + .filter(Objects::nonNull) + .map(Controllable::getControllerId) + .anyMatch(this.getControllerId()::equals); + default: + return false; + } + } + + @Override + public HezrouTriggeredAbility copy() { + return new HezrouTriggeredAbility(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/s/SizzlingBarrage.java b/Mage.Sets/src/mage/cards/s/SizzlingBarrage.java index b814d6ea560..4c5732ea0be 100644 --- a/Mage.Sets/src/mage/cards/s/SizzlingBarrage.java +++ b/Mage.Sets/src/mage/cards/s/SizzlingBarrage.java @@ -1,22 +1,15 @@ package mage.cards.s; -import mage.MageObjectReference; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.WatcherScope; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.filter.predicate.permanent.BlockedThisTurnPredicate; import mage.target.TargetPermanent; -import mage.watchers.Watcher; +import mage.watchers.common.BlockedThisTurnWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -27,7 +20,7 @@ public final class SizzlingBarrage extends CardImpl { private static final FilterPermanent filter = new FilterCreaturePermanent("creature that blocked this turn"); static { - filter.add(SizzlingBarragePredicate.instance); + filter.add(BlockedThisTurnPredicate.instance); } public SizzlingBarrage(UUID ownerId, CardSetInfo setInfo) { @@ -36,7 +29,7 @@ public final class SizzlingBarrage extends CardImpl { // Sizzling Barrage deals 4 damage to target creature that blocked this turn. this.getSpellAbility().addEffect(new DamageTargetEffect(4)); this.getSpellAbility().addTarget(new TargetPermanent(filter)); - this.getSpellAbility().addWatcher(new SizzlingBarrageWatcher()); + this.getSpellAbility().addWatcher(new BlockedThisTurnWatcher()); } private SizzlingBarrage(final SizzlingBarrage card) { @@ -48,40 +41,3 @@ public final class SizzlingBarrage extends CardImpl { return new SizzlingBarrage(this); } } - -enum SizzlingBarragePredicate implements Predicate { - instance; - - @Override - public boolean apply(Permanent input, Game game) { - SizzlingBarrageWatcher watcher = game.getState().getWatcher(SizzlingBarrageWatcher.class); - return watcher != null && watcher.checkCreature(input, game); - } -} - -class SizzlingBarrageWatcher extends Watcher { - - private final Set blockers = new HashSet<>(); - - SizzlingBarrageWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.BLOCKER_DECLARED) { - return; - } - blockers.add(new MageObjectReference(event.getSourceId(), game)); - } - - @Override - public void reset() { - super.reset(); - this.blockers.clear(); - } - - boolean checkCreature(Permanent permanent, Game game) { - return blockers.stream().anyMatch(mor -> mor.refersTo(permanent, game)); - } -} diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index e0647542c95..504c284a888 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -305,6 +305,7 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Hedron Archive", 861, Rarity.UNCOMMON, mage.cards.h.HedronArchive.class)); cards.add(new SetCardInfo("Herald's Horn", 862, Rarity.UNCOMMON, mage.cards.h.HeraldsHorn.class)); cards.add(new SetCardInfo("Hex", 757, Rarity.RARE, mage.cards.h.Hex.class)); + cards.add(new SetCardInfo("Hezrou", 131, Rarity.COMMON, mage.cards.h.Hezrou.class)); cards.add(new SetCardInfo("High Priest of Penance", 848, Rarity.RARE, mage.cards.h.HighPriestOfPenance.class)); cards.add(new SetCardInfo("Highland Forest", 896, Rarity.COMMON, mage.cards.h.HighlandForest.class)); cards.add(new SetCardInfo("Hoarding Ogre", 181, Rarity.COMMON, mage.cards.h.HoardingOgre.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/HezrouTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/HezrouTest.java new file mode 100644 index 00000000000..08e44084059 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/HezrouTest.java @@ -0,0 +1,84 @@ +package org.mage.test.cards.single.clb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class HezrouTest extends CardTestPlayerBase { + + private static final String hezrou = "Hezrou"; // 6/6 + // Whenever one or more creatures you control become blocked, each blocking creature gets -1/-1 until end of turn. + private static final String stench = "Demonic Stench"; // {B} Instant + // Each creature that blocked this turn gets -1/-1 until end of turn. + + private static final String kraken = "Kraken Hatchling"; // 0/4 + private static final String guard = "Maritime Guard"; // 1/3 + private static final String fortress = "Fortress Crab"; // 1/6 + private static final String turtle = "Aegis Turtle"; // 0/5 + private static final String pangolin = "Gloom Pangolin"; // 1/5 + private static final String wishcoin = "Wishcoin Crab"; // 2/5 + + private void setupCreatures() { + addCard(Zone.BATTLEFIELD, playerA, kraken); + addCard(Zone.BATTLEFIELD, playerA, guard); + addCard(Zone.BATTLEFIELD, playerA, fortress); + addCard(Zone.BATTLEFIELD, playerB, turtle); + addCard(Zone.BATTLEFIELD, playerB, pangolin); + addCard(Zone.BATTLEFIELD, playerB, wishcoin); + } + + @Test + public void testTrigger() { + setupCreatures(); + addCard(Zone.BATTLEFIELD, playerA, hezrou); + + attack(1, playerA, kraken, playerB); + attack(1, playerA, guard, playerB); + attack(1, playerA, fortress, playerB); + block(1, playerB, turtle, kraken); + block(1, playerB, pangolin, guard); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 19); + assertPowerToughness(playerA, kraken, 0, 4); + assertPowerToughness(playerA, guard, 1, 3); + assertPowerToughness(playerA, fortress, 1, 6); + assertPowerToughness(playerB, turtle, -1, 4); + assertPowerToughness(playerB, pangolin, 0, 4); + assertPowerToughness(playerB, wishcoin, 2, 5); + } + + @Test + public void testAdventure() { + setupCreatures(); + addCard(Zone.HAND, playerA, hezrou); + addCard(Zone.BATTLEFIELD, playerA, "Swamp"); + + attack(1, playerA, kraken, playerB); + attack(1, playerA, guard, playerB); + attack(1, playerA, fortress, playerB); + block(1, playerB, turtle, kraken); + block(1, playerB, pangolin, guard); + + castSpell(1, PhaseStep.END_COMBAT, playerA, stench); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 19); + assertPowerToughness(playerA, kraken, 0, 4); + assertPowerToughness(playerA, guard, 1, 3); + assertPowerToughness(playerA, fortress, 1, 6); + assertPowerToughness(playerB, turtle, -1, 4); + assertPowerToughness(playerB, pangolin, 0, 4); + assertPowerToughness(playerB, wishcoin, 2, 5); + } + +} diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/BlockedThisTurnPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/BlockedThisTurnPredicate.java new file mode 100644 index 00000000000..30718c1742f --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/BlockedThisTurnPredicate.java @@ -0,0 +1,26 @@ +package mage.filter.predicate.permanent; + +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.watchers.common.BlockedThisTurnWatcher; + +/** + * Requires BlockedThisTurnWatcher to be added to the card + * + * @author xenohedron + */ +public enum BlockedThisTurnPredicate implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + BlockedThisTurnWatcher watcher = game.getState().getWatcher(BlockedThisTurnWatcher.class); + return watcher != null && watcher.checkIfBlocked(input, game); + } + + @Override + public String toString() { + return "blocked this turn"; + } +} diff --git a/Mage/src/main/java/mage/watchers/common/BlockedThisTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/BlockedThisTurnWatcher.java index 70b08e29c3d..62804a17a2a 100644 --- a/Mage/src/main/java/mage/watchers/common/BlockedThisTurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/BlockedThisTurnWatcher.java @@ -1,4 +1,3 @@ - package mage.watchers.common; import mage.MageObjectReference; @@ -34,12 +33,7 @@ public class BlockedThisTurnWatcher extends Watcher { } public boolean checkIfBlocked(Permanent permanent, Game game) { - for (MageObjectReference mor : blockedThisTurnCreatures) { - if (mor.refersTo(permanent, game)) { - return true; - } - } - return false; + return blockedThisTurnCreatures.stream().anyMatch(mor -> mor.refersTo(permanent, game)); } @Override