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

@ -11,6 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.NamePredicate;
@ -22,6 +23,7 @@ import java.util.UUID;
public final class AjanisAid extends CardImpl {
private static final FilterCard filter = new FilterCard("Ajani, Valiant Protector");
private static final FilterPermanent filterPrevent = new FilterCreaturePermanent("creature of your choice");
static {
filter.add(new NamePredicate("Ajani, Valiant Protector"));
@ -35,7 +37,7 @@ public final class AjanisAid extends CardImpl {
this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryGraveyardPutInHandEffect(filter), true));
// Sacrifice Ajani's Aid: Prevent all combat damage a creature of your choice would deal this turn.
Effect effect = new PreventDamageByChosenSourceEffect(new FilterCreaturePermanent("creature of your choice"), true);
Effect effect = new PreventDamageByChosenSourceEffect(filterPrevent, true);
this.addAbility(new SimpleActivatedAbility(effect, new SacrificeSourceCost()));
}

View file

@ -15,7 +15,7 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.TargetSource;
import mage.target.Target;
import java.util.Set;
import java.util.UUID;
@ -53,7 +53,7 @@ public final class BoneMask extends CardImpl {
enum BoneMaskEffectPreventionApplier implements PreventNextDamageFromChosenSourceEffect.ApplierOnPrevention {
instance;
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;
}

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -21,10 +21,10 @@ import java.util.UUID;
*/
public final class BurrentonForgeTender extends CardImpl {
private static final FilterObject filterObject = new FilterObject("a red");
private static final FilterSource filter = new FilterSource("a red source");
static {
filterObject.add(new ColorPredicate(ObjectColor.RED));
filter.add(new ColorPredicate(ObjectColor.RED));
}
public BurrentonForgeTender(UUID ownerId, CardSetInfo setInfo) {
@ -38,7 +38,7 @@ public final class BurrentonForgeTender extends CardImpl {
this.addAbility(ProtectionAbility.from(ObjectColor.RED));
// Sacrifice Burrenton Forge-Tender: Prevent all damage a red source of your choice would deal this turn.
this.addAbility(new SimpleActivatedAbility(new PreventDamageByChosenSourceEffect(filterObject), new SacrificeSourceCost()));
this.addAbility(new SimpleActivatedAbility(new PreventDamageByChosenSourceEffect(filter), new SacrificeSourceCost()));
}

View file

@ -9,7 +9,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import java.util.UUID;
@ -18,7 +18,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionArtifacts extends CardImpl {
private static final FilterObject filter = new FilterObject("artifact source");
private static final FilterSource filter = new FilterSource("artifact source");
static {
filter.add(CardType.ARTIFACT.getPredicate());

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionBlack extends CardImpl {
private static final FilterObject filter = new FilterObject("black source");
private static final FilterSource filter = new FilterSource("black source");
static {
filter.add(new ColorPredicate(ObjectColor.BLACK));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionBlue extends CardImpl {
private static final FilterObject filter = new FilterObject("blue source");
private static final FilterSource filter = new FilterSource("blue source");
static {
filter.add(new ColorPredicate(ObjectColor.BLUE));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionGreen extends CardImpl {
private static final FilterObject filter = new FilterObject("green source");
private static final FilterSource filter = new FilterSource("green source");
static {
filter.add(new ColorPredicate(ObjectColor.GREEN));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionRed extends CardImpl {
private static final FilterObject filter = new FilterObject("red source");
private static final FilterSource filter = new FilterSource("red source");
static {
filter.add(new ColorPredicate(ObjectColor.RED));

View file

@ -10,6 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate;
@ -20,7 +21,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionShadow extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature of your choice with shadow");
private static final FilterPermanent filter = new FilterCreaturePermanent("a creature of your choice with shadow");
static {
filter.add(new AbilityPredicate(ShadowAbility.class));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class CircleOfProtectionWhite extends CardImpl {
private static final FilterObject filter = new FilterObject("white source");
private static final FilterSource filter = new FilterSource("white source");
static {
filter.add(new ColorPredicate(ObjectColor.WHITE));

View file

@ -1,7 +1,6 @@
package mage.cards.c;
import java.util.UUID;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.PayEnergyCost;
@ -11,11 +10,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Zone;
import mage.filter.FilterObject;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class ConsulateSurveillance extends CardImpl {
@ -28,7 +26,7 @@ public final class ConsulateSurveillance extends CardImpl {
// Pay {E}{E}: Prevent all damage that would be dealt to you this turn by a source of your choice.
this.addAbility(new SimpleActivatedAbility(
new PreventAllDamageFromChosenSourceToYouEffect(Duration.EndOfTurn, new FilterObject("source"), false),
new PreventAllDamageFromChosenSourceToYouEffect(Duration.EndOfTurn),
new PayEnergyCost(2)));
}

View file

@ -1,28 +1,25 @@
package mage.cards.d;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.PreventNextDamageFromChosenSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.TargetSource;
import java.util.UUID;
/**
*
* @author ThomasLerner
*/
public final class DarkSphere extends CardImpl {
@ -48,19 +45,17 @@ public final class DarkSphere extends CardImpl {
}
class DarkSpherePreventionEffect extends ReplacementEffectImpl {
class DarkSpherePreventionEffect extends PreventNextDamageFromChosenSourceEffect {
private final TargetSource targetSource;
private static final FilterSource filter = new FilterSource("source");
public DarkSpherePreventionEffect() {
super(Duration.OneUse, Outcome.RedirectDamage);
super(Duration.EndOfTurn, true, filter);
this.staticText = "The next time a source of your choice would deal damage to you this turn, prevent half that damage, rounded down";
this.targetSource = new TargetSource(new FilterObject("source of your choice"));
}
private DarkSpherePreventionEffect(final DarkSpherePreventionEffect effect) {
super(effect);
this.targetSource = effect.targetSource.copy();
}
@Override
@ -68,40 +63,24 @@ class DarkSpherePreventionEffect extends ReplacementEffectImpl {
return new DarkSpherePreventionEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source);
DamageEvent damageEvent = (DamageEvent) event;
if (controller != null) {
controller.damage((int) Math.ceil(damageEvent.getAmount() / 2.0), damageEvent.getSourceId(), source, game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), damageEvent.getAppliedEffects());
int damage = damageEvent.getAmount();
if (controller == null || damage <= 0) {
return false;
}
controller.damage(
(int) Math.ceil(damage / 2.0), damageEvent.getSourceId(), source, game,
damageEvent.isCombatDamage(), damageEvent.isPreventable(), damageEvent.getAppliedEffects()
);
StringBuilder sb = new StringBuilder(sourceObject != null ? sourceObject.getLogName() : "");
sb.append(": ").append(damageEvent.getAmount() / 2).append(" damage prevented");
sb.append(": ").append(damage / 2).append(" damage prevented");
sb.append(" from ").append(controller.getLogName());
game.informPlayers(sb.toString());
discard(); // only one use
return true;
}
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGE_PLAYER;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getSourceId().equals(targetSource.getFirstTarget()) && event.getTargetId().equals(source.getControllerId())) {
return true;
}
return false;
}
}

View file

@ -10,7 +10,7 @@ import mage.constants.Duration;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.TargetSource;
import mage.target.Target;
import java.util.UUID;
@ -44,12 +44,12 @@ public final class DeflectingPalm extends CardImpl {
enum DeflectingPalmPreventionApplier implements PreventNextDamageFromChosenSourceEffect.ApplierOnPrevention {
instance;
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;
}
int prevented = data.getPreventedDamage();
UUID objectControllerId = game.getControllerId(targetSource.getFirstTarget());
UUID objectControllerId = game.getControllerId(target.getFirstTarget());
Player objectController = game.getPlayer(objectControllerId);
if (objectController == null) {
return false;

View file

@ -1,10 +1,6 @@
package mage.cards.d;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.PreventionEffectImpl;
@ -14,18 +10,22 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.filter.FilterObject;
import mage.players.Player;
import mage.target.TargetSource;
import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
*
* @author L_J
*/
public final class DesperateGambit extends CardImpl {
@ -122,10 +122,14 @@ class DesperateGambitEffect extends PreventionEffectImpl {
}
}
/**
* TODO: should not be needed with the proper {@link FilterSource}.
* make sure it works properly with 108.4a if you do
*/
class TargetControlledSource extends TargetSource {
public TargetControlledSource() {
super(1, 1, new FilterObject("source you control"));
super(1, 1, new FilterSource("source you control"));
}
private TargetControlledSource(final TargetControlledSource target) {

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate;
@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class GreaterRealmOfPreservation extends CardImpl {
private static final FilterObject filter = new FilterObject("black or red source");
private static final FilterSource filter = new FilterSource("black or red source");
static {
filter.add(Predicates.or(new ColorPredicate(ObjectColor.BLACK), new ColorPredicate(ObjectColor.RED)));

View file

@ -1,7 +1,6 @@
package mage.cards.j;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
@ -12,8 +11,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
@ -23,8 +21,9 @@ import mage.players.Player;
import mage.target.TargetSource;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author Quercitron
*/
public final class JadeMonolith extends CardImpl {
@ -55,7 +54,7 @@ class JadeMonolithRedirectionEffect extends ReplacementEffectImpl {
public JadeMonolithRedirectionEffect() {
super(Duration.OneUse, Outcome.RedirectDamage);
this.staticText = "The next time a source of your choice would deal damage to target creature this turn, that source deals that damage to you instead";
this.targetSource = new TargetSource(new FilterObject("source of your choice"));
this.targetSource = new TargetSource(new FilterSource("source of your choice"));
}
private JadeMonolithRedirectionEffect(final JadeMonolithRedirectionEffect effect) {

View file

@ -1,7 +1,5 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.ObjectColor;
@ -15,8 +13,11 @@ import mage.abilities.effects.PreventionEffectImpl;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterObject;
import mage.constants.AbilityWord;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.SharesColorPredicate;
import mage.game.ExileZone;
import mage.game.Game;
@ -27,8 +28,9 @@ import mage.target.TargetSource;
import mage.target.common.TargetCardInGraveyard;
import mage.util.CardUtil;
import java.util.UUID;
/**
*
* @author noahg
*/
public final class MournersShield extends CardImpl {
@ -93,6 +95,10 @@ class MournersShieldImprintEffect extends OneShotEffect {
}
}
/**
* TODO: custom effect should not be needed with the properly set up {@link FilterSource}
* and {@link mage.abilities.effects.common.PreventDamageByChosenSourceEffect}
*/
class MournersShieldEffect extends PreventionEffectImpl {
private TargetSource target;
@ -139,9 +145,9 @@ class MournersShieldEffect extends PreventionEffectImpl {
}
}
}
FilterObject filterObject = new FilterObject("a source of your choice that shares a color with the exiled card");
filterObject.add(new SharesColorPredicate(colorsAmongImprinted));
this.target = new TargetSource(filterObject);
FilterSource filterSource = new FilterSource("a source of your choice that shares a color with the exiled card");
filterSource.add(new SharesColorPredicate(colorsAmongImprinted));
this.target = new TargetSource(filterSource);
this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
if (target.getFirstTarget() != null) {
mageObjectReference = new MageObjectReference(target.getFirstTarget(), game);

View file

@ -12,7 +12,7 @@ import mage.constants.CardType;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.target.TargetSource;
import mage.target.Target;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -47,12 +47,12 @@ public final class NewWayForward extends CardImpl {
enum NewWayForwardPreventionApplier implements PreventNextDamageFromChosenSourceEffect.ApplierOnPrevention {
instance;
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;
}
int prevented = data.getPreventedDamage();
UUID objectControllerId = game.getControllerId(targetSource.getFirstTarget());
UUID objectControllerId = game.getControllerId(target.getFirstTarget());
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
new DamageTargetEffect(prevented)
.setTargetPointer(new FixedTarget(objectControllerId)),

View file

@ -9,7 +9,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate;
@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class Penance extends CardImpl {
private static final FilterObject filter = new FilterObject("black or red source");
private static final FilterSource filter = new FilterSource("black or red source");
static {
filter.add(Predicates.or(new ColorPredicate(ObjectColor.BLACK), new ColorPredicate(ObjectColor.RED)));

View file

@ -13,7 +13,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -23,7 +23,7 @@ import java.util.UUID;
*/
public final class PilgrimOfJustice extends CardImpl {
private static final FilterObject filter = new FilterObject("red source");
private static final FilterSource filter = new FilterSource("red source");
static {
filter.add(new ColorPredicate(ObjectColor.RED));

View file

@ -13,7 +13,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -23,7 +23,7 @@ import java.util.UUID;
*/
public final class PilgrimOfVirtue extends CardImpl {
private static final FilterObject filter = new FilterObject("black source");
private static final FilterSource filter = new FilterSource("black source");
static {
filter.add(new ColorPredicate(ObjectColor.BLACK));

View file

@ -1,8 +1,6 @@
package mage.cards.p;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -14,9 +12,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterObject;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ChosenColorPredicate;
import java.util.UUID;
@ -25,6 +22,12 @@ import java.util.UUID;
*/
public final class PrismaticCircle extends CardImpl {
private static FilterSource filter = new FilterSource("a source of your choice of the chosen color");
static {
filter.add(ChosenColorPredicate.TRUE);
}
public PrismaticCircle(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
@ -36,7 +39,7 @@ public final class PrismaticCircle extends CardImpl {
// {1}: The next time a source of your choice of the chosen color would deal damage to you this turn, prevent that damage.
this.addAbility(new SimpleActivatedAbility(
new PrismaticCircleEffect(),
new PreventNextDamageFromChosenSourceEffect(Duration.EndOfTurn, true, filter),
new ManaCostsImpl<>("{1}")
));
}
@ -50,29 +53,3 @@ public final class PrismaticCircle extends CardImpl {
return new PrismaticCircle(this);
}
}
// TODO: create a FilterSource that can handle ChosenColorPredicate.TRUE and simplify this.
class PrismaticCircleEffect extends PreventNextDamageFromChosenSourceEffect {
PrismaticCircleEffect() {
super(Duration.EndOfTurn, true);
staticText = "The next time a source of your choice of the chosen color would deal damage to you this turn, prevent that damage.";
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
FilterObject filter = targetSource.getFilter();
filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(source.getSourceId() + "_color")));
}
private PrismaticCircleEffect(final PrismaticCircleEffect effect) {
super(effect);
}
@Override
public PrismaticCircleEffect copy() {
return new PrismaticCircleEffect(this);
}
}

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import java.util.UUID;
@ -19,7 +19,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionArtifacts extends CardImpl {
private static final FilterObject filter = new FilterObject("artifact source");
private static final FilterSource filter = new FilterSource("artifact source");
static {
filter.add(CardType.ARTIFACT.getPredicate());

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionBlack extends CardImpl {
private static final FilterObject filter = new FilterObject("black source");
private static final FilterSource filter = new FilterSource("black source");
static {
filter.add(new ColorPredicate(ObjectColor.BLACK));

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionBlue extends CardImpl {
private static final FilterObject filter = new FilterObject("blue source");
private static final FilterSource filter = new FilterSource("blue source");
static {
filter.add(new ColorPredicate(ObjectColor.BLUE));

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionGreen extends CardImpl {
private static final FilterObject filter = new FilterObject("green source");
private static final FilterSource filter = new FilterSource("green source");
static {
filter.add(new ColorPredicate(ObjectColor.GREEN));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import java.util.UUID;
@ -19,7 +19,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionLands extends CardImpl {
private static final FilterObject filter = new FilterObject("land source");
private static final FilterSource filter = new FilterSource("land source");
static {
filter.add(CardType.LAND.getPredicate());

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionRed extends CardImpl {
private static final FilterObject filter = new FilterObject("red source");
private static final FilterSource filter = new FilterSource("red source");
static {
filter.add(new ColorPredicate(ObjectColor.RED));

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.FilterObject;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class RuneOfProtectionWhite extends CardImpl {
private static final FilterObject filter = new FilterObject("white source");
private static final FilterSource filter = new FilterSource("white source");
static {
filter.add(new ColorPredicate(ObjectColor.WHITE));

View file

@ -13,7 +13,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetSource;
import mage.target.Target;
import java.util.UUID;
@ -77,11 +77,11 @@ enum ShadowbanePreventionApplier implements PreventNextDamageFromChosenSourceEff
return "If damage from a black source is prevented this way, you gain that much life";
}
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;
}
MageObject sourceObject = game.getObject(targetSource.getFirstTarget());
MageObject sourceObject = game.getObject(target.getFirstTarget());
if (!sourceObject.getColor(game).isBlack()) {
return false;
}

View file

@ -1,8 +1,6 @@
package mage.cards.s;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -13,9 +11,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterObject;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ChosenColorPredicate;
import java.util.UUID;
@ -25,6 +22,12 @@ import java.util.UUID;
public final class StoryCircle extends CardImpl {
private static FilterSource filter = new FilterSource("a source of your choice of the chosen color");
static {
filter.add(ChosenColorPredicate.TRUE);
}
public StoryCircle(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}");
@ -32,7 +35,7 @@ public final class StoryCircle extends CardImpl {
this.addAbility(new AsEntersBattlefieldAbility(new ChooseColorEffect(Outcome.Neutral)));
// {W}: The next time a source of your choice of the chosen color would deal damage to you this turn, prevent that damage.
this.addAbility(new SimpleActivatedAbility(
new StoryCircleEffect(),
new PreventNextDamageFromChosenSourceEffect(Duration.EndOfTurn, true, filter),
new ManaCostsImpl<>("{W}")
));
}
@ -46,29 +49,3 @@ public final class StoryCircle extends CardImpl {
return new StoryCircle(this);
}
}
// TODO: create a FilterSource that can handle ChosenColorPredicate.TRUE and simplify this.
class StoryCircleEffect extends PreventNextDamageFromChosenSourceEffect {
StoryCircleEffect() {
super(Duration.EndOfTurn, true);
staticText = "The next time a source of your choice of the chosen color would deal damage to you this turn, prevent that damage.";
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
FilterObject filter = targetSource.getFilter();
filter.add(new ColorPredicate((ObjectColor) game.getState().getValue(source.getSourceId() + "_color")));
}
private StoryCircleEffect(final StoryCircleEffect effect) {
super(effect);
}
@Override
public StoryCircleEffect copy() {
return new StoryCircleEffect(this);
}
}

View file

@ -0,0 +1,73 @@
package org.mage.test.cards.single.drk;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class DarkSphereTest extends CardTestPlayerBase {
/**
* {@link mage.cards.d.DarkSphere Dark Sphere} {0}
* Artifact
* {T}, Sacrifice this artifact: The next time a source of your choice would deal damage to you this turn, prevent half that damage, rounded down.
*/
private static final String sphere = "Dark Sphere";
@Test
public void test_DamageOnCreature_NoPrevent() {
addCard(Zone.BATTLEFIELD, playerA, sphere, 1);
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker", 1); // 2/1
addCard(Zone.BATTLEFIELD, playerA, "Caelorna, Coral Tyrant"); // 0/8
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice");
setChoice(playerA, "Goblin Piker"); // source to prevent from
attack(2, playerB, "Goblin Piker", playerA);
block(2, playerA, "Caelorna, Coral Tyrant", "Goblin Piker");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertDamageReceived(playerA, "Caelorna, Coral Tyrant", 2); // no prevent
}
@Test
public void test_DamageOnYou_Prevent() {
addCard(Zone.BATTLEFIELD, playerA, sphere, 1);
addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm", 1); // 6/4
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice");
setChoice(playerA, "Craw Wurm"); // source to prevent from
attack(2, playerB, "Craw Wurm", playerA);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20 - 3);
}
@Test
public void test_DoubleStrike_Prevent_ThenConsumedAndNoPrevent() {
addCard(Zone.BATTLEFIELD, playerA, sphere, 1);
addCard(Zone.BATTLEFIELD, playerB, "Blade Historian", 1); // 2/3 "Attacking creatures you control have double strike."
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice");
setChoice(playerA, "Blade Historian"); // source to prevent from
attack(2, playerB, "Blade Historian", playerA);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20 - 3);
}
}

View file

@ -42,7 +42,7 @@ public class CircleOfProtectionTest extends CardTestPlayerBase {
execute();
assertDamageReceived(playerA, "Caelorna, Coral Tyrant", 2); // no prevent
assertLife(playerB, 20);
assertLife(playerA, 20);
assertTappedCount("Plains", true, 1);
}

View file

@ -97,7 +97,7 @@ public class StoryCircleTest extends CardTestPlayerBase {
setChoice(playerA, "Green"); // color chosen
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}");
setChoice(playerA, "Goblin Piker"); // TODO: this should not be a valid choice for TargetSource(chosencolor)
// no valid choice
attack(2, playerB, "Goblin Piker", playerA);
@ -105,7 +105,7 @@ public class StoryCircleTest extends CardTestPlayerBase {
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20); // TODO: Should not prevent.
assertLife(playerA, 20 - 2);
assertTappedCount("Plains", true, 4);
}
@ -144,13 +144,13 @@ public class StoryCircleTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}", null, "Lightning Bolt");
setChoice(playerA, "Lightning Bolt"); // TODO: this should not be a valid choice for TargetSource(chosencolor)
// no valid choice
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20); // TODO: Should not prevent.
assertLife(playerA, 20 - 3);
assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertTappedCount("Plains", true, 4);
}
@ -192,13 +192,13 @@ public class StoryCircleTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blisterstick Shaman");
addTarget(playerB, playerA); // target for Shaman trigger
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}", null, "stack ability (When");
setChoice(playerA, "Blisterstick Shaman"); // TODO: this should not be a valid choice for TargetSource(chosencolor)
// no valid choice
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20); // TODO: Should not prevent.
assertLife(playerA, 20 - 1);
assertPermanentCount(playerB, "Blisterstick Shaman", 1);
assertTappedCount("Plains", true, 4);
}
@ -238,14 +238,14 @@ public class StoryCircleTest extends CardTestPlayerBase {
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{R},", playerA);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}", null, "stack ability ({R}");
setChoice(playerA, "Anaba Shaman"); // TODO: this should not be a valid choice for TargetSource(chosencolor)
// no valid choice
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertTappedCount("Anaba Shaman", true, 1);
assertLife(playerA, 20); // TODO: Should not prevent.
assertLife(playerA, 20 - 1);
assertTappedCount("Plains", true, 4);
}
}

View file

@ -0,0 +1,57 @@
package org.mage.test.cards.single.tmp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class CircleOfProtectionShadowTest extends CardTestPlayerBase {
/**
* {@link mage.cards.c.CircleOfProtectionShadow Circle of Protection: Shadow} {1}{W}
* Enchantment
* {1}: The next time a creature of your choice with shadow would deal damage to you this turn, prevent that damage.
*/
private static final String circle = "Circle of Protection: Shadow";
@Test
public void test_NotShadow_NoPrevent() {
addCard(Zone.BATTLEFIELD, playerA, circle, 1);
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker", 1); // 2/1
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}");
// no creature with shadow to choose
attack(2, playerB, "Goblin Piker", playerA);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20 - 2);
assertTappedCount("Plains", true, 1);
}
@Test
public void test_Shadow_Prevent() {
addCard(Zone.BATTLEFIELD, playerA, circle, 1);
addCard(Zone.BATTLEFIELD, playerB, "Dauthi Marauder", 1); // 2/1
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}");
setChoice(playerA, "Dauthi Marauder");
attack(2, playerB, "Dauthi Marauder", playerA);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20);
assertTappedCount("Plains", true, 1);
}
}

View file

@ -0,0 +1,45 @@
package org.mage.test.cards.single.tmp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class InvulnerabilityTest extends CardTestPlayerBase {
/**
* {@link mage.cards.i.Invulnerability Invulnerability} {1}{W}
* Instant
* Buyback {3} (You may pay an additional {3} as you cast this spell. If you do, put this card into your hand as it resolves.)
* The next time a source of your choice would deal damage to you this turn, prevent that damage.
*/
private static final String invulnerability = "Invulnerability";
@Test
public void test_EmblemDamage_Prevent() {
addCard(Zone.HAND, playerA, invulnerability, 1);
addCard(Zone.BATTLEFIELD, playerB, "Chandra, Awakened Inferno", 1); // +2: Each opponent gets an emblem with At the beginning of your upkeep, this emblem deals 1 damage to you.
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "+2");
checkEmblemCount("Chandra Emblem has been created", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Emblem Chandra", 1);
// turn 3: prevent
castSpell(3, PhaseStep.UPKEEP, playerA, invulnerability);
setChoice(playerA, false); // don't pay for Buyback
setChoice(playerA, "Emblem Chandra"); // choice for source
checkLife("prevention turn 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, 20);
// turn 5: no prevent to check effect ending
setStrictChooseMode(true);
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20 - 1);
}
}

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