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.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.mageobject.NamePredicate;
@ -22,6 +23,7 @@ import java.util.UUID;
public final class AjanisAid extends CardImpl { public final class AjanisAid extends CardImpl {
private static final FilterCard filter = new FilterCard("Ajani, Valiant Protector"); private static final FilterCard filter = new FilterCard("Ajani, Valiant Protector");
private static final FilterPermanent filterPrevent = new FilterCreaturePermanent("creature of your choice");
static { static {
filter.add(new NamePredicate("Ajani, Valiant Protector")); 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)); 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. // 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())); this.addAbility(new SimpleActivatedAbility(effect, new SacrificeSourceCost()));
} }

View file

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

View file

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

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/ */
public final class CircleOfProtectionBlack extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.BLACK)); filter.add(new ColorPredicate(ObjectColor.BLACK));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/ */
public final class CircleOfProtectionBlue extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.BLUE)); filter.add(new ColorPredicate(ObjectColor.BLUE));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/ */
public final class CircleOfProtectionGreen extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.GREEN)); filter.add(new ColorPredicate(ObjectColor.GREEN));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/ */
public final class CircleOfProtectionRed extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.RED)); filter.add(new ColorPredicate(ObjectColor.RED));

View file

@ -10,6 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.AbilityPredicate;
@ -20,7 +21,7 @@ import java.util.UUID;
*/ */
public final class CircleOfProtectionShadow extends CardImpl { 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 { static {
filter.add(new AbilityPredicate(ShadowAbility.class)); filter.add(new AbilityPredicate(ShadowAbility.class));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -20,7 +20,7 @@ import java.util.UUID;
*/ */
public final class CircleOfProtectionWhite extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.WHITE)); filter.add(new ColorPredicate(ObjectColor.WHITE));

View file

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

View file

@ -1,28 +1,25 @@
package mage.cards.d; package mage.cards.d;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.PreventNextDamageFromChosenSourceEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.filter.FilterSource;
import mage.constants.Zone;
import mage.filter.FilterObject;
import mage.game.Game; import mage.game.Game;
import mage.game.events.DamageEvent; import mage.game.events.DamageEvent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetSource;
import java.util.UUID;
/** /**
*
* @author ThomasLerner * @author ThomasLerner
*/ */
public final class DarkSphere extends CardImpl { 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() { 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.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) { private DarkSpherePreventionEffect(final DarkSpherePreventionEffect effect) {
super(effect); super(effect);
this.targetSource = effect.targetSource.copy();
} }
@Override @Override
@ -68,40 +63,24 @@ class DarkSpherePreventionEffect extends ReplacementEffectImpl {
return new DarkSpherePreventionEffect(this); 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 @Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source); MageObject sourceObject = game.getObject(source);
DamageEvent damageEvent = (DamageEvent) event; DamageEvent damageEvent = (DamageEvent) event;
if (controller != null) { int damage = damageEvent.getAmount();
controller.damage((int) Math.ceil(damageEvent.getAmount() / 2.0), damageEvent.getSourceId(), source, game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), damageEvent.getAppliedEffects()); 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() : ""); 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()); sb.append(" from ").append(controller.getLogName());
game.informPlayers(sb.toString()); game.informPlayers(sb.toString());
discard(); // only one use discard(); // only one use
return true; 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.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetSource; import mage.target.Target;
import java.util.UUID; import java.util.UUID;
@ -44,12 +44,12 @@ public final class DeflectingPalm extends CardImpl {
enum DeflectingPalmPreventionApplier implements PreventNextDamageFromChosenSourceEffect.ApplierOnPrevention { enum DeflectingPalmPreventionApplier implements PreventNextDamageFromChosenSourceEffect.ApplierOnPrevention {
instance; 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) { if (data == null || data.getPreventedDamage() <= 0) {
return false; return false;
} }
int prevented = data.getPreventedDamage(); int prevented = data.getPreventedDamage();
UUID objectControllerId = game.getControllerId(targetSource.getFirstTarget()); UUID objectControllerId = game.getControllerId(target.getFirstTarget());
Player objectController = game.getPlayer(objectControllerId); Player objectController = game.getPlayer(objectControllerId);
if (objectController == null) { if (objectController == null) {
return false; return false;

View file

@ -1,10 +1,6 @@
package mage.cards.d; 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.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.PreventionEffectImpl; import mage.abilities.effects.PreventionEffectImpl;
@ -14,18 +10,22 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.FilterSource;
import mage.game.Game; import mage.game.Game;
import mage.game.events.DamageEvent; import mage.game.events.DamageEvent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
import mage.filter.FilterObject;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetSource; import mage.target.TargetSource;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author L_J * @author L_J
*/ */
public final class DesperateGambit extends CardImpl { 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 { class TargetControlledSource extends TargetSource {
public TargetControlledSource() { public TargetControlledSource() {
super(1, 1, new FilterObject("source you control")); super(1, 1, new FilterSource("source you control"));
} }
private TargetControlledSource(final TargetControlledSource target) { private TargetControlledSource(final TargetControlledSource target) {

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
@ -21,7 +21,7 @@ import java.util.UUID;
*/ */
public final class GreaterRealmOfPreservation extends CardImpl { 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 { static {
filter.add(Predicates.or(new ColorPredicate(ObjectColor.BLACK), new ColorPredicate(ObjectColor.RED))); filter.add(Predicates.or(new ColorPredicate(ObjectColor.BLACK), new ColorPredicate(ObjectColor.RED)));

View file

@ -1,7 +1,6 @@
package mage.cards.j; package mage.cards.j;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
@ -12,8 +11,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.filter.FilterSource;
import mage.filter.FilterObject;
import mage.game.Game; import mage.game.Game;
import mage.game.events.DamageEvent; import mage.game.events.DamageEvent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
@ -23,8 +21,9 @@ import mage.players.Player;
import mage.target.TargetSource; import mage.target.TargetSource;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/** /**
*
* @author Quercitron * @author Quercitron
*/ */
public final class JadeMonolith extends CardImpl { public final class JadeMonolith extends CardImpl {
@ -55,7 +54,7 @@ class JadeMonolithRedirectionEffect extends ReplacementEffectImpl {
public JadeMonolithRedirectionEffect() { public JadeMonolithRedirectionEffect() {
super(Duration.OneUse, Outcome.RedirectDamage); 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.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) { private JadeMonolithRedirectionEffect(final JadeMonolithRedirectionEffect effect) {

View file

@ -1,7 +1,5 @@
package mage.cards.m; package mage.cards.m;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.ObjectColor; import mage.ObjectColor;
@ -15,8 +13,11 @@ import mage.abilities.effects.PreventionEffectImpl;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.AbilityWord;
import mage.filter.FilterObject; import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.SharesColorPredicate; import mage.filter.predicate.mageobject.SharesColorPredicate;
import mage.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
@ -27,8 +28,9 @@ import mage.target.TargetSource;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.UUID;
/** /**
*
* @author noahg * @author noahg
*/ */
public final class MournersShield extends CardImpl { 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 { class MournersShieldEffect extends PreventionEffectImpl {
private TargetSource target; 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"); FilterSource filterSource = new FilterSource("a source of your choice that shares a color with the exiled card");
filterObject.add(new SharesColorPredicate(colorsAmongImprinted)); filterSource.add(new SharesColorPredicate(colorsAmongImprinted));
this.target = new TargetSource(filterObject); this.target = new TargetSource(filterSource);
this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game); this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
if (target.getFirstTarget() != null) { if (target.getFirstTarget() != null) {
mageObjectReference = new MageObjectReference(target.getFirstTarget(), game); mageObjectReference = new MageObjectReference(target.getFirstTarget(), game);

View file

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

View file

@ -9,7 +9,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
@ -20,7 +20,7 @@ import java.util.UUID;
*/ */
public final class Penance extends CardImpl { 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 { static {
filter.add(Predicates.or(new ColorPredicate(ObjectColor.BLACK), new ColorPredicate(ObjectColor.RED))); 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.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -23,7 +23,7 @@ import java.util.UUID;
*/ */
public final class PilgrimOfJustice extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.RED)); filter.add(new ColorPredicate(ObjectColor.RED));

View file

@ -13,7 +13,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -23,7 +23,7 @@ import java.util.UUID;
*/ */
public final class PilgrimOfVirtue extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.BLACK)); filter.add(new ColorPredicate(ObjectColor.BLACK));

View file

@ -1,8 +1,6 @@
package mage.cards.p; package mage.cards.p;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
@ -14,9 +12,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ChosenColorPredicate;
import mage.game.Game;
import java.util.UUID; import java.util.UUID;
@ -25,6 +22,12 @@ import java.util.UUID;
*/ */
public final class PrismaticCircle extends CardImpl { 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) { public PrismaticCircle(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); 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. // {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( this.addAbility(new SimpleActivatedAbility(
new PrismaticCircleEffect(), new PreventNextDamageFromChosenSourceEffect(Duration.EndOfTurn, true, filter),
new ManaCostsImpl<>("{1}") new ManaCostsImpl<>("{1}")
)); ));
} }
@ -50,29 +53,3 @@ public final class PrismaticCircle extends CardImpl {
return new PrismaticCircle(this); 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.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import java.util.UUID; import java.util.UUID;
@ -19,7 +19,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionArtifacts extends CardImpl { 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 { static {
filter.add(CardType.ARTIFACT.getPredicate()); filter.add(CardType.ARTIFACT.getPredicate());

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionBlack extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.BLACK)); filter.add(new ColorPredicate(ObjectColor.BLACK));

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionBlue extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.BLUE)); filter.add(new ColorPredicate(ObjectColor.BLUE));

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionGreen extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.GREEN)); filter.add(new ColorPredicate(ObjectColor.GREEN));

View file

@ -10,7 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import java.util.UUID; import java.util.UUID;
@ -19,7 +19,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionLands extends CardImpl { 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 { static {
filter.add(CardType.LAND.getPredicate()); filter.add(CardType.LAND.getPredicate());

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionRed extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.RED)); filter.add(new ColorPredicate(ObjectColor.RED));

View file

@ -11,7 +11,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
import java.util.UUID; import java.util.UUID;
@ -21,7 +21,7 @@ import java.util.UUID;
*/ */
public final class RuneOfProtectionWhite extends CardImpl { 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 { static {
filter.add(new ColorPredicate(ObjectColor.WHITE)); filter.add(new ColorPredicate(ObjectColor.WHITE));

View file

@ -13,7 +13,7 @@ import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetSource; import mage.target.Target;
import java.util.UUID; 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"; 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) { if (data == null || data.getPreventedDamage() <= 0) {
return false; return false;
} }
MageObject sourceObject = game.getObject(targetSource.getFirstTarget()); MageObject sourceObject = game.getObject(target.getFirstTarget());
if (!sourceObject.getColor(game).isBlack()) { if (!sourceObject.getColor(game).isBlack()) {
return false; return false;
} }

View file

@ -1,8 +1,6 @@
package mage.cards.s; package mage.cards.s;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
@ -13,9 +11,8 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ChosenColorPredicate;
import mage.game.Game;
import java.util.UUID; import java.util.UUID;
@ -25,6 +22,12 @@ import java.util.UUID;
public final class StoryCircle extends CardImpl { 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) { public StoryCircle(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); 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))); 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. // {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( this.addAbility(new SimpleActivatedAbility(
new StoryCircleEffect(), new PreventNextDamageFromChosenSourceEffect(Duration.EndOfTurn, true, filter),
new ManaCostsImpl<>("{W}") new ManaCostsImpl<>("{W}")
)); ));
} }
@ -46,29 +49,3 @@ public final class StoryCircle extends CardImpl {
return new StoryCircle(this); 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(); execute();
assertDamageReceived(playerA, "Caelorna, Coral Tyrant", 2); // no prevent assertDamageReceived(playerA, "Caelorna, Coral Tyrant", 2); // no prevent
assertLife(playerB, 20); assertLife(playerA, 20);
assertTappedCount("Plains", true, 1); assertTappedCount("Plains", true, 1);
} }

View file

@ -97,7 +97,7 @@ public class StoryCircleTest extends CardTestPlayerBase {
setChoice(playerA, "Green"); // color chosen setChoice(playerA, "Green"); // color chosen
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}"); 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); attack(2, playerB, "Goblin Piker", playerA);
@ -105,7 +105,7 @@ public class StoryCircleTest extends CardTestPlayerBase {
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerA, 20); // TODO: Should not prevent. assertLife(playerA, 20 - 2);
assertTappedCount("Plains", true, 4); assertTappedCount("Plains", true, 4);
} }
@ -144,13 +144,13 @@ public class StoryCircleTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}", null, "Lightning Bolt"); 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); setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerA, 20); // TODO: Should not prevent. assertLife(playerA, 20 - 3);
assertGraveyardCount(playerB, "Lightning Bolt", 1); assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertTappedCount("Plains", true, 4); assertTappedCount("Plains", true, 4);
} }
@ -192,13 +192,13 @@ public class StoryCircleTest extends CardTestPlayerBase {
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blisterstick Shaman"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blisterstick Shaman");
addTarget(playerB, playerA); // target for Shaman trigger addTarget(playerB, playerA); // target for Shaman trigger
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}", null, "stack ability (When"); 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); setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertLife(playerA, 20); // TODO: Should not prevent. assertLife(playerA, 20 - 1);
assertPermanentCount(playerB, "Blisterstick Shaman", 1); assertPermanentCount(playerB, "Blisterstick Shaman", 1);
assertTappedCount("Plains", true, 4); 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, playerB, "{R},", playerA);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}", null, "stack ability ({R}"); 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); setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertTappedCount("Anaba Shaman", true, 1); assertTappedCount("Anaba Shaman", true, 1);
assertLife(playerA, 20); // TODO: Should not prevent. assertLife(playerA, 20 - 1);
assertTappedCount("Plains", true, 4); 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.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.target.TargetSource; import mage.target.TargetSource;
@ -17,14 +17,14 @@ public class PreventAllDamageFromChosenSourceToYouEffect extends PreventionEffec
protected final TargetSource targetSource; protected final TargetSource targetSource;
public PreventAllDamageFromChosenSourceToYouEffect(Duration duration) { 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); 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); super(duration, Integer.MAX_VALUE, onlyCombat);
this.targetSource = new TargetSource(filter); this.targetSource = new TargetSource(filter);
this.staticText = setText(); this.staticText = setText();

