diff --git a/Mage.Sets/src/mage/cards/v/VoloItinerantScholar.java b/Mage.Sets/src/mage/cards/v/VoloItinerantScholar.java new file mode 100644 index 00000000000..08102f78493 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoloItinerantScholar.java @@ -0,0 +1,100 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ChooseABackgroundAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.VolosJournalToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VoloItinerantScholar extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent("permanent you control named Volo's Journal"); + + static { + filter.add(new NamePredicate("Volo's Journal")); + } + + public VoloItinerantScholar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // When Volo enters the battlefield, create Volo's Journal, a legendary colorless artifact token with hexproof and "Whenever you cast a creature spell, note one of its creature types that hasn't been noted for this artifact." + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new VolosJournalToken()))); + + // {2}, {T}: Draw a card for each creature type noted for target permanent you control named Volo's Journal. + Ability ability = new SimpleActivatedAbility(new VoloItinerantScholarEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // Choose a Background + this.addAbility(ChooseABackgroundAbility.getInstance()); + } + + private VoloItinerantScholar(final VoloItinerantScholar card) { + super(card); + } + + @Override + public VoloItinerantScholar copy() { + return new VoloItinerantScholar(this); + } +} + +class VoloItinerantScholarEffect extends OneShotEffect { + + VoloItinerantScholarEffect() { + super(Outcome.Benefit); + staticText = "draw a card for each creature type noted for target permanent you control named Volo's Journal"; + } + + private VoloItinerantScholarEffect(final VoloItinerantScholarEffect effect) { + super(effect); + } + + @Override + public VoloItinerantScholarEffect copy() { + return new VoloItinerantScholarEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + return player != null && permanent != null + && player.drawCards( + VolosJournalToken.getNotedTypes( + game, permanent.getId(), permanent.getZoneChangeCounter(game) + ).size(), source, game + ) > 0; + } +} diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index 2e21b38879e..5dc30c7e603 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -594,6 +594,7 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Viconia, Drow Apostate", 156, Rarity.UNCOMMON, mage.cards.v.ViconiaDrowApostate.class)); cards.add(new SetCardInfo("Vivien, Champion of the Wilds", 838, Rarity.RARE, mage.cards.v.VivienChampionOfTheWilds.class)); cards.add(new SetCardInfo("Volcanic Torrent", 813, Rarity.UNCOMMON, mage.cards.v.VolcanicTorrent.class)); + cards.add(new SetCardInfo("Volo, Itinerant Scholar", 103, Rarity.MYTHIC, mage.cards.v.VoloItinerantScholar.class)); cards.add(new SetCardInfo("Vrock", 157, Rarity.UNCOMMON, mage.cards.v.Vrock.class)); cards.add(new SetCardInfo("Wand of Wonder", 204, Rarity.RARE, mage.cards.w.WandOfWonder.class)); cards.add(new SetCardInfo("Wandering Fumarole", 928, Rarity.RARE, mage.cards.w.WanderingFumarole.class)); diff --git a/Mage/src/main/java/mage/game/permanent/token/VolosJournalToken.java b/Mage/src/main/java/mage/game/permanent/token/VolosJournalToken.java new file mode 100644 index 00000000000..1c1a3d4a3b7 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/VolosJournalToken.java @@ -0,0 +1,128 @@ +package mage.game.permanent.token; + +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.HexproofAbility; +import mage.choices.ChoiceCreatureType; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.util.RandomUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VolosJournalToken extends TokenImpl { + + private static final FilterCard filter = new FilterCard("spells"); + + public VolosJournalToken() { + super("Volo's Journal", "Volo's Journal, a legendary colorless artifact token with hexproof and \"Whenever you cast a creature spell, note one of its creature types that hasn't been noted for this artifact.\""); + addSuperType(SuperType.LEGENDARY); + this.cardType.add(CardType.ARTIFACT); + this.addAbility(HexproofAbility.getInstance()); + this.addAbility(new SpellCastControllerTriggeredAbility( + new VolosJournalTokenEffect(), StaticFilters.FILTER_SPELL_A_CREATURE, false + ).addHint(VolosJournalTokenHint.instance)); + } + + public VolosJournalToken(final VolosJournalToken token) { + super(token); + } + + public VolosJournalToken copy() { + return new VolosJournalToken(this); + } + + public static Set getNotedTypes(Game game, Ability source) { + return getNotedTypes(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + } + + public static Set getNotedTypes(Game game, UUID sourceId, int zcc) { + String key = "notedTypes_" + sourceId + '_' + zcc; + Object value = game.getState().getValue(key); + if (value == null) { + Set types = new HashSet<>(); + game.getState().setValue(key, types); + return types; + } + return (Set) value; + } +} + +enum VolosJournalTokenHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + Set types = VolosJournalToken.getNotedTypes(game, ability); + int size = types.size(); + if (size > 0) { + return "Creature types noted: " + size + " (" + String.join(", ", types) + ')'; + } + return "No creature types currently noted"; + } + + @Override + public VolosJournalTokenHint copy() { + return this; + } +} + +class VolosJournalTokenEffect extends OneShotEffect { + + VolosJournalTokenEffect() { + super(Outcome.Benefit); + staticText = "note one of its creature types that hasn't been noted for this artifact"; + } + + private VolosJournalTokenEffect(final VolosJournalTokenEffect effect) { + super(effect); + } + + @Override + public VolosJournalTokenEffect copy() { + return new VolosJournalTokenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set types = VolosJournalToken.getNotedTypes(game, source); + Spell spell = (Spell) getValue("spellCast"); + if (spell == null) { + return false; + } + ChoiceCreatureType choice = new ChoiceCreatureType(); + choice.getChoices().removeIf(types::contains); + if (!spell.isAllCreatureTypes(game)) { + spell.getSubtype(game) + .stream() + .filter(subType -> subType.getSubTypeSet() == SubTypeSet.CreatureType) + .map(SubType::getDescription) + .forEach(s -> choice.getChoices().removeIf(s::equals)); + } + switch (choice.getChoices().size()) { + case 0: + return false; + case 1: + types.add(RandomUtil.randomFromCollection(choice.getChoices())); + return true; + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.choose(outcome, choice, game); + types.add(choice.getChoice()); + return true; + } +}