diff --git a/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java b/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java new file mode 100644 index 00000000000..c583bfc2a54 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java @@ -0,0 +1,215 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.AffinityForArtifactsAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.*; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author Xanderhall, xenohedron + */ +public final class ChissGoriaForgeTyrant extends CardImpl { + + public ChissGoriaForgeTyrant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{R}{R}{R}"); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DRAGON); + + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Affinity for artifacts + this.addAbility(new AffinityForArtifactsAbility()); + + // Flying, haste + this.addAbility(FlyingAbility.getInstance()); + this.addAbility(HasteAbility.getInstance()); + + // Whenever Chiss-Goria, Forge Tyrant attacks, exile the top five cards of your library. You may cast an artifact spell from among them this turn. If you do, it has affinity for artifacts. + this.addAbility(new AttacksTriggeredAbility(new ChissGoriaForgeTyrantEffect()), new ChissGoriaForgeTyrantWatcher()); + } + + private ChissGoriaForgeTyrant(final ChissGoriaForgeTyrant card) { + super(card); + } + + @Override + public ChissGoriaForgeTyrant copy() { + return new ChissGoriaForgeTyrant(this); + } +} + +class ChissGoriaForgeTyrantEffect extends OneShotEffect { + + ChissGoriaForgeTyrantEffect() { + super(Outcome.Benefit); + staticText = "exile the top five cards of your library. You may cast an artifact spell from among them this turn. If you do, it gains affinity for artifacts."; + } + + private ChissGoriaForgeTyrantEffect(final ChissGoriaForgeTyrantEffect effect) { + super(effect); + } + + @Override + public ChissGoriaForgeTyrantEffect copy() { + return new ChissGoriaForgeTyrantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + + // Return if no cards exiled, effect still applied + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 5)); + if (cards.isEmpty()) { + return true; + } + player.moveCardsToExile(cards.getCards(game), source, game, false, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source)); + cards.retainZone(Zone.EXILED, game); + + Cards castableCards = new CardsImpl(cards.getCards(StaticFilters.FILTER_CARD_NON_LAND, game)); + Set morSet = new HashSet<>(); + castableCards.stream() + .map(uuid -> new MageObjectReference(uuid, game)) + .forEach(morSet::add); + + game.addEffect(new ChissGoriaForgeTyrantCanPlayEffect(morSet), source); + game.addEffect(new ChissGoriaForgeTyrantAffinityEffect(morSet), source); + return true; + } +} + +class ChissGoriaForgeTyrantCanPlayEffect extends AsThoughEffectImpl { + + private final Set morSet = new HashSet<>(); + + ChissGoriaForgeTyrantCanPlayEffect(Set morSet) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + this.morSet.addAll(morSet); + } + + private ChissGoriaForgeTyrantCanPlayEffect(final ChissGoriaForgeTyrantCanPlayEffect effect) { + super(effect); + this.morSet.addAll(effect.morSet); + } + + @Override + public ChissGoriaForgeTyrantCanPlayEffect copy() { + return new ChissGoriaForgeTyrantCanPlayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId)) { + return false; + } + UUID objectIdToCast = CardUtil.getMainCardId(game, sourceId); + Card card = game.getCard(objectIdToCast); + + return (card != null && card.isArtifact(game) + && morSet.stream().anyMatch(mor -> mor.refersTo(objectIdToCast, game)) + && ChissGoriaForgeTyrantWatcher.checkRef(source, morSet, game)); + } +} + +class ChissGoriaForgeTyrantAffinityEffect extends ContinuousEffectImpl { + + private final Set morSet = new HashSet<>(); + + public ChissGoriaForgeTyrantAffinityEffect(Set morSet) { + super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.Benefit); + this.morSet.addAll(morSet); + } + + protected ChissGoriaForgeTyrantAffinityEffect(final ChissGoriaForgeTyrantAffinityEffect effect) { + super(effect); + this.morSet.addAll(effect.morSet); + } + + @Override + public ChissGoriaForgeTyrantAffinityEffect copy() { + return new ChissGoriaForgeTyrantAffinityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (!ChissGoriaForgeTyrantWatcher.checkRef(source, morSet, game)) { + discard(); + return false; + } + + for (Card card : game.getExile().getAllCardsByRange(game, source.getControllerId())) { + if (morSet.contains(new MageObjectReference(card, game)) && card.isArtifact(game)) { + game.getState().addOtherAbility(card, new AffinityForArtifactsAbility()); + } + } + + for (StackObject stackObject : game.getStack()) { + if (!(stackObject instanceof Spell) || !stackObject.isControlledBy(source.getControllerId())) { + continue; + } + Card card = game.getCard(stackObject.getSourceId()); + if (card != null && morSet.contains(new MageObjectReference(card, game, -1))) { + game.getState().addOtherAbility(card, new AffinityForArtifactsAbility()); + } + } + return true; + } +} + +class ChissGoriaForgeTyrantWatcher extends Watcher { + + private final Map> morMap = new HashMap<>(); + private static final Set emptySet = new HashSet<>(); + + ChissGoriaForgeTyrantWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + return; + } + MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + Spell spell = game.getSpell(event.getTargetId()); + if (mor == null || spell == null) { + return; + } + morMap.computeIfAbsent(mor, x -> new HashSet<>()).add(new MageObjectReference(spell.getMainCard(), game, -1)); + } + + static boolean checkRef(Ability source, Set morSet, Game game) { + ChissGoriaForgeTyrantWatcher watcher = game.getState().getWatcher(ChissGoriaForgeTyrantWatcher.class); + return watcher != null + && watcher.morMap.getOrDefault(new MageObjectReference(source.getSourceObject(game), game), emptySet) + .stream() + .noneMatch(morSet::contains); + } +} diff --git a/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOneCommander.java b/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOneCommander.java index 7c61312df5d..1410919788e 100644 --- a/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOneCommander.java +++ b/Mage.Sets/src/mage/sets/PhyrexiaAllWillBeOneCommander.java @@ -37,6 +37,7 @@ public final class PhyrexiaAllWillBeOneCommander extends ExpansionSet { cards.add(new SetCardInfo("Castle Ardenvale", 149, Rarity.RARE, mage.cards.c.CastleArdenvale.class)); cards.add(new SetCardInfo("Castle Embereth", 150, Rarity.RARE, mage.cards.c.CastleEmbereth.class)); cards.add(new SetCardInfo("Chain Reaction", 97, Rarity.RARE, mage.cards.c.ChainReaction.class)); + cards.add(new SetCardInfo("Chiss-Goria, Forge Tyrant", 25, Rarity.MYTHIC, mage.cards.c.ChissGoriaForgeTyrant.class)); cards.add(new SetCardInfo("Chromatic Lantern", 127, Rarity.RARE, mage.cards.c.ChromaticLantern.class)); cards.add(new SetCardInfo("Clever Concealment", 5, Rarity.RARE, mage.cards.c.CleverConcealment.class)); cards.add(new SetCardInfo("Collective Effort", 61, Rarity.RARE, mage.cards.c.CollectiveEffort.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/onc/ChissGoriaForgeTyrantTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/onc/ChissGoriaForgeTyrantTest.java new file mode 100644 index 00000000000..a756c277692 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/onc/ChissGoriaForgeTyrantTest.java @@ -0,0 +1,118 @@ +package org.mage.test.cards.single.onc; + +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import mage.constants.PhaseStep; +import mage.constants.Zone; + +/** + * @author Xanderhall + */ +public class ChissGoriaForgeTyrantTest extends CardTestPlayerBase { + + private static final String CHISS = "Chiss-Goria, Forge Tyrant"; + private static final String CHALICE = "Marble Chalice"; + private static final String COLOSSUS = "Blightsteel Colossus"; + private static final String GOBLIN = "Goblin Assailant"; + + @Test + public void testCastArtifact() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 15); + addCard(Zone.BATTLEFIELD, playerA, CHALICE, 6); + addCard(Zone.HAND, playerA, CHISS); + addCard(Zone.LIBRARY, playerA, "Mountain", 4); + addCard(Zone.LIBRARY, playerA, COLOSSUS, 1); + skipInitShuffling(); + + // Chiss-Goria should cost 3 mana, leaving 12 untapped + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, CHISS); + + attack(1, playerA, CHISS); + // Chiss's on attack effect triggers, exiles the cards + // Colossus should cost 6, tapping all mana + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, COLOSSUS, true); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 4); + assertPermanentCount(playerA, COLOSSUS, 1); + assertPermanentCount(playerA, CHISS, 1); + assertTappedCount("Mountain", true, 9); + } + + public void testCastCardGainingArtifact() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.BATTLEFIELD, playerA, CHALICE, 6); + addCard(Zone.HAND, playerA, CHISS); + addCard(Zone.HAND, playerA, "Encroaching Mycosynth", 1); + addCard(Zone.LIBRARY, playerA, "Mountain", 4); + addCard(Zone.LIBRARY, playerA, GOBLIN, 1); + skipInitShuffling(); + + // Chiss-Goria should cost 3 mana, leaving 1 untapped + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, CHISS); + + // Chiss's on attack effect triggers, exiles the cards, no artifacts. + attack(1, playerA, CHISS); + + // Cast Encroaching Mycosynth, turning permanent spells into artifacts + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Encroaching Mycosynth"); + + // Gobling Assailant should be castable for 1 red, leaving all lands tapped + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, GOBLIN, true); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 4); + assertPermanentCount(playerA, GOBLIN, 1); + assertPermanentCount(playerA, CHISS, 1); + assertTappedCount("Mountain", true, 4); + assertTappedCount("Island", true, 4); + } + + public void testCastTwoArtifacts() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.BATTLEFIELD, playerA, CHALICE, 12); + addCard(Zone.BATTLEFIELD, playerA, CHISS, 1); + addCard(Zone.HAND, playerA, "Relentless Assault", 1); + addCard(Zone.LIBRARY, playerA, "Mountain", 4); + addCard(Zone.LIBRARY, playerA, COLOSSUS, 1); + addCard(Zone.LIBRARY, playerA, "Mountain", 4); + addCard(Zone.LIBRARY, playerA, COLOSSUS, 1); + skipInitShuffling(); + + // Chiss-Goria should cost 3 mana, leaving 4 untapped + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, CHISS); + + // Chiss's on attack effect triggers, exiles the cards + attack(1, playerA, CHISS); + + // Add another combat phase + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Relentless Assault"); + + // Chiss's effect triggers again + attack(1, playerA, CHISS); + + // Should be able to cast both Colossuses + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, COLOSSUS, true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, COLOSSUS, true); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertExileCount(playerA, 4); + assertPermanentCount(playerA, COLOSSUS, 2); + assertPermanentCount(playerA, CHISS, 1); + assertTappedCount("Mountain", true, 7); + } +}