refactor: add FilterSource to match TargetSource (#13703)

Small fix of enabling TargetSource to choose/target a Command Object
This commit is contained in:
Susucre 2025-06-01 15:52:51 +02:00 committed by GitHub
parent fc15cefa23
commit cfd51d7dce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 522 additions and 278 deletions

View file

@ -4,7 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.target.TargetSource;
@ -17,14 +17,14 @@ public class PreventAllDamageFromChosenSourceToYouEffect extends PreventionEffec
protected final TargetSource targetSource;
public PreventAllDamageFromChosenSourceToYouEffect(Duration duration) {
this(duration, new FilterObject("source"));
this(duration, new FilterSource());
}
public PreventAllDamageFromChosenSourceToYouEffect(Duration duration, FilterObject filter) {
public PreventAllDamageFromChosenSourceToYouEffect(Duration duration, FilterSource filter) {
this(duration, filter, false);
}
public PreventAllDamageFromChosenSourceToYouEffect(Duration duration, FilterObject filter, boolean onlyCombat) {
public PreventAllDamageFromChosenSourceToYouEffect(Duration duration, FilterSource filter, boolean onlyCombat) {
super(duration, Integer.MAX_VALUE, onlyCombat);
this.targetSource = new TargetSource(filter);
this.staticText = setText();

View file

@ -7,9 +7,12 @@ import mage.abilities.Ability;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterObject;
import mage.filter.FilterPermanent;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.TargetSource;
/**
@ -18,25 +21,30 @@ import mage.target.TargetSource;
public class PreventDamageByChosenSourceEffect extends PreventionEffectImpl {
private TargetSource target;
private Target target;
private MageObjectReference mageObjectReference;
public PreventDamageByChosenSourceEffect() {
this(new FilterObject("a source"));
this(new FilterSource("a source"));
}
public PreventDamageByChosenSourceEffect(FilterObject filterObject) {
this(filterObject, false);
public PreventDamageByChosenSourceEffect(FilterSource filterSource) {
this(filterSource, false);
}
public PreventDamageByChosenSourceEffect(FilterObject filterObject, boolean onlyCombat) {
public PreventDamageByChosenSourceEffect(FilterSource filterSource, boolean onlyCombat) {
this(new TargetSource(filterSource), filterSource.getMessage(), onlyCombat);
}
public PreventDamageByChosenSourceEffect(FilterPermanent filterPermanent, boolean onlyCombat) {
this(new TargetPermanent(filterPermanent), filterPermanent.getMessage(), onlyCombat);
}
private PreventDamageByChosenSourceEffect(Target target, String filterMessage, boolean onlyCombat) {
super(Duration.EndOfTurn, Integer.MAX_VALUE, onlyCombat);
if (!filterObject.getMessage().endsWith("source")) {
filterObject.setMessage(filterObject.getMessage() + " source");
}
this.target = new TargetSource(filterObject);
this.target = target;
staticText = "Prevent all" + (onlyCombat ? " combat" : "")
+ " damage " + filterObject.getMessage() + " of your choice would deal this turn";
+ " damage " + filterMessage + " of your choice would deal this turn";
}
protected PreventDamageByChosenSourceEffect(final PreventDamageByChosenSourceEffect effect) {

View file

@ -5,10 +5,13 @@ import mage.abilities.effects.PreventionEffectData;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterObject;
import mage.filter.FilterPermanent;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.TargetSource;
import mage.util.CardUtil;
@ -20,12 +23,12 @@ import java.util.UUID;
*/
public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImpl {
protected final TargetSource targetSource;
protected final Target target;
private final boolean toYou;
private final ApplierOnPrevention onPrevention;
public interface ApplierOnPrevention extends Serializable {
boolean apply(PreventionEffectData data, TargetSource targetsource, GameEvent event, Ability source, Game game);
boolean apply(PreventionEffectData data, Target target, GameEvent event, Ability source, Game game);
String getText();
}
@ -35,7 +38,7 @@ public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImp
return "You gain life equal to the damage prevented this way";
}
public boolean apply(PreventionEffectData data, TargetSource targetSource, GameEvent event, Ability source, Game game) {
public boolean apply(PreventionEffectData data, Target target, GameEvent event, Ability source, Game game) {
if (data == null || data.getPreventedDamage() <= 0) {
return false;
}
@ -50,24 +53,36 @@ public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImp
};
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou) {
this(duration, toYou, new FilterObject("source"));
this(duration, toYou, new FilterSource());
}
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterObject filter) {
this(duration, toYou, filter, false, null);
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterSource filterSource) {
this(duration, toYou, filterSource, false, null);
}
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, ApplierOnPrevention onPrevention) {
this(duration, toYou, new FilterObject("source"), onPrevention);
this(duration, toYou, new FilterSource(), onPrevention);
}
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterObject filter, ApplierOnPrevention onPrevention) {
this(duration, toYou, filter, false, onPrevention);
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterSource filterSource, ApplierOnPrevention onPrevention) {
this(duration, toYou, filterSource, false, onPrevention);
}
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterObject filter, boolean onlyCombat, ApplierOnPrevention onPrevention) {
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterSource filterSource, boolean onlyCombat, ApplierOnPrevention onPrevention) {
this(duration, toYou, new TargetSource(filterSource), onlyCombat, onPrevention);
}
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterPermanent filterPermanent) {
this(duration, toYou, filterPermanent, false, null);
}
public PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, FilterPermanent filterPermanent, boolean onlyCombat, ApplierOnPrevention onPrevention) {
this(duration, toYou, new TargetPermanent(filterPermanent), onlyCombat, onPrevention);
}
private PreventNextDamageFromChosenSourceEffect(Duration duration, boolean toYou, Target target, boolean onlyCombat, ApplierOnPrevention onPrevention) {
super(duration, Integer.MAX_VALUE, onlyCombat);
this.targetSource = new TargetSource(filter);
this.target = target;
this.toYou = toYou;
this.onPrevention = onPrevention;
this.staticText = setText();
@ -75,7 +90,7 @@ public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImp
protected PreventNextDamageFromChosenSourceEffect(final PreventNextDamageFromChosenSourceEffect effect) {
super(effect);
this.targetSource = effect.targetSource.copy();
this.target = effect.target.copy();
this.toYou = effect.toYou;
this.onPrevention = effect.onPrevention;
}
@ -89,8 +104,8 @@ public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImp
public void init(Ability source, Game game) {
super.init(source, game);
UUID controllerId = source.getControllerId();
if (this.targetSource.canChoose(controllerId, source, game)) {
this.targetSource.choose(Outcome.PreventDamage, controllerId, source.getSourceId(), source, game);
if (this.target.canChoose(controllerId, source, game)) {
this.target.choose(Outcome.PreventDamage, controllerId, source.getSourceId(), source, game);
}
}
@ -99,7 +114,7 @@ public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImp
PreventionEffectData data = preventDamageAction(event, source, game);
discard();
if (onPrevention != null) {
onPrevention.apply(data, targetSource, event, source, game);
onPrevention.apply(data, target, event, source, game);
}
return false;
}
@ -108,12 +123,12 @@ public class PreventNextDamageFromChosenSourceEffect extends PreventionEffectImp
public boolean applies(GameEvent event, Ability source, Game game) {
return super.applies(event, source, game)
&& (!toYou || event.getTargetId().equals(source.getControllerId()))
&& event.getSourceId().equals(targetSource.getFirstTarget());
&& target.getTargets().contains(event.getSourceId());
}
private String setText() {
StringBuilder sb = new StringBuilder("The next time ")
.append(CardUtil.addArticle(targetSource.getFilter().getMessage()));
.append(CardUtil.addArticle(target.getFilter().getMessage()));
sb.append(" of your choice would deal damage");
if (toYou) {
sb.append(" to you");

View file

@ -1,12 +1,11 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.target.TargetSource;
@ -19,14 +18,14 @@ public class PreventNextDamageFromChosenSourceToTargetEffect extends PreventionE
protected final TargetSource targetSource;
public PreventNextDamageFromChosenSourceToTargetEffect(Duration duration) {
this(duration, new FilterObject<>("source"));
this(duration, new FilterSource());
}
public PreventNextDamageFromChosenSourceToTargetEffect(Duration duration, FilterObject<MageObject> filter) {
public PreventNextDamageFromChosenSourceToTargetEffect(Duration duration, FilterSource filter) {
this(duration, filter, false);
}
public PreventNextDamageFromChosenSourceToTargetEffect(Duration duration, FilterObject<MageObject> filter, boolean onlyCombat) {
public PreventNextDamageFromChosenSourceToTargetEffect(Duration duration, FilterSource filter, boolean onlyCombat) {
super(duration, Integer.MAX_VALUE, onlyCombat);
this.targetSource = new TargetSource(filter);
}

View file

@ -0,0 +1,77 @@
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<MageObject> {
protected final List<ObjectSourcePlayerPredicate<MageObject>> extraPredicates = new ArrayList<>();
public FilterSource() {
super("source");
}
public FilterSource(String name) {
super(name);
}
private FilterSource(final FilterSource filter) {
super(filter);
this.extraPredicates.addAll(filter.extraPredicates);
}
@Override
public FilterSource copy() {
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
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class, Card.class, StackObject.class, CommandObject.class);
extraPredicates.add(predicate);
return this;
}
@Override
public boolean checkObjectClass(Object object) {
return object instanceof Permanent
|| object instanceof Card
|| 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<MageObject> osp = new ObjectSourcePlayer<>(object, sourceControllerId, source);
return extraPredicates.stream().allMatch(p -> p.apply(osp, game));
}
@Override
public List<Predicate> getExtraPredicates() {
return new ArrayList<>(extraPredicates);
}
}

View file

@ -2,40 +2,52 @@
package mage.target;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Zone;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.command.CommandObject;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.players.Player;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* 609.7a. If an effect requires a player to choose a source of damage, they may choose a permanent;
* a spell on the stack (including a permanent spell); any object referred to by an object on the stack,
* by a replacement or prevention effect that's waiting to apply, or by a delayed triggered ability
* that's waiting to trigger (even if that object is no longer in the zone it used to be in); or a
* face-up object in the command zone. A source doesn't need to be capable of dealing damage to be
* a legal choice. The source is chosen when the effect is created. If the player chooses a permanent,
* the effect will apply to the next damage dealt by that permanent, regardless of whether it's combat
* damage or damage dealt as the result of a spell or ability. If the player chooses a permanent spell,
* the effect will apply to any damage dealt by that spell and any damage dealt by the permanent that
* spell becomes when it resolves.
*
* @author BetaSteward_at_googlemail.com
*/
public class TargetSource extends TargetObject {
protected final FilterObject filter;
protected final FilterSource filter;
public TargetSource() {
this(1, 1, new FilterObject("source of your choice"));
this(1, 1, new FilterSource("source of your choice"));
}
public TargetSource(FilterObject filter) {
public TargetSource(FilterSource filter) {
this(1, 1, filter);
}
public TargetSource(int numTargets, FilterObject filter) {
public TargetSource(int numTargets, FilterSource filter) {
this(numTargets, numTargets, filter);
}
public TargetSource(int minNumTargets, int maxNumTargets, FilterObject filter) {
public TargetSource(int minNumTargets, int maxNumTargets, FilterSource filter) {
super(minNumTargets, maxNumTargets, Zone.ALL, true);
this.filter = filter;
this.targetName = filter.getMessage();
@ -47,7 +59,7 @@ public class TargetSource extends TargetObject {
}
@Override
public FilterObject getFilter() {
public FilterSource getFilter() {
return filter;
}
@ -78,15 +90,16 @@ public class TargetSource extends TargetObject {
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
return canChoose(sourceControllerId, game);
public boolean canChoose(UUID sourceControllerId, Game game) {
return canChoose(sourceControllerId, (Ability) null, game);
}
@Override
public boolean canChoose(UUID sourceControllerId, Game game) {
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0;
for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) && filter.match(stackObject, game)) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& filter.match(stackObject, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
@ -94,7 +107,7 @@ public class TargetSource extends TargetObject {
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (filter.match(permanent, game)) {
if (filter.match(permanent, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
@ -103,7 +116,7 @@ public class TargetSource extends TargetObject {
}
for (Player player : game.getPlayers().values()) {
for (Card card : player.getGraveyard().getCards(game)) {
if (filter.match(card, game)) {
if (filter.match(card, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
@ -112,7 +125,15 @@ public class TargetSource extends TargetObject {
}
}
for (Card card : game.getExile().getAllCards(game)) {
if (filter.match(card, game)) {
if (filter.match(card, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
for (CommandObject commandObject : game.getState().getCommand()) {
if (filter.match(commandObject, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
@ -123,35 +144,41 @@ public class TargetSource extends TargetObject {
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
return possibleTargets(sourceControllerId, game);
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
return possibleTargets(sourceControllerId, (Ability) null, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) && filter.match(stackObject, game)) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& filter.match(stackObject, sourceControllerId, source, game)) {
possibleTargets.add(stackObject.getId());
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (filter.match(permanent, game)) {
if (filter.match(permanent, sourceControllerId, source, game)) {
possibleTargets.add(permanent.getId());
}
}
for (Player player : game.getPlayers().values()) {
for (Card card : player.getGraveyard().getCards(game)) {
if (filter.match(card, game)) {
if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId());
}
}
}
for (Card card : game.getExile().getAllCards(game)) {
if (filter.match(card, game)) {
if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId());
}
}
for (CommandObject commandObject : game.getState().getCommand()) {
if (filter.match(commandObject, sourceControllerId, source, game)) {
possibleTargets.add(commandObject.getId());
}
}
return possibleTargets;
}