More targets (#13776)

This commit is contained in:
ssk97 2025-06-21 06:08:51 -07:00 committed by GitHub
parent 2fedf9c211
commit a935045e1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 101 additions and 177 deletions

View file

@ -42,10 +42,13 @@ public final class AlaniaDivergentStorm extends CardImpl {
// Whenever you cast a spell, if it's the first instant spell, the first sorcery spell, or the first Otter
// spell other than Alania you've cast this turn, you may have target opponent draw a card. If you do, copy
// that spell. You may choose new targets for the copy.
this.addAbility(new SpellCastControllerTriggeredAbility(
new DoIfCostPaid(new CopyTargetStackObjectEffect(true), new AlaniaDivergentStormCost()),
Ability ability = new SpellCastControllerTriggeredAbility(
new DoIfCostPaid(new CopyTargetStackObjectEffect(true).setText("copy that spell. You may choose new targets for the copy")
, new AlaniaDivergentStormCost()),
null, false, SetTargetPointer.SPELL
).withInterveningIf(AlaniaDivergentStormCondition.instance), new AlaniaDivergentStormWatcher());
).withInterveningIf(AlaniaDivergentStormCondition.instance);
ability.addTarget(new TargetOpponent());
this.addAbility(ability, new AlaniaDivergentStormWatcher());
}
private AlaniaDivergentStorm(final AlaniaDivergentStorm card) {
@ -58,12 +61,10 @@ public final class AlaniaDivergentStorm extends CardImpl {
}
}
// Based on MarathWillOfTheWildRemoveCountersCost
class AlaniaDivergentStormCost extends CostImpl {
AlaniaDivergentStormCost() {
this.text = "have target opponent draw a card";
this.addTarget(new TargetOpponent());
}
private AlaniaDivergentStormCost(AlaniaDivergentStormCost cost) {
@ -73,32 +74,18 @@ class AlaniaDivergentStormCost extends CostImpl {
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
Player player = game.getPlayer(controllerId);
if (player == null) {
return false;
}
for (UUID opponentID : game.getOpponents(controllerId)) {
Player opponent = game.getPlayer(opponentID);
if (opponent == null) {
continue;
}
if (opponent.canBeTargetedBy(source.getSourceObject(game), controllerId, source, game)) {
return true;
}
}
return false;
Player opponent = game.getPlayer(source.getFirstTarget());
return player != null && opponent != null;
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
this.getTargets().clearChosen();
paid = false;
if (this.getTargets().choose(Outcome.DrawCard, controllerId, source.getSourceId(), source, game)) {
Player opponent = game.getPlayer(this.getTargets().getFirstTarget());
Player opponent = game.getPlayer(source.getFirstTarget());
if (opponent == null || !opponent.canRespond()) {
return false;
}
paid = opponent.drawCards(1, source, game) > 0;
}
return paid;
}

View file

@ -11,16 +11,16 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public final class CyclopsGladiator extends CardImpl {
@ -36,6 +36,8 @@ public final class CyclopsGladiator extends CardImpl {
// Whenever Cyclops Gladiator attacks, you may have it deal damage equal to its power to target creature defending player controls.
// If you do, that creature deals damage equal to its power to Cyclops Gladiator.
Ability ability = new AttacksTriggeredAbility(new CyclopsGladiatorEffect(), true);
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE));
ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
this.addAbility(ability);
}
@ -63,24 +65,13 @@ class CyclopsGladiatorEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
UUID defenderId = game.getCombat().getDefenderId(source.getSourceId());
if (defenderId != null) {
FilterCreaturePermanent filter = new FilterCreaturePermanent("creature defending player controls");
filter.add(new ControllerIdPredicate(defenderId));
TargetPermanent target = new TargetPermanent(filter);
Player player = game.getPlayer(source.getControllerId());
if (target.canChoose(source.getControllerId(), source, game)) {
if (player != null && player.chooseTarget(Outcome.Detriment, target, source, game)) {
Permanent permanent = game.getPermanent(target.getFirstTarget());
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
Permanent cyclops = game.getPermanent(source.getSourceId());
if (permanent != null && cyclops != null) {
permanent.damage(cyclops.getPower().getValue(), cyclops.getId(), source, game, false, true);
cyclops.damage(permanent.getPower().getValue(), permanent.getId(), source, game, false, true);
return true;
}
}
}
}
return false;
}

View file

@ -1,25 +1,25 @@
package mage.cards.j;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.DiesThisOrAnotherTriggeredAbility;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil;
import java.util.UUID;
@ -27,6 +27,14 @@ import java.util.UUID;
* @author notgreat
*/
public final class JackdawSavior extends CardImpl {
private static final FilterControlledCreaturePermanent flyingFilter = new FilterControlledCreaturePermanent("creature you control with flying");
private static final FilterCard filterCard = new FilterCreatureCard("another target creature card with lesser mana value from your graveyard");
static {
flyingFilter.add(new AbilityPredicate(FlyingAbility.class));
filterCard.add(JackdawSaviorPredicate.instance);
}
public JackdawSavior(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
@ -39,7 +47,9 @@ public final class JackdawSavior extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// Whenever Jackdaw Savior or another creature you control with flying dies, return another target creature card with lesser mana value from your graveyard to the battlefield.
this.addAbility(new JackdawSaviorDiesThisOrAnotherTriggeredAbility());
Ability ability = new DiesThisOrAnotherTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), false, flyingFilter);
ability.addTarget(new TargetCardInYourGraveyard(filterCard));
this.addAbility(ability);
}
private JackdawSavior(final JackdawSavior card) {
@ -52,40 +62,14 @@ public final class JackdawSavior extends CardImpl {
}
}
class JackdawSaviorDiesThisOrAnotherTriggeredAbility extends DiesThisOrAnotherTriggeredAbility {
private static final FilterControlledCreaturePermanent flyingFilter = new FilterControlledCreaturePermanent("creature you control with flying");
static {
flyingFilter.add(new AbilityPredicate(FlyingAbility.class));
}
public JackdawSaviorDiesThisOrAnotherTriggeredAbility() {
super(new ReturnFromGraveyardToBattlefieldTargetEffect().setText(
"return another target creature card with lesser mana value from your graveyard to the battlefield"),
false, flyingFilter);
}
protected JackdawSaviorDiesThisOrAnotherTriggeredAbility(final JackdawSaviorDiesThisOrAnotherTriggeredAbility ability) {
super(ability);
}
enum JackdawSaviorPredicate implements ObjectSourcePlayerPredicate<MageObject> {
instance;
@Override
public JackdawSaviorDiesThisOrAnotherTriggeredAbility copy() {
return new JackdawSaviorDiesThisOrAnotherTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (super.checkTrigger(event, game)) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
FilterCard filter = new FilterCreatureCard();
filter.add(Predicates.not(new MageObjectReferencePredicate(zEvent.getTargetId(), game)));
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, zEvent.getTarget().getManaValue()));
filter.setMessage("target creature card other than "+zEvent.getTarget().getLogName()+" with mana value less than "+zEvent.getTarget().getManaValue());
this.getTargets().clear();
this.addTarget(new TargetCardInYourGraveyard(filter));
return true;
}
return false;
public boolean apply(ObjectSourcePlayer<MageObject> input, Game game) {
Permanent diedCreature = CardUtil.getEffectValueFromAbility(input.getSource(), "creatureDied", Permanent.class).orElse(null);
return diedCreature != null
&& !input.getObject().getId().equals(diedCreature.getId())
&& input.getObject().getManaValue() < diedCreature.getManaValue();
}
}

