From 6e85b2e4ab5e6faca69804b250d4bf4fc908b403 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:17:38 +0200 Subject: [PATCH 1/4] initial implementation --- Mage/src/main/java/mage/filter/Filter.java | 7 +- .../src/main/java/mage/filter/FilterCard.java | 78 +++++-------------- .../src/main/java/mage/filter/FilterImpl.java | 44 ++++++++++- .../main/java/mage/filter/FilterInPlay.java | 18 ----- .../src/main/java/mage/filter/FilterMana.java | 2 + .../main/java/mage/filter/FilterObject.java | 32 +++++--- .../java/mage/filter/FilterPermanent.java | 48 +++--------- .../main/java/mage/filter/FilterPlayer.java | 47 +++-------- .../main/java/mage/filter/FilterSource.java | 38 ++------- .../main/java/mage/filter/FilterSpell.java | 12 +-- .../java/mage/filter/FilterStackObject.java | 41 +++------- .../common/FilterPermanentOrPlayer.java | 5 +- .../FilterPermanentOrSuspendedCard.java | 7 +- .../filter/common/FilterSpellOrPermanent.java | 3 +- .../mage/target/common/TargetOpponent.java | 1 - 15 files changed, 132 insertions(+), 251 deletions(-) delete mode 100644 Mage/src/main/java/mage/filter/FilterInPlay.java diff --git a/Mage/src/main/java/mage/filter/Filter.java b/Mage/src/main/java/mage/filter/Filter.java index 90486321ab9..0eea30f5a35 100644 --- a/Mage/src/main/java/mage/filter/Filter.java +++ b/Mage/src/main/java/mage/filter/Filter.java @@ -1,5 +1,7 @@ package mage.filter; +import mage.abilities.Ability; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.util.Copyable; @@ -7,6 +9,7 @@ import mage.util.Copyable; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.UUID; /** * @param @@ -21,6 +24,8 @@ public interface Filter extends Serializable, Copyable> { boolean match(E o, Game game); + boolean match(E object, UUID sourceControllerId, Ability source, Game game); + Filter add(Predicate predicate); boolean checkObjectClass(Object object); @@ -37,7 +42,7 @@ public interface Filter extends Serializable, Copyable> { List> getPredicates(); - default List getExtraPredicates() { + default List> getExtraPredicates() { return new ArrayList<>(); } } diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index ccdb72f6e52..2ba1edbe035 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -1,9 +1,7 @@ package mage.filter; -import mage.abilities.Ability; import mage.cards.Card; import mage.constants.TargetController; -import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; @@ -24,7 +22,6 @@ import java.util.stream.Collectors; public class FilterCard extends FilterObject { private static final long serialVersionUID = 1L; - protected final List> extraPredicates = new ArrayList<>(); public FilterCard() { super("card"); @@ -36,56 +33,6 @@ public class FilterCard extends FilterObject { protected FilterCard(final FilterCard filter) { super(filter); - this.extraPredicates.addAll(filter.extraPredicates); - } - - //20130711 708.6c - /* If anything performs a comparison involving multiple characteristics or - * values of one or more split cards in any zone other than the stack or - * involving multiple characteristics or values of one or more fused split - * spells, each characteristic or value is compared separately. If each of - * the individual comparisons would return a “yes” answer, the whole - * comparison returns a “yes” answer. The individual comparisons may involve - * different halves of the same split card. - */ - @Override - public boolean match(Card card, Game game) { - if (card == null) { - return false; - } - return super.match(card, game); - } - - public boolean match(Card card, UUID playerId, Game game) { - return match(card, playerId, null, game); - } - - public boolean match(Card card, UUID playerId, Ability source, Game game) { - if (!this.match(card, game)) { - return false; - } - ObjectSourcePlayer osp = new ObjectSourcePlayer<>(card, playerId, source); - return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); - } - - public final void add(ObjectSourcePlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } - - // verify check - checkPredicateIsSuitableForCardFilter(predicate); - Predicates.makeSurePredicateCompatibleWithFilter(predicate, Card.class); - - extraPredicates.add(predicate); - } - - public Set filter(Set cards, Game game) { - return cards.stream().filter(card -> match(card, game)).collect(Collectors.toSet()); - } - - public boolean hasPredicates() { - return !predicates.isEmpty() || !extraPredicates.isEmpty(); } @Override @@ -93,13 +40,16 @@ public class FilterCard extends FilterObject { return new FilterCard(this); } - @Override - public List getExtraPredicates() { - return new ArrayList<>(extraPredicates); + public boolean match(Card card, UUID playerId, Game game) { + return match(card, playerId, null, game); + } + + public Set filter(Set cards, Game game) { + return cards.stream().filter(card -> match(card, game)).collect(Collectors.toSet()); } public static void checkPredicateIsSuitableForCardFilter(Predicate predicate) { - // card filter can't contain controller predicate (only permanents on battlefield have controller) + // card filter can't contain controller predicate (only permanents on battlefield and StackObjects have controller) List list = new ArrayList<>(); Predicates.collectAllComponents(predicate, list); if (list.stream().anyMatch(TargetController.ControllerPredicate.class::isInstance)) { @@ -107,8 +57,22 @@ public class FilterCard extends FilterObject { } } + public FilterCard withMessage(String message) { this.setMessage(message); return this; } + + @Override + public void add(ObjectSourcePlayerPredicate predicate) { + // verify checks + checkPredicateIsSuitableForCardFilter(predicate); + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Card.class); + this.addExtra(predicate); + } + + @Override + public boolean checkObjectClass(Object object) { + return object instanceof Card; + } } diff --git a/Mage/src/main/java/mage/filter/FilterImpl.java b/Mage/src/main/java/mage/filter/FilterImpl.java index 428610eabbb..8575e0b7063 100644 --- a/Mage/src/main/java/mage/filter/FilterImpl.java +++ b/Mage/src/main/java/mage/filter/FilterImpl.java @@ -1,11 +1,15 @@ package mage.filter; +import mage.abilities.Ability; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.game.Game; import java.util.ArrayList; import java.util.List; +import java.util.UUID; /** * @param @@ -14,7 +18,8 @@ import java.util.List; */ public abstract class FilterImpl implements Filter { - protected List> predicates = new ArrayList<>(); + private List> predicates = new ArrayList<>(); + private List> extraPredicates = new ArrayList<>(); protected String message; protected boolean lockedFilter; // Helps to prevent "accidentally" modifying the StaticFilters objects @@ -29,6 +34,7 @@ public abstract class FilterImpl implements Filter { protected FilterImpl(final FilterImpl filter) { this.message = filter.message; this.predicates = new ArrayList<>(filter.predicates); + this.extraPredicates.addAll(filter.extraPredicates); this.lockedFilter = false;// After copying a filter it's allowed to modify } @@ -40,8 +46,16 @@ public abstract class FilterImpl implements Filter { return false; } + public boolean match(E object, UUID sourceControllerId, Ability source, Game game) { + if (!this.match(object, game)) { + return false; + } + ObjectSourcePlayer osp = new ObjectSourcePlayer<>(object, sourceControllerId, source); + return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); + } + @Override - public final Filter add(Predicate predicate) { + public Filter add(Predicate predicate) { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } @@ -49,6 +63,24 @@ public abstract class FilterImpl implements Filter { return this; } + /** + * Make sure on setting a new Filter that you overwrite this method + * and call Predicates.makeSurePredicateCompatibleWithFilter + * to check that the filter is able to process objects + * of the right kind. Helps with checks the Compiler can't do + * due to ObjectSourcePlayer casting in the this.match(4 arguments). + */ + public void add(ObjectSourcePlayerPredicate predicate) { + addExtra(predicate); + } + + public void addExtra(ObjectSourcePlayerPredicate predicate) { + if (isLockedFilter()) { + throw new UnsupportedOperationException("You may not modify a locked filter"); + } + extraPredicates.add(predicate); + } + @Override public String getMessage() { return message; @@ -80,4 +112,12 @@ public abstract class FilterImpl implements Filter { public List> getPredicates() { return predicates; } + + public List> getExtraPredicates() { + return new ArrayList<>(extraPredicates); + } + + public boolean hasPredicates() { + return !predicates.isEmpty() || !extraPredicates.isEmpty(); + } } diff --git a/Mage/src/main/java/mage/filter/FilterInPlay.java b/Mage/src/main/java/mage/filter/FilterInPlay.java deleted file mode 100644 index ba0b9979a7e..00000000000 --- a/Mage/src/main/java/mage/filter/FilterInPlay.java +++ /dev/null @@ -1,18 +0,0 @@ -package mage.filter; - -import mage.abilities.Ability; -import mage.game.Game; - -import java.util.UUID; - -/** - * @param - * @author BetaSteward_at_googlemail.com - */ -public interface FilterInPlay extends Filter { - - boolean match(E o, UUID playerId, Ability source, Game game); - - @Override - FilterInPlay copy(); -} diff --git a/Mage/src/main/java/mage/filter/FilterMana.java b/Mage/src/main/java/mage/filter/FilterMana.java index a8584e7fcd8..81410fe112f 100644 --- a/Mage/src/main/java/mage/filter/FilterMana.java +++ b/Mage/src/main/java/mage/filter/FilterMana.java @@ -7,6 +7,8 @@ import java.util.ArrayList; import java.util.List; /** + * TODO: Not really a Filter. Should be renamed? + * * @author nantuko */ public class FilterMana implements Serializable { diff --git a/Mage/src/main/java/mage/filter/FilterObject.java b/Mage/src/main/java/mage/filter/FilterObject.java index 6795d65b76d..5d816f60531 100644 --- a/Mage/src/main/java/mage/filter/FilterObject.java +++ b/Mage/src/main/java/mage/filter/FilterObject.java @@ -2,24 +2,17 @@ package mage.filter; import mage.MageObject; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicates; /** + * // TODO: migrate all FilterObject to more specific ones, then remove this class? * - * @author North * @param + * @author North */ public class FilterObject extends FilterImpl { - @Override - public FilterObject copy() { - return new FilterObject<>(this); - } - - @Override - public boolean checkObjectClass(Object object) { - return object instanceof MageObject; - } - public FilterObject(String name) { super(name); } @@ -27,4 +20,21 @@ public class FilterObject extends FilterImpl { protected FilterObject(final FilterObject filter) { super(filter); } + + @Override + public FilterObject copy() { + return new FilterObject(this); + } + + @Override + public void add(ObjectSourcePlayerPredicate predicate) { + // verify checks + Predicates.makeSurePredicateCompatibleWithFilter(predicate, MageObject.class); + this.addExtra(predicate); + } + + @Override + public boolean checkObjectClass(Object object) { + return object instanceof MageObject; + } } diff --git a/Mage/src/main/java/mage/filter/FilterPermanent.java b/Mage/src/main/java/mage/filter/FilterPermanent.java index 9eaee653392..dbe3c0ff4af 100644 --- a/Mage/src/main/java/mage/filter/FilterPermanent.java +++ b/Mage/src/main/java/mage/filter/FilterPermanent.java @@ -1,25 +1,16 @@ package mage.filter; -import mage.abilities.Ability; import mage.constants.SubType; -import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import mage.game.Game; import mage.game.permanent.Permanent; -import java.util.ArrayList; -import java.util.List; import java.util.Set; -import java.util.UUID; /** * @author North */ -public class FilterPermanent extends FilterObject implements FilterInPlay { - - protected final List> extraPredicates = new ArrayList<>(); +public class FilterPermanent extends FilterObject { public FilterPermanent() { super("permanent"); @@ -43,32 +34,6 @@ public class FilterPermanent extends FilterObject implements FilterIn protected FilterPermanent(final FilterPermanent filter) { super(filter); - this.extraPredicates.addAll(filter.extraPredicates); - } - - @Override - public boolean checkObjectClass(Object object) { - return object instanceof Permanent; - } - - @Override - public boolean match(Permanent permanent, UUID playerId, Ability source, Game game) { - if (!this.match(permanent, game) || !permanent.isPhasedIn()) { - return false; - } - ObjectSourcePlayer osp = new ObjectSourcePlayer<>(permanent, playerId, source); - return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); - } - - public final void add(ObjectSourcePlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } - - // verify check - Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class); - - extraPredicates.add(predicate); } @Override @@ -77,7 +42,14 @@ public class FilterPermanent extends FilterObject implements FilterIn } @Override - public List getExtraPredicates() { - return new ArrayList<>(extraPredicates); + public void add(ObjectSourcePlayerPredicate predicate) { + // verify checks + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class); + this.addExtra(predicate); + } + + @Override + public boolean checkObjectClass(Object object) { + return object instanceof Permanent; } } diff --git a/Mage/src/main/java/mage/filter/FilterPlayer.java b/Mage/src/main/java/mage/filter/FilterPlayer.java index 766cba8253b..e33775a2ba5 100644 --- a/Mage/src/main/java/mage/filter/FilterPlayer.java +++ b/Mage/src/main/java/mage/filter/FilterPlayer.java @@ -1,25 +1,15 @@ package mage.filter; -import mage.abilities.Ability; -import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import mage.game.Game; import mage.players.Player; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - /** * @author BetaSteward_at_googlemail.com * @author North */ public class FilterPlayer extends FilterImpl { - protected final List> extraPredicates = new ArrayList<>(); - public FilterPlayer() { this("player"); } @@ -30,32 +20,6 @@ public class FilterPlayer extends FilterImpl { protected FilterPlayer(final FilterPlayer filter) { super(filter); - this.extraPredicates.addAll(filter.extraPredicates); - } - - public FilterPlayer add(ObjectSourcePlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } - - // verify check - Predicates.makeSurePredicateCompatibleWithFilter(predicate, Player.class); - - extraPredicates.add(predicate); - return this; - } - - @Override - public boolean checkObjectClass(Object object) { - return object instanceof Player; - } - - public boolean match(Player checkPlayer, UUID sourceControllerId, Ability source, Game game) { - if (!this.match(checkPlayer, game)) { - return false; - } - ObjectSourcePlayer osp = new ObjectSourcePlayer<>(checkPlayer, sourceControllerId, source); - return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); } @Override @@ -64,7 +28,14 @@ public class FilterPlayer extends FilterImpl { } @Override - public List getExtraPredicates() { - return new ArrayList<>(extraPredicates); + public void add(ObjectSourcePlayerPredicate predicate) { + // verify checks + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Player.class); + this.addExtra(predicate); + } + + @Override + public boolean checkObjectClass(Object object) { + return object instanceof Player; } } diff --git a/Mage/src/main/java/mage/filter/FilterSource.java b/Mage/src/main/java/mage/filter/FilterSource.java index 335e30b4f9f..b7285223a1f 100644 --- a/Mage/src/main/java/mage/filter/FilterSource.java +++ b/Mage/src/main/java/mage/filter/FilterSource.java @@ -2,28 +2,18 @@ package mage.filter; import mage.MageObject; -import mage.abilities.Ability; import mage.cards.Card; -import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import mage.game.Game; import mage.game.command.CommandObject; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - /** * @author Susucr */ public class FilterSource extends FilterObject { - protected final List> extraPredicates = new ArrayList<>(); - public FilterSource() { super("source"); } @@ -34,7 +24,6 @@ public class FilterSource extends FilterObject { private FilterSource(final FilterSource filter) { super(filter); - this.extraPredicates.addAll(filter.extraPredicates); } @Override @@ -42,16 +31,12 @@ public class FilterSource extends FilterObject { return new FilterSource(this); } - public FilterSource add(ObjectSourcePlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } - - // verify check -- make sure predicates work with all 3 Class that could be a Source + @Override + public void add(ObjectSourcePlayerPredicate predicate) { + // verify checks + // A source can be a lot of different things, so a variety of predicates can be fed here Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class, Card.class, StackObject.class, CommandObject.class); - - extraPredicates.add(predicate); - return this; + this.addExtra(predicate); } @Override @@ -61,17 +46,4 @@ public class FilterSource extends FilterObject { || object instanceof StackObject || object instanceof CommandObject; } - - public boolean match(MageObject object, UUID sourceControllerId, Ability source, Game game) { - if (!this.match(object, game)) { - return false; - } - ObjectSourcePlayer osp = new ObjectSourcePlayer<>(object, sourceControllerId, source); - return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); - } - - @Override - public List getExtraPredicates() { - return new ArrayList<>(extraPredicates); - } } diff --git a/Mage/src/main/java/mage/filter/FilterSpell.java b/Mage/src/main/java/mage/filter/FilterSpell.java index 619fd45b0e9..a2a56ff759a 100644 --- a/Mage/src/main/java/mage/filter/FilterSpell.java +++ b/Mage/src/main/java/mage/filter/FilterSpell.java @@ -1,12 +1,7 @@ package mage.filter; -import java.util.UUID; - -import mage.abilities.Ability; -import mage.game.Game; import mage.game.stack.Spell; -import mage.game.stack.StackObject; /** * @author North, Quercitron @@ -26,11 +21,8 @@ public class FilterSpell extends FilterStackObject { } @Override - public boolean match(StackObject stackObject, UUID playerId, Ability source, Game game) { - if (!(stackObject instanceof Spell)) { - return false; - } - return super.match(stackObject, playerId, source, game); + public boolean checkObjectClass(Object object) { + return object instanceof Spell; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterStackObject.java b/Mage/src/main/java/mage/filter/FilterStackObject.java index aa3b506c1ae..53f59d7c131 100644 --- a/Mage/src/main/java/mage/filter/FilterStackObject.java +++ b/Mage/src/main/java/mage/filter/FilterStackObject.java @@ -1,25 +1,16 @@ package mage.filter; -import mage.abilities.Ability; import mage.cards.Card; -import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import mage.game.Game; import mage.game.stack.Spell; import mage.game.stack.StackObject; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - /** * @author North */ public class FilterStackObject extends FilterObject { - protected final List> extraPredicates = new ArrayList<>(); public FilterStackObject() { this("spell or ability"); @@ -31,27 +22,6 @@ public class FilterStackObject extends FilterObject { protected FilterStackObject(final FilterStackObject filter) { super(filter); - this.extraPredicates.addAll(filter.extraPredicates); - } - - public boolean match(StackObject stackObject, UUID playerId, Ability source, Game game) { - if (!this.match(stackObject, game)) { - return false; - } - ObjectSourcePlayer osp = new ObjectSourcePlayer<>(stackObject, playerId, source); - return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); - } - - public final void add(ObjectSourcePlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } - - // verify check - // Spell implements Card interface, so it can use some default predicates like owner - Predicates.makeSurePredicateCompatibleWithFilter(predicate, StackObject.class, Spell.class, Card.class); - - extraPredicates.add(predicate); } @Override @@ -59,8 +29,15 @@ public class FilterStackObject extends FilterObject { return new FilterStackObject(this); } + public final void add(ObjectSourcePlayerPredicate predicate) { + // verify checks + // Spell implements Card interface, so it can use some default predicates like owner + Predicates.makeSurePredicateCompatibleWithFilter(predicate, StackObject.class, Spell.class, Card.class); + this.addExtra(predicate); + } + @Override - public List getExtraPredicates() { - return new ArrayList<>(extraPredicates); + public boolean checkObjectClass(Object object) { + return object instanceof StackObject; } } diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java index 385d18f915d..ac18369a15b 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java @@ -3,11 +3,8 @@ package mage.filter.common; import mage.MageItem; import mage.abilities.Ability; import mage.filter.FilterImpl; -import mage.filter.FilterInPlay; import mage.filter.FilterPermanent; import mage.filter.FilterPlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -17,7 +14,7 @@ import java.util.UUID; /** * @author nantuko */ -public class FilterPermanentOrPlayer extends FilterImpl implements FilterInPlay { +public class FilterPermanentOrPlayer extends FilterImpl { protected final FilterPermanent permanentFilter; protected final FilterPlayer playerFilter; diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java index 1dec36f6875..7bde90a10ff 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java @@ -1,7 +1,5 @@ package mage.filter.common; -import java.util.UUID; - import mage.MageObject; import mage.abilities.Ability; import mage.abilities.keyword.SuspendAbility; @@ -9,16 +7,17 @@ import mage.cards.Card; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.FilterImpl; -import mage.filter.FilterInPlay; import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * @author emerald000 */ -public class FilterPermanentOrSuspendedCard extends FilterImpl implements FilterInPlay { +public class FilterPermanentOrSuspendedCard extends FilterImpl { protected FilterCard cardFilter; protected FilterPermanent permanentFilter; diff --git a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java index fa5af86f978..8757abc3d60 100644 --- a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java +++ b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java @@ -3,7 +3,6 @@ package mage.filter.common; import mage.MageObject; import mage.abilities.Ability; import mage.filter.FilterImpl; -import mage.filter.FilterInPlay; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; import mage.game.Game; @@ -15,7 +14,7 @@ import java.util.UUID; /** * @author LevelX */ -public class FilterSpellOrPermanent extends FilterImpl implements FilterInPlay { +public class FilterSpellOrPermanent extends FilterImpl { protected FilterPermanent permanentFilter; protected FilterSpell spellFilter; diff --git a/Mage/src/main/java/mage/target/common/TargetOpponent.java b/Mage/src/main/java/mage/target/common/TargetOpponent.java index 3fbe826de18..7c52c08a6c9 100644 --- a/Mage/src/main/java/mage/target/common/TargetOpponent.java +++ b/Mage/src/main/java/mage/target/common/TargetOpponent.java @@ -1,7 +1,6 @@ package mage.target.common; import mage.filter.FilterOpponent; -import mage.filter.FilterPlayer; import mage.target.TargetPlayer; /** From 19b8a80ab5f83ca8cc293ed8f04cd067d35c152f Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sun, 1 Jun 2025 18:31:34 +0200 Subject: [PATCH 2/4] more refactor, add MultiFilterImpl --- .../src/mage/cards/b/BrutalExpulsion.java | 3 +- Mage.Sets/src/mage/cards/c/CommitMemory.java | 8 +- .../src/mage/cards/u/Unsubstantiate.java | 9 +- Mage/src/main/java/mage/filter/Filter.java | 18 ++- .../main/java/mage/filter/FilterAbility.java | 16 ++- .../src/main/java/mage/filter/FilterImpl.java | 14 +- .../main/java/mage/filter/FilterSpell.java | 4 +- .../java/mage/filter/FilterStackObject.java | 3 +- .../java/mage/filter/MultiFilterImpl.java | 122 ++++++++++++++++++ .../mage/filter/common/FilterAnyTarget.java | 2 +- .../FilterCreaturePlayerOrPlaneswalker.java | 2 +- .../mage/filter/common/FilterDefender.java | 7 +- .../common/FilterPermanentOrPlayer.java | 64 ++------- .../FilterPermanentOrSuspendedCard.java | 72 ++--------- .../filter/common/FilterSpellOrPermanent.java | 74 ++--------- 15 files changed, 208 insertions(+), 210 deletions(-) create mode 100644 Mage/src/main/java/mage/filter/MultiFilterImpl.java diff --git a/Mage.Sets/src/mage/cards/b/BrutalExpulsion.java b/Mage.Sets/src/mage/cards/b/BrutalExpulsion.java index 8eec6a17a4a..64066e1d184 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalExpulsion.java +++ b/Mage.Sets/src/mage/cards/b/BrutalExpulsion.java @@ -10,7 +10,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterSpellOrPermanent; import mage.target.common.TargetCreatureOrPlaneswalker; import mage.target.common.TargetSpellOrPermanent; @@ -26,7 +25,7 @@ public final class BrutalExpulsion extends CardImpl { private static final FilterSpellOrPermanent filter = new FilterSpellOrPermanent("spell or creature"); static { - filter.setPermanentFilter(new FilterCreaturePermanent()); + filter.getPermanentFilter().add(CardType.CREATURE.getPredicate()); } public BrutalExpulsion(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/c/CommitMemory.java b/Mage.Sets/src/mage/cards/c/CommitMemory.java index 0c5f6b523ca..49e3cdc7e8a 100644 --- a/Mage.Sets/src/mage/cards/c/CommitMemory.java +++ b/Mage.Sets/src/mage/cards/c/CommitMemory.java @@ -1,7 +1,6 @@ package mage.cards.c; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -13,16 +12,17 @@ import mage.cards.SplitCard; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SpellAbilityType; -import mage.filter.common.FilterNonlandPermanent; import mage.filter.common.FilterSpellOrPermanent; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetSpellOrPermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class CommitMemory extends SplitCard { @@ -30,7 +30,7 @@ public final class CommitMemory extends SplitCard { private static final FilterSpellOrPermanent filter = new FilterSpellOrPermanent("spell or nonland permanent"); static { - filter.setPermanentFilter(new FilterNonlandPermanent()); + filter.getPermanentFilter().add(Predicates.not(CardType.LAND.getPredicate())); } public CommitMemory(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/u/Unsubstantiate.java b/Mage.Sets/src/mage/cards/u/Unsubstantiate.java index 764262a00e6..0e3fd807e54 100644 --- a/Mage.Sets/src/mage/cards/u/Unsubstantiate.java +++ b/Mage.Sets/src/mage/cards/u/Unsubstantiate.java @@ -1,17 +1,16 @@ package mage.cards.u; -import java.util.UUID; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterSpellOrPermanent; import mage.target.common.TargetSpellOrPermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class Unsubstantiate extends CardImpl { @@ -19,11 +18,11 @@ public final class Unsubstantiate extends CardImpl { private static final FilterSpellOrPermanent filter = new FilterSpellOrPermanent("spell or creature"); static { - filter.setPermanentFilter(new FilterCreaturePermanent()); + filter.getPermanentFilter().add(CardType.CREATURE.getPredicate()); } public Unsubstantiate(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Return target spell or creature to its owner's hand. this.getSpellAbility().addTarget(new TargetSpellOrPermanent(1, 1, filter, false)); diff --git a/Mage/src/main/java/mage/filter/Filter.java b/Mage/src/main/java/mage/filter/Filter.java index 0eea30f5a35..44b47979b3b 100644 --- a/Mage/src/main/java/mage/filter/Filter.java +++ b/Mage/src/main/java/mage/filter/Filter.java @@ -28,6 +28,20 @@ public interface Filter extends Serializable, Copyable> { Filter add(Predicate predicate); + /** + * Make sure on setting a new Filter that you overwrite this method + * and call Predicates.makeSurePredicateCompatibleWithFilter + * to check that the filter is able to process objects + * of the right kind. Helps with checks the Compiler can't do + * due to ObjectSourcePlayer casting in the this.match(4 arguments). + *

