Implement Chiss-Goria, Forge Tyrant (#10804)

* implements Chiss-Goria, Forge Tyrant

* updated test wording

* changed implementation to ContinuousEffect instead of CostModificationEffect

* fix code granting Affinity for Artifacts ability

* added more test cases to confirm effect working as intended

* add null check

* added artifact check to effect applying

* fix duration

---------

Co-authored-by: xenohedron <xenohedron@users.noreply.github.com>
This commit is contained in:
Vivian Greenslade 2023-08-18 01:20:14 -02:30 committed by GitHub
parent 70e2ff96f9
commit 95de216f8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 334 additions and 0 deletions

View file

@ -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<MageObjectReference> 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<MageObjectReference> morSet = new HashSet<>();
ChissGoriaForgeTyrantCanPlayEffect(Set<MageObjectReference> 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<MageObjectReference> morSet = new HashSet<>();
public ChissGoriaForgeTyrantAffinityEffect(Set<MageObjectReference> 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<MageObjectReference, Set<MageObjectReference>> morMap = new HashMap<>();
private static final Set<MageObjectReference> 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<MageObjectReference> 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);
}
}

View file

@ -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));

View file

@ -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);
}
}