View file

@ -12,25 +12,25 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.filter.predicate.permanent.CrewedSourceThisTurnPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
import mage.util.functions.CopyApplier;
import mage.watchers.Watcher;
import java.util.*;
import java.util.stream.Collectors;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class MindlinkMech extends CardImpl {
public MindlinkMech(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}");
@ -60,9 +60,18 @@ public final class MindlinkMech extends CardImpl {
class MindlinkMechTriggeredAbility extends TriggeredAbilityImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent("nonlegendary creature that crewed it this turn");
static {
filter.add(CrewedSourceThisTurnPredicate.instance);
filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate()));
}
MindlinkMechTriggeredAbility() {
super(Zone.BATTLEFIELD, new MindlinkMechEffect());
this.addWatcher(new MindlinkMechWatcher());
this.addTarget(new TargetPermanent(filter));
this.setTriggerPhrase("Whenever {this} becomes crewed for the first time each turn");
}
private MindlinkMechTriggeredAbility(final MindlinkMechTriggeredAbility ability) {
@ -81,26 +90,13 @@ class MindlinkMechTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getSourceId().equals(getSourceId()) || !MindlinkMechWatcher.checkVehicle(this, game)) {
return false;
}
this.getTargets().clear();
this.addTarget(new TargetPermanent(MindlinkMechWatcher.makeFilter(this, game)));
return true;
}
@Override
public String getRule() {
return "Whenever {this} becomes crewed for the first time each turn, until end of turn, " +
"{this} becomes a copy of target nonlegendary creature that crewed it this turn, " +
"except it's 4/3, it's a Vehicle artifact in addition to its other types, and it has flying.";
return event.getSourceId().equals(getSourceId()) && MindlinkMechWatcher.checkVehicle(this, game);
}
}
class MindlinkMechWatcher extends Watcher {
private final Map<MageObjectReference, Integer> crewCount = new HashMap<>();
private final Map<MageObjectReference, Set<MageObjectReference>> crewMap = new HashMap<>();
private static final FilterPermanent invalidFilter = new FilterPermanent();
static {
@ -114,34 +110,16 @@ class MindlinkMechWatcher extends Watcher {
@Override
public void watch(GameEvent event, Game game) {
Permanent vehicle;
Permanent crewer;
switch (event.getType()) {
case VEHICLE_CREWED:
if (event.getType() == GameEvent.EventType.VEHICLE_CREWED) {
vehicle = game.getPermanent(event.getTargetId());
crewer = null;
break;
case CREWED_VEHICLE:
vehicle = game.getPermanent(event.getSourceId());
crewer = game.getPermanent(event.getTargetId());
break;
default:
return;
}
if (vehicle == null) {
return;
}
if (crewer == null) {
crewCount.compute(new MageObjectReference(vehicle, game), (m, i) -> i == null ? 1 : Integer.sum(i, 1));
return;
}
crewMap.computeIfAbsent(new MageObjectReference(vehicle, game), x -> new HashSet<>()).add(new MageObjectReference(crewer, game));
}
@Override
public void reset() {
super.reset();
crewCount.clear();
crewMap.clear();
}
public static boolean checkVehicle(Ability source, Game game) {
@ -151,34 +129,14 @@ class MindlinkMechWatcher extends Watcher {
.crewCount
.getOrDefault(new MageObjectReference(source), 0) < 2;
}
public static FilterPermanent makeFilter(Ability source, Game game) {
Set<MageObjectReferencePredicate> predicates = game
.getState()
.getWatcher(MindlinkMechWatcher.class)
.crewMap
.computeIfAbsent(new MageObjectReference(game.getPermanent(source.getSourceId()), game), x -> new HashSet<>())
.stream()
.filter(mor -> {
Permanent permanent = mor.getPermanent(game);
return permanent != null && !permanent.isLegendary(game) && permanent.isCreature(game);
}).map(MageObjectReferencePredicate::new)
.collect(Collectors.toSet());
if (predicates.isEmpty()) {
return invalidFilter;
}
FilterPermanent filterPermanent = new FilterPermanent(
"nonlegendary creature that crewed " + CardUtil.getSourceName(game, source) + " this turn"
);
filterPermanent.add(Predicates.or(predicates));
return filterPermanent;
}
}
class MindlinkMechEffect extends OneShotEffect {
MindlinkMechEffect() {
super(Outcome.Benefit);
this.setText("until end of turn, {this} becomes a copy of target nonlegendary creature that crewed it this turn, " +
"except it's 4/3, it's a Vehicle artifact in addition to its other types, and it has flying.");
}
private MindlinkMechEffect(final MindlinkMechEffect effect) {

View file

@ -7,11 +7,15 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.filter.FilterCard;
import mage.filter.common.FilterPermanentCard;
import mage.filter.predicate.card.OwnerIdPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInGraveyard;
import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster;
import java.util.UUID;
@ -20,6 +24,7 @@ import java.util.UUID;
* @author Xanderhall
*/
public final class NeyamShaiMurad extends CardImpl {
FilterCard filter = new FilterPermanentCard("permanent card from their graveyard");
public NeyamShaiMurad(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}");
@ -32,10 +37,11 @@ public final class NeyamShaiMurad extends CardImpl {
// Rogue Trader -- Whenever Neyam Shai Murad deals combat damage to a player, you may have that player return target permanent card from their graveyard to their hand.
// If you do, that player chooses a permanent card in your graveyard, then you put it onto the battlefield under your control.
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new NeyamShaiMuradEffect(), true, true)
.withFlavorWord("Rogue Trader")
); //TODO: The first should target when triggered, not on resolution
Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new NeyamShaiMuradEffect(), true, true)
.withFlavorWord("Rogue Trader");
ability.addTarget(new TargetCardInGraveyard(filter));
ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster(true));
this.addAbility(ability);
}
private NeyamShaiMurad(final NeyamShaiMurad card) {
@ -67,28 +73,26 @@ class NeyamShaiMuradEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
Object playerId = getValue("damagedPlayer");
Player controller = game.getPlayer(source.getControllerId());
Cards playerGraveyard = player.getGraveyard();
if (controller == null || !(playerId instanceof UUID)) {
return false;
}
Player player = game.getPlayer((UUID) playerId);
Cards cards = new CardsImpl(getTargetPointer().getTargets(game, source));
cards.retainZone(Zone.GRAVEYARD, game); //verify the target card is still in the graveyard
if (player == null || cards.isEmpty() || !player.moveCards(cards, Zone.HAND, source, game)) {
return false;
}
Cards controllerGraveyard = controller.getGraveyard();
if (player == null || controller == null || playerGraveyard.isEmpty()) {
FilterCard filter = new FilterPermanentCard("a permanent card in your graveyard");
filter.add(new OwnerIdPredicate(source.getControllerId()));
TargetCardInGraveyard target = new TargetCardInGraveyard(1, 1, filter, true);
if (!target.choose(Outcome.PutCreatureInPlay, player.getId(), source, game)) {
return false;
}
TargetCardInGraveyard target = new TargetCardInGraveyard(StaticFilters.FILTER_CARD_PERMANENT);
if (!controller.chooseTarget(Outcome.ReturnToHand, playerGraveyard, target, source, game)
|| !playerGraveyard.contains(target.getFirstTarget())
|| !player.moveCards(playerGraveyard.get(target.getFirstTarget(), game), Zone.HAND, source, game)) {
return false;
}
target.clearChosen();
target.withNotTarget(true);
if (!player.choose(Outcome.PutCreatureInPlay, controllerGraveyard, target, source, game)) {
return false;
}
return controllerGraveyard.contains(target.getFirstTarget()) && controller.moveCards(controllerGraveyard.get(target.getFirstTarget(), game), Zone.BATTLEFIELD, source, game);
}
}

View file

@ -33,8 +33,8 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
addTarget(playerA, playerB); // Who draws?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
@ -61,8 +61,8 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Stormcatch Mentor", true);
addTarget(playerA, playerB); // Who draws?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
@ -93,16 +93,16 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Frolicking Familiar", true);
addTarget(playerA, playerB); // Who draws?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Coruscation Mage", true);
setChoice(playerA, "No"); // Offspring?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blow Off Steam", true);
setChoice(playerA, "Whenever you cast an instant", 2); // Add Frolicking Familiar triggers first
setChoice(playerA, "Whenever you cast a noncreature"); // Add Coruscation Mage trigger
// Alania's trigger will add last
addTarget(playerA, playerB); // Who draws?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
addTarget(playerA, playerB);
setChoice(playerA, "No"); // Change target?
@ -137,8 +137,8 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Acrobatic Leap", true);
addTarget(playerA, playerB); // Who draws?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
addTarget(playerA, alania); // Target creature
setChoice(playerA, "No"); // Change target?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Recall", true);
@ -170,8 +170,8 @@ public class AlaniaDivergentStormTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, alania, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gift of the Fae", true);
addTarget(playerA, playerB); // Who draws?
setChoice(playerA, "Yes"); // Copy spell?
setChoice(playerA, "PlayerB"); // Who draws?
addTarget(playerA, alania); // Target creature
setChoice(playerA, "No"); // Change target?
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Maximize Velocity", true);