diff --git a/Mage.Sets/src/mage/cards/k/KorvoldGleefulGlutton.java b/Mage.Sets/src/mage/cards/k/KorvoldGleefulGlutton.java new file mode 100644 index 00000000000..2fa9e95236b --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KorvoldGleefulGlutton.java @@ -0,0 +1,166 @@ +package mage.cards.k; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.Watcher; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author Xanderhall + */ +public final class KorvoldGleefulGlutton extends CardImpl { + + public KorvoldGleefulGlutton(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DRAGON); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // This spell costs {1} less to cast for each card type among permanents you've sacrificed this turn. + Ability staticAbility = new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionSourceEffect(CardTypesAmongSacrificedPermanentsCount.instance) + .setText("this spell costs {1} less to cast for each card type among permanents you've sacrificed this turn") + ); + staticAbility.addHint(new ValueHint("Card types among permanents you've sacrificed this turn", CardTypesAmongSacrificedPermanentsCount.instance)); + + this.addAbility(staticAbility, new KorvoldGleefulGluttonWatcher()); + + // Flying, Trample, Haste + this.addAbility(FlyingAbility.getInstance()); + this.addAbility(TrampleAbility.getInstance()); + this.addAbility(HasteAbility.getInstance()); + + // Whenever Korvold deals combat damage to a player, put X +1/+1 counters on Korvold and draw X cards, where X is the number of permanent types among cards in your graveyard. + Ability combatDamageAbility = new DealsCombatDamageToAPlayerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(), PermanentTypesInGraveyardCount.instance, true), + false + ); + combatDamageAbility.addEffect(new DrawCardSourceControllerEffect(PermanentTypesInGraveyardCount.instance).setText("and draw X cards, where X is the number of permanent types among cards in your graveyard")); + combatDamageAbility.addHint(new ValueHint("Permanent types among cards in your graveyard", PermanentTypesInGraveyardCount.instance)); + this.addAbility(combatDamageAbility); + } + + private KorvoldGleefulGlutton(final KorvoldGleefulGlutton card) { + super(card); + } + + @Override + public KorvoldGleefulGlutton copy() { + return new KorvoldGleefulGlutton(this); + } +} + +class KorvoldGleefulGluttonWatcher extends Watcher { + + private final Map> map = new HashMap<>(); + + KorvoldGleefulGluttonWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SACRIFICED_PERMANENT) { + return; + } + + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + + if (permanent != null) { + permanent.getCardType(game).forEach(type -> + map.computeIfAbsent(event.getPlayerId(), (key) -> new HashSet<>()).add(type) + ); + } + } + + @Override + public void reset() { + super.reset(); + map.clear(); + } + + public int getNumberOfTypes(UUID playerId) { + return map.computeIfAbsent(playerId, (key) -> new HashSet<>()).size(); + }; +} + +enum CardTypesAmongSacrificedPermanentsCount implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + KorvoldGleefulGluttonWatcher watcher = game.getState().getWatcher(KorvoldGleefulGluttonWatcher.class); + return watcher == null ? 0 : watcher.getNumberOfTypes(sourceAbility.getControllerId()); + } + + @Override + public CardTypesAmongSacrificedPermanentsCount copy() { + return instance; + } + + @Override + public String getMessage() { + return "card types among permanents you sacrificed"; + } +} + +enum PermanentTypesInGraveyardCount implements DynamicValue { + instance; + + @Override + public PermanentTypesInGraveyardCount copy() { + return instance; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Player player = game.getPlayer(sourceAbility.getControllerId()); + if (player == null) { + return 0; + } + + return player.getGraveyard().getCards(game).stream() + .map(card -> card.getCardType(game)) + .flatMap(types -> types.stream().filter(CardType::isPermanentType)) + .distinct() + .mapToInt(x -> 1) + .sum(); + } + + @Override + public String getMessage() { + return "permanent types among cards in your graveyard"; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java b/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java index ead6be7f88b..f9ae86ffbff 100644 --- a/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java +++ b/Mage.Sets/src/mage/sets/WildsOfEldraineCommander.java @@ -81,6 +81,7 @@ public final class WildsOfEldraineCommander extends ExpansionSet { cards.add(new SetCardInfo("Kindred Dominance", 113, Rarity.RARE, mage.cards.k.KindredDominance.class)); cards.add(new SetCardInfo("Knickknack Ouphe", 18, Rarity.RARE, mage.cards.k.KnickknackOuphe.class)); cards.add(new SetCardInfo("Kor Spiritdancer", 69, Rarity.RARE, mage.cards.k.KorSpiritdancer.class)); + cards.add(new SetCardInfo("Korvold, Gleeful Glutton", 26, Rarity.MYTHIC, mage.cards.k.KorvoldGleefulGlutton.class)); cards.add(new SetCardInfo("Krosan Verge", 163, Rarity.UNCOMMON, mage.cards.k.KrosanVerge.class)); cards.add(new SetCardInfo("Loamcrafter Faun", 19, Rarity.RARE, mage.cards.l.LoamcrafterFaun.class)); cards.add(new SetCardInfo("Malleable Impostor", 10, Rarity.RARE, mage.cards.m.MalleableImpostor.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/KorvoldGleefulGluttonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/KorvoldGleefulGluttonTest.java new file mode 100644 index 00000000000..8e05076691e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/woc/KorvoldGleefulGluttonTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.woc; + +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; + +public class KorvoldGleefulGluttonTest extends CardTestPlayerBase { + + /** + This spell costs {1} less to cast for each card type among permanents you’ve sacrificed this turn. + Flying, trample, haste + Whenever Korvold deals combat damage to a player, put X +1/+1 counters on Korvold and draw X cards, where X is the number of permanent types among cards in your graveyard. + */ + private final String KORVOLD = "Korvold, Gleeful Glutton"; + private final String OVEN = "Witch's Oven"; + private final String CAT = "Cauldron Familiar"; + private final String DOG = "Spirited Companion"; + + @Test + public void testEffects() { + addCard(Zone.HAND, playerA, KORVOLD); + addCard(Zone.BATTLEFIELD, playerA, OVEN); + addCard(Zone.BATTLEFIELD, playerA, DOG); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.GRAVEYARD, playerA, CAT); + + // Sacrifice dog to oven + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice"); + setChoice(playerA, DOG); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // Sacrifice food to cat + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice a Food"); + setChoice(playerA, "Food Token"); + + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // Korvold should be reduced by 3 (enchantment, creature, artifact sacrificed) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, KORVOLD); + + // Korvold attacks, effect should trigger for X=2 because of dog + attack(1, playerA, KORVOLD); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + + assertCounterCount(KORVOLD, CounterType.P1P1, 2); + assertHandCount(playerA, 2); + } +}