forked from External/mage
[LCI] Implement Deeproot Pilgrimage (#11350)
This commit is contained in:
parent
77b9faad84
commit
27b8d3e198
9 changed files with 298 additions and 4 deletions
46
Mage.Sets/src/mage/cards/d/DeeprootPilgrimage.java
Normal file
46
Mage.Sets/src/mage/cards/d/DeeprootPilgrimage.java
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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} <br>
|
||||
* Deeproot Pilgrimage {1}{U} <br>
|
||||
* Enchantment <br>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
* <p>
|
||||
|
|
@ -860,6 +859,27 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
}
|
||||
}
|
||||
|
||||
public void addSimultaneousTapped(TappedEvent tappedEvent, Game game) {
|
||||
// Combine multiple tapped events in the single event (batch)
|
||||
|
||||
boolean isTappedBatchUsed = false;
|
||||
for (GameEvent event : simultaneousEvents) {
|
||||
if (event instanceof TappedBatchEvent) {
|
||||
// Adding to the existing batch
|
||||
((TappedBatchEvent) event).addEvent(tappedEvent);
|
||||
isTappedBatchUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// new batch
|
||||
if (!isTappedBatchUsed) {
|
||||
TappedBatchEvent batch = new TappedBatchEvent();
|
||||
batch.addEvent(tappedEvent);
|
||||
addSimultaneousEvent(batch, game);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleEvent(GameEvent event, Game game) {
|
||||
watchers.watch(event, game);
|
||||
delayed.checkTriggers(event, game);
|
||||
|
|
|
|||
|
|
@ -350,12 +350,17 @@ public class GameEvent implements Serializable {
|
|||
flag is it tapped for combat
|
||||
*/
|
||||
TAPPED,
|
||||
TAPPED_FOR_MANA,
|
||||
/* TAPPED_FOR_MANA
|
||||
During calculation of the available mana for a player the "TappedForMana" event is fired to simulate triggered mana production.
|
||||
By checking the inCheckPlayableState these events are handled to give back only the available mana of instead really producing mana.
|
||||
IMPORTANT: Triggered non mana abilities have to ignore the event if game.inCheckPlayableState is true.
|
||||
*/
|
||||
TAPPED_FOR_MANA,
|
||||
/* TAPPED_BATCH
|
||||
combine all TAPPED events occuring at the same time in a single event
|
||||
*/
|
||||
TAPPED_BATCH,
|
||||
|
||||
UNTAP, UNTAPPED,
|
||||
FLIP, FLIPPED,
|
||||
TRANSFORMING, TRANSFORMED,
|
||||
|
|
|
|||
56
Mage/src/main/java/mage/game/events/TappedBatchEvent.java
Normal file
56
Mage/src/main/java/mage/game/events/TappedBatchEvent.java
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package mage.game.events;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public class TappedBatchEvent extends GameEvent implements BatchGameEvent<TappedEvent> {
|
||||
|
||||
private final Set<TappedEvent> events = new HashSet<>();
|
||||
|
||||
public TappedBatchEvent() {
|
||||
super(EventType.TAPPED_BATCH, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TappedEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getTargets() {
|
||||
return events.stream()
|
||||
.map(GameEvent::getTargetId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAmount() {
|
||||
return events
|
||||
.stream()
|
||||
.mapToInt(GameEvent::getAmount)
|
||||
.sum();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // events can store a diff value, so search it from events list instead
|
||||
public UUID getTargetId() {
|
||||
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list or use CardUtil.getEventTargets(event)");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // events can store a diff value, so search it from events list instead
|
||||
public UUID getSourceId() {
|
||||
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list.");
|
||||
}
|
||||
|
||||
public void addEvent(TappedEvent event) {
|
||||
this.events.add(event);
|
||||
}
|
||||
}
|
||||
14
Mage/src/main/java/mage/game/events/TappedEvent.java
Normal file
14
Mage/src/main/java/mage/game/events/TappedEvent.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package mage.game.events;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public class TappedEvent extends GameEvent {
|
||||
public TappedEvent(UUID targetId, Ability source, UUID playerId, boolean forCombat) {
|
||||
super(EventType.TAPPED, targetId, source, playerId, 0, forCombat);
|
||||
}
|
||||
}
|
||||
|
|
@ -561,7 +561,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
//20091005 - 701.15a
|
||||
if (!tapped && !replaceEvent(EventType.TAP, game)) {
|
||||
this.tapped = true;
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.TAPPED, objectId, source, source == null ? null : source.getControllerId(), 0, forCombat));
|
||||
TappedEvent event = new TappedEvent(objectId, source, source == null ? null : source.getControllerId(), forCombat);
|
||||
game.fireEvent(event);
|
||||
game.getState().addSimultaneousTapped(event, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue