diff --git a/Mage.Sets/src/mage/cards/l/LethalScheme.java b/Mage.Sets/src/mage/cards/l/LethalScheme.java index 95c1853393..faca4b3d94 100644 --- a/Mage.Sets/src/mage/cards/l/LethalScheme.java +++ b/Mage.Sets/src/mage/cards/l/LethalScheme.java @@ -1,9 +1,5 @@ package mage.cards.l; -import java.util.*; -import java.util.stream.Collectors; - -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DestroyTargetEffect; @@ -15,14 +11,18 @@ import mage.choices.Choice; import mage.choices.ChoiceImpl; import mage.constants.CardType; import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ConvokedSourcePredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreatureOrPlaneswalker; -import mage.watchers.common.EachCreatureThatConvokedSourceWatcher; + +import java.util.*; +import java.util.stream.Collectors; /** - * * @author Susucre */ public final class LethalScheme extends CardImpl { @@ -37,7 +37,6 @@ public final class LethalScheme extends CardImpl { this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); // Each creature that convoked Lethal Scheme connives. - this.getSpellAbility().addWatcher(new EachCreatureThatConvokedSourceWatcher()); this.getSpellAbility().addEffect(new LethalSchemeEffect()); } @@ -54,6 +53,12 @@ public final class LethalScheme extends CardImpl { // Based loosely on "Venerated Loxodon" and "Change of Plans" class LethalSchemeEffect extends OneShotEffect { + private static final FilterPermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(ConvokedSourcePredicate.SPELL); + } + public LethalSchemeEffect() { super(Outcome.Benefit); this.staticText = "Each creature that convoked Lethal Scheme connives."; @@ -70,30 +75,18 @@ class LethalSchemeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - EachCreatureThatConvokedSourceWatcher watcher = game.getState().getWatcher(EachCreatureThatConvokedSourceWatcher.class); - if (watcher == null) { - return false; - } - - MageObjectReference mor = new MageObjectReference(source.getSourceId(), game); - Set creatures = watcher.getConvokingCreatures(mor); - if (creatures == null) { - return false; - } - Set> playerPermanentsPairs = - creatures - .stream() - .map(creatureMOR -> creatureMOR.getPermanentOrLKIBattlefield(game)) - .filter(Objects::nonNull) - .map(permanent -> new AbstractMap.SimpleEntry<>(permanent.getControllerId(),permanent)) - .collect(Collectors.toSet()); + game.getBattlefield() + .getActivePermanents(filter, source.getControllerId(), source, game) + .stream() + .map(permanent -> new AbstractMap.SimpleEntry<>(permanent.getControllerId(), permanent)) + .collect(Collectors.toSet()); Map> permanentsPerPlayer = new HashMap<>(); playerPermanentsPairs.forEach(pair -> { Player player = game.getPlayer(pair.getKey()); - if(!permanentsPerPlayer.containsKey(player)){ + if (!permanentsPerPlayer.containsKey(player)) { permanentsPerPlayer.put(player, new HashSet<>()); } permanentsPerPlayer.get(player).add(pair.getValue()); @@ -104,13 +97,13 @@ class LethalSchemeEffect extends OneShotEffect { } for (Player player : game - .getState() - .getPlayersInRange(source.getControllerId(), game) - .stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .filter(permanentsPerPlayer::containsKey) - .collect(Collectors.toList())) { + .getState() + .getPlayersInRange(source.getControllerId(), game) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .filter(permanentsPerPlayer::containsKey) + .collect(Collectors.toList())) { Set permanents = permanentsPerPlayer.get(player); diff --git a/Mage.Sets/src/mage/cards/v/VeneratedLoxodon.java b/Mage.Sets/src/mage/cards/v/VeneratedLoxodon.java index 7a0afbb621..6219c1cae0 100644 --- a/Mage.Sets/src/mage/cards/v/VeneratedLoxodon.java +++ b/Mage.Sets/src/mage/cards/v/VeneratedLoxodon.java @@ -1,29 +1,31 @@ package mage.cards.v; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObjectReference; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; import mage.abilities.keyword.ConvokeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.watchers.common.EachCreatureThatConvokedSourceWatcher; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ConvokedSourcePredicate; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class VeneratedLoxodon extends CardImpl { + private static final FilterPermanent filter = new FilterCreaturePermanent("creature that convoked it"); + + static { + filter.add(ConvokedSourcePredicate.PERMANENT); + } + public VeneratedLoxodon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); @@ -36,7 +38,9 @@ public final class VeneratedLoxodon extends CardImpl { this.addAbility(new ConvokeAbility()); // When Venerated Loxodon enters the battlefield, put a +1/+1 counter on each creature that convoked it. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VeneratedLoxodonEffect(), false), new EachCreatureThatConvokedSourceWatcher()); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), false + )); } private VeneratedLoxodon(final VeneratedLoxodon card) { @@ -48,40 +52,3 @@ public final class VeneratedLoxodon extends CardImpl { return new VeneratedLoxodon(this); } } - -class VeneratedLoxodonEffect extends OneShotEffect { - - public VeneratedLoxodonEffect() { - super(Outcome.BoostCreature); - this.staticText = "put a +1/+1 counter on each creature that convoked it"; - } - - public VeneratedLoxodonEffect(final VeneratedLoxodonEffect effect) { - super(effect); - } - - @Override - public VeneratedLoxodonEffect copy() { - return new VeneratedLoxodonEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - EachCreatureThatConvokedSourceWatcher watcher = game.getState().getWatcher(EachCreatureThatConvokedSourceWatcher.class); - if (watcher != null) { - MageObjectReference mor = new MageObjectReference(source.getSourceId(), source.getSourceObjectZoneChangeCounter() - 1, game); // -1 because of spell on the stack - Set creatures = watcher.getConvokingCreatures(mor); - if (creatures != null) { - for (MageObjectReference creatureMOR : creatures) { - Permanent creature = creatureMOR.getPermanent(game); - if (creature != null) { - creature.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game); - } - } - } - return true; - } - return false; - } -} - diff --git a/Mage.Sets/src/mage/cards/z/ZephyrSinger.java b/Mage.Sets/src/mage/cards/z/ZephyrSinger.java new file mode 100644 index 0000000000..233f472ed0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZephyrSinger.java @@ -0,0 +1,62 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.ConvokeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ConvokedSourcePredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ZephyrSinger extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature that convoked it"); + + static { + filter.add(ConvokedSourcePredicate.PERMANENT); + } + + public ZephyrSinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}"); + + this.subtype.add(SubType.SIREN); + this.subtype.add(SubType.PIRATE); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // Convoke + this.addAbility(new ConvokeAbility()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // When Zephyr Singer enters the battlefield, put a flying counter on each creature that convoked it. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new AddCountersAllEffect(CounterType.FLYING.createInstance(), filter) + )); + } + + private ZephyrSinger(final ZephyrSinger card) { + super(card); + } + + @Override + public ZephyrSinger copy() { + return new ZephyrSinger(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MarchOfTheMachine.java b/Mage.Sets/src/mage/sets/MarchOfTheMachine.java index 91b1062ede..40d287e911 100644 --- a/Mage.Sets/src/mage/sets/MarchOfTheMachine.java +++ b/Mage.Sets/src/mage/sets/MarchOfTheMachine.java @@ -237,6 +237,7 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Wrenn's Resolve", 173, Rarity.COMMON, mage.cards.w.WrennsResolve.class)); cards.add(new SetCardInfo("Xerex Strobe-Knight", 85, Rarity.UNCOMMON, mage.cards.x.XerexStrobeKnight.class)); cards.add(new SetCardInfo("Yargle and Multani", 256, Rarity.RARE, mage.cards.y.YargleAndMultani.class)); + cards.add(new SetCardInfo("Zephyr Singer", 86, Rarity.RARE, mage.cards.z.ZephyrSinger.class)); cards.add(new SetCardInfo("Zephyr Winder", 328, Rarity.COMMON, mage.cards.z.ZephyrWinder.class)); cards.add(new SetCardInfo("Zhalfirin Lancer", 45, Rarity.UNCOMMON, mage.cards.z.ZhalfirinLancer.class)); cards.add(new SetCardInfo("Zhalfirin Shapecraft", 87, Rarity.COMMON, mage.cards.z.ZhalfirinShapecraft.class)); diff --git a/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java index bfde10f3c2..e20810067c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java @@ -30,6 +30,7 @@ import mage.players.ManaPool; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetControlledCreaturePermanent; +import mage.watchers.common.ConvokeWatcher; import java.util.ArrayList; import java.util.List; @@ -79,6 +80,7 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana public ConvokeAbility() { super(Zone.ALL, null); // all AlternateManaPaymentAbility must use ALL zone to calculate playable abilities this.setRuleAtTheTop(true); + this.addWatcher(new ConvokeWatcher()); this.addHint(new ValueHint("Untapped creatures you control", new PermanentsOnBattlefieldCount(filterUntapped))); } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java new file mode 100644 index 0000000000..39fa7f6cc5 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java @@ -0,0 +1,28 @@ +package mage.filter.predicate.permanent; + +import mage.MageObjectReference; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.watchers.common.ConvokeWatcher; + +/** + * @author TheElk801 + */ +public enum ConvokedSourcePredicate implements ObjectSourcePlayerPredicate { + PERMANENT(-1), + SPELL(0); + private final int offset; + + ConvokedSourcePredicate(int offset) { + this.offset = offset; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return ConvokeWatcher.checkConvoke( + new MageObjectReference(input.getSource(), offset), input.getObject(), game + ); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/EachCreatureThatConvokedSourceWatcher.java b/Mage/src/main/java/mage/watchers/common/ConvokeWatcher.java similarity index 57% rename from Mage/src/main/java/mage/watchers/common/EachCreatureThatConvokedSourceWatcher.java rename to Mage/src/main/java/mage/watchers/common/ConvokeWatcher.java index b14a4baab9..a1ed34ecd4 100644 --- a/Mage/src/main/java/mage/watchers/common/EachCreatureThatConvokedSourceWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ConvokeWatcher.java @@ -16,11 +16,11 @@ import java.util.Set; /** * @author LevelX2 */ -public class EachCreatureThatConvokedSourceWatcher extends Watcher { +public class ConvokeWatcher extends Watcher { private final Map> convokingCreatures = new HashMap<>(); - public EachCreatureThatConvokedSourceWatcher() { + public ConvokeWatcher() { super(WatcherScope.GAME); } @@ -29,26 +29,28 @@ public class EachCreatureThatConvokedSourceWatcher extends Watcher { if (event.getType() != GameEvent.EventType.CONVOKED) { return; } - Spell spell = game.getSpell(event.getSourceId()); Permanent tappedCreature = game.getPermanentOrLKIBattlefield(event.getTargetId()); if (spell == null || tappedCreature == null) { return; } - - MageObjectReference convokedSpell = new MageObjectReference(spell.getSourceId(), game); - Set creatures; - if (convokingCreatures.containsKey(convokedSpell)) { - creatures = convokingCreatures.get(convokedSpell); - } else { - creatures = new HashSet<>(); - convokingCreatures.put(convokedSpell, creatures); - } - creatures.add(new MageObjectReference(tappedCreature, game)); + convokingCreatures + .computeIfAbsent(new MageObjectReference(spell.getSourceId(), game), x -> new HashSet<>()) + .add(new MageObjectReference(tappedCreature, game)); } - public Set getConvokingCreatures(MageObjectReference mor) { - return convokingCreatures.get(mor); + public static Set getConvokingCreatures(MageObjectReference mor, Game game) { + return game + .getState() + .getWatcher(ConvokeWatcher.class) + .convokingCreatures + .get(mor); + } + + public static boolean checkConvoke(MageObjectReference mor, Permanent permanent, Game game) { + return getConvokingCreatures(mor, game) + .stream() + .anyMatch(m -> m.refersTo(permanent, game)); } @Override