+ * (method should then call this.addExtra(predicate) after verify checks) + */ + void add(ObjectSourcePlayerPredicate predicate); + + // TODO: if someone can find a way to not have to add this (overload of add made it necessary to introduce) + void addExtra(ObjectSourcePlayerPredicate predicate); + boolean checkObjectClass(Object object); String getMessage(); @@ -36,9 +50,9 @@ public interface Filter extends Serializable, Copyable> { Filter copy(); - public boolean isLockedFilter(); + boolean isLockedFilter(); - public void setLockedFilter(boolean lockedFilter); + void setLockedFilter(boolean lockedFilter); List> getPredicates(); diff --git a/Mage/src/main/java/mage/filter/FilterAbility.java b/Mage/src/main/java/mage/filter/FilterAbility.java index 887d3b3eb65..66fc6bf1fed 100644 --- a/Mage/src/main/java/mage/filter/FilterAbility.java +++ b/Mage/src/main/java/mage/filter/FilterAbility.java @@ -4,11 +4,13 @@ package mage.filter; import mage.abilities.Ability; import mage.constants.AbilityType; import mage.constants.Zone; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; +import mage.game.stack.StackObject; /** - * * @author North */ public class FilterAbility extends FilterImpl { @@ -38,6 +40,18 @@ public class FilterAbility extends FilterImpl { return new AbilityTypePredicate(type); } + @Override + public Filter add(Predicate predicate) { + return super.add(predicate); + } + + @Override + public void add(ObjectSourcePlayerPredicate predicate) { + // Verify Checks + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Ability.class, StackObject.class); + this.addExtra(predicate); + } + @Override public boolean checkObjectClass(Object object) { return object instanceof Ability; diff --git a/Mage/src/main/java/mage/filter/FilterImpl.java b/Mage/src/main/java/mage/filter/FilterImpl.java index 8575e0b7063..bca981cedd0 100644 --- a/Mage/src/main/java/mage/filter/FilterImpl.java +++ b/Mage/src/main/java/mage/filter/FilterImpl.java @@ -63,18 +63,8 @@ public abstract class FilterImpl implements Filter { return this; } - /** - * Make sure on setting a new Filter that you overwrite this method - * and call Predicates.makeSurePredicateCompatibleWithFilter - * to check that the filter is able to process objects - * of the right kind. Helps with checks the Compiler can't do - * due to ObjectSourcePlayer casting in the this.match(4 arguments). - */ - public void add(ObjectSourcePlayerPredicate predicate) { - addExtra(predicate); - } - - public void addExtra(ObjectSourcePlayerPredicate predicate) { + @Override + public final void addExtra(ObjectSourcePlayerPredicate predicate) { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } diff --git a/Mage/src/main/java/mage/filter/FilterSpell.java b/Mage/src/main/java/mage/filter/FilterSpell.java index a2a56ff759a..1abaabdb0d1 100644 --- a/Mage/src/main/java/mage/filter/FilterSpell.java +++ b/Mage/src/main/java/mage/filter/FilterSpell.java @@ -1,6 +1,7 @@ package mage.filter; +import mage.cards.Card; import mage.game.stack.Spell; /** @@ -22,7 +23,8 @@ public class FilterSpell extends FilterStackObject { @Override public boolean checkObjectClass(Object object) { - return object instanceof Spell; + return object instanceof Spell + || object instanceof Card; // TODO: investigate. Is sometimes used for checking a spell's characteristic before cast } @Override diff --git a/Mage/src/main/java/mage/filter/FilterStackObject.java b/Mage/src/main/java/mage/filter/FilterStackObject.java index 53f59d7c131..0ba2ebe4d42 100644 --- a/Mage/src/main/java/mage/filter/FilterStackObject.java +++ b/Mage/src/main/java/mage/filter/FilterStackObject.java @@ -38,6 +38,7 @@ public class FilterStackObject extends FilterObject { @Override public boolean checkObjectClass(Object object) { - return object instanceof StackObject; + return object instanceof StackObject + || object instanceof Card; // TODO: investigate. Is sometimes used for checking a spell's characteristic before cast } } diff --git a/Mage/src/main/java/mage/filter/MultiFilterImpl.java b/Mage/src/main/java/mage/filter/MultiFilterImpl.java new file mode 100644 index 00000000000..dbbfc862100 --- /dev/null +++ b/Mage/src/main/java/mage/filter/MultiFilterImpl.java @@ -0,0 +1,122 @@ +package mage.filter; + +import mage.abilities.Ability; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicate; +import mage.game.Game; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * Make a Filter out of multiple inner filters. + * + * @author Susucr + */ +public abstract class MultiFilterImpl implements Filter { + + protected List> innerFilters = new ArrayList<>(); + private String message; + + @Override + public abstract MultiFilterImpl copy(); + + protected MultiFilterImpl(String name, Filter... filters) { + this.message = name; + this.innerFilters.addAll(Arrays.stream(filters).collect(Collectors.toList())); + } + + protected MultiFilterImpl(final MultiFilterImpl filter) { + this.message = filter.message; + for (Filter innerFilter : filter.innerFilters) { + this.innerFilters.add(innerFilter.copy()); + } + } + + @Override + public boolean match(E object, Game game) { + return innerFilters + .stream() + .anyMatch((Filter filter) -> filter.match(object, game)); + } + + public boolean match(E object, UUID sourceControllerId, Ability source, Game game) { + return innerFilters + .stream() + .anyMatch((Filter filter) -> filter.match(object, sourceControllerId, source, game)); + } + + @Override + public Filter add(Predicate predicate) { + innerFilters.forEach((Filter filter) -> filter.add(predicate)); + return this; + } + + @Override + public void add(ObjectSourcePlayerPredicate predicate) { + innerFilters.forEach((Filter filter) -> filter.add(predicate)); + } + + @Override + public void addExtra(ObjectSourcePlayerPredicate predicate) { + innerFilters.forEach((Filter filter) -> filter.addExtra(predicate)); + } + + @Override + public boolean checkObjectClass(Object object) { + return innerFilters + .stream() + .anyMatch((Filter filter) -> filter.checkObjectClass(object)); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public final void setMessage(String message) { + if (isLockedFilter()) { + throw new UnsupportedOperationException("You may not modify a locked filter"); + } + this.message = message; + } + + @Override + public String toString() { + return message; + } + + @Override + public boolean isLockedFilter() { + return innerFilters.stream().anyMatch((Filter filter) -> filter.isLockedFilter()); + } + + @Override + public void setLockedFilter(boolean lockedFilter) { + innerFilters.forEach((Filter filter) -> filter.setLockedFilter(lockedFilter)); + } + + public List> getPredicates() { + List> predicates = new ArrayList<>(); + for (Filter filter : innerFilters) { + for (Predicate predicate : filter.getPredicates()) { + predicates.add(predicate); + } + } + return predicates; + } + + public List> getExtraPredicates() { + List> predicates = new ArrayList<>(); + for (Filter filter : innerFilters) { + for (ObjectSourcePlayerPredicate predicate : filter.getExtraPredicates()) { + predicates.add(predicate); + } + } + return predicates; + } +} diff --git a/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java b/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java index 0958607c370..ad4bf776e2b 100644 --- a/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java +++ b/Mage/src/main/java/mage/filter/common/FilterAnyTarget.java @@ -14,7 +14,7 @@ public class FilterAnyTarget extends FilterPermanentOrPlayer { public FilterAnyTarget(String name) { super(name); - this.permanentFilter.add(Predicates.or( + this.getPermanentFilter().add(Predicates.or( CardType.CREATURE.getPredicate(), CardType.PLANESWALKER.getPredicate(), CardType.BATTLE.getPredicate() diff --git a/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java b/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java index 996205563c6..ee8afd2a19a 100644 --- a/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java +++ b/Mage/src/main/java/mage/filter/common/FilterCreaturePlayerOrPlaneswalker.java @@ -16,7 +16,7 @@ public class FilterCreaturePlayerOrPlaneswalker extends FilterPermanentOrPlayer public FilterCreaturePlayerOrPlaneswalker(String name) { super(name); - this.permanentFilter.add(Predicates.or( + this.getPermanentFilter().add(Predicates.or( CardType.CREATURE.getPredicate(), CardType.PLANESWALKER.getPredicate() )); diff --git a/Mage/src/main/java/mage/filter/common/FilterDefender.java b/Mage/src/main/java/mage/filter/common/FilterDefender.java index 61b335dd0a1..5920d9b5a10 100644 --- a/Mage/src/main/java/mage/filter/common/FilterDefender.java +++ b/Mage/src/main/java/mage/filter/common/FilterDefender.java @@ -3,7 +3,6 @@ package mage.filter.common; import mage.constants.CardType; import mage.filter.predicate.Predicates; import mage.filter.predicate.other.PlayerIdPredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.filter.predicate.permanent.PermanentIdPredicate; import java.util.Set; @@ -17,17 +16,17 @@ public class FilterDefender extends FilterPermanentOrPlayer { public FilterDefender(Set defenders) { super("player, planeswalker, or battle to attack"); - this.permanentFilter.add(Predicates.or( + this.getPermanentFilter().add(Predicates.or( CardType.PLANESWALKER.getPredicate(), CardType.BATTLE.getPredicate() )); - this.permanentFilter.add(Predicates.or( + this.getPermanentFilter().add(Predicates.or( defenders .stream() .map(PermanentIdPredicate::new) .collect(Collectors.toList()) )); - this.playerFilter.add(Predicates.or( + this.getPlayerFilter().add(Predicates.or( defenders .stream() .map(PlayerIdPredicate::new) diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java index ac18369a15b..136477a1c03 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrPlayer.java @@ -1,23 +1,14 @@ package mage.filter.common; import mage.MageItem; -import mage.abilities.Ability; -import mage.filter.FilterImpl; import mage.filter.FilterPermanent; import mage.filter.FilterPlayer; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; - -import java.util.UUID; +import mage.filter.MultiFilterImpl; /** * @author nantuko */ -public class FilterPermanentOrPlayer extends FilterImpl { - - protected final FilterPermanent permanentFilter; - protected final FilterPlayer playerFilter; +public class FilterPermanentOrPlayer extends MultiFilterImpl { public FilterPermanentOrPlayer() { this("player or permanent"); @@ -28,52 +19,11 @@ public class FilterPermanentOrPlayer extends FilterImpl { } public FilterPermanentOrPlayer(String name, FilterPermanent permanentFilter, FilterPlayer playerFilter) { - super(name); - this.permanentFilter = permanentFilter; - this.playerFilter = playerFilter; + super(name, permanentFilter, playerFilter); } protected FilterPermanentOrPlayer(final FilterPermanentOrPlayer filter) { super(filter); - this.permanentFilter = filter.permanentFilter.copy(); - this.playerFilter = filter.playerFilter.copy(); - } - - @Override - public boolean checkObjectClass(Object object) { - return true; - } - - @Override - public boolean match(MageItem o, Game game) { - if (super.match(o, game)) { - if (o instanceof Player) { - return playerFilter.match((Player) o, game); - } else if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, game); - } - } - return false; - } - - @Override - public boolean match(MageItem o, UUID playerId, Ability source, Game game) { - if (super.match(o, game)) { // process predicates - if (o instanceof Player) { - return playerFilter.match((Player) o, playerId, source, game); - } else if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, playerId, source, game); - } - } - return false; - } - - public FilterPermanent getPermanentFilter() { - return this.permanentFilter; - } - - public FilterPlayer getPlayerFilter() { - return this.playerFilter; } @Override @@ -81,4 +31,12 @@ public class FilterPermanentOrPlayer extends FilterImpl { return new FilterPermanentOrPlayer(this); } + public FilterPermanent getPermanentFilter() { + return (FilterPermanent) this.innerFilters.get(0); + } + + public FilterPlayer getPlayerFilter() { + return (FilterPlayer) this.innerFilters.get(1); + } + } diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java index 7bde90a10ff..082d8626f33 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java @@ -1,88 +1,42 @@ package mage.filter.common; import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.keyword.SuspendAbility; -import mage.cards.Card; import mage.counters.CounterType; import mage.filter.FilterCard; -import mage.filter.FilterImpl; import mage.filter.FilterPermanent; +import mage.filter.MultiFilterImpl; import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; - -import java.util.UUID; /** * @author emerald000 */ -public class FilterPermanentOrSuspendedCard extends FilterImpl { - - protected FilterCard cardFilter; - protected FilterPermanent permanentFilter; +public class FilterPermanentOrSuspendedCard extends MultiFilterImpl { public FilterPermanentOrSuspendedCard() { this("permanent or suspended card"); } public FilterPermanentOrSuspendedCard(String name) { - super(name); - permanentFilter = new FilterPermanent(); - cardFilter = new FilterCard(); - cardFilter.add(new AbilityPredicate(SuspendAbility.class)); - cardFilter.add(CounterType.TIME.getPredicate()); + super(name, new FilterPermanent(), new FilterCard()); + this.getCardFilter().add(new AbilityPredicate(SuspendAbility.class)); + this.getCardFilter().add(CounterType.TIME.getPredicate()); } protected FilterPermanentOrSuspendedCard(final FilterPermanentOrSuspendedCard filter) { super(filter); - this.permanentFilter = filter.permanentFilter.copy(); - this.cardFilter = filter.cardFilter.copy(); - } - - @Override - public boolean checkObjectClass(Object object) { - return object instanceof MageObject; - } - - @Override - public boolean match(MageObject o, Game game) { - if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, game); - } else if (o instanceof Card) { - return cardFilter.match((Card) o, game); - } - return false; - } - - @Override - public boolean match(MageObject o, UUID playerId, Ability source, Game game) { - if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, playerId, source, game); - } else if (o instanceof Card) { - return cardFilter.match((Card) o, playerId, source, game); - } - return false; - } - - public FilterPermanent getPermanentFilter() { - return this.permanentFilter; - } - - public FilterCard getCardFilter() { - return this.cardFilter; - } - - public void setPermanentFilter(FilterPermanent permanentFilter) { - this.permanentFilter = permanentFilter; - } - - public void setCardFilter(FilterCard cardFilter) { - this.cardFilter = cardFilter; } @Override public FilterPermanentOrSuspendedCard copy() { return new FilterPermanentOrSuspendedCard(this); } + + public FilterPermanent getPermanentFilter() { + return (FilterPermanent) this.innerFilters.get(0); + } + + public FilterCard getCardFilter() { + return (FilterCard) this.innerFilters.get(1); + } } diff --git a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java index 8757abc3d60..37cde8d496b 100644 --- a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java +++ b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java @@ -1,86 +1,25 @@ package mage.filter.common; import mage.MageObject; -import mage.abilities.Ability; -import mage.filter.FilterImpl; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.game.stack.Spell; - -import java.util.UUID; +import mage.filter.MultiFilterImpl; /** * @author LevelX */ -public class FilterSpellOrPermanent extends FilterImpl { - - protected FilterPermanent permanentFilter; - protected FilterSpell spellFilter; +public class FilterSpellOrPermanent extends MultiFilterImpl { public FilterSpellOrPermanent() { this("spell or permanent"); } public FilterSpellOrPermanent(String name) { - super(name); - permanentFilter = new FilterPermanent(); - spellFilter = new FilterSpell(); + super(name, new FilterPermanent(), new FilterSpell()); } protected FilterSpellOrPermanent(final FilterSpellOrPermanent filter) { super(filter); - this.permanentFilter = filter.permanentFilter.copy(); - this.spellFilter = filter.spellFilter.copy(); - } - - @Override - public boolean checkObjectClass(Object object) { - return object instanceof MageObject; - } - - @Override - public boolean match(MageObject o, Game game) { - if (o instanceof Spell) { - return spellFilter.match((Spell) o, game); - } else if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, game); - } - return false; - } - - @Override - public boolean match(MageObject o, UUID playerId, Ability source, Game game) { - if (o instanceof Spell) { - return spellFilter.match((Spell) o, playerId, source, game); - } else if (o instanceof Permanent) { - return permanentFilter.match((Permanent) o, playerId, source, game); - } - return false; - } - - @Override - public void setLockedFilter(boolean lockedFilter) { - super.setLockedFilter(lockedFilter); - spellFilter.setLockedFilter(lockedFilter); - permanentFilter.setLockedFilter(lockedFilter); - } - - public FilterPermanent getPermanentFilter() { - return this.permanentFilter; - } - - public FilterSpell getSpellFilter() { - return this.spellFilter; - } - - public void setPermanentFilter(FilterPermanent permanentFilter) { - this.permanentFilter = permanentFilter; - } - - public void setSpellFilter(FilterSpell spellFilter) { - this.spellFilter = spellFilter; } @Override @@ -88,4 +27,11 @@ public class FilterSpellOrPermanent extends FilterImpl { return new FilterSpellOrPermanent(this); } + public FilterPermanent getPermanentFilter() { + return (FilterPermanent) this.innerFilters.get(0); + } + + public FilterSpell getSpellFilter() { + return (FilterSpell) this.innerFilters.get(1); + } } From d1f190a287f1f2b5e3a7f6e38c8717ca26accc20 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:46:21 +0200 Subject: [PATCH 3/4] apply review --- .../src/main/java/mage/filter/FilterCard.java | 3 ++ .../src/main/java/mage/filter/FilterImpl.java | 3 +- .../java/mage/filter/MultiFilterImpl.java | 7 ++++ .../FilterPermanentOrSuspendedCard.java | 32 ++++++++++++++++++ .../filter/common/FilterSpellOrPermanent.java | 33 +++++++++++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index 2ba1edbe035..c101228703d 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -73,6 +73,9 @@ public class FilterCard extends FilterObject { @Override public boolean checkObjectClass(Object object) { + // TODO: investigate if we can/should exclude Permanent here. + // as it does extend Card (if so do cleanup the + // MultiFilterImpl that match Permanent and Card/Spell) return object instanceof Card; } } diff --git a/Mage/src/main/java/mage/filter/FilterImpl.java b/Mage/src/main/java/mage/filter/FilterImpl.java index bca981cedd0..83d4383a6a6 100644 --- a/Mage/src/main/java/mage/filter/FilterImpl.java +++ b/Mage/src/main/java/mage/filter/FilterImpl.java @@ -34,7 +34,7 @@ public abstract class FilterImpl implements Filter { protected FilterImpl(final FilterImpl filter) { this.message = filter.message; this.predicates = new ArrayList<>(filter.predicates); - this.extraPredicates.addAll(filter.extraPredicates); + this.extraPredicates = new ArrayList<>(filter.extraPredicates); this.lockedFilter = false;// After copying a filter it's allowed to modify } @@ -46,6 +46,7 @@ public abstract class FilterImpl implements Filter { return false; } + @Override public boolean match(E object, UUID sourceControllerId, Ability source, Game game) { if (!this.match(object, game)) { return false; diff --git a/Mage/src/main/java/mage/filter/MultiFilterImpl.java b/Mage/src/main/java/mage/filter/MultiFilterImpl.java index dbbfc862100..f29fe0cb830 100644 --- a/Mage/src/main/java/mage/filter/MultiFilterImpl.java +++ b/Mage/src/main/java/mage/filter/MultiFilterImpl.java @@ -13,6 +13,9 @@ import java.util.stream.Collectors; /** * Make a Filter out of multiple inner filters. + * If making a multi filter out of filterA and filterB, + * any object match the multi filter if it either match + * filterA or match filterB. * * @author Susucr */ @@ -26,6 +29,9 @@ public abstract class MultiFilterImpl implements Filter { protected MultiFilterImpl(String name, Filter... filters) { this.message = name; + if (filters.length < 2) { + throw new IllegalArgumentException("Wrong code usage: MultiFilterImpl should have at least 2 inner filters"); + } this.innerFilters.addAll(Arrays.stream(filters).collect(Collectors.toList())); } @@ -43,6 +49,7 @@ public abstract class MultiFilterImpl implements Filter { .anyMatch((Filter filter) -> filter.match(object, game)); } + @Override public boolean match(E object, UUID sourceControllerId, Ability source, Game game) { return innerFilters .stream() diff --git a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java index 082d8626f33..f6dc93650b3 100644 --- a/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java +++ b/Mage/src/main/java/mage/filter/common/FilterPermanentOrSuspendedCard.java @@ -1,12 +1,18 @@ package mage.filter.common; import mage.MageObject; +import mage.abilities.Ability; import mage.abilities.keyword.SuspendAbility; +import mage.cards.Card; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.MultiFilterImpl; import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; /** * @author emerald000 @@ -27,6 +33,32 @@ public class FilterPermanentOrSuspendedCard extends MultiFilterImpl super(filter); } + @Override + public boolean match(MageObject object, Game game) { + // We can not rely on super.match, since Permanent extend Card + // so a Permanent could get filtered by the FilterCard + if (object instanceof Permanent) { + return getPermanentFilter().match((Permanent) object, game); + } else if (object instanceof Card) { + return getCardFilter().match((Card) object, game); + } else { + return false; + } + } + + @Override + public boolean match(MageObject object, UUID sourceControllerId, Ability source, Game game) { + // We can not rely on super.match, since Permanent extend Card + // so a Permanent could get filtered by the FilterCard + if (object instanceof Permanent) { + return getPermanentFilter().match((Permanent) object, sourceControllerId, source, game); + } else if (object instanceof Card) { + return getCardFilter().match((Card) object, sourceControllerId, source, game); + } else { + return false; + } + } + @Override public FilterPermanentOrSuspendedCard copy() { return new FilterPermanentOrSuspendedCard(this); diff --git a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java index 37cde8d496b..39be00d4084 100644 --- a/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java +++ b/Mage/src/main/java/mage/filter/common/FilterSpellOrPermanent.java @@ -1,9 +1,15 @@ package mage.filter.common; import mage.MageObject; +import mage.abilities.Ability; import mage.filter.FilterPermanent; import mage.filter.FilterSpell; import mage.filter.MultiFilterImpl; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; + +import java.util.UUID; /** * @author LevelX @@ -22,6 +28,33 @@ public class FilterSpellOrPermanent extends MultiFilterImpl { super(filter); } + + @Override + public boolean match(MageObject object, Game game) { + // We can not rely on super.match, since Permanent extend Card + // and currently FilterSpell accepts to filter Cards + if (object instanceof Permanent) { + return getPermanentFilter().match((Permanent) object, game); + } else if (object instanceof Spell) { + return getSpellFilter().match((Spell) object, game); + } else { + return false; + } + } + + @Override + public boolean match(MageObject object, UUID sourceControllerId, Ability source, Game game) { + // We can not rely on super.match, since Permanent extend Card + // and currently FilterSpell accepts to filter Cards + if (object instanceof Permanent) { + return getPermanentFilter().match((Permanent) object, sourceControllerId, source, game); + } else if (object instanceof Spell) { + return getSpellFilter().match((Spell) object, sourceControllerId, source, game); + } else { + return false; + } + } + @Override public FilterSpellOrPermanent copy() { return new FilterSpellOrPermanent(this); From 141d78a158c66614502a76a8f7860f2ea1535013 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:43:00 +0200 Subject: [PATCH 4/4] apply more review changes --- Mage/src/main/java/mage/filter/Filter.java | 5 +++-- .../main/java/mage/filter/FilterAbility.java | 8 ++----- .../src/main/java/mage/filter/FilterCard.java | 5 ++--- .../src/main/java/mage/filter/FilterImpl.java | 16 +++++++------- .../main/java/mage/filter/FilterObject.java | 3 ++- .../java/mage/filter/FilterPermanent.java | 22 ++++++++++++++++++- .../main/java/mage/filter/FilterPlayer.java | 3 ++- .../main/java/mage/filter/FilterSource.java | 3 ++- .../java/mage/filter/FilterStackObject.java | 4 +++- .../java/mage/filter/MultiFilterImpl.java | 14 +++++++----- Mage/src/main/java/mage/players/ManaPool.java | 5 +++-- 11 files changed, 57 insertions(+), 31 deletions(-) diff --git a/Mage/src/main/java/mage/filter/Filter.java b/Mage/src/main/java/mage/filter/Filter.java index 44b47979b3b..38f17f8d9ae 100644 --- a/Mage/src/main/java/mage/filter/Filter.java +++ b/Mage/src/main/java/mage/filter/Filter.java @@ -37,9 +37,10 @@ public interface Filter extends Serializable, Copyable> { *

