From 27b8d3e19869bfe192a2b80cf2a5f4fe0ff67507 Mon Sep 17 00:00:00 2001
From: Susucre <34709007+Susucre@users.noreply.github.com>
Date: Thu, 26 Oct 2023 18:06:10 +0200
Subject: [PATCH] [LCI] Implement Deeproot Pilgrimage (#11350)
---
.../src/mage/cards/d/DeeprootPilgrimage.java | 46 ++++++++
.../src/mage/sets/TheLostCavernsOfIxalan.java | 1 +
.../single/lci/DeeprootPilgrimageTest.java | 102 ++++++++++++++++++
...ecomesTappedOneOrMoreTriggeredAbility.java | 48 +++++++++
Mage/src/main/java/mage/game/GameState.java | 24 ++++-
.../main/java/mage/game/events/GameEvent.java | 7 +-
.../mage/game/events/TappedBatchEvent.java | 56 ++++++++++
.../java/mage/game/events/TappedEvent.java | 14 +++
.../mage/game/permanent/PermanentImpl.java | 4 +-
9 files changed, 298 insertions(+), 4 deletions(-)
create mode 100644 Mage.Sets/src/mage/cards/d/DeeprootPilgrimage.java
create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DeeprootPilgrimageTest.java
create mode 100644 Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java
create mode 100644 Mage/src/main/java/mage/game/events/TappedBatchEvent.java
create mode 100644 Mage/src/main/java/mage/game/events/TappedEvent.java
diff --git a/Mage.Sets/src/mage/cards/d/DeeprootPilgrimage.java b/Mage.Sets/src/mage/cards/d/DeeprootPilgrimage.java
new file mode 100644
index 00000000000..5cb0568bdc7
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/d/DeeprootPilgrimage.java
@@ -0,0 +1,46 @@
+package mage.cards.d;
+
+import mage.abilities.common.BecomesTappedOneOrMoreTriggeredAbility;
+import mage.abilities.effects.common.CreateTokenEffect;
+import mage.cards.CardImpl;
+import mage.cards.CardSetInfo;
+import mage.constants.CardType;
+import mage.constants.SubType;
+import mage.constants.Zone;
+import mage.filter.FilterPermanent;
+import mage.filter.common.FilterControlledPermanent;
+import mage.filter.predicate.permanent.TokenPredicate;
+import mage.game.permanent.token.MerfolkHexproofToken;
+
+import java.util.UUID;
+
+/**
+ * @author Susucr
+ */
+public final class DeeprootPilgrimage extends CardImpl {
+
+ private static final FilterPermanent filter = new FilterControlledPermanent(SubType.MERFOLK, "nontoken Merfolk you control");
+
+ static {
+ filter.add(TokenPredicate.FALSE);
+ }
+
+ public DeeprootPilgrimage(UUID ownerId, CardSetInfo setInfo) {
+ super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}");
+
+ // Whenever one or more nontoken Merfolk you control become tapped, create a 1/1 blue Merfolk creature token with hexproof.
+ this.addAbility(new BecomesTappedOneOrMoreTriggeredAbility(
+ Zone.BATTLEFIELD, new CreateTokenEffect(new MerfolkHexproofToken()), false, filter
+ ));
+ }
+
+ private DeeprootPilgrimage(final DeeprootPilgrimage card) {
+ super(card);
+ }
+
+ @Override
+ public DeeprootPilgrimage copy() {
+ return new DeeprootPilgrimage(this);
+ }
+}
+
diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java
index 7cc97d102be..1359e90ee3a 100644
--- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java
+++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java
@@ -27,6 +27,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet {
cards.add(new SetCardInfo("Cavern of Souls", 269, Rarity.MYTHIC, mage.cards.c.CavernOfSouls.class));
cards.add(new SetCardInfo("Cenote Scout", 178, Rarity.UNCOMMON, mage.cards.c.CenoteScout.class));
cards.add(new SetCardInfo("Chart a Course", 48, Rarity.UNCOMMON, mage.cards.c.ChartACourse.class));
+ cards.add(new SetCardInfo("Deeproot Pilgrimage", 52, Rarity.RARE, mage.cards.d.DeeprootPilgrimage.class));
cards.add(new SetCardInfo("Didact Echo", 53, Rarity.COMMON, mage.cards.d.DidactEcho.class));
cards.add(new SetCardInfo("Dinotomaton", 144, Rarity.COMMON, mage.cards.d.Dinotomaton.class));
cards.add(new SetCardInfo("Forest", 401, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS));
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DeeprootPilgrimageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DeeprootPilgrimageTest.java
new file mode 100644
index 00000000000..a247a87c9bb
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DeeprootPilgrimageTest.java
@@ -0,0 +1,102 @@
+package org.mage.test.cards.single.lci;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author Susucr
+ */
+public class DeeprootPilgrimageTest extends CardTestPlayerBase {
+
+ /**
+ * {@link mage.cards.d.DeeprootPilgrimage}
+ * Deeproot Pilgrimage {1}{U}
+ * Enchantment
+ * Whenever one or more nontoken Merfolk you control become tapped, create a 1/1 blue Merfolk creature token with hexproof.
+ */
+ private static final String pilgrimage = "Deeproot Pilgrimage";
+
+ // {2}{U}{U} Sorcery
+ // Tap all creatures target player controls. Those creatures don’t untap during that player’s next untap step.
+ private static final String sleep = "Sleep";
+
+ // 3/2 Vanilla merfolk
+ private static final String commando = "Coral Commando";
+
+ @Test
+ public void test_batch_tapped() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, pilgrimage);
+ addCard(Zone.BATTLEFIELD, playerA, commando, 4);
+
+ addCard(Zone.HAND, playerA, sleep);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sleep", playerA);
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertTappedCount(commando, true, 4);
+ assertPermanentCount(playerA, "Merfolk Token", 1);
+ }
+
+ @Test
+ public void test_triggering_on_attack() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, pilgrimage);
+ addCard(Zone.BATTLEFIELD, playerA, commando);
+
+ attack(1, playerA, commando, playerB);
+
+ setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
+ execute();
+
+ assertTappedCount(commando, true, 1);
+ assertPermanentCount(playerA, "Merfolk Token", 1);
+ }
+
+ @Test
+ public void test_not_triggering_on_non_merfolk() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, pilgrimage);
+ addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2, not a merfolk.
+
+ addCard(Zone.HAND, playerA, sleep);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sleep", playerA);
+ // no trigger, bears is no Merfolk
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertTappedCount("Grizzly Bears", true, 1);
+ assertPermanentCount(playerA, "Merfolk Token", 0);
+ }
+
+ @Test
+ public void test_not_triggering_on_opp_merfolks() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, pilgrimage);
+ addCard(Zone.BATTLEFIELD, playerB, commando);
+
+ addCard(Zone.HAND, playerA, sleep);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sleep", playerB);
+ // no trigger, as Pilgrimage only triggers on Merfolk you control
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertTappedCount(commando, true, 1);
+ assertPermanentCount(playerA, "Merfolk Token", 0);
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java
new file mode 100644
index 00000000000..8ca7082e7c6
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/common/BecomesTappedOneOrMoreTriggeredAbility.java
@@ -0,0 +1,48 @@
+package mage.abilities.common;
+
+import mage.abilities.TriggeredAbilityImpl;
+import mage.abilities.effects.Effect;
+import mage.constants.Zone;
+import mage.filter.FilterPermanent;
+import mage.game.Game;
+import mage.game.events.GameEvent;
+import mage.game.events.TappedBatchEvent;
+
+/**
+ * @author Susucr
+ */
+public class BecomesTappedOneOrMoreTriggeredAbility extends TriggeredAbilityImpl {
+
+ protected FilterPermanent filter;
+
+ public BecomesTappedOneOrMoreTriggeredAbility(Zone zone, Effect effect, boolean optional, FilterPermanent filter) {
+ super(zone, effect, optional);
+ this.filter = filter;
+ setTriggerPhrase("Whenever one or more " + filter.getMessage() + " become tapped, ");
+ }
+
+ protected BecomesTappedOneOrMoreTriggeredAbility(final BecomesTappedOneOrMoreTriggeredAbility ability) {
+ super(ability);
+ this.filter = ability.filter.copy();
+ }
+
+ @Override
+ public BecomesTappedOneOrMoreTriggeredAbility copy() {
+ return new BecomesTappedOneOrMoreTriggeredAbility(this);
+ }
+
+ @Override
+ public boolean checkEventType(GameEvent event, Game game) {
+ return event.getType() == GameEvent.EventType.TAPPED_BATCH;
+ }
+
+ @Override
+ public boolean checkTrigger(GameEvent event, Game game) {
+ TappedBatchEvent batchEvent = (TappedBatchEvent) event;
+ return batchEvent
+ .getTargets()
+ .stream()
+ .map(game::getPermanent)
+ .anyMatch(p -> filter.match(p, getControllerId(), this, game));
+ }
+}
diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java
index 895594904c0..cc0ba1f0fd2 100644
--- a/Mage/src/main/java/mage/game/GameState.java
+++ b/Mage/src/main/java/mage/game/GameState.java
@@ -1,5 +1,6 @@
package mage.game;
+import static java.util.Collections.emptyList;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
@@ -44,8 +45,6 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
-import static java.util.Collections.emptyList;
-
/**
* @author BetaSteward_at_googlemail.com
*
@@ -860,6 +859,27 @@ public class GameState implements Serializable, Copyable