From c3f7a9ab2142285f1d262c8ddf9c01c0d1ccd013 Mon Sep 17 00:00:00 2001 From: ssk97 Date: Mon, 17 Jul 2023 20:47:37 -0700 Subject: [PATCH] Simplify implementation and fix bugs of Kaito, Dancing Shadow (#10639) Implement PermanentReferenceInCollectionPredicate used by that convert existing cards to use new Predicate as needed Replaces PermanentInListPredicate for longer term effects --- Mage.Sets/src/mage/cards/b/BrineHag.java | 26 +-- .../src/mage/cards/k/KaitoDancingShadow.java | 183 ++++-------------- Mage.Sets/src/mage/cards/r/RagingRiver.java | 6 +- .../permanent/PermanentInListPredicate.java | 2 +- ...rmanentReferenceInCollectionPredicate.java | 33 ++++ 5 files changed, 84 insertions(+), 166 deletions(-) create mode 100644 Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java diff --git a/Mage.Sets/src/mage/cards/b/BrineHag.java b/Mage.Sets/src/mage/cards/b/BrineHag.java index c2d395285e2..35546f66b73 100644 --- a/Mage.Sets/src/mage/cards/b/BrineHag.java +++ b/Mage.Sets/src/mage/cards/b/BrineHag.java @@ -1,9 +1,10 @@ package mage.cards.b; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; +import java.util.Set; +import java.util.HashSet; + import mage.MageInt; import mage.MageObjectReference; import mage.abilities.Ability; @@ -16,9 +17,8 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.PermanentInListPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -77,22 +77,10 @@ class BrineHagEffect extends OneShotEffect { return false; } - List list = new ArrayList<>(); - for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { - Player player = game.getPlayer(playerId); - if (player == null) { - continue; - } - - for (Permanent creature : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game)) { - if (sourcePermanent.getDealtDamageByThisTurn().contains(new MageObjectReference(creature.getId(), game))) { - list.add(creature); - } - } - } - if (!list.isEmpty()) { + Set set = new HashSet<>(sourcePermanent.getDealtDamageByThisTurn()); + if (!set.isEmpty()) { FilterCreaturePermanent filter = new FilterCreaturePermanent(); - filter.add(new PermanentInListPredicate(list)); + filter.add(new PermanentReferenceInCollectionPredicate(set)); game.addEffect(new SetBasePowerToughnessAllEffect(0, 2, Duration.Custom, filter, true), source); } return true; diff --git a/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java b/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java index 057eeaa9ff3..01b0b218539 100644 --- a/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java +++ b/Mage.Sets/src/mage/cards/k/KaitoDancingShadow.java @@ -1,8 +1,8 @@ package mage.cards.k; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.Filter; +import mage.MageObjectReference; +import mage.constants.*; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.cards.Card; import mage.cards.CardImpl; @@ -10,31 +10,29 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.combat.CantAttackTargetEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect; -import mage.constants.*; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.DamagedPlayerBatchEvent; import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.DroneToken; import mage.players.Player; -import mage.target.TargetObject; import mage.target.TargetPermanent; import mage.watchers.Watcher; import java.util.*; +import java.util.stream.Collectors; /** * - * @author @stwalsh4118 + * @author notgreat */ public final class KaitoDancingShadow extends CardImpl { @@ -46,7 +44,8 @@ public final class KaitoDancingShadow extends CardImpl { this.setStartingLoyalty(3); // Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of Kaito twice this turn rather than only once. - Ability ability = new KaitoDancingShadowTriggeredAbility(); + Ability ability = new DealCombatDamageControlledTriggeredAbility(Zone.BATTLEFIELD, new KaitoDancingShadowEffect(), true); + ability.addWatcher(new KaitoDancingShadowWatcher()); this.addAbility(ability); // +1: Up to one target creature can't attack or block until your next turn. @@ -72,45 +71,6 @@ public final class KaitoDancingShadow extends CardImpl { } } -class KaitoDancingShadowTriggeredAbility extends TriggeredAbilityImpl { - - KaitoDancingShadowTriggeredAbility() { - super(Zone.BATTLEFIELD, new KaitoDancingShadowEffect()); - this.setTriggerPhrase("Whenever one or more creatures you control deal combat damage to a player, "); - this.addWatcher(new KaitoDancingShadowWatcher()); - - } - - private KaitoDancingShadowTriggeredAbility(final KaitoDancingShadowTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER_BATCH; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedPlayerBatchEvent dEvent = (DamagedPlayerBatchEvent) event; - for (DamagedEvent damagedEvent : dEvent.getEvents()) { - if (!damagedEvent.isCombatDamage()) { - continue; - } - Permanent permanent = game.getPermanent(damagedEvent.getSourceId()); - if (permanent != null && permanent.isControlledBy(getControllerId())) { - return true; - } - } - return false; - } - - @Override - public KaitoDancingShadowTriggeredAbility copy() { - return new KaitoDancingShadowTriggeredAbility(this); - } -} - class KaitoDancingShadowEffect extends OneShotEffect { KaitoDancingShadowEffect() { @@ -133,13 +93,18 @@ class KaitoDancingShadowEffect extends OneShotEffect { if (watcher == null) { return false; } - TargetCreatureThatDealtCombatDamage target = new TargetCreatureThatDealtCombatDamage(0, 1, watcher.getPermanents()); + Player damagedPlayer = game.getPlayer(targetPointer.getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { + if (controller == null || damagedPlayer == null) { return false; } - if (controller.chooseUse(outcome, "Return a creature card that dealt damage to hand?", source, game) && target.chooseTarget(Outcome.ReturnToHand, source.getControllerId(), source, game)) { - Card card = game.getCard(target.getFirstTarget()); + FilterPermanent filter = new FilterPermanent(); + filter.add(new PermanentReferenceInCollectionPredicate( + watcher.getPermanents(controller.getId(),damagedPlayer.getId()))); + TargetPermanent target = new TargetPermanent(0, 1, filter, true); + target.setTargetName("creature to return to hand?"); + if (target.chooseTarget(Outcome.ReturnToHand, source.getControllerId(), source, game)) { + Card card = game.getPermanent(target.getFirstTarget()); if (card != null) { controller.moveCards(card, Zone.HAND, source, game); @@ -147,13 +112,18 @@ class KaitoDancingShadowEffect extends OneShotEffect { game.addEffect(effect, source); } } + return true; } } class KaitoDancingShadowWatcher extends Watcher { + //A creature you control that dealt damage to a player - does not apply across multiple combat steps - private final List permanents = new ArrayList<>(); + //Player ID -> List of permanents they controlled that dealt damage + private final Map> permanents = new HashMap<>(); + //MOR -> Player they dealt damage to + private final Map damageTarget = new HashMap<>(); KaitoDancingShadowWatcher() { super(WatcherScope.GAME); @@ -163,6 +133,7 @@ class KaitoDancingShadowWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.COMBAT_DAMAGE_STEP_POST) { permanents.clear(); + damageTarget.clear(); return; } if (event.getType() != GameEvent.EventType.DAMAGED_PLAYER @@ -173,91 +144,18 @@ class KaitoDancingShadowWatcher extends Watcher { if (creature == null) { return; } - permanents.add(creature); + MageObjectReference mor = new MageObjectReference(creature, game); + damageTarget.put(mor, event.getPlayerId()); + + List list = permanents.computeIfAbsent(creature.getControllerId(), (key) -> new ArrayList<>()); + list.add(mor); } - public List getPermanents() { - return permanents; - } -} - -class TargetCreatureThatDealtCombatDamage extends TargetObject { - - protected List permanents; - private Permanent firstTarget = null; - - public TargetCreatureThatDealtCombatDamage() { - super(); - } - - public TargetCreatureThatDealtCombatDamage(final TargetCreatureThatDealtCombatDamage target) { - super(target); - this.firstTarget = target.firstTarget; - } - - - public TargetCreatureThatDealtCombatDamage(int minNumTargets, int maxNumTargets, List permanents) { - super(minNumTargets, maxNumTargets, Zone.BATTLEFIELD, true); - this.permanents = permanents; - } - - - @Override - public boolean canTarget(UUID id, Game game) { - Card card = game.getCard(id); - if (card != null && game.getState().getZone(card.getId()) == Zone.BATTLEFIELD) { - for (Permanent permanent : permanents) { - if (permanent.getId().equals(id)) { - return true; - } - } - } - return false; - } - - @Override - public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); - return super.chooseTarget(Outcome.Benefit, playerId, source, game); - } - - - @Override - public TargetCreatureThatDealtCombatDamage copy() { - return new TargetCreatureThatDealtCombatDamage(this); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return permanents.size() > 0; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - for (Permanent permanent : permanents) { - if (permanent != null && permanent.isControlledBy(sourceControllerId)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; - } - - @Override - public Filter getFilter() { - return null; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - // TODO Auto-generated method stub - return null; + //Return the set of permanents that the controller controlled which dealt combat damage to the player + public Set getPermanents(UUID controllerID, UUID damagedPlayerID) { + return permanents.get(controllerID).stream() + .filter((mor) -> damagedPlayerID.equals(damageTarget.get(mor))) + .collect(Collectors.toSet()); } } @@ -278,13 +176,12 @@ class KaitoDancingShadowIncreaseLoyaltyUseEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - for (Permanent permanent : game.getBattlefield().getActivePermanents( - StaticFilters.FILTER_CONTROLLED_PERMANENT_PLANESWALKER, - source.getControllerId(), source, game - )) { - permanent.setLoyaltyActivationsAvailable(2); + Permanent kaito = source.getSourcePermanentIfItStillExists(game); + if (kaito == null) { + discard(); + return false; } - + kaito.setLoyaltyActivationsAvailable(2); return true; } diff --git a/Mage.Sets/src/mage/cards/r/RagingRiver.java b/Mage.Sets/src/mage/cards/r/RagingRiver.java index 157501fc9b1..97de05453dc 100644 --- a/Mage.Sets/src/mage/cards/r/RagingRiver.java +++ b/Mage.Sets/src/mage/cards/r/RagingRiver.java @@ -16,7 +16,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.filter.predicate.permanent.PermanentInListPredicate; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.permanent.Permanent; @@ -136,10 +136,10 @@ class RagingRiverEffect extends OneShotEffect { if (controller.choosePile(outcome, attacker.getName() + ": attacking " + defender.getName(), leftLog, rightLog, game)) { - filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentInListPredicate(left)))); + filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentReferenceInCollectionPredicate(left, game)))); game.informPlayers(attacker.getLogName() + ": attacks left (" + defender.getLogName() + ")"); } else { - filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentInListPredicate(right)))); + filter.add(Predicates.not(Predicates.or(new AbilityPredicate(FlyingAbility.class), new PermanentReferenceInCollectionPredicate(right, game)))); game.informPlayers(attacker.getLogName() + ": attacks right (" + defender.getLogName() + ")"); } } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java index 5a03ec6d6d8..99ea97bb6bd 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentInListPredicate.java @@ -20,6 +20,6 @@ public class PermanentInListPredicate implements Predicate { @Override public boolean apply(Permanent input, Game game) { - return permanents.contains(input); + return (permanents != null && permanents.contains(input)); } } diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java new file mode 100644 index 00000000000..4793fd92bee --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/permanent/PermanentReferenceInCollectionPredicate.java @@ -0,0 +1,33 @@ + +package mage.filter.predicate.permanent; + +import mage.MageObjectReference; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * + * @author notgreat + */ +public class PermanentReferenceInCollectionPredicate implements Predicate { + private final Collection references; + + public PermanentReferenceInCollectionPredicate(Collection references) { + //Note: it is assumed that the collection passed in isn't ever mutated afterwards + this.references = references; + } + public PermanentReferenceInCollectionPredicate(Collection permanents, Game game) { + this.references = permanents.stream().map((p) -> new MageObjectReference(p, game)) + .collect(Collectors.toSet()); + } + + @Override + public boolean apply(Permanent input, Game game) { + return (references != null && + references.contains(new MageObjectReference(input, game))); + } +}