View file

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

View file

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

View file

@ -1,12 +1,11 @@
package mage.abilities.effects.common; package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.abilities.effects.PreventionEffectImpl; import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.target.TargetSource; import mage.target.TargetSource;
@ -19,14 +18,14 @@ public class PreventNextDamageFromChosenSourceToTargetEffect extends PreventionE
protected final TargetSource targetSource; protected final TargetSource targetSource;
public PreventNextDamageFromChosenSourceToTargetEffect(Duration duration) { 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); 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); super(duration, Integer.MAX_VALUE, onlyCombat);
this.targetSource = new TargetSource(filter); 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; package mage.target;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterObject; import mage.filter.FilterSource;
import mage.game.Game; import mage.game.Game;
import mage.game.command.CommandObject;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
import mage.players.Player; 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 * @author BetaSteward_at_googlemail.com
*/ */
public class TargetSource extends TargetObject { public class TargetSource extends TargetObject {
protected final FilterObject filter; protected final FilterSource filter;
public TargetSource() { 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); this(1, 1, filter);
} }
public TargetSource(int numTargets, FilterObject filter) { public TargetSource(int numTargets, FilterSource filter) {
this(numTargets, numTargets, 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); super(minNumTargets, maxNumTargets, Zone.ALL, true);
this.filter = filter; this.filter = filter;
this.targetName = filter.getMessage(); this.targetName = filter.getMessage();
@ -47,7 +59,7 @@ public class TargetSource extends TargetObject {
} }
@Override @Override
public FilterObject getFilter() { public FilterSource getFilter() {
return filter; return filter;
} }
@ -78,15 +90,16 @@ public class TargetSource extends TargetObject {
} }
@Override @Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { public boolean canChoose(UUID sourceControllerId, Game game) {
return canChoose(sourceControllerId, game); return canChoose(sourceControllerId, (Ability) null, game);
} }
@Override @Override
public boolean canChoose(UUID sourceControllerId, Game game) { public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0; int count = 0;
for (StackObject stackObject : game.getStack()) { 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++; count++;
if (count >= this.minNumberOfTargets) { if (count >= this.minNumberOfTargets) {
return true; return true;
@ -94,7 +107,7 @@ public class TargetSource extends TargetObject {
} }
} }
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (filter.match(permanent, game)) { if (filter.match(permanent, sourceControllerId, source, game)) {
count++; count++;
if (count >= this.minNumberOfTargets) { if (count >= this.minNumberOfTargets) {
return true; return true;
@ -103,7 +116,7 @@ public class TargetSource extends TargetObject {
} }
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
for (Card card : player.getGraveyard().getCards(game)) { for (Card card : player.getGraveyard().getCards(game)) {
if (filter.match(card, game)) { if (filter.match(card, sourceControllerId, source, game)) {
count++; count++;
if (count >= this.minNumberOfTargets) { if (count >= this.minNumberOfTargets) {
return true; return true;
@ -112,7 +125,15 @@ public class TargetSource extends TargetObject {
} }
} }
for (Card card : game.getExile().getAllCards(game)) { 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++; count++;
if (count >= this.minNumberOfTargets) { if (count >= this.minNumberOfTargets) {
return true; return true;
@ -123,35 +144,41 @@ public class TargetSource extends TargetObject {
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
return possibleTargets(sourceControllerId, game); return possibleTargets(sourceControllerId, (Ability) null, game);
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
for (StackObject stackObject : game.getStack()) { 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()); possibleTargets.add(stackObject.getId());
} }
} }
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (filter.match(permanent, game)) { if (filter.match(permanent, sourceControllerId, source, game)) {
possibleTargets.add(permanent.getId()); possibleTargets.add(permanent.getId());
} }
} }
for (Player player : game.getPlayers().values()) { for (Player player : game.getPlayers().values()) {
for (Card card : player.getGraveyard().getCards(game)) { for (Card card : player.getGraveyard().getCards(game)) {
if (filter.match(card, game)) { if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId()); possibleTargets.add(card.getId());
} }
} }
} }
for (Card card : game.getExile().getAllCards(game)) { for (Card card : game.getExile().getAllCards(game)) {
if (filter.match(card, game)) { if (filter.match(card, sourceControllerId, source, game)) {
possibleTargets.add(card.getId()); possibleTargets.add(card.getId());
} }
} }
for (CommandObject commandObject : game.getState().getCommand()) {
if (filter.match(commandObject, sourceControllerId, source, game)) {
possibleTargets.add(commandObject.getId());
}
}
return possibleTargets; return possibleTargets;
} }