Attacks player with creatures triggered ability, Implement [BLC] Echoing Assault (#13764)

Create AttacksPlayerWithCreaturesTriggeredAbility, and tests for that ability via Soaring Lightbringer.
Implement Echoing Assault.
This commit is contained in:
ssk97 2025-06-18 05:54:36 -07:00 committed by GitHub
parent a7258604c6
commit 1fe0d92c86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 404 additions and 371 deletions

View file

@ -1,7 +1,7 @@
package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
@ -12,12 +12,11 @@ import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Controllable;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.DemonToken;
import mage.players.Player;
@ -30,6 +29,7 @@ import java.util.UUID;
* @author TheElk801
*/
public final class DemonicCovenant extends CardImpl {
FilterPermanent filter = new FilterControlledPermanent(SubType.DEMON,"Demons you control");
public DemonicCovenant(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.KINDRED, CardType.ENCHANTMENT}, "{4}{B}{B}");
@ -37,12 +37,14 @@ public final class DemonicCovenant extends CardImpl {
this.subtype.add(SubType.DEMON);
// Whenever one or more Demons you control attack a player, you draw a card and lose 1 life.
this.addAbility(new DemonicCovenantTriggeredAbility());
Ability abilityAttack = new AttacksPlayerWithCreaturesTriggeredAbility(new DrawCardSourceControllerEffect(1, true), filter, SetTargetPointer.NONE);
abilityAttack.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life"));
this.addAbility(abilityAttack);
// At the beginning of your end step, create a 5/5 black Demon creature token with flying, then mill two cards. If two cards that share all their card types were milled this way, sacrifice Demonic Covenant.
Ability ability = new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new DemonToken()));
ability.addEffect(new DemonicCovenantEffect());
this.addAbility(ability);
Ability abilityEndStep = new BeginningOfEndStepTriggeredAbility(new CreateTokenEffect(new DemonToken()));
abilityEndStep.addEffect(new DemonicCovenantEffect());
this.addAbility(abilityEndStep);
}
private DemonicCovenant(final DemonicCovenant card) {
@ -55,40 +57,6 @@ public final class DemonicCovenant extends CardImpl {
}
}
class DemonicCovenantTriggeredAbility extends TriggeredAbilityImpl {
DemonicCovenantTriggeredAbility() {
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1, true));
this.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life"));
this.setTriggerPhrase("Whenever one or more Demons you control attack a player, ");
}
private DemonicCovenantTriggeredAbility(final DemonicCovenantTriggeredAbility ability) {
super(ability);
}
@Override
public DemonicCovenantTriggeredAbility copy() {
return new DemonicCovenantTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return game.getPlayer(event.getTargetId()) != null
&& ((DefenderAttackedEvent) event)
.getAttackers(game)
.stream()
.filter(permanent -> permanent.hasSubtype(SubType.DEMON, game))
.map(Controllable::getControllerId)
.anyMatch(this::isControlledBy);
}
}
class DemonicCovenantEffect extends OneShotEffect {
DemonicCovenantEffect() {

View file

@ -0,0 +1,107 @@
package mage.cards.e;
import mage.abilities.Ability;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author notgreat
*/
public final class EchoingAssault extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent("nontoken creature that's attacking that player");
static {
filter.add(TokenPredicate.FALSE);
filter.add(EchoingAssaultPredicate.instance);
}
public EchoingAssault(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{R}");
// Creature tokens you control have menace.
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
new MenaceAbility(false), Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_TOKENS
)));
// Whenever you attack a player, choose target nontoken creature that's attacking that player. Create a token that's a copy of that creature, except it's 1/1. The token enters tapped and attacking that player. Sacrifice it at the beginning of the next end step.
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(new EchoingAssaultEffect(), SetTargetPointer.NONE);
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}
private EchoingAssault(final EchoingAssault card) {
super(card);
}
@Override
public EchoingAssault copy() {
return new EchoingAssault(this);
}
}
enum EchoingAssaultPredicate implements ObjectSourcePlayerPredicate<Permanent> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return CardUtil.getEffectValueFromAbility(input.getSource(), "playerAttacked", UUID.class)
.filter(uuid -> uuid.equals(game.getCombat().getDefenderId(input.getObject().getId())))
.isPresent();
}
}
class EchoingAssaultEffect extends OneShotEffect {
EchoingAssaultEffect() {
super(Outcome.Benefit);
staticText = "choose target nontoken creature that's attacking that player. Create a token that's a copy of that creature, except it's 1/1. The token enters tapped and attacking that player. Sacrifice it at the beginning of the next end step.";
}
private EchoingAssaultEffect(final EchoingAssaultEffect effect) {
super(effect);
}
@Override
public EchoingAssaultEffect copy() {
return new EchoingAssaultEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Object attackedPlayer = getValue("playerAttacked");
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null || !(attackedPlayer instanceof UUID)) {
return false;
}
CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(null, null, false,
1, true, true, (UUID) attackedPlayer, 1, 1, false);
effect.setSavedPermanent(permanent);
effect.apply(game, source);
effect.sacrificeTokensCreatedAtNextEndStep(game, source);
return true;
}
}

View file

@ -2,7 +2,7 @@ package mage.cards.f;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
@ -14,7 +14,7 @@ import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
@ -29,12 +29,11 @@ import java.util.UUID;
*/
public final class FirkraagCunningInstigator extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent();
private static final FilterPermanent filterDragons = new FilterControlledCreaturePermanent("Dragons you control");
private static final FilterPermanent filterHadToAttack = new FilterCreaturePermanent();
private static final FilterPermanent filterDragons = new FilterControlledPermanent(SubType.DRAGON, "Dragons you control");
static {
filter.add(FirkraagCunningInstigatorPredicate.instance);
filterDragons.add(SubType.DRAGON.getPredicate());
filterHadToAttack.add(FirkraagCunningInstigatorPredicate.instance);
}
public FirkraagCunningInstigator(UUID ownerId, CardSetInfo setInfo) {
@ -52,7 +51,7 @@ public final class FirkraagCunningInstigator extends CardImpl {
this.addAbility(HasteAbility.getInstance());
// Whenever one or more Dragons you control attack an opponent, goad target creature that player controls.
Ability abilityGoad = new AttacksWithCreaturesTriggeredAbility(Zone.BATTLEFIELD, new GoadTargetEffect(), 1, filterDragons, true);
Ability abilityGoad = new AttacksPlayerWithCreaturesTriggeredAbility(new GoadTargetEffect(), 1, filterDragons, SetTargetPointer.PLAYER, true);
abilityGoad.addTarget(new TargetPermanent(new FilterCreaturePermanent("target creature that player controls")));
abilityGoad.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
this.addAbility(abilityGoad);
@ -61,7 +60,7 @@ public final class FirkraagCunningInstigator extends CardImpl {
Ability ability = new DealsDamageToAPlayerAllTriggeredAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance())
.setText("you put a +1/+1 counter on {this}"),
filter, false, SetTargetPointer.NONE,
filterHadToAttack, false, SetTargetPointer.NONE,
true, false, TargetController.OPPONENT
).setTriggerPhrase("Whenever a creature deals combat damage to one of your opponents, " +
"if that creature had to attack this combat, ");

View file

@ -2,7 +2,7 @@ package mage.cards.g;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
@ -17,9 +17,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.target.common.TargetCreaturePermanent;
import mage.target.TargetPermanent;
import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import java.util.UUID;
@ -29,16 +30,17 @@ import java.util.UUID;
*/
public final class GornogTheRedReaper extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.WARRIOR, "Attacking Warriors");
private static final FilterPermanent filter2 = new FilterPermanent(SubType.COWARD, "Cowards your opponents control");
private static final FilterPermanent filterAttackWarrior = new FilterCreaturePermanent(SubType.WARRIOR, "Attacking Warriors");
private static final FilterPermanent filterWarrior = new FilterControlledPermanent(SubType.WARRIOR, "Warriors you control");
private static final FilterPermanent filterCoward = new FilterPermanent(SubType.COWARD, "Cowards your opponents control");
static {
filter.add(AttackingPredicate.instance);
filter2.add(TargetController.OPPONENT.getControllerPredicate());
filterAttackWarrior.add(AttackingPredicate.instance);
filterCoward.add(TargetController.OPPONENT.getControllerPredicate());
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter2, null);
private static final Hint hint = new ValueHint(filter2.getMessage(), xValue);
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filterCoward, null);
private static final Hint hint = new ValueHint(filterCoward.getMessage(), xValue);
public GornogTheRedReaper(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
@ -56,16 +58,16 @@ public final class GornogTheRedReaper extends CardImpl {
this.addAbility(new SimpleStaticAbility(new CowardsCantBlockWarriorsEffect()));
// Whenever one or more Warriors you control attack a player, target creature that player controls becomes a Coward.
Ability ability = new AttacksWithCreaturesTriggeredAbility(Zone.BATTLEFIELD,
new BecomesCreatureTypeTargetEffect(Duration.EndOfGame, SubType.COWARD),
1, filter, true);
ability.addTarget(new TargetCreaturePermanent());
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(
new BecomesCreatureTypeTargetEffect(Duration.EndOfGame, SubType.COWARD).setText("target creature that player controls becomes a Coward"),
filterWarrior, SetTargetPointer.PLAYER);
ability.addTarget(new TargetPermanent());
ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
this.addAbility(ability);
// Attacking Warriors you control get +X/+0, where X is the number of Cowards your opponents control.
this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(
xValue, StaticValue.get(0), Duration.WhileOnBattlefield, filter, false
xValue, StaticValue.get(0), Duration.WhileOnBattlefield, filterAttackWarrior, false
)).addHint(hint));
}