* (method should then call this.addExtra(predicate) after verify checks) */ - void add(ObjectSourcePlayerPredicate predicate); + Filter add(ObjectSourcePlayerPredicate predicate); - // TODO: if someone can find a way to not have to add this (overload of add made it necessary to introduce) + // TODO: if someone can find a way to not have to add this + // Compiler was confused between overloads void addExtra(ObjectSourcePlayerPredicate predicate); boolean checkObjectClass(Object object); diff --git a/Mage/src/main/java/mage/filter/FilterAbility.java b/Mage/src/main/java/mage/filter/FilterAbility.java index 66fc6bf1fed..6859e7ed92f 100644 --- a/Mage/src/main/java/mage/filter/FilterAbility.java +++ b/Mage/src/main/java/mage/filter/FilterAbility.java @@ -41,15 +41,11 @@ public class FilterAbility extends FilterImpl { } @Override - public Filter add(Predicate predicate) { - return super.add(predicate); - } - - @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public FilterAbility add(ObjectSourcePlayerPredicate predicate) { // Verify Checks Predicates.makeSurePredicateCompatibleWithFilter(predicate, Ability.class, StackObject.class); this.addExtra(predicate); + return this; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index e7aa44a073c..edffb2e0f47 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -5,11 +5,9 @@ import mage.constants.TargetController; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import mage.game.Game; import java.util.ArrayList; import java.util.List; -import java.util.UUID; /** * Works with cards only. For objects like commanders you must override your canTarget method. @@ -54,11 +52,12 @@ public class FilterCard extends FilterObject { } @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public FilterCard add(ObjectSourcePlayerPredicate predicate) { // verify checks checkPredicateIsSuitableForCardFilter(predicate); Predicates.makeSurePredicateCompatibleWithFilter(predicate, Card.class); this.addExtra(predicate); + return this; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterImpl.java b/Mage/src/main/java/mage/filter/FilterImpl.java index 83d4383a6a6..28ac687668d 100644 --- a/Mage/src/main/java/mage/filter/FilterImpl.java +++ b/Mage/src/main/java/mage/filter/FilterImpl.java @@ -51,24 +51,20 @@ public abstract class FilterImpl implements Filter { if (!this.match(object, game)) { return false; } - ObjectSourcePlayer osp = new ObjectSourcePlayer<>(object, sourceControllerId, source); + ObjectSourcePlayer osp = new ObjectSourcePlayer<>(object, sourceControllerId, source); return extraPredicates.stream().allMatch(p -> p.apply(osp, game)); } @Override public Filter add(Predicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } + checkUnlockedFilter(); predicates.add(predicate); return this; } @Override public final void addExtra(ObjectSourcePlayerPredicate predicate) { - if (isLockedFilter()) { - throw new UnsupportedOperationException("You may not modify a locked filter"); - } + checkUnlockedFilter(); extraPredicates.add(predicate); } @@ -79,10 +75,14 @@ public abstract class FilterImpl implements Filter { @Override public final void setMessage(String message) { + checkUnlockedFilter(); + this.message = message; + } + + protected void checkUnlockedFilter() { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } - this.message = message; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterObject.java b/Mage/src/main/java/mage/filter/FilterObject.java index 5d816f60531..f1fdd611b9c 100644 --- a/Mage/src/main/java/mage/filter/FilterObject.java +++ b/Mage/src/main/java/mage/filter/FilterObject.java @@ -27,10 +27,11 @@ public class FilterObject extends FilterImpl { } @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public FilterObject add(ObjectSourcePlayerPredicate predicate) { // verify checks Predicates.makeSurePredicateCompatibleWithFilter(predicate, MageObject.class); this.addExtra(predicate); + return this; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterPermanent.java b/Mage/src/main/java/mage/filter/FilterPermanent.java index dbe3c0ff4af..3d8f8b40c5a 100644 --- a/Mage/src/main/java/mage/filter/FilterPermanent.java +++ b/Mage/src/main/java/mage/filter/FilterPermanent.java @@ -1,11 +1,14 @@ package mage.filter; +import mage.abilities.Ability; import mage.constants.SubType; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicates; +import mage.game.Game; import mage.game.permanent.Permanent; import java.util.Set; +import java.util.UUID; /** * @author North @@ -42,10 +45,27 @@ public class FilterPermanent extends FilterObject { } @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public FilterPermanent add(ObjectSourcePlayerPredicate predicate) { // verify checks Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class); this.addExtra(predicate); + return this; + } + + @Override + public boolean match(Permanent permanent, Game game) { + // TODO: if we can trust the target/checks using FilterPermanent to filter out Phased out permanent, + // this overload would be no longer necessary. + return super.match(permanent, game) + && permanent.isPhasedIn(); + } + + @Override + public boolean match(Permanent permanent, UUID sourceControllerId, Ability source, Game game) { + // TODO: if we can trust the target/checks using FilterPermanent to filter out Phased out permanent, + // this overload would be no longer necessary. + return super.match(permanent, sourceControllerId, source, game) + && permanent.isPhasedIn(); } @Override diff --git a/Mage/src/main/java/mage/filter/FilterPlayer.java b/Mage/src/main/java/mage/filter/FilterPlayer.java index e33775a2ba5..86949e3774a 100644 --- a/Mage/src/main/java/mage/filter/FilterPlayer.java +++ b/Mage/src/main/java/mage/filter/FilterPlayer.java @@ -28,10 +28,11 @@ public class FilterPlayer extends FilterImpl { } @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public FilterPlayer add(ObjectSourcePlayerPredicate predicate) { // verify checks Predicates.makeSurePredicateCompatibleWithFilter(predicate, Player.class); this.addExtra(predicate); + return this; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterSource.java b/Mage/src/main/java/mage/filter/FilterSource.java index b7285223a1f..dd62cbc0733 100644 --- a/Mage/src/main/java/mage/filter/FilterSource.java +++ b/Mage/src/main/java/mage/filter/FilterSource.java @@ -32,11 +32,12 @@ public class FilterSource extends FilterObject { } @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public FilterSource add(ObjectSourcePlayerPredicate predicate) { // verify checks // A source can be a lot of different things, so a variety of predicates can be fed here Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class, Card.class, StackObject.class, CommandObject.class); this.addExtra(predicate); + return this; } @Override diff --git a/Mage/src/main/java/mage/filter/FilterStackObject.java b/Mage/src/main/java/mage/filter/FilterStackObject.java index 0ba2ebe4d42..7b4aef37379 100644 --- a/Mage/src/main/java/mage/filter/FilterStackObject.java +++ b/Mage/src/main/java/mage/filter/FilterStackObject.java @@ -29,11 +29,13 @@ public class FilterStackObject extends FilterObject { return new FilterStackObject(this); } - public final void add(ObjectSourcePlayerPredicate predicate) { + @Override + public final FilterStackObject add(ObjectSourcePlayerPredicate predicate) { // verify checks // Spell implements Card interface, so it can use some default predicates like owner Predicates.makeSurePredicateCompatibleWithFilter(predicate, StackObject.class, Spell.class, Card.class); this.addExtra(predicate); + return this; } @Override diff --git a/Mage/src/main/java/mage/filter/MultiFilterImpl.java b/Mage/src/main/java/mage/filter/MultiFilterImpl.java index f29fe0cb830..27b1b158316 100644 --- a/Mage/src/main/java/mage/filter/MultiFilterImpl.java +++ b/Mage/src/main/java/mage/filter/MultiFilterImpl.java @@ -32,7 +32,10 @@ public abstract class MultiFilterImpl implements Filter { if (filters.length < 2) { throw new IllegalArgumentException("Wrong code usage: MultiFilterImpl should have at least 2 inner filters"); } - this.innerFilters.addAll(Arrays.stream(filters).collect(Collectors.toList())); + this.innerFilters.addAll( + Arrays.stream(filters) + .map(f -> f.copy()) + .collect(Collectors.toList())); } protected MultiFilterImpl(final MultiFilterImpl filter) { @@ -46,25 +49,26 @@ public abstract class MultiFilterImpl implements Filter { public boolean match(E object, Game game) { return innerFilters .stream() - .anyMatch((Filter filter) -> filter.match(object, game)); + .anyMatch((Filter filter) -> filter.checkObjectClass(object) && filter.match(object, game)); } @Override public boolean match(E object, UUID sourceControllerId, Ability source, Game game) { return innerFilters .stream() - .anyMatch((Filter filter) -> filter.match(object, sourceControllerId, source, game)); + .anyMatch((Filter filter) -> filter.checkObjectClass(object) && filter.match(object, sourceControllerId, source, game)); } @Override - public Filter add(Predicate predicate) { + public MultiFilterImpl add(Predicate predicate) { innerFilters.forEach((Filter filter) -> filter.add(predicate)); return this; } @Override - public void add(ObjectSourcePlayerPredicate predicate) { + public MultiFilterImpl add(ObjectSourcePlayerPredicate predicate) { innerFilters.forEach((Filter filter) -> filter.add(predicate)); + return this; } @Override diff --git a/Mage/src/main/java/mage/players/ManaPool.java b/Mage/src/main/java/mage/players/ManaPool.java index 82bca7edcef..e4f8c001575 100644 --- a/Mage/src/main/java/mage/players/ManaPool.java +++ b/Mage/src/main/java/mage/players/ManaPool.java @@ -101,7 +101,7 @@ public class ManaPool implements Serializable { /** * @param manaType the mana type that should be paid * @param ability - * @param filter + * @param filter filters the source of mana, only matching source are accepted. * @param game * @param costToPay complete costs to pay (needed to check conditional * mana) @@ -140,7 +140,8 @@ public class ManaPool implements Serializable { } for (ManaPoolItem mana : manaItems) { - if (filter != null && !filter.match(mana.getSourceObject(), game)) { + MageObject sourceObject = mana.getSourceObject(); + if (filter != null && (!filter.checkObjectClass(sourceObject) || !filter.match(sourceObject, game))) { // If here, then mana source does not match the filter // However, alternate mana payment abilities such as convoke won't match the filter but are valid // So we need to do some ugly checks to allow them