Fix auto-choose targets for activated abilities and spells (#13036)

* rework Armory Automaton

* remove redundant "setRequired(false)" from effects that separate into piles

* replace setRequired(false) with minTargets 0

* remove setRequired(false) where minTargets already 0

* remove setRequired(false) where preceded by chooseUse

* Revert "Player auto-choose respects required targets (#10557)"

This reverts commit fb8424556e.
This commit is contained in:
xenohedron 2024-10-26 20:23:50 -04:00 committed by GitHub
parent 737e67963d
commit 5070f8bef7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 32 additions and 83 deletions

View file

@ -681,7 +681,7 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
UUID responseId = required ? target.tryToAutoChoose(abilityControllerId, source, game) : null;
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
// responseId is null if a choice couldn't be automatically made
if (responseId == null) {
@ -780,7 +780,7 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
UUID responseId = required ? target.tryToAutoChoose(abilityControllerId, source, game) : null;
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
// responseId is null if a choice couldn't be automatically made
if (responseId == null) {
@ -877,8 +877,7 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
UUID responseId = required ? target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets)
: null;
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets);
if (responseId == null) {
Map<String, Serializable> options = getOptions(target, null);
@ -958,8 +957,7 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
UUID responseId = required ? target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets)
: null;
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets);
if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
@ -1041,7 +1039,7 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
UUID responseId = required ? target.tryToAutoChoose(abilityControllerId, source, game) : null;
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
// responseId is null if a choice couldn't be automatically made
if (responseId == null) {

View file

@ -10,25 +10,23 @@ import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterArtifactPermanent;
import mage.filter.common.FilterEquipmentPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardIdPredicate;
import mage.filter.predicate.permanent.AttachedToPredicate;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author spjspj
* @author xenohedron
*/
public final class ArmoryAutomaton extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("Equipment");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public ArmoryAutomaton(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}");
@ -36,8 +34,10 @@ public final class ArmoryAutomaton extends CardImpl {
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Whenever Armory Automaton enters the battlefield or attacks, attach any number of target Equipment to it.
this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ArmoryAutomatonEffect()));
// Whenever Armory Automaton enters or attacks, you may attach any number of target Equipment to it.
Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ArmoryAutomatonEffect(), true);
ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, filter));
this.addAbility(ability);
}
private ArmoryAutomaton(final ArmoryAutomaton card) {
@ -52,12 +52,6 @@ public final class ArmoryAutomaton extends CardImpl {
class ArmoryAutomatonEffect extends OneShotEffect {
private static final FilterArtifactPermanent filter = new FilterArtifactPermanent("Equipment");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
ArmoryAutomatonEffect() {
super(Outcome.Benefit);
this.staticText = "attach any number of target Equipment to it";
@ -74,38 +68,16 @@ class ArmoryAutomatonEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Permanent sourcePermanent = game.getPermanent(source.getSourceId());
if (player != null && sourcePermanent != null) {
// dynamic filter (can't selects own attaches and can't selects twice)
FilterPermanent currentFilter = new FilterEquipmentPermanent();
FilterPermanent filterSourceId = new FilterPermanent();
filterSourceId.add(new CardIdPredicate(source.getSourceId()));
currentFilter.add(Predicates.not(new AttachedToPredicate(filterSourceId)));
int countBattlefield = game.getBattlefield().getActivePermanents(currentFilter, source.getControllerId(), source, game).size();
while (player.canRespond() && countBattlefield > 0 && player.chooseUse(Outcome.Benefit, "Select and attach a target Equipment?", source, game)) {
Target targetEquipment = new TargetPermanent(currentFilter);
targetEquipment.setRequired(false);
if (player.choose(Outcome.Benefit, targetEquipment, source, game) && targetEquipment.getFirstTarget() != null) {
currentFilter.add(Predicates.not(new PermanentIdPredicate(targetEquipment.getFirstTarget()))); // exclude selected for next time
Permanent aura = game.getPermanent(targetEquipment.getFirstTarget());
if (aura != null) {
Permanent attachedTo = game.getPermanent(aura.getAttachedTo());
if (attachedTo != null) {
attachedTo.removeAttachment(aura.getId(), source, game);
}
sourcePermanent.addAttachment(aura.getId(), source, game);
}
} else {
break;
}
countBattlefield = game.getBattlefield().getActivePermanents(currentFilter, source.getControllerId(), source, game).size();
}
return true;
Permanent creature = source.getSourcePermanentIfItStillExists(game);
if (creature == null) {
return false;
}
return false;
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
Permanent equipment = game.getPermanent(targetId);
if (equipment != null) {
creature.addAttachment(equipment.getId(), source, game);
}
}
return true;
}
}

View file

@ -77,7 +77,6 @@ class BrilliantUltimatumEffect extends OneShotEffect {
Player opponent = game.getPlayer(targetOpponent.getFirstTarget());
if (opponent != null) {
TargetCard target = new TargetCard(0, pile2.size(), Zone.EXILED, new FilterCard("cards to put in the first pile"));
target.setRequired(false);
Cards pile1 = new CardsImpl();
List<Card> pileOne = new ArrayList<>();
List<Card> pileTwo = new ArrayList<>();

View file

@ -69,7 +69,6 @@ class CreditVoucherEffect extends OneShotEffect {
if (controller != null && sourceObject != null) {
FilterCard filter = new FilterCard("card in your hand to shuffle away");
TargetCardInHand target = new TargetCardInHand(0, controller.getHand().size(), filter);
target.setRequired(false);
int amountShuffled = 0;
if (target.canChoose(source.getControllerId(), source, game) && target.choose(Outcome.Neutral, source.getControllerId(), source.getSourceId(), source, game)) {
if (!target.getTargets().isEmpty()) {

View file

@ -70,7 +70,6 @@ class DoOrDieEffect extends OneShotEffect {
filter.add(new ControllerIdPredicate(targetPlayer.getId()));
TargetCreaturePermanent creatures = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true);
List<Permanent> pile1 = new ArrayList<>();
creatures.setRequired(false);
if (player.choose(Outcome.Neutral, creatures, source, game)) {
List<UUID> targets = creatures.getTargets();
for (UUID targetId : targets) {

View file

@ -75,7 +75,6 @@ class FightOrFlightEffect extends OneShotEffect {
filter.add(new ControllerIdPredicate(targetPlayer.getId()));
TargetCreaturePermanent creatures = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true);
List<Permanent> pile1 = new ArrayList<>();
creatures.setRequired(false);
if (player.choose(Outcome.Neutral, creatures, source, game)) {
List<UUID> targets = creatures.getTargets();
for (UUID targetId : targets) {

View file

@ -1,4 +1,3 @@
package mage.cards.g;
import mage.MageInt;
@ -76,8 +75,7 @@ class GenesisHydraPutOntoBattlefieldEffect extends OneShotEffect {
FilterCard filter = new FilterPermanentCard("a nonland permanent card with mana value " + count + " or less to put onto the battlefield");
filter.add(Predicates.not(CardType.LAND.getPredicate()));
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, count + 1));
TargetCard target1 = new TargetCard(Zone.LIBRARY, filter);
target1.setRequired(false);
TargetCard target1 = new TargetCard(0, 1, Zone.LIBRARY, filter);
if (cards.count(filter, source.getSourceId(), source, game) > 0) {
if (controller.choose(Outcome.PutCardInPlay, cards, target1, source, game)) {
Card card = cards.get(target1.getFirstTarget(), game);

View file

@ -87,7 +87,6 @@ class KnickknackOuphePutOntoBattlefieldEffect extends OneShotEffect {
if (cards.count(filter, controller.getId(), source, game) > 0) {
TargetCard targetAuras = new TargetCard(0, count, Zone.LIBRARY, filter);
targetAuras.setRequired(false);
if (controller.choose(Outcome.PutCardInPlay, cards, targetAuras, source, game)) {
targetAuras.getTargets().stream().forEach(t -> {

View file

@ -86,7 +86,6 @@ class LilianaOfTheVeilEffect extends OneShotEffect {
filter.add(new ControllerIdPredicate(targetPlayer.getId()));
TargetPermanent target = new TargetPermanent(0, count, filter, true);
List<Permanent> pile1 = new ArrayList<>();
target.setRequired(false);
if (player.choose(Outcome.Neutral, target, source, game)) {
List<UUID> targets = target.getTargets();
for (UUID targetId : targets) {

View file

@ -83,7 +83,6 @@ class MercadianLiftEffect extends OneShotEffect {
if (target.canChoose(controller.getId(), source, game)
&& controller.chooseUse(Outcome.PutCardInPlay, "Put " + filter.getMessage() + " from your hand onto the battlefield?", source, game)
&& controller.choose(Outcome.PutCardInPlay, target, source, game)) {
target.setRequired(false);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
return controller.moveCards(card, Zone.BATTLEFIELD, source, game);

View file

@ -8,7 +8,6 @@ import mage.abilities.effects.common.InfoEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ColoredManaSymbol;
import mage.filter.common.FilterCreatureSpell;
import mage.game.permanent.token.KithkinSoldierToken;
import mage.target.TargetSpell;
@ -24,15 +23,13 @@ public final class RepelIntruders extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W/U}");
// Create two 1/1 white Kithkin Soldier creature tokens if {W} was spent to cast Repel Intruders. Counter up to one target creature spell if {U} was spent to cast Repel Intruders.
TargetSpell target = new TargetSpell(0, 1, new FilterCreatureSpell());
target.setRequired(false);
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(
new CreateTokenEffect(new KithkinSoldierToken(), 2),
ManaWasSpentCondition.WHITE, "Create two 1/1 white Kithkin Soldier creature tokens if {W} was spent to cast this spell"));
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(
new CounterTargetEffect(),
ManaWasSpentCondition.BLUE, "Counter up to one target creature spell if {U} was spent to cast this spell"));
this.getSpellAbility().addTarget(target);
this.getSpellAbility().addTarget(new TargetSpell(0, 1, new FilterCreatureSpell()));
this.getSpellAbility().addEffect(new InfoEffect("<i>(Do both if {W}{U} was spent.)</i>"));
}
@ -46,4 +43,3 @@ public final class RepelIntruders extends CardImpl {
return new RepelIntruders(this);
}
}

View file

@ -74,8 +74,7 @@ class ReversalOfFortuneEffect extends OneShotEffect {
opponent.revealCards("Reveal", revealedCards, game);
//You may copy an instant or sorcery card in it
TargetCard target = new TargetCard(1, Zone.HAND, new FilterInstantOrSorceryCard());
target.setRequired(false);
TargetCard target = new TargetCard(0, 1, Zone.HAND, new FilterInstantOrSorceryCard());
if (controller.choose(Outcome.PlayForFree, revealedCards, target, source, game)) {
Card card = revealedCards.get(target.getFirstTarget(), game);
//If you do, you may cast the copy without paying its mana cost

View file

@ -63,7 +63,6 @@ class ScrollRackEffect extends OneShotEffect {
if (controller != null && sourceObject != null) {
FilterCard filter = new FilterCard("card in your hand to exile");
TargetCardInHand target = new TargetCardInHand(0, controller.getHand().size(), filter);
target.setRequired(false);
int amountExiled = 0;
if (target.canChoose(source.getControllerId(), source, game) && target.choose(Outcome.Neutral, source.getControllerId(), source.getSourceId(), source, game)) {
if (!target.getTargets().isEmpty()) {

View file

@ -76,9 +76,8 @@ class SpectersShriekEffect extends OneShotEffect {
+ player.getName() + "'s hand?", source, game)) {
return false;
}
TargetCard target = new TargetCard(Zone.HAND, new FilterNonlandCard());
TargetCard target = new TargetCard(0, 1, Zone.HAND, new FilterNonlandCard());
target.withNotTarget(true);
target.setRequired(false);
if (!controller.chooseTarget(Outcome.Benefit, player.getHand(), target, source, game)) {
return false;
}

View file

@ -79,7 +79,6 @@ class StandOrFallEffect extends OneShotEffect {
FilterCreaturePermanent opponentFilter = new FilterCreaturePermanent();
opponentFilter.add(new ControllerIdPredicate(oppId));
TargetCreaturePermanent creatures = new TargetCreaturePermanent(0, Integer.MAX_VALUE, opponentFilter, true);
creatures.setRequired(false);
List<Permanent> pile1 = new ArrayList<>();
if (player.choose(Outcome.Neutral, creatures, source, game)) {
List<UUID> targets = creatures.getTargets();

View file

@ -80,7 +80,6 @@ class TruthOrTaleEffect extends OneShotEffect {
if (opponent != null) {
TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, new FilterCard("cards to put in the first pile"));
List<Card> pile1 = new ArrayList<>();
target.setRequired(false);
if (controller.choose(Outcome.Neutral, cards, target, source, game)) {
List<UUID> targets = target.getTargets();
for (UUID targetId : targets) {

View file

@ -1,4 +1,3 @@
package mage.cards.v;
import java.util.UUID;
@ -76,8 +75,7 @@ class VendilionCliqueEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = source.getSourceObject(game);
if (player != null && controller != null && sourceObject != null) {
TargetCard targetCard = new TargetCard(Zone.ALL, new FilterNonlandCard());
targetCard.setRequired(false);
TargetCard targetCard = new TargetCard(0, 1, Zone.ALL, new FilterNonlandCard());
if (controller.choose(Outcome.Discard, player.getHand(), targetCard, source, game)) {
Card card = game.getCard(targetCard.getFirstTarget());
if (card != null) {

View file

@ -103,7 +103,6 @@ class WhimsOfTheFateEffect extends OneShotEffect {
} else {
target = new TargetSecondPilePermanent(playerPiles.get(1), filter);
}
target.setRequired(false);
currentPlayer.chooseTarget(outcome, target, source, game);
StringBuilder message = new StringBuilder(currentPlayer.getLogName()).append(" pile ").append(i).append(": ");
if (target.getTargets().isEmpty()) {

View file

@ -370,7 +370,7 @@ public abstract class TargetImpl implements Target {
}
} else {
// Try to autochoosen
UUID autoChosenId = required ? tryToAutoChoose(playerId, source, game) : null;
UUID autoChosenId = tryToAutoChoose(playerId, source, game);
if (autoChosenId != null) {
addTarget(autoChosenId, source, game);
} else if (!targetController.chooseTarget(outcome, this, source, game)) { // If couldn't autochoose ask player