View file

@ -1,18 +1,14 @@
package mage.cards.h;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.PutCards;
import mage.constants.SetTargetPointer;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import java.util.UUID;
@ -27,7 +23,9 @@ public final class HornOfTheMark extends CardImpl {
this.supertype.add(SuperType.LEGENDARY);
// Whenever two or more creatures you control attack a player, look at the top five cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
this.addAbility(new HornOfTheMarkTriggeredAbility());
this.addAbility(new AttacksPlayerWithCreaturesTriggeredAbility(
new LookLibraryAndPickControllerEffect(5, 1, StaticFilters.FILTER_CARD_CREATURE_A, PutCards.HAND, PutCards.BOTTOM_RANDOM),
2, StaticFilters.FILTER_CONTROLLED_CREATURES, SetTargetPointer.NONE, false));
}
private HornOfTheMark(final HornOfTheMark card) {
@ -39,39 +37,3 @@ public final class HornOfTheMark extends CardImpl {
return new HornOfTheMark(this);
}
}
class HornOfTheMarkTriggeredAbility extends TriggeredAbilityImpl {
HornOfTheMarkTriggeredAbility() {
super(Zone.BATTLEFIELD, new LookLibraryAndPickControllerEffect(
5, 1, StaticFilters.FILTER_CARD_CREATURE_A, PutCards.HAND, PutCards.BOTTOM_RANDOM
));
this.setTriggerPhrase("Whenever two or more creatures you control attack a player, ");
}
private HornOfTheMarkTriggeredAbility(final HornOfTheMarkTriggeredAbility ability) {
super(ability);
}
@Override
public HornOfTheMarkTriggeredAbility copy() {
return new HornOfTheMarkTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
DefenderAttackedEvent dEvent = ((DefenderAttackedEvent) event);
return game.getPlayer(dEvent.getTargetId()) != null
&& dEvent
.getAttackers(game)
.stream()
.map(Controllable::getControllerId)
.filter(this::isControlledBy)
.count() >= 2;
}
}

View file

@ -1,22 +1,20 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.effects.common.*;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import mage.target.targetpointer.FixedTarget;
import java.util.Set;
@ -26,6 +24,7 @@ import java.util.UUID;
* @author TheElk801
*/
public final class KarazikarTheEyeTyrant extends CardImpl {
FilterPermanent filter = new FilterCreaturePermanent("creature that player controls");
public KarazikarTheEyeTyrant(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}");
@ -36,7 +35,11 @@ public final class KarazikarTheEyeTyrant extends CardImpl {
this.toughness = new MageInt(5);
// Whenever you attack a player, tap target creature that player controls and goad it.
this.addAbility(new KarazikarTheEyeTyrantFirstTriggeredAbility());
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(new TapTargetEffect(), SetTargetPointer.PLAYER);
ability.addEffect(new GoadTargetEffect().setText("goad it. " + GoadTargetEffect.goadReminderText).concatBy("and"));
ability.addTarget(new TargetPermanent(filter));
ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
this.addAbility(ability);
// Whenever an opponent attacks another one of your opponents, you and the attacking player each draw a card and lose 1 life.
this.addAbility(new KarazikarTheEyeTyrantSecondTriggeredAbility());
@ -52,49 +55,6 @@ public final class KarazikarTheEyeTyrant extends CardImpl {
}
}
class KarazikarTheEyeTyrantFirstTriggeredAbility extends TriggeredAbilityImpl {
KarazikarTheEyeTyrantFirstTriggeredAbility() {
super(Zone.BATTLEFIELD, new TapTargetEffect(), false);
this.addEffect(new GoadTargetEffect());
}
private KarazikarTheEyeTyrantFirstTriggeredAbility(final KarazikarTheEyeTyrantFirstTriggeredAbility ability) {
super(ability);
}
@Override
public KarazikarTheEyeTyrantFirstTriggeredAbility copy() {
return new KarazikarTheEyeTyrantFirstTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!isControlledBy(event.getPlayerId())) {
return false;
}
Player player = game.getPlayer(event.getTargetId());
if (player == null) {
return false;
}
FilterPermanent filter = new FilterCreaturePermanent("creature controlled by " + player.getName());
filter.add(new ControllerIdPredicate(player.getId()));
this.getTargets().clear();
this.addTarget(new TargetPermanent(filter));
return true;
}
@Override
public String getRule() {
return "Whenever you attack a player, tap target creature that player controls and goad it.";
}
}
class KarazikarTheEyeTyrantSecondTriggeredAbility extends TriggeredAbilityImpl {
KarazikarTheEyeTyrantSecondTriggeredAbility() {

View file

@ -1,19 +1,18 @@
package mage.cards.l;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.Ability;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterAttackingCreature;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.target.TargetPermanent;
import java.util.UUID;
@ -23,6 +22,12 @@ import java.util.UUID;
*/
public final class LandrovalHorizonWitness extends CardImpl {
private static final FilterPermanent filter
= new FilterAttackingCreature("attacking creature without flying");
static {
filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
}
public LandrovalHorizonWitness(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}");
@ -36,7 +41,11 @@ public final class LandrovalHorizonWitness extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// Whenever two or more creatures you control attack a player, target attacking creature without flying gains flying until end of turn.
this.addAbility(new LandrovalHorizonWitnessTriggeredAbility());
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(
new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn),
2, StaticFilters.FILTER_CONTROLLED_CREATURES, SetTargetPointer.NONE, false);
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}
private LandrovalHorizonWitness(final LandrovalHorizonWitness card) {
@ -48,40 +57,3 @@ public final class LandrovalHorizonWitness extends CardImpl {
return new LandrovalHorizonWitness(this);
}
}
class LandrovalHorizonWitnessTriggeredAbility extends TriggeredAbilityImpl {
private static final FilterPermanent filter
= new FilterAttackingCreature("attacking creature without flying");
static {
filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
}
LandrovalHorizonWitnessTriggeredAbility() {
super(Zone.BATTLEFIELD, new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn));
this.addTarget(new TargetPermanent(filter));
setTriggerPhrase("Whenever two or more creatures you control attack a player, ");
}
private LandrovalHorizonWitnessTriggeredAbility(final LandrovalHorizonWitnessTriggeredAbility ability) {
super(ability);
}
@Override
public LandrovalHorizonWitnessTriggeredAbility copy() {
return new LandrovalHorizonWitnessTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(event.getPlayerId())
&& game.getPlayer(event.getTargetId()) != null
&& ((DefenderAttackedEvent) event).getAttackers(game).size() >= 2;
}
}

View file

@ -1,17 +1,16 @@
package mage.cards.m;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.game.permanent.token.FoodToken;
import java.util.UUID;
@ -20,6 +19,7 @@ import java.util.UUID;
* @author TheElk801
*/
public final class MeriadocBrandybuck extends CardImpl {
FilterPermanent filter = new FilterControlledPermanent(SubType.HALFLING,"Halflings you control");
public MeriadocBrandybuck(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}");
@ -31,7 +31,7 @@ public final class MeriadocBrandybuck extends CardImpl {
this.toughness = new MageInt(2);
// Whenever one or more Halflings you control attack a player, create a Food token.
this.addAbility(new MeriadocBrandybuckTriggeredAbility());
this.addAbility(new AttacksPlayerWithCreaturesTriggeredAbility(new CreateTokenEffect(new FoodToken()), filter, SetTargetPointer.NONE));
}
private MeriadocBrandybuck(final MeriadocBrandybuck card) {
@ -43,35 +43,3 @@ public final class MeriadocBrandybuck extends CardImpl {
return new MeriadocBrandybuck(this);
}
}
class MeriadocBrandybuckTriggeredAbility extends TriggeredAbilityImpl {
MeriadocBrandybuckTriggeredAbility() {
super(Zone.BATTLEFIELD, new CreateTokenEffect(new FoodToken()));
setTriggerPhrase("Whenever one or more Halflings you control attack a player, ");
}
private MeriadocBrandybuckTriggeredAbility(final MeriadocBrandybuckTriggeredAbility ability) {
super(ability);
}
@Override
public MeriadocBrandybuckTriggeredAbility copy() {
return new MeriadocBrandybuckTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(event.getPlayerId())
&& game.getPlayer(event.getTargetId()) != null
&& ((DefenderAttackedEvent) event)
.getAttackers(game)
.stream()
.anyMatch(permanent -> permanent.hasSubtype(SubType.HALFLING, game));
}
}

View file

@ -2,7 +2,7 @@ package mage.cards.n;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
@ -12,11 +12,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
@ -33,11 +32,13 @@ import java.util.UUID;
*/
public final class NeyaliSunsVanguard extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("attacking tokens");
private static final FilterPermanent filterAttacking = new FilterPermanent("attacking tokens");
private static final FilterPermanent filterControlled = new FilterControlledPermanent("tokens you control");
static {
filter.add(AttackingPredicate.instance);
filter.add(TokenPredicate.TRUE);
filterAttacking.add(AttackingPredicate.instance);
filterAttacking.add(TokenPredicate.TRUE);
filterControlled.add(TokenPredicate.TRUE);
}
public NeyaliSunsVanguard(UUID ownerId, CardSetInfo setInfo) {
@ -51,11 +52,13 @@ public final class NeyaliSunsVanguard extends CardImpl {
// Attacking tokens you control have double strike.
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter
DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filterAttacking
)));
// Whenever one or more tokens you control attack a player, exile the top card of your library. During any turn you attacked with a token, you may play that card.
this.addAbility(new NeyaliSunsVanguardTriggeredAbility());
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(new NeyaliSunsVanguardEffect(), filterControlled, SetTargetPointer.NONE);
ability.addWatcher(new NeyaliSunsVanguardWatcher());
this.addAbility(ability);
}
private NeyaliSunsVanguard(final NeyaliSunsVanguard card) {
@ -68,49 +71,11 @@ public final class NeyaliSunsVanguard extends CardImpl {
}
}
class NeyaliSunsVanguardTriggeredAbility extends TriggeredAbilityImpl {
NeyaliSunsVanguardTriggeredAbility() {
super(Zone.BATTLEFIELD, new NeyaliSunsVanguardEffect());
this.addWatcher(new NeyaliSunsVanguardWatcher());
}
private NeyaliSunsVanguardTriggeredAbility(final NeyaliSunsVanguardTriggeredAbility ability) {
super(ability);
}
@Override
public NeyaliSunsVanguardTriggeredAbility copy() {
return new NeyaliSunsVanguardTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return game.getPlayer(event.getTargetId()) != null
&& ((DefenderAttackedEvent) event)
.getAttackers(game)
.stream()
.filter(PermanentToken.class::isInstance)
.map(Controllable::getControllerId)
.anyMatch(this::isControlledBy);
}
@Override
public String getRule() {
return "Whenever one or more tokens you control attack a player, exile the top card of your library. " +
"During any turn you attacked with a token, you may play that card.";
}
}
class NeyaliSunsVanguardEffect extends OneShotEffect {
NeyaliSunsVanguardEffect() {
super(Outcome.Benefit);
this.setText("exile the top card of your library. During any turn you attacked with a token, you may play that card.");
}
private NeyaliSunsVanguardEffect(final NeyaliSunsVanguardEffect effect) {

View file

@ -1,21 +1,21 @@
package mage.cards.o;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.Ability;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.MentorAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
@ -27,6 +27,12 @@ import java.util.UUID;
*/
public final class OrdruunMentor extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent("creature that's attacking that player");
static {
filter.add(OrdruunMentorPredicate.instance);
}
public OrdruunMentor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R/W}");
@ -39,7 +45,10 @@ public final class OrdruunMentor extends CardImpl {
this.addAbility(new MentorAbility());
// Whenever you attack a player, target creature that's attacking that player gains first strike until end of turn.
this.addAbility(new OrdruunMentorTriggeredAbility());
Ability ability = new AttacksPlayerWithCreaturesTriggeredAbility(
new GainAbilityTargetEffect(FirstStrikeAbility.getInstance()), SetTargetPointer.NONE);
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}
private OrdruunMentor(final OrdruunMentor card) {
@ -51,54 +60,13 @@ public final class OrdruunMentor extends CardImpl {
return new OrdruunMentor(this);
}
}
class OrdruunMentorTriggeredAbility extends TriggeredAbilityImpl {
private enum OrdruunMentorPredicate implements ObjectSourcePlayerPredicate<Permanent> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return CardUtil.getEffectValueFromAbility(
input.getSource(), "playerAttacked", UUID.class
)
.filter(uuid -> uuid.equals(game.getCombat().getDefenderId(input.getObject().getId())))
.isPresent();
}
}
private static final FilterPermanent filter = new FilterCreaturePermanent("creature that's attacking that player");
static {
filter.add(OrdruunMentorPredicate.instance);
}
OrdruunMentorTriggeredAbility() {
super(Zone.BATTLEFIELD, new GainAbilityTargetEffect(FirstStrikeAbility.getInstance()).setText(""));
this.setTriggerPhrase("Whenever you attack a player, ");
this.addTarget(new TargetPermanent(filter));
}
private OrdruunMentorTriggeredAbility(final OrdruunMentorTriggeredAbility ability) {
super(ability);
}
enum OrdruunMentorPredicate implements ObjectSourcePlayerPredicate<Permanent> {
instance;
@Override
public OrdruunMentorTriggeredAbility copy() {
return new OrdruunMentorTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!isControlledBy(event.getPlayerId()) || game.getPlayer(event.getTargetId()) == null) {
return false;
}
this.getEffects().setValue("playerAttacked", event.getTargetId());
return true;
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return CardUtil.getEffectValueFromAbility(input.getSource(), "playerAttacked", UUID.class)
.filter(uuid -> uuid.equals(game.getCombat().getDefenderId(input.getObject().getId())))
.isPresent();
}
}

