From 7adbf452584cf77495773eb24f2d871fce89d809 Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Sat, 23 Dec 2023 00:01:59 -0600 Subject: [PATCH] Added Tarrian's Journal // The Tomb of Aclazotz --- .../src/mage/cards/t/TarriansJournal.java | 69 +++++ .../src/mage/cards/t/TheTombOfAclazotz.java | 276 ++++++++++++++++++ .../src/mage/sets/TheLostCavernsOfIxalan.java | 2 + Mage/src/main/java/mage/MageIdentifier.java | 3 +- 4 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/t/TarriansJournal.java create mode 100644 Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java diff --git a/Mage.Sets/src/mage/cards/t/TarriansJournal.java b/Mage.Sets/src/mage/cards/t/TarriansJournal.java new file mode 100644 index 00000000000..3eaa82610e4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TarriansJournal.java @@ -0,0 +1,69 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package mage.cards.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; + +/** + * + * @author jeffwadsworth + */ +public class TarriansJournal extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another artifact or creature"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + public TarriansJournal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{B}"); + this.supertype.add(SuperType.LEGENDARY); + + this.secondSideCardClazz = mage.cards.t.TheTombOfAclazotz.class; + this.color.setBlack(true); + + // T: Sacrifice another artifact or creature: Draw a card. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility(new DrawCardSourceControllerEffect(1), new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(filter)); + this.addAbility(ability); + + this.addAbility(new TransformAbility()); + Ability transformAbility = new SimpleActivatedAbility(new TransformSourceEffect(), new ManaCostsImpl("{2}")); + transformAbility.addCost(new TapSourceCost()); + this.addAbility(transformAbility); + + } + + private TarriansJournal(final TarriansJournal card) { + super(card); + } + + @Override + public TarriansJournal copy() { + return new TarriansJournal(this); + } + +} diff --git a/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java b/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java new file mode 100644 index 00000000000..ed719665d50 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheTombOfAclazotz.java @@ -0,0 +1,276 @@ +package mage.cards.t; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import mage.MageIdentifier; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.counter.AddCounterEnteringCreatureEffect; +import mage.abilities.mana.BlackManaAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +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.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +/** + * + * @author jeffwadsworth + */ +public class TheTombOfAclazotz extends CardImpl { + + public TheTombOfAclazotz(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.LAND}, null); + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.CAVE); + + // this is the second face of Tarrian's Journal + this.nightCard = true; + + // {T}: Add {B}. + this.addAbility(new BlackManaAbility()); + + // You may cast a creature spell from your graveyard this turn. If you do, it enters the battlefield with a finality counter on it and is a Vampire in addition to its other types. + Ability castSpellAbility = new SimpleActivatedAbility(new TheTombOfAclazotzEffect(), new TapSourceCost()); + castSpellAbility.setIdentifier(MageIdentifier.TheTombOfAclazotzWatcher); + castSpellAbility.addWatcher(new TheTombOfAclazotzWatcher()); + this.addAbility(castSpellAbility); + + } + + private TheTombOfAclazotz(final TheTombOfAclazotz card) { + super(card); + } + + @Override + public TheTombOfAclazotz copy() { + return new TheTombOfAclazotz(this); + } +} + +class TheTombOfAclazotzEffect extends AsThoughEffectImpl { + + TheTombOfAclazotzEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + staticText = "You may cast a creature spell from your graveyard this turn. If you do, it enters the battlefield with a finality counter on it and is a Vampire in addition to its other types. (If a creature with a finality counter on it would die, exile it instead.)"; + } + + private TheTombOfAclazotzEffect(final TheTombOfAclazotzEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public TheTombOfAclazotzEffect copy() { + return new TheTombOfAclazotzEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + TheTombOfAclazotzWatcher watcher = game.getState().getWatcher(TheTombOfAclazotzWatcher.class); + if (watcher != null) { + watcher.addPlayable(source, game); + watcher.addPlayFromAnywhereEffect(this.getId()); + } + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + TheTombOfAclazotzWatcher watcher = game.getState().getWatcher(TheTombOfAclazotzWatcher.class); + if (watcher == null + || !watcher.checkPermission(playerId, source, game) + || game.getState().getZone(objectId) != Zone.GRAVEYARD) { + return false; + } + Card card = game.getCard(objectId); + if (card != null + && affectedAbility instanceof SpellAbility + && card.getOwnerId().equals(playerId) + && card.isCreature(game)) { + return true; + } + return false; + } +} + +class TheTombOfAclazotzWatcher extends Watcher { + + private final Map> morMap = new HashMap<>(); + private MageObjectReference mor; + private UUID playFromAnywhereEffectId; + + TheTombOfAclazotzWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) + && event.hasApprovingIdentifier(MageIdentifier.TheTombOfAclazotzWatcher)) { + Spell target = game.getSpell(event.getTargetId()); + Card card = target.getCard(); + if (card != null) { + mor = new MageObjectReference(card.getId(), card.getZoneChangeCounter(game), game); + if (mor != null) { + game.getState().addEffect(new AddCounterEnteringCreatureEffect(new MageObjectReference(target.getCard(), game), + CounterType.FINALITY.createInstance(), Outcome.Neutral), + target.getSpellAbility()); + game.getState().addEffect(new AddSubtypeEnteringCreatureEffect(new MageObjectReference(target.getCard(), game), SubType.VAMPIRE, Outcome.Benefit), target.getSpellAbility()); + // Rule 728.2 we must insure the effect is used (creature is cast successfully) before discarding the play effect + UUID playEffectId = this.getPlayFromAnywhereEffect(); + if (playEffectId != null + && game.getContinuousEffects().getApplicableAsThoughEffects(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, game).listIterator().next().getId().equals(playEffectId)) { + // discard the play effect + game.getContinuousEffects().getApplicableAsThoughEffects(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, game).listIterator().next().discard(); + } + } + } + } + } + + boolean checkPermission(UUID playerId, Ability source, Game game) { + if (!playerId.equals(source.getControllerId())) { + return false; + } + MageObjectReference mor = new MageObjectReference( + source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game + ); + return morMap.computeIfAbsent(mor, m -> new HashMap<>()).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(), CardUtil::setOrIncrementValue); + } + + void addPlayFromAnywhereEffect(UUID uuid) { + playFromAnywhereEffectId = uuid; + } + + UUID getPlayFromAnywhereEffect() { + return playFromAnywhereEffectId; + } + + @Override + public void reset() { + morMap.clear(); + super.reset(); + } + +} + +class AddSubtypeEnteringCreatureEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + private final SubType subType; + + public AddSubtypeEnteringCreatureEffect(MageObjectReference mor, SubType subType, Outcome outcome) { + super(Duration.WhileOnBattlefield, outcome); + this.mor = mor; + this.subType = subType; + } + + private AddSubtypeEnteringCreatureEffect(final AddSubtypeEnteringCreatureEffect effect) { + super(effect); + this.mor = effect.mor; + this.subType = effect.subType; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); + return permanent != null + && mor.refersTo(permanent, game); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent target = ((EntersTheBattlefieldEvent) event).getTarget(); + if (target != null) { + AddCardSubTypeEnteringTargetEffect effect = new AddCardSubTypeEnteringTargetEffect(subType, Duration.WhileOnBattlefield); + effect.setTargetPointer(new FixedTarget(target, game)); + game.addEffect(effect, source); + + } + return false; + } + + @Override + public AddSubtypeEnteringCreatureEffect copy() { + return new AddSubtypeEnteringCreatureEffect(this); + } +} + +class AddCardSubTypeEnteringTargetEffect extends ContinuousEffectImpl { + + private final SubType addedSubType; + + public AddCardSubTypeEnteringTargetEffect(SubType addedSubType, Duration duration) { + super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + this.addedSubType = addedSubType; + } + + protected AddCardSubTypeEnteringTargetEffect(final AddCardSubTypeEnteringTargetEffect effect) { + super(effect); + this.addedSubType = effect.addedSubType; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(game.getObject(targetPointer.getFixedTarget(game, source).getTarget()).getId()); + if (target != null) { + target.addSubType(game, addedSubType); + } + if (target == null) { + discard(); + } + return false; + } + + @Override + public AddCardSubTypeEnteringTargetEffect copy() { + return new AddCardSubTypeEnteringTargetEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index f60326df175..383979e071a 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -313,6 +313,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 397, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swashbuckler's Whip", 263, Rarity.UNCOMMON, mage.cards.s.SwashbucklersWhip.class)); cards.add(new SetCardInfo("Synapse Necromage", 125, Rarity.UNCOMMON, mage.cards.s.SynapseNecromage.class)); + cards.add(new SetCardInfo("Tarrian's Journal", 126, Rarity.RARE, mage.cards.t.TarriansJournal.class)); cards.add(new SetCardInfo("Tarrian's Soulcleaver", 264, Rarity.RARE, mage.cards.t.TarriansSoulcleaver.class)); cards.add(new SetCardInfo("Tectonic Hazard", 169, Rarity.COMMON, mage.cards.t.TectonicHazard.class)); cards.add(new SetCardInfo("Tecutlan, the Searing Rift", 135, Rarity.RARE, mage.cards.t.TecutlanTheSearingRift.class)); @@ -333,6 +334,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("The Mycotyrant", 235, Rarity.MYTHIC, mage.cards.t.TheMycotyrant.class)); cards.add(new SetCardInfo("The Myriad Pools", 363, Rarity.RARE, mage.cards.t.TheMyriadPools.class)); cards.add(new SetCardInfo("The Skullspore Nexus", 212, Rarity.MYTHIC, mage.cards.t.TheSkullsporeNexus.class)); + cards.add(new SetCardInfo("The Tomb of Aclazotz", 126, Rarity.RARE, mage.cards.t.TheTombOfAclazotz.class)); cards.add(new SetCardInfo("Thousand Moons Crackshot", 37, Rarity.COMMON, mage.cards.t.ThousandMoonsCrackshot.class)); cards.add(new SetCardInfo("Thousand Moons Infantry", 38, Rarity.COMMON, mage.cards.t.ThousandMoonsInfantry.class)); cards.add(new SetCardInfo("Thousand Moons Smithy", 39, Rarity.RARE, mage.cards.t.ThousandMoonsSmithy.class)); diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index 4db6d63ec78..baca1c2db65 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -67,7 +67,8 @@ public enum MageIdentifier { ScourgeOfNelTothAlternateCast, SqueeDubiousMonarchAlternateCast, WorldheartPhoenixAlternateCast, - XandersPactAlternateCast; + XandersPactAlternateCast, + TheTombOfAclazotzWatcher; /** * Additional text if there is need to differentiate two very similar effects