[MOM] Implement Zephyr Singer

This commit is contained in:
theelk801 2023-04-09 13:19:13 -04:00
parent 47fe90458f
commit 2c0486673f
7 changed files with 150 additions and 95 deletions

View file

@ -1,9 +1,5 @@
package mage.cards.l; package mage.cards.l;
import java.util.*;
import java.util.stream.Collectors;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect;
@ -15,14 +11,18 @@ import mage.choices.Choice;
import mage.choices.ChoiceImpl; import mage.choices.ChoiceImpl;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; 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.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCreatureOrPlaneswalker; import mage.target.common.TargetCreatureOrPlaneswalker;
import mage.watchers.common.EachCreatureThatConvokedSourceWatcher;
import java.util.*;
import java.util.stream.Collectors;
/** /**
*
* @author Susucre * @author Susucre
*/ */
public final class LethalScheme extends CardImpl { public final class LethalScheme extends CardImpl {
@ -37,7 +37,6 @@ public final class LethalScheme extends CardImpl {
this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker()); this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker());
// Each creature that convoked Lethal Scheme connives. // Each creature that convoked Lethal Scheme connives.
this.getSpellAbility().addWatcher(new EachCreatureThatConvokedSourceWatcher());
this.getSpellAbility().addEffect(new LethalSchemeEffect()); this.getSpellAbility().addEffect(new LethalSchemeEffect());
} }
@ -54,6 +53,12 @@ public final class LethalScheme extends CardImpl {
// Based loosely on "Venerated Loxodon" and "Change of Plans" // Based loosely on "Venerated Loxodon" and "Change of Plans"
class LethalSchemeEffect extends OneShotEffect { class LethalSchemeEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterCreaturePermanent();
static {
filter.add(ConvokedSourcePredicate.SPELL);
}
public LethalSchemeEffect() { public LethalSchemeEffect() {
super(Outcome.Benefit); super(Outcome.Benefit);
this.staticText = "Each creature that convoked Lethal Scheme connives."; this.staticText = "Each creature that convoked Lethal Scheme connives.";
@ -70,30 +75,18 @@ class LethalSchemeEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { 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<MageObjectReference> creatures = watcher.getConvokingCreatures(mor);
if (creatures == null) {
return false;
}
Set<AbstractMap.SimpleEntry<UUID, Permanent>> playerPermanentsPairs = Set<AbstractMap.SimpleEntry<UUID, Permanent>> playerPermanentsPairs =
creatures game.getBattlefield()
.getActivePermanents(filter, source.getControllerId(), source, game)
.stream() .stream()
.map(creatureMOR -> creatureMOR.getPermanentOrLKIBattlefield(game)) .map(permanent -> new AbstractMap.SimpleEntry<>(permanent.getControllerId(), permanent))
.filter(Objects::nonNull)
.map(permanent -> new AbstractMap.SimpleEntry<>(permanent.getControllerId(),permanent))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
Map<Player, Set<Permanent>> permanentsPerPlayer = new HashMap<>(); Map<Player, Set<Permanent>> permanentsPerPlayer = new HashMap<>();
playerPermanentsPairs.forEach(pair -> { playerPermanentsPairs.forEach(pair -> {
Player player = game.getPlayer(pair.getKey()); Player player = game.getPlayer(pair.getKey());
if(!permanentsPerPlayer.containsKey(player)){ if (!permanentsPerPlayer.containsKey(player)) {
permanentsPerPlayer.put(player, new HashSet<>()); permanentsPerPlayer.put(player, new HashSet<>());
} }
permanentsPerPlayer.get(player).add(pair.getValue()); permanentsPerPlayer.get(player).add(pair.getValue());

View file

@ -1,29 +1,31 @@
package mage.cards.v; package mage.cards.v;
import java.util.Set;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersAllEffect;
import mage.abilities.keyword.ConvokeAbility; import mage.abilities.keyword.ConvokeAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.filter.FilterPermanent;
import mage.game.permanent.Permanent; import mage.filter.common.FilterCreaturePermanent;
import mage.watchers.common.EachCreatureThatConvokedSourceWatcher; import mage.filter.predicate.permanent.ConvokedSourcePredicate;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class VeneratedLoxodon extends CardImpl { 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) { public VeneratedLoxodon(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}");
@ -36,7 +38,9 @@ public final class VeneratedLoxodon extends CardImpl {
this.addAbility(new ConvokeAbility()); this.addAbility(new ConvokeAbility());
// When Venerated Loxodon enters the battlefield, put a +1/+1 counter on each creature that convoked it. // 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) { private VeneratedLoxodon(final VeneratedLoxodon card) {
@ -48,40 +52,3 @@ public final class VeneratedLoxodon extends CardImpl {
return new VeneratedLoxodon(this); 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<MageObjectReference> 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;
}
}

View file

@ -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);
}
}

View file

@ -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("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("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("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("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 Lancer", 45, Rarity.UNCOMMON, mage.cards.z.ZhalfirinLancer.class));
cards.add(new SetCardInfo("Zhalfirin Shapecraft", 87, Rarity.COMMON, mage.cards.z.ZhalfirinShapecraft.class)); cards.add(new SetCardInfo("Zhalfirin Shapecraft", 87, Rarity.COMMON, mage.cards.z.ZhalfirinShapecraft.class));

View file

@ -30,6 +30,7 @@ import mage.players.ManaPool;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.watchers.common.ConvokeWatcher;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -79,6 +80,7 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana
public ConvokeAbility() { public ConvokeAbility() {
super(Zone.ALL, null); // all AlternateManaPaymentAbility must use ALL zone to calculate playable abilities super(Zone.ALL, null); // all AlternateManaPaymentAbility must use ALL zone to calculate playable abilities
this.setRuleAtTheTop(true); this.setRuleAtTheTop(true);
this.addWatcher(new ConvokeWatcher());
this.addHint(new ValueHint("Untapped creatures you control", new PermanentsOnBattlefieldCount(filterUntapped))); this.addHint(new ValueHint("Untapped creatures you control", new PermanentsOnBattlefieldCount(filterUntapped)));
} }

View file

@ -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> {
PERMANENT(-1),
SPELL(0);
private final int offset;
ConvokedSourcePredicate(int offset) {
this.offset = offset;
}
@Override
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return ConvokeWatcher.checkConvoke(
new MageObjectReference(input.getSource(), offset), input.getObject(), game
);
}
}

View file

@ -16,11 +16,11 @@ import java.util.Set;
/** /**
* @author LevelX2 * @author LevelX2
*/ */
public class EachCreatureThatConvokedSourceWatcher extends Watcher { public class ConvokeWatcher extends Watcher {
private final Map<MageObjectReference, Set<MageObjectReference>> convokingCreatures = new HashMap<>(); private final Map<MageObjectReference, Set<MageObjectReference>> convokingCreatures = new HashMap<>();
public EachCreatureThatConvokedSourceWatcher() { public ConvokeWatcher() {
super(WatcherScope.GAME); super(WatcherScope.GAME);
} }
@ -29,26 +29,28 @@ public class EachCreatureThatConvokedSourceWatcher extends Watcher {
if (event.getType() != GameEvent.EventType.CONVOKED) { if (event.getType() != GameEvent.EventType.CONVOKED) {
return; return;
} }
Spell spell = game.getSpell(event.getSourceId()); Spell spell = game.getSpell(event.getSourceId());
Permanent tappedCreature = game.getPermanentOrLKIBattlefield(event.getTargetId()); Permanent tappedCreature = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (spell == null || tappedCreature == null) { if (spell == null || tappedCreature == null) {
return; return;
} }
convokingCreatures
MageObjectReference convokedSpell = new MageObjectReference(spell.getSourceId(), game); .computeIfAbsent(new MageObjectReference(spell.getSourceId(), game), x -> new HashSet<>())
Set<MageObjectReference> creatures; .add(new MageObjectReference(tappedCreature, game));
if (convokingCreatures.containsKey(convokedSpell)) {
creatures = convokingCreatures.get(convokedSpell);
} else {
creatures = new HashSet<>();
convokingCreatures.put(convokedSpell, creatures);
}
creatures.add(new MageObjectReference(tappedCreature, game));
} }
public Set<MageObjectReference> getConvokingCreatures(MageObjectReference mor) { public static Set<MageObjectReference> getConvokingCreatures(MageObjectReference mor, Game game) {
return convokingCreatures.get(mor); 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 @Override