View file

@ -2,7 +2,7 @@ package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksPlayerWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
@ -13,9 +13,7 @@ import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.GlimmerToken;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -47,7 +45,7 @@ public final class SoaringLightbringer extends CardImpl {
)));
// Whenever you attack a player, create a 1/1 white Glimmer enchantment creature token that's tapped and attacking that player.
this.addAbility(new SoaringLightbringerTriggeredAbility());
this.addAbility(new AttacksPlayerWithCreaturesTriggeredAbility(new SoaringLightbringerEffect(), SetTargetPointer.PLAYER));
}
private SoaringLightbringer(final SoaringLightbringer card) {
@ -60,37 +58,6 @@ public final class SoaringLightbringer extends CardImpl {
}
}
class SoaringLightbringerTriggeredAbility extends TriggeredAbilityImpl {
SoaringLightbringerTriggeredAbility() {
super(Zone.BATTLEFIELD, new SoaringLightbringerEffect());
setTriggerPhrase("Whenever you attack a player, ");
}
private SoaringLightbringerTriggeredAbility(final SoaringLightbringerTriggeredAbility ability) {
super(ability);
}
@Override
public SoaringLightbringerTriggeredAbility copy() {
return new SoaringLightbringerTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!isControlledBy(event.getPlayerId()) || game.getPlayer(event.getTargetId()) == null) {
return false;
}
this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
return true;
}
}
class SoaringLightbringerEffect extends OneShotEffect {
SoaringLightbringerEffect() {

View file

@ -93,6 +93,7 @@ public final class BloomburrowCommander extends ExpansionSet {
cards.add(new SetCardInfo("Devilish Valet", 195, Rarity.RARE, mage.cards.d.DevilishValet.class));
cards.add(new SetCardInfo("Domri, Anarch of Bolas", 98, Rarity.RARE, mage.cards.d.DomriAnarchOfBolas.class));
cards.add(new SetCardInfo("Dusk // Dawn", 138, Rarity.RARE, mage.cards.d.DuskDawn.class));
cards.add(new SetCardInfo("Echoing Assault", 24, Rarity.RARE, mage.cards.e.EchoingAssault.class));
cards.add(new SetCardInfo("Elspeth, Sun's Champion", 97, Rarity.MYTHIC, mage.cards.e.ElspethSunsChampion.class));
cards.add(new SetCardInfo("End-Raze Forerunners", 214, Rarity.RARE, mage.cards.e.EndRazeForerunners.class));
cards.add(new SetCardInfo("Esika's Chariot", 215, Rarity.RARE, mage.cards.e.EsikasChariot.class));

View file

@ -0,0 +1,86 @@
package org.mage.test.cards.single.dsc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4Players;
/**
* @author notgreat
*/
public class SoaringLightbringerTest extends CardTestCommander4Players {
@Test
public void test_AttacksDoubled() {
addCard(Zone.BATTLEFIELD, playerA, "Soaring Lightbringer");
addCard(Zone.BATTLEFIELD, playerA, "Memnite");
attack(1, playerA, "Soaring Lightbringer", playerB);
attack(1, playerA, "Memnite", playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertTappedCount("Glimmer Token", true, 1);
assertLife(playerB, 20 - 4 - 1 - 1);
}
@Test
public void test_AttacksTwo() {
addCard(Zone.BATTLEFIELD, playerA, "Soaring Lightbringer");
addCard(Zone.BATTLEFIELD, playerA, "Memnite");
attack(1, playerA, "Soaring Lightbringer", playerB);
attack(1, playerA, "Memnite", playerD);
setChoice(playerA, "Whenever"); // Order triggers
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertTappedCount("Glimmer Token", true, 2);
assertLife(playerB, 20 - 4 - 1);
assertLife(playerD, 20 - 1 - 1);
}
@Test
public void test_AttacksPlaneswalker() {
addCard(Zone.BATTLEFIELD, playerD, "Soaring Lightbringer");
addCard(Zone.BATTLEFIELD, playerD, "Memnite");
addCard(Zone.HAND, playerA, "Nissa Revane");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nissa Revane");
attack(2, playerD, "Memnite", "Nissa Revane");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_COMBAT);
execute();
assertPermanentCount(playerD, "Glimmer Token", 0);
assertCounterCount("Nissa Revane", CounterType.LOYALTY, 1);
}
@Test
public void test_AttacksEnters() {
addCard(Zone.BATTLEFIELD, playerA, "Soaring Lightbringer");
addCard(Zone.BATTLEFIELD, playerA, "Falconer Adept");
attack(1, playerA, "Falconer Adept", playerB);
setChoice(playerA, "Whenever"); // Order triggers
addTarget(playerA, playerC);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertTappedCount("Glimmer Token", true, 1);
assertTappedCount("Bird Token", true, 1);
assertLife(playerB, 20 - 2 - 1);
assertLife(playerC, 20 - 1);
}
}

View file

@ -0,0 +1,107 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* based heavily on AttacksWithCreaturesTriggeredAbility
* @author notgreat
*/
public class AttacksPlayerWithCreaturesTriggeredAbility extends TriggeredAbilityImpl {
private final FilterPermanent filter;
private final int minAttackers;
private final boolean onlyOpponents;
private final SetTargetPointer setTargetPointer;
public AttacksPlayerWithCreaturesTriggeredAbility(Effect effect, SetTargetPointer setTargetPointer) {
this(effect, StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED, setTargetPointer);
}
public AttacksPlayerWithCreaturesTriggeredAbility(Effect effect, FilterPermanent filter, SetTargetPointer setTargetPointer) {
this(effect, 1, filter, setTargetPointer, false);
}
public AttacksPlayerWithCreaturesTriggeredAbility(Effect effect, int minAttackers, FilterPermanent filter, SetTargetPointer setTargetPointer, boolean onlyOpponents) {
this(Zone.BATTLEFIELD, effect, minAttackers, filter, setTargetPointer, onlyOpponents, false);
}
public AttacksPlayerWithCreaturesTriggeredAbility(Zone zone, Effect effect, int minAttackers, FilterPermanent filter, SetTargetPointer setTargetPointer, boolean onlyOpponents, boolean optional) {
super(zone, effect, optional);
this.filter = filter;
this.minAttackers = minAttackers;
this.onlyOpponents = onlyOpponents;
this.setTargetPointer = setTargetPointer;
if (minAttackers == 1 && StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED.equals(filter) && !onlyOpponents) {
setTriggerPhrase("Whenever you attack a player, ");
} else {
setTriggerPhrase("Whenever " + CardUtil.numberToText(minAttackers) + " or more " + filter.getMessage() +
" attack " + (onlyOpponents ? "an opponent" : "a player") + ", ");
}
}
protected AttacksPlayerWithCreaturesTriggeredAbility(final AttacksPlayerWithCreaturesTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.minAttackers = ability.minAttackers;
this.onlyOpponents = ability.onlyOpponents;
this.setTargetPointer = ability.setTargetPointer;
}
@Override
public AttacksPlayerWithCreaturesTriggeredAbility copy() {
return new AttacksPlayerWithCreaturesTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player player = game.getPlayer(getControllerId());
UUID attackedId = event.getTargetId();
if (player == null || game.getPlayer(attackedId) == null) {
return false;
}
DefenderAttackedEvent attackedEvent = (DefenderAttackedEvent) event;
List<Permanent> attackers = attackedEvent.getAttackers(game).stream()
.filter(permanent -> filter.match(permanent, controllerId, this, game))
.collect(Collectors.toList());
if (attackers.size() < minAttackers || (onlyOpponents && !game.isOpponent(player, attackedId))) {
return false;
}
switch (setTargetPointer){
case NONE:
break;
case PLAYER:
getEffects().setTargetPointer(new FixedTarget(attackedId));
break;
case PERMANENT:
getEffects().setTargetPointer(new FixedTargets(new ArrayList<>(attackers), game));
break;
default:
throw new UnsupportedOperationException("Unexpected setTargetPointer in AttacksPlayerWithCreaturesTriggeredAbility: " + setTargetPointer);
}
this.getEffects().setValue("playerAttacked",attackedId);
return true;
}
}

View file

@ -25,6 +25,7 @@ public class GoadTargetEffect extends ContinuousEffectImpl {
* turn of the controller of that spell or ability, that creature attacks
* each combat if able and attacks a player other than that player if able.
*/
public static String goadReminderText = "<i>(Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)</i>";
public GoadTargetEffect() {
this(Duration.UntilYourNextTurn);
}
@ -74,6 +75,6 @@ public class GoadTargetEffect extends ContinuousEffectImpl {
return staticText;
}
return "goad " + getTargetPointer().describeTargets(mode.getTargets(), "that creature")
+ ". <i>(Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)</i>";
+ ". "+goadReminderText;
}
}