mirror of
https://github.com/correl/mage.git
synced 2025-01-11 19:13:02 +00:00
Implemented Idol of Endurance
This commit is contained in:
parent
0960794db4
commit
e914c38986
3 changed files with 391 additions and 0 deletions
265
Mage.Sets/src/mage/cards/i/IdolOfEndurance.java
Normal file
265
Mage.Sets/src/mage/cards/i/IdolOfEndurance.java
Normal file
|
@ -0,0 +1,265 @@
|
|||
package mage.cards.i;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.DelayedTriggeredAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.common.FilterCreatureCard;
|
||||
import mage.filter.predicate.mageobject.ConvertedManaCostPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class IdolOfEndurance extends CardImpl {
|
||||
|
||||
public IdolOfEndurance(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}");
|
||||
|
||||
// When Idol of Endurance enters the battlefield, exile all creature cards with converted mana cost 3 or less from your graveyard until Idol of Endurance leaves the battlefield.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new IdolOfEnduranceExileEffect()));
|
||||
|
||||
// {1}{W}, {T}: Until end of turn, you may cast a creature spell from among the cards exiled with Idol of Endurance without paying its mana cost.
|
||||
Ability ability = new SimpleActivatedAbility(
|
||||
new IdolOfEnduranceCastFromExileEffect(), new ManaCostsImpl<>("{1}{W}")
|
||||
);
|
||||
ability.addCost(new TapSourceCost());
|
||||
this.addAbility(ability, new IdolOfEnduranceWatcher());
|
||||
}
|
||||
|
||||
private IdolOfEndurance(final IdolOfEndurance card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdolOfEndurance copy() {
|
||||
return new IdolOfEndurance(this);
|
||||
}
|
||||
}
|
||||
|
||||
class IdolOfEnduranceExileEffect extends OneShotEffect {
|
||||
|
||||
private static final FilterCard filter = new FilterCreatureCard();
|
||||
|
||||
static {
|
||||
filter.add(new ConvertedManaCostPredicate(ComparisonType.FEWER_THAN, 4));
|
||||
}
|
||||
|
||||
IdolOfEnduranceExileEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "exile all creature cards with converted mana cost 3 or less from your graveyard until {this} leaves the battlefield";
|
||||
}
|
||||
|
||||
private IdolOfEnduranceExileEffect(final IdolOfEnduranceExileEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdolOfEnduranceExileEffect copy() {
|
||||
return new IdolOfEnduranceExileEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (player == null || permanent == null) {
|
||||
return false;
|
||||
}
|
||||
Cards cards = new CardsImpl(player.getGraveyard().getCards(filter, game));
|
||||
MageObjectReference mor = new MageObjectReference(permanent, game);
|
||||
player.moveCards(cards, Zone.EXILED, source, game);
|
||||
Set<MageObjectReference> morSet = cards
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(card -> new MageObjectReference(card, game))
|
||||
.collect(Collectors.toSet());
|
||||
game.getState().setValue("" + mor.getSourceId() + mor.getZoneChangeCounter(), morSet);
|
||||
game.addDelayedTriggeredAbility(new IdolOfEnduranceDelayedTrigger(morSet), source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class IdolOfEnduranceDelayedTrigger extends DelayedTriggeredAbility {
|
||||
|
||||
IdolOfEnduranceDelayedTrigger(Set<MageObjectReference> morSet) {
|
||||
super(new IdolOfEnduranceLeaveEffect(morSet), Duration.Custom, true, false);
|
||||
this.usesStack = false;
|
||||
this.setRuleVisible(false);
|
||||
}
|
||||
|
||||
private IdolOfEnduranceDelayedTrigger(final IdolOfEnduranceDelayedTrigger ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getTargetId().equals(this.getSourceId())) {
|
||||
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
||||
if (zEvent.getFromZone() == Zone.BATTLEFIELD) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdolOfEnduranceDelayedTrigger copy() {
|
||||
return new IdolOfEnduranceDelayedTrigger(this);
|
||||
}
|
||||
}
|
||||
|
||||
class IdolOfEnduranceLeaveEffect extends OneShotEffect {
|
||||
|
||||
private final Set<MageObjectReference> morSet = new HashSet<>();
|
||||
|
||||
IdolOfEnduranceLeaveEffect(Set<MageObjectReference> morSet) {
|
||||
super(Outcome.Benefit);
|
||||
this.morSet.addAll(morSet);
|
||||
}
|
||||
|
||||
private IdolOfEnduranceLeaveEffect(final IdolOfEnduranceLeaveEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdolOfEnduranceLeaveEffect copy() {
|
||||
return new IdolOfEnduranceLeaveEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
return player != null && player.moveCards(
|
||||
morSet.stream()
|
||||
.map(mor -> mor.getCard(game))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet()),
|
||||
Zone.GRAVEYARD, source, game
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IdolOfEnduranceCastFromExileEffect extends AsThoughEffectImpl {
|
||||
|
||||
IdolOfEnduranceCastFromExileEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit);
|
||||
staticText = "until end of turn, you may cast a creature spell from among the cards exiled with {this} without paying its mana cost";
|
||||
}
|
||||
|
||||
private IdolOfEnduranceCastFromExileEffect(final IdolOfEnduranceCastFromExileEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdolOfEnduranceCastFromExileEffect copy() {
|
||||
return new IdolOfEnduranceCastFromExileEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
IdolOfEnduranceWatcher watcher = game.getState().getWatcher(IdolOfEnduranceWatcher.class);
|
||||
if (watcher != null) {
|
||||
watcher.addPlayable(source, game);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) {
|
||||
IdolOfEnduranceWatcher watcher = game.getState().getWatcher(IdolOfEnduranceWatcher.class);
|
||||
if (watcher == null || !watcher.checkPermission(affectedControllerId, source, game)) {
|
||||
return false;
|
||||
}
|
||||
Object value = game.getState().getValue("" + source.getSourceId() + source.getSourceObjectZoneChangeCounter());
|
||||
if (!(value instanceof Set)) {
|
||||
discard();
|
||||
return false;
|
||||
}
|
||||
Set<MageObjectReference> morSet = (Set<MageObjectReference>) value;
|
||||
if (game.getState().getZone(sourceId) != Zone.EXILED
|
||||
|| morSet.stream().noneMatch(mor -> mor.refersTo(sourceId, game))) {
|
||||
return false;
|
||||
}
|
||||
Card card = game.getCard(sourceId);
|
||||
if (card == null || !card.isCreature() || card.isLand()) {
|
||||
return false;
|
||||
}
|
||||
return allowCardToPlayWithoutMana(sourceId, source, affectedControllerId, game);
|
||||
}
|
||||
}
|
||||
|
||||
class IdolOfEnduranceWatcher extends Watcher {
|
||||
|
||||
private final Map<MageObjectReference, Map<UUID, Integer>> morMap = new HashMap<>();
|
||||
|
||||
IdolOfEnduranceWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
|
||||
if (event.getAdditionalReference() == null) {
|
||||
return;
|
||||
}
|
||||
morMap.computeIfAbsent(event.getAdditionalReference(), m -> new HashMap<>())
|
||||
.compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
morMap.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
boolean checkPermission(UUID playerId, Ability source, Game game) {
|
||||
if (!playerId.equals(source.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
MageObjectReference mor = new MageObjectReference(
|
||||
source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game
|
||||
);
|
||||
if (!morMap.containsKey(mor)) {
|
||||
return false;
|
||||
}
|
||||
return morMap.get(mor).getOrDefault(playerId, 0) > 0;
|
||||
}
|
||||
|
||||
void addPlayable(Ability source, Game game) {
|
||||
MageObjectReference mor = new MageObjectReference(
|
||||
source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game
|
||||
);
|
||||
morMap.computeIfAbsent(mor, m -> new HashMap<>())
|
||||
.compute(source.getControllerId(), (u, i) -> i == null ? 1 : Integer.sum(i, 1));
|
||||
}
|
||||
}
|
|
@ -142,6 +142,7 @@ public final class CoreSet2021 extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Hobblefiend", 152, Rarity.COMMON, mage.cards.h.Hobblefiend.class));
|
||||
cards.add(new SetCardInfo("Hooded Blightfang", 104, Rarity.RARE, mage.cards.h.HoodedBlightfang.class));
|
||||
cards.add(new SetCardInfo("Hunter's Edge", 189, Rarity.COMMON, mage.cards.h.HuntersEdge.class));
|
||||
cards.add(new SetCardInfo("Idol of Endurance", 23, Rarity.RARE, mage.cards.i.IdolOfEndurance.class));
|
||||
cards.add(new SetCardInfo("Igneous Cur", 153, Rarity.COMMON, mage.cards.i.IgneousCur.class));
|
||||
cards.add(new SetCardInfo("Indulging Patrician", 219, Rarity.UNCOMMON, mage.cards.i.IndulgingPatrician.class));
|
||||
cards.add(new SetCardInfo("Infernal Scarring", 105, Rarity.COMMON, mage.cards.i.InfernalScarring.class));
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
package org.mage.test.cards.single;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class IdolOfEnduranceTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String idol = "Idol of Endurance";
|
||||
private static final String key = "Voltaic Key";
|
||||
private static final String sqr = "Squire";
|
||||
private static final String glrskr = "Glory Seeker";
|
||||
|
||||
@Test
|
||||
public void testIdol() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
|
||||
addCard(Zone.HAND, playerA, idol);
|
||||
addCard(Zone.GRAVEYARD, playerA, sqr);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, idol);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sqr);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, sqr, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdol2() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
|
||||
addCard(Zone.HAND, playerA, idol);
|
||||
addCard(Zone.GRAVEYARD, playerA, sqr);
|
||||
addCard(Zone.GRAVEYARD, playerA, glrskr);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, idol);
|
||||
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}");
|
||||
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sqr);
|
||||
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, glrskr);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, sqr, 1);
|
||||
assertPermanentCount(playerA, glrskr, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdolTwice() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 8);
|
||||
addCard(Zone.BATTLEFIELD, playerA, key);
|
||||
addCard(Zone.HAND, playerA, idol);
|
||||
addCard(Zone.GRAVEYARD, playerA, sqr);
|
||||
addCard(Zone.GRAVEYARD, playerA, glrskr);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, idol);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1},", idol);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sqr);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, glrskr);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, sqr, 1);
|
||||
assertPermanentCount(playerA, glrskr, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdolTwice2() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 8);
|
||||
addCard(Zone.BATTLEFIELD, playerA, key);
|
||||
addCard(Zone.HAND, playerA, idol);
|
||||
addCard(Zone.GRAVEYARD, playerA, sqr);
|
||||
addCard(Zone.GRAVEYARD, playerA, glrskr);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, idol);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sqr);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1},", idol);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, glrskr);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, sqr, 1);
|
||||
assertPermanentCount(playerA, glrskr, 1);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue