From 008662be5e6505447945b4a32e74301ccaf33d44 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:21:52 +0200 Subject: [PATCH] [LTR] Implement Shelob, Child of Ungoliant (#10558) * [LTR] Implement Shelob, Child of Ungoliant This is almost working. I could not figure out why, but tokens made with the trigger still have subtypes, even though it has only artifact as its type. * fix subtype of the token copy not being cleaned. fixed Myrkul, Lord of Bones that had the same issue. removed game argument from CreateTokenCopyTargetEffect's PermanentModifier. It was only used by Myrkul, and with no effect. --- .../mage/cards/a/AniktheaHandOfErebos.java | 2 +- .../mage/cards/k/KinzuOfTheBleakCoven.java | 2 +- .../src/mage/cards/m/MishraEminentOne.java | 15 +- .../src/mage/cards/m/MyrkulLordOfBones.java | 5 +- .../mage/cards/s/SauronTheNecromancer.java | 2 +- .../mage/cards/s/ShelobChildOfUngoliant.java | 236 ++++++++++++++++++ .../TheLordOfTheRingsTalesOfMiddleEarth.java | 1 + .../common/CreateTokenCopyTargetEffect.java | 4 +- 8 files changed, 253 insertions(+), 14 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java diff --git a/Mage.Sets/src/mage/cards/a/AniktheaHandOfErebos.java b/Mage.Sets/src/mage/cards/a/AniktheaHandOfErebos.java index b4a9337ac86..0b338b08ef6 100644 --- a/Mage.Sets/src/mage/cards/a/AniktheaHandOfErebos.java +++ b/Mage.Sets/src/mage/cards/a/AniktheaHandOfErebos.java @@ -96,7 +96,7 @@ class AniktheaHandOfErebosEffect extends OneShotEffect { } player.moveCards(card, Zone.EXILED, source, game); new CreateTokenCopyTargetEffect() - .setPermanentModifier((token, g) -> { + .setPermanentModifier((token) -> { token.addCardType(CardType.CREATURE); token.addSubType(SubType.ZOMBIE); token.setColor(ObjectColor.BLACK); diff --git a/Mage.Sets/src/mage/cards/k/KinzuOfTheBleakCoven.java b/Mage.Sets/src/mage/cards/k/KinzuOfTheBleakCoven.java index be7e1456581..7c1c9e65c9a 100644 --- a/Mage.Sets/src/mage/cards/k/KinzuOfTheBleakCoven.java +++ b/Mage.Sets/src/mage/cards/k/KinzuOfTheBleakCoven.java @@ -99,7 +99,7 @@ class KinzuOfTheBleakCovenEffect extends OneShotEffect { player.moveCards(card, Zone.EXILED, source, game); return new CreateTokenCopyTargetEffect().setSavedPermanent( new PermanentCard(card, source.getControllerId(), game) - ).setPermanentModifier((token, g) -> { + ).setPermanentModifier((token) -> { token.setPower(1); // 1/1 token.setToughness(1); token.addAbility(new ToxicAbility(1)); // Add Toxic (is additive) diff --git a/Mage.Sets/src/mage/cards/m/MishraEminentOne.java b/Mage.Sets/src/mage/cards/m/MishraEminentOne.java index 7290fe7228b..d227bf03180 100644 --- a/Mage.Sets/src/mage/cards/m/MishraEminentOne.java +++ b/Mage.Sets/src/mage/cards/m/MishraEminentOne.java @@ -79,13 +79,14 @@ class MishraEminentOneEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect().setPermanentModifier((token, g) -> { - token.setName("Mishra's Warform"); - token.setPower(4); - token.setToughness(4); - token.addCardType(CardType.ARTIFACT, CardType.CREATURE); - token.addSubType(SubType.CONSTRUCT); - }); + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect().setPermanentModifier( + (token) -> { + token.setName("Mishra's Warform"); + token.setPower(4); + token.setToughness(4); + token.addCardType(CardType.ARTIFACT, CardType.CREATURE); + token.addSubType(SubType.CONSTRUCT); + }); effect.apply(game, source); game.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn) .setTargetPointer(new FixedTargets(effect.getAddedPermanents(), game)), source); diff --git a/Mage.Sets/src/mage/cards/m/MyrkulLordOfBones.java b/Mage.Sets/src/mage/cards/m/MyrkulLordOfBones.java index 85e1332cb2d..3e714a6e49a 100644 --- a/Mage.Sets/src/mage/cards/m/MyrkulLordOfBones.java +++ b/Mage.Sets/src/mage/cards/m/MyrkulLordOfBones.java @@ -103,10 +103,11 @@ class MyrkulLordOfBonesEffect extends OneShotEffect { player.moveCards(card, Zone.EXILED, source, game); return new CreateTokenCopyTargetEffect().setSavedPermanent( new PermanentCard(CardUtil.getDefaultCardSideForBattlefield(game, card), source.getControllerId(), game) - ).setPermanentModifier((token, g) -> { + ).setPermanentModifier((token) -> { token.removeAllCardTypes(); token.addCardType(CardType.ENCHANTMENT); - token.retainAllEnchantmentSubTypes(g); + // We keep enchantment subtypes, clearing the rest. + token.getSubtype().retainAll(SubType.getEnchantmentTypes()); }).apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/s/SauronTheNecromancer.java b/Mage.Sets/src/mage/cards/s/SauronTheNecromancer.java index 0d8829d5d8d..a0f519322b9 100644 --- a/Mage.Sets/src/mage/cards/s/SauronTheNecromancer.java +++ b/Mage.Sets/src/mage/cards/s/SauronTheNecromancer.java @@ -89,7 +89,7 @@ class SauronTheNecromancerEffect extends OneShotEffect { CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( null, null, false, 1, true, true ); - effect.setPermanentModifier(((token, g) -> { + effect.setPermanentModifier(((token) -> { token.setColor(ObjectColor.BLACK); token.addSubType(SubType.WRAITH); token.setPower(3); diff --git a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java new file mode 100644 index 00000000000..7fb386dee92 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -0,0 +1,236 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * + * @author Susucr + */ +public final class ShelobChildOfUngoliant extends CardImpl { + + private static FilterControlledCreaturePermanent filterSpiders = + new FilterControlledCreaturePermanent(SubType.SPIDER, "other spiders you control"); + + static { + filterSpiders.add(AnotherPredicate.instance); + } + + public ShelobChildOfUngoliant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.DEMON); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"), false)); + + // Other Spiders you control have deathtouch and ward {2}. + Ability buff = new SimpleStaticAbility(new GainAbilityControlledEffect( + DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield, + filterSpiders + ).setText("other Spiders you control have deathtouch")); + buff.addEffect(new GainAbilityControlledEffect( + new WardAbility(new ManaCostsImpl<>("{2}")), Duration.WhileOnBattlefield, + filterSpiders + ).setText(" and ward {2}")); + this.addAbility(buff); + + // Whenever another creature dealt damage this turn by a Spider you controlled dies, + // create a token that's a copy of that creature, except it's a Food artifact with + // "{2}, {T}, Sacrifice this artifact: You gain 3 life," and it loses all other card types. + this.addAbility( + new ShelobChildOfUngoliantTriggeredAbility( + new ShelobChildOfUngoliantEffect() + ) + ); + } + + private ShelobChildOfUngoliant(final ShelobChildOfUngoliant card) { + super(card); + } + + @Override + public ShelobChildOfUngoliant copy() { + return new ShelobChildOfUngoliant(this); + } +} + +class ShelobChildOfUngoliantWatcher extends Watcher { + private static FilterControlledCreaturePermanent spiderFilter = + new FilterControlledCreaturePermanent(SubType.SPIDER,"spiders you controlled"); + + // We store every permanent, as a non-creature may be dealt damage, + // then become a creature then die. + // map players to damaged permanents by a spider under that player's control. + private final Map> damagedPermanents = new HashMap<>(); + + public ShelobChildOfUngoliantWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.DAMAGED_PERMANENT && !game.isSimulation()) { + Permanent damagedPermanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + Permanent spider = game.getPermanentOrLKIBattlefield(event.getSourceId()); + if (damagedPermanent == null || spider == null) { + return; + } + if(!spiderFilter.match(spider, game)){ + return; + } + + UUID playerUUID = spider.getControllerId(); + Set setForThatPlayer = damagedPermanents.getOrDefault(spider.getControllerId(), new HashSet<>()); + // Not sure this test is necessary, as the spiderFilter is a FilterControlledPermanent + if (controllerId != null && controllerId.equals(game.getControllerId(event.getSourceId()))) { + setForThatPlayer.add(new MageObjectReference(event.getTargetId(), game)); + damagedPermanents.put(playerUUID, setForThatPlayer); + } + } + } + + @Override + public void reset() { + super.reset(); + damagedPermanents.clear(); + } + + public boolean wasDamaged(UUID playerUUID, Permanent permanent, Game game) { + if(!damagedPermanents.containsKey(playerUUID)){ + return false; + } + + return damagedPermanents.get(playerUUID) + .contains(new MageObjectReference(permanent, game)); + } +} + +class ShelobChildOfUngoliantTriggeredAbility extends TriggeredAbilityImpl { + + public ShelobChildOfUngoliantTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); + this.addWatcher(new ShelobChildOfUngoliantWatcher()); + this.setTriggerPhrase("Whenever another creature dealt damage this turn by a Spider you controlled dies, "); + } + + public ShelobChildOfUngoliantTriggeredAbility(final ShelobChildOfUngoliantTriggeredAbility ability) { + super(ability); + } + + @Override + public ShelobChildOfUngoliantTriggeredAbility copy() { + return new ShelobChildOfUngoliantTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (((ZoneChangeEvent) event).isDiesEvent()) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + Permanent dyingPermanent = zEvent.getTarget(); + if (StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE.match(dyingPermanent, game)) { + ShelobChildOfUngoliantWatcher watcher = game.getState().getWatcher(ShelobChildOfUngoliantWatcher.class); + if(watcher == null){ + return false; + } + + if(!watcher.wasDamaged(this.controllerId, dyingPermanent, game)){ + return false; + } + + for (Effect effect : getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId(), game)); + } + + return true; + } + } + return false; + } +} + +class ShelobChildOfUngoliantEffect extends OneShotEffect { + + public ShelobChildOfUngoliantEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "create a token that's a copy of that creature, except it's a Food artifact with " + + "\"{2}, {T}, Sacrifice this artifact: You gain 3 life,\" and it loses all other card types."; + } + + public ShelobChildOfUngoliantEffect(final ShelobChildOfUngoliantEffect effect) { + super(effect); + } + + @Override + public ShelobChildOfUngoliantEffect copy() { + return new ShelobChildOfUngoliantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent copyFrom = targetPointer.getFirstTargetPermanentOrLKI(game, source); + if(controller == null || copyFrom == null) { + return false; + } + + return new CreateTokenCopyTargetEffect().setSavedPermanent(copyFrom) + .setPermanentModifier((token) -> { + token.removeAllCardTypes(); + // We keep artifact subtypes, clearing the rest. + token.getSubtype().retainAll(SubType.getArtifactTypes()); + token.addCardType(CardType.ARTIFACT); + token.addSubType(SubType.FOOD); + + // {2}, {T}, Sacrifice this artifact: You gain 3 life. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(3), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + SacrificeSourceCost cost = new SacrificeSourceCost(); + cost.setText("Sacrifice this artifact"); + ability.addCost(cost); + token.addAbility(ability); + }).apply(game, source); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index 6bc71797215..487861015a1 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -231,6 +231,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Shadowfax, Lord of Horses", 227, Rarity.UNCOMMON, mage.cards.s.ShadowfaxLordOfHorses.class)); cards.add(new SetCardInfo("Shagrat, Loot Bearer", 228, Rarity.RARE, mage.cards.s.ShagratLootBearer.class)); cards.add(new SetCardInfo("Shelob's Ambush", 108, Rarity.COMMON, mage.cards.s.ShelobsAmbush.class)); + cards.add(new SetCardInfo("Shelob, Child of Ungoliant", 230, Rarity.RARE, mage.cards.s.ShelobChildOfUngoliant.class)); cards.add(new SetCardInfo("Shire Scarecrow", 249, Rarity.COMMON, mage.cards.s.ShireScarecrow.class)); cards.add(new SetCardInfo("Shire Shirriff", 30, Rarity.UNCOMMON, mage.cards.s.ShireShirriff.class)); cards.add(new SetCardInfo("Shire Terrace", 261, Rarity.COMMON, mage.cards.s.ShireTerrace.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index aabf29e231c..de46df9383a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -34,7 +34,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { @FunctionalInterface public interface PermanentModifier { - void apply(Token token, Game game); + void apply(Token token); } private final Set> abilityClazzesToRemove; @@ -241,7 +241,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { } additionalAbilities.stream().forEach(token::addAbility); if (permanentModifier != null) { - permanentModifier.apply(token, game); + permanentModifier.apply(token); } if (!this.abilityClazzesToRemove.isEmpty()) {