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,24 +10,23 @@ 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 {
public ConsulateSurveillance(UUID ownerId, CardSetInfo setInfo) { public ConsulateSurveillance(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{W}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}");
// When Consulate Surveillance enters the battlefield, you get {E}{E}{E}{E}. // When Consulate Surveillance enters the battlefield, you get {E}{E}{E}{E}.
this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(4))); this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(4)));
// 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) {
StringBuilder sb = new StringBuilder(sourceObject != null ? sourceObject.getLogName() : ""); return false;
sb.append(": ").append(damageEvent.getAmount() / 2).append(" damage prevented");
sb.append(" from ").append(controller.getLogName());
game.informPlayers(sb.toString());
discard(); // only one use
return true;
} }
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(damage / 2).append(" damage prevented");
sb.append(" from ").append(controller.getLogName());
game.informPlayers(sb.toString());
discard(); // only one use
return true;
} }
@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,24 +10,28 @@ 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 {
public DesperateGambit(UUID ownerId, CardSetInfo setInfo) { public DesperateGambit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{R}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}");
// Choose a source you control and flip a coin. If you win the flip, the next time that source would deal damage this turn, it deals double that damage instead. If you lose the flip, the next time it would deal damage this turn, prevent that damage. // Choose a source you control and flip a coin. If you win the flip, the next time that source would deal damage this turn, it deals double that damage instead. If you lose the flip, the next time it would deal damage this turn, prevent that damage.
this.getSpellAbility().addEffect(new DesperateGambitEffect()); this.getSpellAbility().addEffect(new DesperateGambitEffect());
@ -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) {
@ -135,16 +139,16 @@ class TargetControlledSource extends TargetSource {
@Override @Override
public boolean canChoose(UUID sourceControllerId, Game game) { public boolean canChoose(UUID sourceControllerId, 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()) if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& Objects.equals(stackObject.getControllerId(), sourceControllerId)) { && Objects.equals(stackObject.getControllerId(), sourceControllerId)) {
count++; count++;
if (count >= this.minNumberOfTargets) { if (count >= this.minNumberOfTargets) {
return true; return true;
} }
} }
} }
for (Permanent permanent: game.getBattlefield().getActivePermanents(sourceControllerId, game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (Objects.equals(permanent.getControllerId(), sourceControllerId)) { if (Objects.equals(permanent.getControllerId(), sourceControllerId)) {
count++; count++;
if (count >= this.minNumberOfTargets) { if (count >= this.minNumberOfTargets) {
@ -177,13 +181,13 @@ class TargetControlledSource extends TargetSource {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, 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()) if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& Objects.equals(stackObject.getControllerId(), sourceControllerId)) { && Objects.equals(stackObject.getControllerId(), sourceControllerId)) {
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 (Objects.equals(permanent.getControllerId(), sourceControllerId)) { if (Objects.equals(permanent.getControllerId(), sourceControllerId)) {
possibleTargets.add(permanent.getId()); possibleTargets.add(permanent.getId());
} }

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,14 +21,15 @@ 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 {
public JadeMonolith(UUID ownerId, CardSetInfo setInfo) { public JadeMonolith(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
// {1}: The next time a source of your choice would deal damage to target creature this turn, that source deals that damage to you instead. // {1}: The next time a source of your choice would deal damage to target creature this turn, that source deals that damage to you instead.
Ability ability = new SimpleActivatedAbility(new JadeMonolithRedirectionEffect(), new GenericManaCost(1)); Ability ability = new SimpleActivatedAbility(new JadeMonolithRedirectionEffect(), new GenericManaCost(1));
@ -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;
@ -131,17 +137,17 @@ class MournersShieldEffect extends PreventionEffectImpl {
noneExiled = true; noneExiled = true;
return; return;
} }
for (UUID imprinted : sourceObject.getImprinted()){ for (UUID imprinted : sourceObject.getImprinted()) {
if (imprinted != null && exileZone.contains(imprinted)){ if (imprinted != null && exileZone.contains(imprinted)) {
Card card = game.getCard(imprinted); Card card = game.getCard(imprinted);
if (card != null) { if (card != null) {
colorsAmongImprinted = colorsAmongImprinted.union(card.getColor(game)); colorsAmongImprinted = colorsAmongImprinted.union(card.getColor(game));
} }
} }
} }
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);
@ -152,7 +158,7 @@ class MournersShieldEffect extends PreventionEffectImpl {
@Override @Override
public boolean applies(GameEvent event, Ability source, Game game) { public boolean applies(GameEvent event, Ability source, Game game) {
if (noneExiled || mageObjectReference == null){ if (noneExiled || mageObjectReference == null) {
return false; return false;
} }
if (super.applies(event, source, game)) { if (super.applies(event, source, 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;
} }