diff --git a/Mage.Client/src/main/java/mage/client/cards/CardArea.java b/Mage.Client/src/main/java/mage/client/cards/CardArea.java index d49afeb9b03..82583d73d94 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardArea.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardArea.java @@ -18,10 +18,8 @@ import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Comparator; +import java.util.*; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; /** @@ -258,7 +256,7 @@ public class CardArea extends JPanel implements CardEventProducer { this.reloaded = false; } - public void selectCards(List selected) { + public void selectCards(Set selected) { for (Component component : cardArea.getComponents()) { if (component instanceof MageCard) { MageCard mageCard = (MageCard) component; @@ -269,7 +267,7 @@ public class CardArea extends JPanel implements CardEventProducer { } } - public void markCards(List marked) { + public void markCards(Set marked) { for (Component component : cardArea.getComponents()) { if (component instanceof MageCard) { MageCard mageCard = (MageCard) component; diff --git a/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java index 34090e0ff8c..0b366daa334 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java @@ -100,11 +100,11 @@ cardArea.loadCards(showCards, bigCard, gameId); if (options != null) { if (options.containsKey("chosenTargets")) { - java.util.List chosenCards = (java.util.List) options.get("chosenTargets"); + java.util.Set chosenCards = (java.util.Set) options.get("chosenTargets"); cardArea.selectCards(chosenCards); } if (options.containsKey("possibleTargets")) { - java.util.List choosableCards = (java.util.List) options.get("possibleTargets"); + java.util.Set choosableCards = (java.util.Set) options.get("possibleTargets"); cardArea.markCards(choosableCards); } if (options.containsKey("queryType") && options.get("queryType") == QueryType.PICK_ABILITY) { diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 6b687a3c305..5c778dac428 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -216,17 +216,9 @@ public final class GamePanel extends javax.swing.JPanel { }); } - public List getPossibleTargets() { - if (options != null && options.containsKey("possibleTargets")) { - return (List) options.get("possibleTargets"); - } else { - return Collections.emptyList(); - } - } - public Set getChosenTargets() { if (options != null && options.containsKey("chosenTargets")) { - return new HashSet<>((List) options.get("chosenTargets")); + return (Set) options.get("chosenTargets"); } else { return Collections.emptySet(); } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index cd7b6569cf8..6a0fbc550b8 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -3,7 +3,6 @@ package mage.player.human; import mage.MageIdentifier; import mage.MageObject; import mage.abilities.*; -import mage.abilities.costs.VariableCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCost; @@ -697,7 +696,7 @@ public class HumanPlayer extends PlayerImpl { } // stop on completed, e.g. X=0 - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { return false; } @@ -729,7 +728,7 @@ public class HumanPlayer extends PlayerImpl { // continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards) } else { // manual choose - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); prepareForResponse(game); if (!isExecutingMacro()) { @@ -747,9 +746,9 @@ public class HumanPlayer extends PlayerImpl { continue; } - if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, game)) { + if (possibleTargets.contains(responseId)) { target.add(responseId, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { break; } } @@ -794,7 +793,7 @@ public class HumanPlayer extends PlayerImpl { // manual choice if (responseId == null) { - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); prepareForResponse(game); if (!isExecutingMacro()) { @@ -814,11 +813,9 @@ public class HumanPlayer extends PlayerImpl { // add new target if (possibleTargets.contains(responseId)) { - if (target.canTarget(abilityControllerId, responseId, source, game)) { - target.addTarget(responseId, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { - return true; - } + target.addTarget(responseId, source, game); + if (target.isChoiceCompleted(abilityControllerId, source, game, null)) { + return true; } } } else { @@ -864,13 +861,7 @@ public class HumanPlayer extends PlayerImpl { UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); while (canRespond()) { - - List possibleTargets = new ArrayList<>(); - for (UUID cardId : cards) { - if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { - possibleTargets.add(cardId); - } - } + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); int count = cards.count(target.getFilter(), abilityControllerId, source, game); @@ -880,7 +871,7 @@ public class HumanPlayer extends PlayerImpl { } // if nothing to choose then show dialog (user must see non selectable items and click on any of them) - // TODO: need research - is it used? + // TODO: need research - is it used (test player and AI player don't see empty dialogs)? if (required && possibleTargets.isEmpty()) { required = false; } @@ -894,7 +885,7 @@ public class HumanPlayer extends PlayerImpl { } else { // manual choose Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); } @@ -916,9 +907,9 @@ public class HumanPlayer extends PlayerImpl { continue; } - if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, cards, game)) { + if (possibleTargets.contains(responseId)) { target.add(responseId, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) { return true; } } @@ -962,12 +953,8 @@ public class HumanPlayer extends PlayerImpl { required = false; } - List possibleTargets = new ArrayList<>(); - for (UUID cardId : cards) { - if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { - possibleTargets.add(cardId); - } - } + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards); + // if nothing to choose then show dialog (user must see non-selectable items and click on any of them) if (possibleTargets.isEmpty()) { required = false; @@ -977,7 +964,7 @@ public class HumanPlayer extends PlayerImpl { if (responseId == null) { Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); @@ -995,9 +982,9 @@ public class HumanPlayer extends PlayerImpl { if (responseId != null) { if (target.contains(responseId)) { // if already included remove it target.remove(responseId); - } else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { + } else if (possibleTargets.contains(responseId)) { target.addTarget(responseId, source, game); - if (target.isChoiceCompleted(abilityControllerId, source, game)) { + if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) { return true; } } @@ -1054,9 +1041,9 @@ public class HumanPlayer extends PlayerImpl { // 1. Select targets // TODO: rework to use existing chooseTarget instead custom select? while (canRespond()) { - Set possibleTargetIds = target.possibleTargets(abilityControllerId, source, game); + Set possibleTargets = target.possibleTargets(abilityControllerId, source, game); boolean required = target.isRequired(source.getSourceId(), game); - if (possibleTargetIds.isEmpty() + if (possibleTargets.isEmpty() || target.getSize() >= target.getMinNumberOfTargets()) { required = false; } @@ -1065,12 +1052,6 @@ public class HumanPlayer extends PlayerImpl { // responseId is null if a choice couldn't be automatically made if (responseId == null) { - List possibleTargets = new ArrayList<>(); - for (UUID targetId : possibleTargetIds) { - if (target.canTarget(abilityControllerId, targetId, source, game)) { - possibleTargets.add(targetId); - } - } // if nothing to choose then show dialog (user must see non selectable items and click on any of them) if (required && possibleTargets.isEmpty()) { required = false; @@ -1078,7 +1059,7 @@ public class HumanPlayer extends PlayerImpl { // selected Map options = getOptions(target, null); - options.put("chosenTargets", (Serializable) target.getTargets()); + options.put("chosenTargets", new HashSet<>(target.getTargets())); if (!possibleTargets.isEmpty()) { options.put("possibleTargets", (Serializable) possibleTargets); } @@ -1087,7 +1068,7 @@ public class HumanPlayer extends PlayerImpl { if (!isExecutingMacro()) { String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters"; String message = target.getMessage(game) + String.format(multiType, amountTotal); - game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargetIds, required, options); + game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargets, required, options); } waitForResponse(game); @@ -1098,9 +1079,7 @@ public class HumanPlayer extends PlayerImpl { if (target.contains(responseId)) { // unselect target.remove(responseId); - } else if (possibleTargetIds.contains(responseId) - && target.canTarget(abilityControllerId, responseId, source, game) - && target.getSize() < amountTotal) { + } else if (possibleTargets.contains(responseId) && target.getSize() < amountTotal) { // select target.addTarget(responseId, source, game); } @@ -2094,32 +2073,33 @@ public class HumanPlayer extends PlayerImpl { if (!canCallFeedback(game)) { return; } - TargetAttackingCreature target = new TargetAttackingCreature(); - // TODO: add canRespond cycle? + // no need in cycle, cause parent selectBlockers used it already if (!canRespond()) { return; } UUID responseId = null; - prepareForResponse(game); if (!isExecutingMacro()) { // possible attackers to block - Set attackers = target.possibleTargets(playerId, null, game); + TargetAttackingCreature target = new TargetAttackingCreature(); Permanent blocker = game.getPermanent(blockerId); - Set possibleTargets = new HashSet<>(); - for (UUID attackerId : attackers) { + Set allAttackers = target.possibleTargets(playerId, null, game); + Set possibleAttackersToBlock = new HashSet<>(); + for (UUID attackerId : allAttackers) { CombatGroup group = game.getCombat().findGroup(attackerId); if (group != null && blocker != null && group.canBlock(blocker, game)) { - possibleTargets.add(attackerId); + possibleAttackersToBlock.add(attackerId); } } - if (possibleTargets.size() == 1) { - responseId = possibleTargets.stream().iterator().next(); + if (possibleAttackersToBlock.size() == 1) { + // auto-choice + responseId = possibleAttackersToBlock.stream().iterator().next(); } else { + prepareForResponse(game); game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)), - possibleTargets, false, getOptions(target, null)); + possibleAttackersToBlock, false, getOptions(target, null)); waitForResponse(game); } } @@ -2174,7 +2154,7 @@ public class HumanPlayer extends PlayerImpl { break; } - return xValue; + return xValue; } @Override diff --git a/Mage.Sets/src/mage/cards/a/AjanisChosen.java b/Mage.Sets/src/mage/cards/a/AjanisChosen.java index c84c947d8ee..534097ade4c 100644 --- a/Mage.Sets/src/mage/cards/a/AjanisChosen.java +++ b/Mage.Sets/src/mage/cards/a/AjanisChosen.java @@ -77,7 +77,7 @@ class AjanisChosenEffect extends OneShotEffect { Permanent oldCreature = game.getPermanent(enchantment.getAttachedTo()); if (oldCreature != null) { boolean canAttach = enchantment.getSpellAbility() == null - || (!enchantment.getSpellAbility().getTargets().isEmpty() && enchantment.getSpellAbility().getTargets().get(0).canTarget(tokenPermanent.getId(), game)); + || (!enchantment.getSpellAbility().getTargets().isEmpty() && enchantment.getSpellAbility().getTargets().get(0).canTarget(tokenPermanent.getId(), source, game)); if (canAttach && controller.chooseUse(Outcome.Neutral, "Attach " + enchantment.getName() + " to the token ?", source, game)) { if (oldCreature.removeAttachment(enchantment.getId(), source, game)) { tokenPermanent.addAttachment(enchantment.getId(), source, game); diff --git a/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java b/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java index 4e748752c45..ca2c720f483 100644 --- a/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java +++ b/Mage.Sets/src/mage/cards/a/AncientBrassDragon.java @@ -108,8 +108,8 @@ class AncientBrassDragonTarget extends TargetCardInGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, xValue, game); } diff --git a/Mage.Sets/src/mage/cards/a/AnuridScavenger.java b/Mage.Sets/src/mage/cards/a/AnuridScavenger.java index 9cbf5efe14d..9fbf8962508 100644 --- a/Mage.Sets/src/mage/cards/a/AnuridScavenger.java +++ b/Mage.Sets/src/mage/cards/a/AnuridScavenger.java @@ -84,7 +84,7 @@ class AnuridScavengerCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java b/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java index 5813da126bd..6c577cd75e5 100644 --- a/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java +++ b/Mage.Sets/src/mage/cards/a/AoTheDawnSky.java @@ -134,8 +134,8 @@ class AoTheDawnSkyTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 4, game); } diff --git a/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java b/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java index 4b4ace434dc..1c2b4b95b6b 100644 --- a/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java +++ b/Mage.Sets/src/mage/cards/a/ArdentDustspeaker.java @@ -79,7 +79,7 @@ class ArdentDustspeakerCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java b/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java index 7e720d5c3c2..9a905eee13e 100644 --- a/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java +++ b/Mage.Sets/src/mage/cards/b/BackFromTheBrink.java @@ -67,7 +67,7 @@ class BackFromTheBrinkCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BattleForBretagard.java b/Mage.Sets/src/mage/cards/b/BattleForBretagard.java index ac11200470e..b05074878a6 100644 --- a/Mage.Sets/src/mage/cards/b/BattleForBretagard.java +++ b/Mage.Sets/src/mage/cards/b/BattleForBretagard.java @@ -147,6 +147,7 @@ class BattleForBretagardTarget extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + Set names = this.getTargets() .stream() .map(game::getPermanent) @@ -159,6 +160,7 @@ class BattleForBretagardTarget extends TargetPermanent { Permanent permanent = game.getPermanent(uuid); return permanent == null || names.contains(permanent.getName()); }); + return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java b/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java index 5a914df491f..f03656b0c32 100644 --- a/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java +++ b/Mage.Sets/src/mage/cards/b/BattlefieldScrounger.java @@ -79,7 +79,7 @@ class BattlefieldScroungerCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java b/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java index eb43b1be046..c8d88660c1b 100644 --- a/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java +++ b/Mage.Sets/src/mage/cards/b/BeguilerOfWills.java @@ -66,12 +66,12 @@ class BeguilerOfWillsTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); int count = game.getBattlefield().countAll(this.filter, source.getControllerId(), game); if (permanent != null && permanent.getPower().getValue() <= count) { - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/b/BlazingHope.java b/Mage.Sets/src/mage/cards/b/BlazingHope.java index 4fc35abe721..06f8e7cec1b 100644 --- a/Mage.Sets/src/mage/cards/b/BlazingHope.java +++ b/Mage.Sets/src/mage/cards/b/BlazingHope.java @@ -49,18 +49,18 @@ class BlazingHopeTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (permanent != null) { if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) - || !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game) + || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) { return false; } } Player controller = game.getPlayer(source.getControllerId()); if (controller != null && permanent.getPower().getValue() >= controller.getLife()) { - return filter.match(permanent, controllerId, source, game); + return filter.match(permanent, playerId, source, game); } } return false; diff --git a/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java b/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java index 6b1449b7d2e..6f0d8f01653 100644 --- a/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java +++ b/Mage.Sets/src/mage/cards/b/BreakingOfTheFellowship.java @@ -1,6 +1,5 @@ package mage.cards.b; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.keyword.TheRingTemptsYouEffect; @@ -8,20 +7,20 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; +import java.util.Set; import java.util.UUID; /** + * TODO: combine with Mutiny + * * @author Susucr */ public final class BreakingOfTheFellowship extends CardImpl { @@ -31,8 +30,8 @@ public final class BreakingOfTheFellowship extends CardImpl { // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. this.getSpellAbility().addEffect(new BreakingOfTheFellowshipEffect()); - this.getSpellAbility().addTarget(new BreakingOfTheFellowshipFirstTarget()); - this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls"))); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.getSpellAbility().addTarget(new BreakingOfTheFellowshipSecondTarget()); // The Ring tempts you. this.getSpellAbility().addEffect(new TheRingTemptsYouEffect()); @@ -76,74 +75,45 @@ class BreakingOfTheFellowshipEffect extends OneShotEffect { } return true; } - } -class BreakingOfTheFellowshipFirstTarget extends TargetPermanent { +class BreakingOfTheFellowshipSecondTarget extends TargetPermanent { - public BreakingOfTheFellowshipFirstTarget() { - super(1, 1, StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, false); + public BreakingOfTheFellowshipSecondTarget() { + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); } - private BreakingOfTheFellowshipFirstTarget(final BreakingOfTheFellowshipFirstTarget target) { + private BreakingOfTheFellowshipSecondTarget(final BreakingOfTheFellowshipSecondTarget target) { super(target); } @Override - public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { - super.addTarget(id, source, game, skipEvent); - // Update the second target - UUID firstController = game.getControllerId(id); - if (firstController != null && source.getTargets().size() > 1) { - Player controllingPlayer = game.getPlayer(firstController); - TargetCreaturePermanent targetCreaturePermanent = (TargetCreaturePermanent) source.getTargets().get(1); - // Set a new filter to the second target with the needed restrictions - FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature that player " + controllingPlayer.getName() + " controls"); - filter.add(new ControllerIdPredicate(firstController)); - filter.add(Predicates.not(new PermanentIdPredicate(id))); - targetCreaturePermanent.replaceFilter(filter); - } - } + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - // can only target, if the controller has at least two targetable creatures - UUID controllingPlayerId = game.getControllerId(id); - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source.getId()); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) { - possibleTargets++; - } + Permanent firstTarget = game.getPermanent(source.getFirstTarget()); + if (firstTarget == null) { + // playable or first target not yet selected + // use all + if (possibleTargets.size() == 1) { + // workaround to make 1 target invalid + possibleTargets.clear(); } - return possibleTargets > 1; + } else { + // real + // filter by same player + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId()); + }); } - return false; + possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id)); + + return possibleTargets; } @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (super.canChoose(sourceControllerId, source, game)) { - UUID controllingPlayerId = game.getControllerId(source.getSourceId()); - for (UUID playerId : game.getOpponents(controllingPlayerId)) { - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, playerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllingPlayerId, source, game)) { - possibleTargets++; - } - } - if (possibleTargets > 1) { - return true; - } - } - } - return false; - } - - @Override - public BreakingOfTheFellowshipFirstTarget copy() { - return new BreakingOfTheFellowshipFirstTarget(this); + public BreakingOfTheFellowshipSecondTarget copy() { + return new BreakingOfTheFellowshipSecondTarget(this); } } diff --git a/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java b/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java index ed40fb64915..e244de72599 100644 --- a/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java +++ b/Mage.Sets/src/mage/cards/c/CallOfTheDeathDweller.java @@ -141,8 +141,8 @@ class CallOfTheDeathDwellerTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 3, game); } diff --git a/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java b/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java index d219c7239ff..035f31d6a6d 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java +++ b/Mage.Sets/src/mage/cards/c/ChandraPyromaster.java @@ -1,8 +1,5 @@ package mage.cards.c; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.ApprovingObject; import mage.MageObject; import mage.abilities.Ability; @@ -17,7 +14,6 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterInstantOrSorceryCard; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.target.TargetCard; @@ -25,6 +21,9 @@ import mage.target.TargetPermanent; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.targetpointer.FixedTarget; +import java.util.Set; +import java.util.UUID; + /** * @author jeffwadsworth */ @@ -111,47 +110,23 @@ class ChandraPyromasterTarget extends TargetPermanent { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (player == null) { - return false; - } - UUID firstTarget = player.getId(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/c/ChaosMutation.java b/Mage.Sets/src/mage/cards/c/ChaosMutation.java index fb4b4cfbc41..bbb0639611e 100644 --- a/Mage.Sets/src/mage/cards/c/ChaosMutation.java +++ b/Mage.Sets/src/mage/cards/c/ChaosMutation.java @@ -124,8 +124,8 @@ class ChaosMutationTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java b/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java index 91b5eea84fd..fa6dcd2fa80 100644 --- a/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java +++ b/Mage.Sets/src/mage/cards/c/CloudsLimitBreak.java @@ -85,8 +85,8 @@ class CloudsLimitBreakTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java b/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java index dbd41c58ddd..d767e99db1d 100644 --- a/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java +++ b/Mage.Sets/src/mage/cards/c/ConspiracyTheorist.java @@ -119,8 +119,7 @@ class ConspiracyTheoristEffect extends OneShotEffect { if (controller != null) { CardsImpl cards = new CardsImpl(discardedCards); TargetCard target = new TargetCard(Zone.GRAVEYARD, new FilterCard("card to exile")); - boolean validTarget = cards.stream() - .anyMatch(card -> target.canTarget(card, game)); + boolean validTarget = cards.stream().anyMatch(card -> target.canTarget(card, source, game)); if (validTarget && controller.chooseUse(Outcome.Benefit, "Exile a card?", source, game)) { if (controller.choose(Outcome.Benefit, cards, target, source, game)) { Card card = cards.get(target.getFirstTarget(), game); diff --git a/Mage.Sets/src/mage/cards/d/DaringThief.java b/Mage.Sets/src/mage/cards/d/DaringThief.java index 44566227ba6..668d2eaf7ca 100644 --- a/Mage.Sets/src/mage/cards/d/DaringThief.java +++ b/Mage.Sets/src/mage/cards/d/DaringThief.java @@ -9,6 +9,7 @@ import mage.abilities.keyword.InspiredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -35,8 +36,12 @@ public final class DaringThief extends CardImpl { this.toughness = new MageInt(3); // Inspired - Whenever Daring Thief becomes untapped, you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it. - Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame, - "you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it", false, true), true); + Ability ability = new InspiredAbility(new ExchangeControlTargetEffect( + Duration.EndOfGame, + "you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it", + false, + true + ), true); ability.addTarget(new TargetControlledPermanentSharingOpponentPermanentCardType()); ability.addTarget(new DaringThiefSecondTarget()); this.addAbility(ability); @@ -66,9 +71,10 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - Set cardTypes = getOpponentPermanentCardTypes(controllerId, game); + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + Set cardTypes = getOpponentPermanentCardTypes(playerId, game); + + if (super.canTarget(playerId, id, source, game)) { Permanent permanent = game.getPermanent(id); for (CardType type : permanent.getCardType(game)) { if (cardTypes.contains(type)) { @@ -81,23 +87,21 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - // get all cardtypes from opponents permanents Set cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game); + Set possibleTargets = new HashSet<>(); MageObject targetSource = game.getObject(source); if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - for (CardType type : permanent.getCardType(game)) { - if (cardTypes.contains(type)) { - possibleTargets.add(permanent.getId()); - break; - } + for (CardType type : permanent.getCardType(game)) { + if (cardTypes.contains(type)) { + possibleTargets.add(permanent.getId()); + break; } } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -119,58 +123,54 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo } } - class DaringThiefSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public DaringThiefSecondTarget() { - super(); - this.filter = this.filter.copy(); - filter.add(TargetController.OPPONENT.getControllerPredicate()); + super(StaticFilters.FILTER_OPPONENTS_PERMANENT); withTargetName("permanent an opponent controls that shares a card type with it"); } private DaringThiefSecondTarget(final DaringThiefSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } - @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.shareTypes(opponentPermanent, game); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (permanent.shareTypes(firstTarget, game)) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by shared type + if (permanent.shareTypes(ownPermanent, game)) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); - return super.chooseTarget(Outcome.Damage, playerId, source, game); + // AI hint with better outcome + return super.chooseTarget(Outcome.GainControl, playerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DeepCavernBat.java b/Mage.Sets/src/mage/cards/d/DeepCavernBat.java index 96b1573da43..67564ae1bd3 100644 --- a/Mage.Sets/src/mage/cards/d/DeepCavernBat.java +++ b/Mage.Sets/src/mage/cards/d/DeepCavernBat.java @@ -91,9 +91,7 @@ class DeepCaverBatEffect extends OneShotEffect { return true; } - TargetCard target = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_A_NON_LAND - ); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/d/DesperateGambit.java b/Mage.Sets/src/mage/cards/d/DesperateGambit.java index a33067ba290..719077d465c 100644 --- a/Mage.Sets/src/mage/cards/d/DesperateGambit.java +++ b/Mage.Sets/src/mage/cards/d/DesperateGambit.java @@ -137,49 +137,12 @@ class TargetControlledSource extends TargetSource { } @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && Objects.equals(stackObject.getControllerId(), sourceControllerId)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) { - if (Objects.equals(permanent.getControllerId(), sourceControllerId)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Player player : game.getPlayers().values()) { - if (Objects.equals(player, game.getPlayer(sourceControllerId))) { - for (Card card : player.getGraveyard().getCards(game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - // 108.4a If anything asks for the controller of a card that doesn't have one (because it's not a permanent or spell), use its owner instead. - for (Card card : game.getExile().getAllCards(game)) { - if (Objects.equals(card.getOwnerId(), sourceControllerId)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - } - return false; + public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); for (StackObject stackObject : game.getStack()) { if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) @@ -205,7 +168,7 @@ class TargetControlledSource extends TargetSource { } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DistendedMindbender.java b/Mage.Sets/src/mage/cards/d/DistendedMindbender.java index d12bec48bd7..fad22da9f7e 100644 --- a/Mage.Sets/src/mage/cards/d/DistendedMindbender.java +++ b/Mage.Sets/src/mage/cards/d/DistendedMindbender.java @@ -90,10 +90,10 @@ class DistendedMindbenderEffect extends OneShotEffect { TargetCard targetThreeOrLess = new TargetCard(1, Zone.HAND, filterThreeOrLess); TargetCard targetFourOrGreater = new TargetCard(1, Zone.HAND, filterFourOrGreater); Cards toDiscard = new CardsImpl(); - if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) { + if (controller.choose(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) { toDiscard.addAll(targetThreeOrLess.getTargets()); } - if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) { + if (controller.choose(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) { toDiscard.addAll(targetFourOrGreater.getTargets()); } opponent.discard(toDiscard, false, source, game); diff --git a/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java b/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java index 1641294bdf5..47486d9fbe5 100644 --- a/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java +++ b/Mage.Sets/src/mage/cards/d/DrafnasRestoration.java @@ -1,25 +1,26 @@ package mage.cards.d; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.common.FilterArtifactCard; import mage.game.Game; -import mage.game.events.TargetEvent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetCardInGraveyard; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + /** - * * @author emerald000 */ public final class DrafnasRestoration extends CardImpl { @@ -53,27 +54,33 @@ class DrafnasRestorationTarget extends TargetCardInGraveyard { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player targetPlayer = game.getPlayer(source.getFirstTarget()); - return targetPlayer != null && targetPlayer.getGraveyard().contains(id) && super.canTarget(id, source, game); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); - if (object instanceof StackObject) { - Player targetPlayer = game.getPlayer(((StackObject) object).getStackAbility().getFirstTarget()); - if (targetPlayer != null) { - for (Card card : targetPlayer.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } - } - } + + Player controller = game.getPlayer(sourceControllerId); + if (controller == null) { + return possibleTargets; } - return possibleTargets; + + Player targetPlayer = game.getPlayer(source.getFirstTarget()); + game.getState().getPlayersInRange(sourceControllerId, game, true).stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .flatMap(player -> player.getGraveyard().getCards(filter, sourceControllerId, source, game).stream()) + .forEach(card -> { + if (targetPlayer == null) { + // playable or not selected - use any + possibleTargets.add(card.getId()); + } else { + // selected, filter by player + if (targetPlayer.getId().equals(card.getControllerOrOwnerId())) { + possibleTargets.add(card.getId()); + } + } + }); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java b/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java index 3c569c5ea9d..b3d7f51fdf7 100644 --- a/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java +++ b/Mage.Sets/src/mage/cards/d/DreamsOfSteelAndOil.java @@ -72,7 +72,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect { filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate())); TargetCard target = new TargetCard(Zone.HAND, filter); target.withNotTarget(true); - controller.chooseTarget(Outcome.Discard, opponent.getHand(), target, source, game); + controller.choose(Outcome.Discard, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { toExile.add(card); @@ -81,7 +81,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect { filter.setMessage("artifact or creature card from " + opponent.getName() + "'s graveyard"); target = new TargetCard(Zone.GRAVEYARD, filter); target.withNotTarget(true); - controller.chooseTarget(Outcome.Exile, opponent.getGraveyard(), target, source, game); + controller.choose(Outcome.Exile, opponent.getGraveyard(), target, source, game); card = game.getCard(target.getFirstTarget()); if (card != null) { toExile.add(card); diff --git a/Mage.Sets/src/mage/cards/d/DruidicRitual.java b/Mage.Sets/src/mage/cards/d/DruidicRitual.java index 83df3b1514f..f5986ca4c9d 100644 --- a/Mage.Sets/src/mage/cards/d/DruidicRitual.java +++ b/Mage.Sets/src/mage/cards/d/DruidicRitual.java @@ -105,11 +105,10 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard { return cardTypeAssigner.getRoleCount(cards, game) >= cards.size(); } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java b/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java index caefc4cb1c2..f7fc09fffcc 100644 --- a/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java +++ b/Mage.Sets/src/mage/cards/e/EliteSpellbinder.java @@ -79,9 +79,7 @@ class EliteSpellbinderEffect extends OneShotEffect { if (controller == null || opponent == null || opponent.getHand().isEmpty()) { return false; } - TargetCard target = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_A_NON_LAND - ); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java b/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java index 113e9841794..8ba2936186f 100644 --- a/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java +++ b/Mage.Sets/src/mage/cards/e/EnchantmentAlteration.java @@ -129,7 +129,7 @@ class EnchantmentAlterationEffect extends OneShotEffect { if (oldPermanent != null && !oldPermanent.equals(permanentToBeAttachedTo)) { Target auraTarget = aura.getSpellAbility().getTargets().get(0); - if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), game)) { + if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), source, game)) { game.informPlayers(aura.getLogName() + " was not attched to " + permanentToBeAttachedTo.getLogName() + " because it's no legal target for the aura"); } else if (oldPermanent.removeAttachment(aura.getId(), source, game)) { game.informPlayers(aura.getLogName() + " was unattached from " + oldPermanent.getLogName() + " and attached to " + permanentToBeAttachedTo.getLogName()); diff --git a/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java b/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java index 85e501262a3..bd56aeb01bd 100644 --- a/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java +++ b/Mage.Sets/src/mage/cards/e/EverythingComesToDust.java @@ -58,7 +58,7 @@ enum EverythingComesToDustPredicate implements ObjectSourcePlayerPredicate set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>()); for (MageObjectReference mor : set){ Permanent convoked = game.getPermanentOrLKIBattlefield(mor); if (convoked.shareCreatureTypes(game, p)){ diff --git a/Mage.Sets/src/mage/cards/e/ExtractBrain.java b/Mage.Sets/src/mage/cards/e/ExtractBrain.java index 9f33ad20f40..34d0d0ac6e3 100644 --- a/Mage.Sets/src/mage/cards/e/ExtractBrain.java +++ b/Mage.Sets/src/mage/cards/e/ExtractBrain.java @@ -8,9 +8,11 @@ import mage.cards.Cards; import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInHand; import mage.target.common.TargetOpponent; import mage.util.CardUtil; @@ -65,9 +67,7 @@ class ExtractBrainEffect extends OneShotEffect { if (controller == null || opponent == null || opponent.getHand().isEmpty() || xValue < 1) { return false; } - TargetCardInHand target = new TargetCardInHand( - Math.min(opponent.getHand().size(), xValue), StaticFilters.FILTER_CARD - ); + TargetCard target = new TargetCard(Math.min(opponent.getHand().size(), xValue), Integer.MAX_VALUE, Zone.HAND, StaticFilters.FILTER_CARD); opponent.choose(Outcome.Detriment, opponent.getHand(), target, source, game); Cards cards = new CardsImpl(target.getTargets()); controller.lookAtCards(source, null, cards, game); diff --git a/Mage.Sets/src/mage/cards/f/Fireball.java b/Mage.Sets/src/mage/cards/f/Fireball.java index b55b90de1b9..17b77fe176b 100644 --- a/Mage.Sets/src/mage/cards/f/Fireball.java +++ b/Mage.Sets/src/mage/cards/f/Fireball.java @@ -134,7 +134,6 @@ class FireballTargetCreatureOrPlayer extends TargetAnyTarget { continue; } - possibleTargets.removeAll(getTargets()); for (UUID targetId : possibleTargets) { TargetAnyTarget target = this.copy(); target.clearChosen(); diff --git a/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java b/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java index 6cd928951b5..1d6dc327fc2 100644 --- a/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java +++ b/Mage.Sets/src/mage/cards/f/FrogkinKidnapper.java @@ -85,7 +85,7 @@ class FrogkinKidnapperEffect extends OneShotEffect { opponent.revealCards(source, opponent.getHand(), game); TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); Cards toRansom = new CardsImpl(); - if (controller.chooseTarget(outcome, opponent.getHand(), target, source, game)) { + if (controller.choose(outcome, opponent.getHand(), target, source, game)) { toRansom.addAll(target.getTargets()); String exileName = "Ransomed (owned by " + opponent.getName() + ")"; diff --git a/Mage.Sets/src/mage/cards/g/GateSmasher.java b/Mage.Sets/src/mage/cards/g/GateSmasher.java index 276d48bc734..a5f77b188c3 100644 --- a/Mage.Sets/src/mage/cards/g/GateSmasher.java +++ b/Mage.Sets/src/mage/cards/g/GateSmasher.java @@ -9,13 +9,12 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.SubType; +import mage.constants.*; import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.ToughnessPredicate; +import mage.target.TargetCard; import mage.target.TargetPermanent; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java b/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java index 049d1ebeb74..9a073fe7d64 100644 --- a/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java +++ b/Mage.Sets/src/mage/cards/g/GauntletsOfChaos.java @@ -69,9 +69,10 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - Set cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), controllerId, game); + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + Set cardTypes = getOpponentPermanentCardTypes(playerId, game); + + if (super.canTarget(playerId, id, source, game)) { Permanent permanent = game.getPermanent(id); for (CardType type : permanent.getCardType(game)) { if (cardTypes.contains(type)) { @@ -84,23 +85,21 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - // get all cardtypes from opponents permanents - Set cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), sourceControllerId, game); + Set cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game); + Set possibleTargets = new HashSet<>(); MageObject targetSource = game.getObject(source); if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - for (CardType type : permanent.getCardType(game)) { - if (cardTypes.contains(type)) { - possibleTargets.add(permanent.getId()); - break; - } + for (CardType type : permanent.getCardType(game)) { + if (cardTypes.contains(type)) { + possibleTargets.add(permanent.getId()); + break; } } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -108,7 +107,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { return new GauntletsOfChaosFirstTarget(this); } - private EnumSet getOpponentPermanentCardTypes(UUID sourceId, UUID sourceControllerId, Game game) { + private EnumSet getOpponentPermanentCardTypes(UUID sourceControllerId, Game game) { Player controller = game.getPlayer(sourceControllerId); EnumSet cardTypes = EnumSet.noneOf(CardType.class); if (controller != null) { @@ -125,8 +124,6 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent { class GauntletsOfChaosSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public GauntletsOfChaosSecondTarget() { super(); this.filter = this.filter.copy(); @@ -136,44 +133,46 @@ class GauntletsOfChaosSecondTarget extends TargetPermanent { private GauntletsOfChaosSecondTarget(final GauntletsOfChaosSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.shareTypes(opponentPermanent, game); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (permanent.shareTypes(firstTarget, game)) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by shared type + if (permanent.shareTypes(ownPermanent, game)) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); - return super.chooseTarget(Outcome.Damage, playerId, source, game); + // AI hint with better outcome + return super.chooseTarget(Outcome.GainControl, playerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java b/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java index 271c34aaa56..f60ad91dab2 100644 --- a/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java +++ b/Mage.Sets/src/mage/cards/g/GhastlordOfFugue.java @@ -82,7 +82,7 @@ class GhastlordOfFugueEffect extends OneShotEffect { TargetCard target = new TargetCard(Zone.HAND, new FilterCard()); target.withNotTarget(true); Card chosenCard = null; - if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { chosenCard = game.getCard(target.getFirstTarget()); } if (chosenCard != null) { diff --git a/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java b/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java index fd51af64dd2..9f538b41201 100644 --- a/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java +++ b/Mage.Sets/src/mage/cards/g/GiltspireAvenger.java @@ -2,7 +2,6 @@ package mage.cards.g; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -12,15 +11,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; import mage.target.TargetPermanent; -import mage.watchers.common.PlayerDamagedBySourceWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -28,6 +23,12 @@ import java.util.UUID; */ public final class GiltspireAvenger extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn"); + + static { + filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU)); + } + public GiltspireAvenger(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}"); this.subtype.add(SubType.HUMAN); @@ -41,7 +42,7 @@ public final class GiltspireAvenger extends CardImpl { // {T}: Destroy target creature that dealt damage to you this turn. Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new TapSourceCost()); - ability.addTarget(new GiltspireAvengerTarget()); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } @@ -55,64 +56,3 @@ public final class GiltspireAvenger extends CardImpl { return new GiltspireAvenger(this); } } - -class GiltspireAvengerTarget extends TargetPermanent { - - public GiltspireAvengerTarget() { - super(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); - targetName = "creature that dealt damage to you this turn"; - } - - private GiltspireAvengerTarget(final GiltspireAvengerTarget target) { - super(target); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, source.getControllerId()); - if (watcher != null && watcher.hasSourceDoneDamage(id, game)) { - return super.canTarget(id, source, game); - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && watcher != null && watcher.hasSourceDoneDamage(targetId, game)) { - possibleTargets.add(targetId); - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets == 0) { - return true; - } - int count = 0; - MageObject targetSource = game.getObject(source); - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) - && watcher != null && watcher.hasSourceDoneDamage(permanent.getId(), game)) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - return false; - } - - @Override - public GiltspireAvengerTarget copy() { - return new GiltspireAvengerTarget(this); - } -} diff --git a/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java b/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java index 4b6b8c25125..06ae97d9e0f 100644 --- a/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java +++ b/Mage.Sets/src/mage/cards/g/GlyphOfDelusion.java @@ -1,9 +1,7 @@ package mage.cards.g; -import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; @@ -11,11 +9,14 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DontUntapInControllersUntapStepSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; @@ -59,8 +60,6 @@ public final class GlyphOfDelusion extends CardImpl { class GlyphOfDelusionSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public GlyphOfDelusionSecondTarget() { super(); withTargetName("target creature that target Wall blocked this turn"); @@ -68,33 +67,39 @@ class GlyphOfDelusionSecondTarget extends TargetPermanent { private GlyphOfDelusionSecondTarget(final GlyphOfDelusionSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class); - if (watcher != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, source, game)) { - if (!targets.containsKey(creature.getId()) && creature.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game))) { - possibleTargets.add(creature.getId()); - } - } - } + + BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class); + if (watcher == null) { + return possibleTargets; + } + + Permanent targetWall = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (targetWall == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by blocked + if (watcher.creatureHasBlockedAttacker(new MageObjectReference(permanent, game), new MageObjectReference(targetWall, game))) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> targetWall != null && targetWall.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); + // AI hint with better outcome return super.chooseTarget(Outcome.Tap, playerId, source, game); } @@ -133,8 +138,7 @@ class GlyphOfDelusionEffect extends OneShotEffect { effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), game)); game.addEffect(effect, source); - BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance()) - ); + BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance())); GainAbilityTargetEffect effect2 = new GainAbilityTargetEffect(ability2, Duration.Custom); effect2.setTargetPointer(new FixedTarget(targetPermanent.getId(), game)); game.addEffect(effect2, source); diff --git a/Mage.Sets/src/mage/cards/g/GoblinArtisans.java b/Mage.Sets/src/mage/cards/g/GoblinArtisans.java index d134b26b94b..1c4a54413ec 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinArtisans.java +++ b/Mage.Sets/src/mage/cards/g/GoblinArtisans.java @@ -81,8 +81,8 @@ class GoblinArtisansTarget extends TargetSpell { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } MageObjectReference sourceRef = new MageObjectReference(source.getSourceObject(game), game); diff --git a/Mage.Sets/src/mage/cards/g/GracefulTakedown.java b/Mage.Sets/src/mage/cards/g/GracefulTakedown.java index 2c946a8ba44..42225c2acb6 100644 --- a/Mage.Sets/src/mage/cards/g/GracefulTakedown.java +++ b/Mage.Sets/src/mage/cards/g/GracefulTakedown.java @@ -61,8 +61,8 @@ class GracefulTakedownTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent permanent = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/g/GrimeGorger.java b/Mage.Sets/src/mage/cards/g/GrimeGorger.java index 2f55fa76495..01ab1679c2b 100644 --- a/Mage.Sets/src/mage/cards/g/GrimeGorger.java +++ b/Mage.Sets/src/mage/cards/g/GrimeGorger.java @@ -129,7 +129,7 @@ class GrimeGorgerTarget extends TargetCardInGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/g/Gurzigost.java b/Mage.Sets/src/mage/cards/g/Gurzigost.java index dfbf9edb8b6..a9faa349cd3 100644 --- a/Mage.Sets/src/mage/cards/g/Gurzigost.java +++ b/Mage.Sets/src/mage/cards/g/Gurzigost.java @@ -92,7 +92,7 @@ class GurzigostCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java b/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java index 22bf2737054..1018125175e 100644 --- a/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java +++ b/Mage.Sets/src/mage/cards/i/IlluminatedFolio.java @@ -1,7 +1,5 @@ package mage.cards.i; -import mage.MageItem; -import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RevealTargetFromHandCost; @@ -19,9 +17,9 @@ import mage.game.Game; import mage.target.common.TargetCardInHand; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** * @author TheElk801 @@ -67,51 +65,8 @@ class IlluminatedFolioTarget extends TargetCardInHand { } @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return super.canChoose(sourceControllerId, source, game) - && !possibleTargets(sourceControllerId, source, game).isEmpty(); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - if (this.getTargets().size() == 1) { - Card card = game.getCard(this.getTargets().get(0)); - possibleTargets.removeIf( - uuid -> !game - .getCard(uuid) - .getColor(game) - .shares(card.getColor(game)) - ); - return possibleTargets; - } - if (possibleTargets.size() < 2) { - possibleTargets.clear(); - return possibleTargets; - } - Set allTargets = possibleTargets - .stream() - .map(game::getCard) - .collect(Collectors.toSet()); - possibleTargets.clear(); - for (ObjectColor color : ObjectColor.getAllColors()) { - Set inColor = allTargets - .stream() - .filter(card -> card.getColor(game).shares(color)) - .collect(Collectors.toSet()); - if (inColor.size() > 1) { - inColor.stream().map(MageItem::getId).forEach(possibleTargets::add); - } - if (possibleTargets.size() == allTargets.size()) { - break; - } - } - return possibleTargets; - } - - @Override - public boolean canTarget(UUID id, Game game) { - if (!super.canTarget(id, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } List targetList = this.getTargets(); @@ -123,9 +78,17 @@ class IlluminatedFolioTarget extends TargetCardInHand { && targetList .stream() .map(game::getCard) + .filter(Objects::nonNull) .anyMatch(c -> c.getColor(game).shares(card.getColor(game))); } + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); + return possibleTargets; + } + @Override public IlluminatedFolioTarget copy() { return new IlluminatedFolioTarget(this); diff --git a/Mage.Sets/src/mage/cards/i/ImpelledGiant.java b/Mage.Sets/src/mage/cards/i/ImpelledGiant.java index 5a32d0d837f..1cd50299212 100644 --- a/Mage.Sets/src/mage/cards/i/ImpelledGiant.java +++ b/Mage.Sets/src/mage/cards/i/ImpelledGiant.java @@ -99,7 +99,7 @@ class ImpelledGiantCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java b/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java index a180880981f..d44cf14a51b 100644 --- a/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java +++ b/Mage.Sets/src/mage/cards/i/InvasionOfGobakhan.java @@ -79,9 +79,7 @@ class InvasionOfGobakhanEffect extends OneShotEffect { if (controller == null || opponent == null || opponent.getHand().isEmpty()) { return false; } - TargetCard target = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_A_NON_LAND - ); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = opponent.getHand().get(target.getFirstTarget(), game); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java b/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java index dd0f1b169ab..47ebfcd443e 100644 --- a/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java +++ b/Mage.Sets/src/mage/cards/i/IwamoriOfTheOpenFist.java @@ -1,32 +1,27 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.*; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetCardInHand; +import mage.target.TargetCard; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class IwamoriOfTheOpenFist extends CardImpl { public IwamoriOfTheOpenFist(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.MONK); @@ -80,15 +75,11 @@ class IwamoriOfTheOpenFistEffect extends OneShotEffect { Cards cards = new CardsImpl(); for (UUID playerId : game.getOpponents(controller.getId())) { Player opponent = game.getPlayer(playerId); - Target target = new TargetCardInHand(filter); - if (opponent != null && target.canChoose(opponent.getId(), source, game)) { - if (opponent.chooseUse(Outcome.PutCreatureInPlay, "Put a legendary creature card from your hand onto the battlefield?", source, game)) { - if (target.chooseTarget(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) { - Card card = game.getCard(target.getFirstTarget()); - if (card != null) { - cards.add(card); - } - } + Target target = new TargetCard(0, 1, Zone.HAND, filter).withChooseHint("put from hand to battlefield"); + if (target.choose(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + cards.add(card); } } } diff --git a/Mage.Sets/src/mage/cards/j/JotunGrunt.java b/Mage.Sets/src/mage/cards/j/JotunGrunt.java index 1d271b5065b..0571e036c0e 100644 --- a/Mage.Sets/src/mage/cards/j/JotunGrunt.java +++ b/Mage.Sets/src/mage/cards/j/JotunGrunt.java @@ -78,7 +78,7 @@ class JotunGruntCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java b/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java index e78ed4ef421..ef35d8307a4 100644 --- a/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java +++ b/Mage.Sets/src/mage/cards/j/JourneyForTheElixir.java @@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; -import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInYourGraveyard; import java.util.Objects; @@ -62,28 +61,31 @@ class JourneyForTheElixirEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { return false; } - TargetCardInLibrary targetCardInLibrary = new JourneyForTheElixirLibraryTarget(); - player.searchLibrary(targetCardInLibrary, source, game); - Cards cards = new CardsImpl(targetCardInLibrary.getTargets()); - TargetCard target = new JourneyForTheElixirGraveyardTarget(cards); - player.choose(outcome, target, source, game); - cards.addAll(target.getTargets()); - player.revealCards(source, cards, game); - player.moveCards(cards, Zone.HAND, source, game); - player.shuffleLibrary(source, game); - return true; + + // search your library and graveyard for 2 cards + Cards allCards = new CardsImpl(); + allCards.addAll(controller.getLibrary().getCardList()); + allCards.addAll(controller.getGraveyard()); + TargetCard target = new JourneyForTheElixirTarget(); + if (controller.choose(Outcome.Benefit, allCards, target, source, game)) { + Cards cards = new CardsImpl(target.getTargets()); + controller.revealCards(source, cards, game); + controller.moveCards(cards, Zone.HAND, source, game); + controller.shuffleLibrary(source, game); + return true; + } + return false; } } -class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary { +class JourneyForTheElixirTarget extends TargetCard { private static final String name = "Jiang Yanggu"; - private static final FilterCard filter - = new FilterCard("a basic land card and a card named Jiang Yanggu"); + private static final FilterCard filter = new FilterCard("a basic land card and a card named Jiang Yanggu"); static { filter.add(Predicates.or( @@ -95,17 +97,17 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary { )); } - JourneyForTheElixirLibraryTarget() { - super(0, 2, filter); + JourneyForTheElixirTarget() { + super(2, 2, Zone.ALL, filter); } - private JourneyForTheElixirLibraryTarget(final JourneyForTheElixirLibraryTarget target) { + private JourneyForTheElixirTarget(final JourneyForTheElixirTarget target) { super(target); } @Override - public JourneyForTheElixirLibraryTarget copy() { - return new JourneyForTheElixirLibraryTarget(this); + public JourneyForTheElixirTarget copy() { + return new JourneyForTheElixirTarget(this); } @Override @@ -117,95 +119,35 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary { if (card == null) { return false; } - if (this.getTargets().isEmpty()) { - return true; - } + Cards cards = new CardsImpl(this.getTargets()); - if (card.isBasic(game) - && card.isLand(game) - && cards + boolean hasLand = cards .getCards(game) .stream() .filter(Objects::nonNull) .filter(c -> c.isBasic(game)) - .anyMatch(c -> c.isLand(game))) { - return false; - } - if (name.equals(card.getName()) - && cards + .anyMatch(c -> c.isLand(game)); + boolean hasJiang = cards .getCards(game) .stream() .map(MageObject::getName) - .anyMatch(name::equals)) { - return false; + .anyMatch(name::equals); + + if (!hasLand && card.isBasic(game) && card.isLand(game)) { + return true; } - return true; - } -} -class JourneyForTheElixirGraveyardTarget extends TargetCardInYourGraveyard { + if (!hasJiang && name.equals(card.getName())) { + return true; + } - private static final String name = "Jiang Yanggu"; - private static final FilterCard filter - = new FilterCard("a basic land card and a card named Jiang Yanggu"); - - static { - filter.add(Predicates.or( - Predicates.and( - SuperType.BASIC.getPredicate(), - CardType.LAND.getPredicate() - ), - new NamePredicate(name) - )); - } - - private final Cards cards = new CardsImpl(); - - JourneyForTheElixirGraveyardTarget(Cards cards) { - super(0, Integer.MAX_VALUE, filter, true); - this.cards.addAll(cards); - } - - private JourneyForTheElixirGraveyardTarget(final JourneyForTheElixirGraveyardTarget target) { - super(target); - this.cards.addAll(target.cards); - } - - @Override - public JourneyForTheElixirGraveyardTarget copy() { - return new JourneyForTheElixirGraveyardTarget(this); + return false; } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - Cards alreadyTargeted = new CardsImpl(this.getTargets()); - alreadyTargeted.addAll(cards); - boolean hasBasic = alreadyTargeted - .getCards(game) - .stream() - .filter(Objects::nonNull) - .filter(c -> c.isLand(game)) - .anyMatch(c -> c.isBasic(game)); - possibleTargets.removeIf(uuid -> { - Card card = game.getCard(uuid); - return card != null - && hasBasic - && card.isLand(game) - && card.isBasic(game); - }); - boolean hasYanggu = alreadyTargeted - .getCards(game) - .stream() - .filter(Objects::nonNull) - .map(MageObject::getName) - .anyMatch(name::equals); - possibleTargets.removeIf(uuid -> { - Card card = game.getCard(uuid); - return card != null - && hasYanggu - && name.equals(card.getName()); - }); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java b/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java index 37d1f67a3b1..eff94a35fce 100644 --- a/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java +++ b/Mage.Sets/src/mage/cards/k/KairiTheSwirlingSky.java @@ -88,8 +88,8 @@ class KairiTheSwirlingSkyTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game); } diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java index 8900516e43b..697e9c531c6 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheBeasts.java @@ -1,7 +1,6 @@ package mage.cards.k; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -15,10 +14,8 @@ import mage.filter.FilterOpponent; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.BeastToken4; -import mage.players.Player; import mage.target.TargetPlayer; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -35,7 +32,7 @@ public final class KeeperOfTheBeasts extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(2); - // {G}, {tap}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield. + // {G}, {T}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield. Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new BeastToken4()).setText("Choose target opponent who controlled more creatures than you did as you activated this ability. Create a 2/2 green Beast creature token."), new ManaCostsImpl<>("{G}")); ability.addCost(new TapSourceCost()); @@ -65,42 +62,12 @@ class KeeperOfTheBeastsTarget extends TargetPlayer { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - int creaturesController = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game); - - for (UUID targetId : availablePossibleTargets) { - if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, targetId, game) > creaturesController) { - possibleTargets.add(targetId); - } - } + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + int myCount = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game); + possibleTargets.removeIf(playerId -> game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) < myCount); return possibleTargets; } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); - Player controller = game.getPlayer(sourceControllerId); - if (controller != null && targetSource != null) { - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game) - < game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) - && !player.hasLeft() - && filter.match(player, sourceControllerId, source, game) - && player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; - } - @Override public KeeperOfTheBeastsTarget copy() { return new KeeperOfTheBeastsTarget(this); diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java index 85b5284fff2..45f0f0f72c7 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheDead.java @@ -1,10 +1,7 @@ package mage.cards.k; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; +import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -15,21 +12,23 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterPlayer; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.TargetPlayer; +import java.util.Set; +import java.util.UUID; + /** - * * @author spjspj */ public final class KeeperOfTheDead extends CardImpl { @@ -95,48 +94,37 @@ class KeeperOfDeadPredicate implements ObjectSourcePlayerPredicate { class KeeperOfTheDeadCreatureTarget extends TargetPermanent { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblack creature that player controls"); + + static { + filter.add(Predicates.not(new ColorPredicate(ObjectColor.BLACK))); + } + public KeeperOfTheDeadCreatureTarget() { - super(1, 1, new FilterCreaturePermanent("nonblack creature that player controls"), false); + super(1, 1, filter); } private KeeperOfTheDeadCreatureTarget(final KeeperOfTheDeadCreatureTarget target) { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - UUID firstTarget = source.getFirstTarget(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK.match(permanent, game) && permanent.isControlledBy(playerId)) { - possibleTargets.add(targetId); - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java index 6e24e7d16b2..b4a54b45b4b 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheLight.java @@ -1,8 +1,6 @@ - package mage.cards.k; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -12,13 +10,11 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterOpponent; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -67,43 +63,19 @@ class KeeperOfTheLightTarget extends TargetPlayer { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - int lifeController = game.getPlayer(sourceControllerId).getLife(); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (UUID targetId : availablePossibleTargets) { - Player opponent = game.getPlayer(targetId); - if (opponent != null) { - int lifeOpponent = opponent.getLife(); - if (lifeOpponent > lifeController) { - possibleTargets.add(targetId); - } - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); Player controller = game.getPlayer(sourceControllerId); - if (controller != null && targetSource != null) { - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null - && controller.getLife() < player.getLife() - && !player.hasLeft() - && filter.match(player, sourceControllerId, source, game) - && player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } + if (controller == null) { + return possibleTargets; } - return false; + + possibleTargets.removeIf(playerId -> { + Player player = game.getPlayer(playerId); + return player == null || player.getLife() >= controller.getLife(); + }); + + return possibleTargets; } @Override diff --git a/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java b/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java index 1543346e478..6283587baa9 100644 --- a/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java +++ b/Mage.Sets/src/mage/cards/k/KeldonBattlewagon.java @@ -105,7 +105,7 @@ class KeldonBattlewagonCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/k/KondasBanner.java b/Mage.Sets/src/mage/cards/k/KondasBanner.java index e706ee087a0..72005df704a 100644 --- a/Mage.Sets/src/mage/cards/k/KondasBanner.java +++ b/Mage.Sets/src/mage/cards/k/KondasBanner.java @@ -7,15 +7,14 @@ import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.keyword.EquipAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.target.TargetCard; import mage.target.TargetPermanent; import java.util.UUID; diff --git a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java index 4e6aa44c4b6..d478459ff8c 100644 --- a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java +++ b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java @@ -15,6 +15,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInLibrary; @@ -107,9 +108,9 @@ class KotoseTheSilentSpiderEffect extends OneShotEffect { cards.addAll(targetCardInGraveyard.getTargets()); filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s hand"); - TargetCardInHand targetCardInHand = new TargetCardInHand(0, Integer.MAX_VALUE, filter); - controller.choose(outcome, opponent.getHand(), targetCardInHand, source, game); - cards.addAll(targetCardInHand.getTargets()); + TargetCard targetCard = new TargetCard(0, Integer.MAX_VALUE, Zone.HAND, filter); + controller.choose(outcome, opponent.getHand(), targetCard, source, game); + cards.addAll(targetCard.getTargets()); filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s library"); TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter); diff --git a/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java b/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java index 12fae5b95ff..50cec499a1a 100644 --- a/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java +++ b/Mage.Sets/src/mage/cards/l/LagrellaTheMagpie.java @@ -130,8 +130,8 @@ class LagrellaTheMagpieTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/l/LethalScheme.java b/Mage.Sets/src/mage/cards/l/LethalScheme.java index 03c07511b81..15f1e57ce00 100644 --- a/Mage.Sets/src/mage/cards/l/LethalScheme.java +++ b/Mage.Sets/src/mage/cards/l/LethalScheme.java @@ -69,7 +69,7 @@ class LethalSchemeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { HashSet convokingCreatures = CardUtil.getSourceCostsTag(game, source, - ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + ConvokeAbility.convokingCreaturesKey, new HashSet<>()); Set> playerPermanentsPairs = convokingCreatures .stream() diff --git a/Mage.Sets/src/mage/cards/l/LivelyDirge.java b/Mage.Sets/src/mage/cards/l/LivelyDirge.java index 39579ba8ca9..a9d8b5ddca2 100644 --- a/Mage.Sets/src/mage/cards/l/LivelyDirge.java +++ b/Mage.Sets/src/mage/cards/l/LivelyDirge.java @@ -103,8 +103,8 @@ class LivelyDirgeTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 4, game); } diff --git a/Mage.Sets/src/mage/cards/l/Lobotomy.java b/Mage.Sets/src/mage/cards/l/Lobotomy.java index 73cb29ae2c2..040971b51f9 100644 --- a/Mage.Sets/src/mage/cards/l/Lobotomy.java +++ b/Mage.Sets/src/mage/cards/l/Lobotomy.java @@ -74,7 +74,7 @@ class LobotomyEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExile TargetCard target = new TargetCard(Zone.HAND, filter); target.withNotTarget(true); Card chosenCard = null; - if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { chosenCard = game.getCard(target.getFirstTarget()); } diff --git a/Mage.Sets/src/mage/cards/l/LongRest.java b/Mage.Sets/src/mage/cards/l/LongRest.java index 04e84e63d25..68532a86f3b 100644 --- a/Mage.Sets/src/mage/cards/l/LongRest.java +++ b/Mage.Sets/src/mage/cards/l/LongRest.java @@ -21,7 +21,6 @@ import java.util.Set; import java.util.UUID; /** - * * @author weirddan455 */ public final class LongRest extends CardImpl { @@ -67,19 +66,22 @@ class LongRestTarget extends TargetCardInYourGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - Set manaValues = new HashSet<>(); + + Set usedManaValues = new HashSet<>(); for (UUID targetId : this.getTargets()) { Card card = game.getCard(targetId); if (card != null) { - manaValues.add(card.getManaValue()); + usedManaValues.add(card.getManaValue()); } } + for (UUID possibleTargetId : super.possibleTargets(sourceControllerId, source, game)) { Card card = game.getCard(possibleTargetId); - if (card != null && !manaValues.contains(card.getManaValue())) { + if (card != null && !usedManaValues.contains(card.getManaValue())) { possibleTargets.add(possibleTargetId); } } + return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java b/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java index 47f751c7aae..73e256f652f 100644 --- a/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java +++ b/Mage.Sets/src/mage/cards/m/MarchFromTheTomb.java @@ -56,8 +56,8 @@ class MarchFromTheTombTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 8, game); } diff --git a/Mage.Sets/src/mage/cards/m/ModifyMemory.java b/Mage.Sets/src/mage/cards/m/ModifyMemory.java index b41118ba499..9c4d25397a9 100644 --- a/Mage.Sets/src/mage/cards/m/ModifyMemory.java +++ b/Mage.Sets/src/mage/cards/m/ModifyMemory.java @@ -86,8 +86,8 @@ class ModifyMemoryTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java b/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java index a619369c7e8..45bcd1a28fd 100644 --- a/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java +++ b/Mage.Sets/src/mage/cards/m/MoorlandRescuer.java @@ -112,8 +112,8 @@ class MoorlandRescuerTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, m -> m.getPower().getValue(), xValue, game); } diff --git a/Mage.Sets/src/mage/cards/m/Mutiny.java b/Mage.Sets/src/mage/cards/m/Mutiny.java index 40caae27b00..50ecaac9d64 100644 --- a/Mage.Sets/src/mage/cards/m/Mutiny.java +++ b/Mage.Sets/src/mage/cards/m/Mutiny.java @@ -1,6 +1,5 @@ package mage.cards.m; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -9,18 +8,16 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; +import java.util.Set; import java.util.UUID; /** + * TODO: combine with BreakingOfTheFellowship + * * @author LevelX2 */ public final class Mutiny extends CardImpl { @@ -30,7 +27,8 @@ public final class Mutiny extends CardImpl { // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. this.getSpellAbility().addEffect(new MutinyEffect()); - this.getSpellAbility().addTarget(new MutinyFirstTarget(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.getSpellAbility().addTarget(new MutinySecondTarget()); this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls"))); } @@ -72,74 +70,45 @@ class MutinyEffect extends OneShotEffect { } return true; } - } -class MutinyFirstTarget extends TargetPermanent { +class MutinySecondTarget extends TargetPermanent { - public MutinyFirstTarget(FilterCreaturePermanent filter) { - super(1, 1, filter, false); + public MutinySecondTarget() { + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); } - private MutinyFirstTarget(final MutinyFirstTarget target) { + private MutinySecondTarget(final MutinySecondTarget target) { super(target); } @Override - public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { - super.addTarget(id, source, game, skipEvent); - // Update the second target - UUID firstController = game.getControllerId(id); - if (firstController != null && source.getTargets().size() > 1) { - Player controllingPlayer = game.getPlayer(firstController); - TargetCreaturePermanent targetCreaturePermanent = (TargetCreaturePermanent) source.getTargets().get(1); - // Set a new filter to the second target with the needed restrictions - FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature that player " + controllingPlayer.getName() + " controls"); - filter.add(new ControllerIdPredicate(firstController)); - filter.add(Predicates.not(new PermanentIdPredicate(id))); - targetCreaturePermanent.replaceFilter(filter); - } - } + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { - // can only target, if the controller has at least two targetable creatures - UUID controllingPlayerId = game.getControllerId(id); - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source.getId()); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) { - possibleTargets++; - } + Permanent firstTarget = game.getPermanent(source.getFirstTarget()); + if (firstTarget == null) { + // playable or first target not yet selected + // use all + if (possibleTargets.size() == 1) { + // workaround to make 1 target invalid + possibleTargets.clear(); } - return possibleTargets > 1; + } else { + // real + // filter by same player + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId()); + }); } - return false; + possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id)); + + return possibleTargets; } @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (super.canChoose(sourceControllerId, source, game)) { - UUID controllingPlayerId = game.getControllerId(source.getSourceId()); - for (UUID playerId : game.getOpponents(controllingPlayerId)) { - int possibleTargets = 0; - MageObject sourceObject = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, playerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, controllingPlayerId, source, game)) { - possibleTargets++; - } - } - if (possibleTargets > 1) { - return true; - } - } - } - return false; - } - - @Override - public MutinyFirstTarget copy() { - return new MutinyFirstTarget(this); + public MutinySecondTarget copy() { + return new MutinySecondTarget(this); } } diff --git a/Mage.Sets/src/mage/cards/n/Nethergoyf.java b/Mage.Sets/src/mage/cards/n/Nethergoyf.java index 858e2718695..955bd409171 100644 --- a/Mage.Sets/src/mage/cards/n/Nethergoyf.java +++ b/Mage.Sets/src/mage/cards/n/Nethergoyf.java @@ -22,10 +22,7 @@ import mage.target.common.TargetCardInYourGraveyard; import mage.util.CardUtil; import java.awt.*; -import java.util.Collection; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -113,7 +110,10 @@ class NethergoyfTarget extends TargetCardInYourGraveyard { return false; } // Check that exiling all the possible cards would have >= 4 different card types - return metCondition(this.possibleTargets(sourceControllerId, source, game), game); + Set idsToCheck = new HashSet<>(); + idsToCheck.addAll(this.getTargets()); + idsToCheck.addAll(this.possibleTargets(sourceControllerId, source, game)); + return metCondition(idsToCheck, game); } private static Set typesAmongSelection(Collection cardsIds, Game game) { diff --git a/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java b/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java index 0f1746723d8..f55584b1dc5 100644 --- a/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java +++ b/Mage.Sets/src/mage/cards/n/NethroiApexOfDeath.java @@ -83,8 +83,8 @@ class NethroiApexOfDeathTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, m -> m.getPower().getValue(), 10, game); } diff --git a/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java b/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java index b14942908a4..321e6980297 100644 --- a/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java +++ b/Mage.Sets/src/mage/cards/n/NicolBolasDragonGod.java @@ -150,7 +150,7 @@ class NicolBolasDragonGodPlusOneEffect extends OneShotEffect { TargetPermanent targetPermanent = new TargetControlledPermanent(); targetPermanent.withNotTarget(true); targetPermanent.setTargetController(opponentId); - if (!targetPermanent.possibleTargets(opponentId, game).isEmpty()) { + if (!targetPermanent.possibleTargets(opponentId, source, game).isEmpty()) { possibleTargetTypes.add(targetPermanent); } diff --git a/Mage.Sets/src/mage/cards/n/NivmagusElemental.java b/Mage.Sets/src/mage/cards/n/NivmagusElemental.java index a602a4adec4..f94d741e0d0 100644 --- a/Mage.Sets/src/mage/cards/n/NivmagusElemental.java +++ b/Mage.Sets/src/mage/cards/n/NivmagusElemental.java @@ -91,7 +91,7 @@ class NivmagusElementalCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/n/NoContest.java b/Mage.Sets/src/mage/cards/n/NoContest.java index 3c0b2732f17..6d25a4de9db 100644 --- a/Mage.Sets/src/mage/cards/n/NoContest.java +++ b/Mage.Sets/src/mage/cards/n/NoContest.java @@ -1,5 +1,6 @@ package mage.cards.n; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.effects.common.FightTargetsEffect; import mage.cards.Card; @@ -15,7 +16,9 @@ import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; +import mage.watchers.common.BlockedAttackerWatcher; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -54,50 +57,27 @@ class TargetCreatureWithLessPowerPermanent extends TargetPermanent { super(target); } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int maxPower = Integer.MIN_VALUE; // get the most powerful controlled creature that can be targeted - Card sourceCard = game.getCard(source.getSourceId()); - if (sourceCard == null) { - return false; - } - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, sourceControllerId, game)) { - if (permanent.getPower().getValue() > maxPower && permanent.canBeTargetedBy(sourceCard, sourceControllerId, source, game)) { - maxPower = permanent.getPower().getValue(); - } - } - // now check, if another creature has less power and can be targeted - FilterCreaturePermanent checkFilter = new FilterCreaturePermanent(); - checkFilter.add(new PowerPredicate(ComparisonType.FEWER_THAN, maxPower)); - for (Permanent permanent : game.getBattlefield().getActivePermanents(checkFilter, sourceControllerId, source, game)) { - if (permanent.canBeTargetedBy(sourceCard, sourceControllerId, source, game)) { - return true; - } - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Spell spell = game.getStack().getSpell(source.getSourceId()); - if (spell != null) { - Permanent firstTarget = getPermanentFromFirstTarget(spell.getSpellAbility(), game); - if (firstTarget != null) { - int power = firstTarget.getPower().getValue(); - // overwrite the filter with the power predicate - filter = new FilterCreaturePermanent("creature with power less than " + power); - filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, power)); + Set possibleTargets = new HashSet<>(); + + Permanent firstPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (firstPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by power + if (firstPermanent.getPower().getValue() > permanent.getPower().getValue()) { + possibleTargets.add(permanent.getId()); + } } } - return super.possibleTargets(sourceControllerId, source, game); - } + possibleTargets.removeIf(id -> firstPermanent != null && firstPermanent.getId().equals(id)); - private Permanent getPermanentFromFirstTarget(Ability source, Game game) { - Permanent firstTarget = null; - if (source.getTargets().size() == 2) { - firstTarget = game.getPermanent(source.getTargets().get(0).getFirstTarget()); - } - return firstTarget; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java b/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java index 8fb3f30b2c9..ebfe51e41a6 100644 --- a/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java +++ b/Mage.Sets/src/mage/cards/o/OKagachiVengefulKami.java @@ -52,7 +52,8 @@ public final class OKagachiVengefulKami extends CardImpl { ability.addTarget(new TargetPermanent(filter)); ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); ability.withInterveningIf(KagachiVengefulKamiCondition.instance); - this.addAbility(ability); + + this.addAbility(ability, new OKagachiVengefulKamiWatcher()); } private OKagachiVengefulKami(final OKagachiVengefulKami card) { diff --git a/Mage.Sets/src/mage/cards/o/ONaginata.java b/Mage.Sets/src/mage/cards/o/ONaginata.java index 104488f9627..16d3ea98f67 100644 --- a/Mage.Sets/src/mage/cards/o/ONaginata.java +++ b/Mage.Sets/src/mage/cards/o/ONaginata.java @@ -9,12 +9,12 @@ import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AttachmentType; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.SubType; +import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.PowerPredicate; +import mage.target.TargetCard; import mage.target.TargetPermanent; import java.util.UUID; @@ -24,7 +24,7 @@ import java.util.UUID; */ public final class ONaginata extends CardImpl { - private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creature with power 3 or greater"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with power 3 or greater"); static { filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 2)); diff --git a/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java b/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java index ad63fcbc14e..72e646c0ab5 100644 --- a/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java +++ b/Mage.Sets/src/mage/cards/o/OildeepGearhulk.java @@ -13,6 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; @@ -81,7 +82,7 @@ class OildeepGearhulkEffect extends OneShotEffect { } controller.lookAtCards(targetPlayer.getName() + " Hand", targetPlayer.getHand(), game); - TargetCard chosenCard = new TargetCardInHand(0, 1, new FilterCard("card to discard")); + TargetCard chosenCard = new TargetCard(0, 1, Zone.HAND, new FilterCard("card to discard")); if (!controller.choose(Outcome.Discard, targetPlayer.getHand(), chosenCard, source, game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java b/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java index 9f1c1c8c302..c41b9ec34a1 100644 --- a/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java +++ b/Mage.Sets/src/mage/cards/o/OrcusPrinceOfUndeath.java @@ -119,8 +119,8 @@ class OrcusPrinceOfUndeathTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, xValue, game); } diff --git a/Mage.Sets/src/mage/cards/p/PairODiceLost.java b/Mage.Sets/src/mage/cards/p/PairODiceLost.java index b6ff94a6107..dd049052382 100644 --- a/Mage.Sets/src/mage/cards/p/PairODiceLost.java +++ b/Mage.Sets/src/mage/cards/p/PairODiceLost.java @@ -101,8 +101,8 @@ class PairODiceLostTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, xValue, game); } diff --git a/Mage.Sets/src/mage/cards/p/PatchUp.java b/Mage.Sets/src/mage/cards/p/PatchUp.java index 16237f5b2e6..6717fd3b0eb 100644 --- a/Mage.Sets/src/mage/cards/p/PatchUp.java +++ b/Mage.Sets/src/mage/cards/p/PatchUp.java @@ -58,8 +58,8 @@ class PatchUpTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 3, game); } diff --git a/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java b/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java index 2170f980cb3..7407044a59c 100644 --- a/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java +++ b/Mage.Sets/src/mage/cards/p/PhenomenonInvestigators.java @@ -105,7 +105,7 @@ class PhenomenonInvestigatorsReturnCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java b/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java index f95a9a46b15..9a9ff1da417 100644 --- a/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java +++ b/Mage.Sets/src/mage/cards/p/PhoenixWardenOfFire.java @@ -94,8 +94,8 @@ class PhoenixWardenOfFireTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game ); diff --git a/Mage.Sets/src/mage/cards/p/PrimordialMist.java b/Mage.Sets/src/mage/cards/p/PrimordialMist.java index 8960f2f2ef2..50718656957 100644 --- a/Mage.Sets/src/mage/cards/p/PrimordialMist.java +++ b/Mage.Sets/src/mage/cards/p/PrimordialMist.java @@ -78,7 +78,7 @@ class PrimordialMistCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/p/ProteanHulk.java b/Mage.Sets/src/mage/cards/p/ProteanHulk.java index afde70d42f8..5a0a7d68168 100644 --- a/Mage.Sets/src/mage/cards/p/ProteanHulk.java +++ b/Mage.Sets/src/mage/cards/p/ProteanHulk.java @@ -64,8 +64,8 @@ class ProteanHulkTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game); } diff --git a/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java b/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java index 4ab7cb7b658..90554e75de2 100644 --- a/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java +++ b/Mage.Sets/src/mage/cards/p/ProtectorOfTheWastes.java @@ -1,36 +1,36 @@ package mage.cards.p; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ExileTargetEffect; -import mage.abilities.keyword.MonstrosityAbility; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.MonstrosityAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterArtifactOrEnchantmentPermanent; +import mage.game.Controllable; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + /** - * * @author Jmlundeen */ public final class ProtectorOfTheWastes extends CardImpl { public ProtectorOfTheWastes(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); - + this.subtype.add(SubType.DRAGON); this.power = new MageInt(5); this.toughness = new MageInt(5); @@ -76,36 +76,19 @@ class ProtectorOfTheWastesTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (!super.canTarget(id, source, game)) { - return false; - } - Permanent permanent = game.getPermanent(id); - if (permanent == null) { - return false; - } - return this.getTargets().stream() + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + + Set usedControllers = this.getTargets().stream() .map(game::getPermanent) .filter(Objects::nonNull) - .noneMatch(perm -> !perm.getId().equals(permanent.getId()) - && perm.isControlledBy(permanent.getControllerId())); - } + .map(Controllable::getControllerId) + .collect(Collectors.toSet()); + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null || usedControllers.contains(permanent.getControllerId()); + }); - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - boolean validTarget = this.getTargets().stream() - .map(game::getPermanent) - .filter(Objects::nonNull) - .noneMatch(perm -> !perm.getId().equals(permanent.getId()) && perm.isControlledBy(permanent.getControllerId())); - if (validTarget) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - } return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/p/PucasMischief.java b/Mage.Sets/src/mage/cards/p/PucasMischief.java index 9b19fbab9b1..7e6c133608b 100644 --- a/Mage.Sets/src/mage/cards/p/PucasMischief.java +++ b/Mage.Sets/src/mage/cards/p/PucasMischief.java @@ -1,21 +1,18 @@ package mage.cards.p; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.filter.predicate.Predicates; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledPermanent; import java.util.HashSet; import java.util.Set; @@ -33,7 +30,7 @@ public final class PucasMischief extends CardImpl { // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. Ability ability = new BeginningOfUpkeepTriggeredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame, rule, false, true), true); - ability.addTarget(new TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_NON_LAND)); ability.addTarget(new PucasMischiefSecondTarget()); this.addAbility(ability); @@ -49,89 +46,53 @@ public final class PucasMischief extends CardImpl { } } -class TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent extends TargetControlledPermanent { - - public TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent() { - super(); - this.filter = this.filter.copy(); - filter.add(Predicates.not(CardType.LAND.getPredicate())); - withTargetName("nonland permanent you control"); - } - - private TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent(final TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent target) { - super(target); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - } - return possibleTargets; - } - - @Override - public TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent copy() { - return new TargetControlledPermanentWithCMCGreaterOrLessThanOpponentPermanent(this); - } -} - class PucasMischiefSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public PucasMischiefSecondTarget() { - super(); - this.filter = this.filter.copy(); - filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(Predicates.not(CardType.LAND.getPredicate())); + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND); withTargetName("permanent an opponent controls with an equal or lesser mana value"); } private PucasMischiefSecondTarget(final PucasMischiefSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.getManaValue() >= opponentPermanent.getManaValue(); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.getManaValue() >= possiblePermanent.getManaValue(); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (firstTarget.getManaValue() >= permanent.getManaValue()) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by cmc + if (ownPermanent.getManaValue() >= permanent.getManaValue()) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); + // AI hint with better outcome return super.chooseTarget(Outcome.GainControl, playerId, source, game); } diff --git a/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java b/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java index ae49d30f05b..812ba34c56e 100644 --- a/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java +++ b/Mage.Sets/src/mage/cards/q/QueenKaylaBinKroog.java @@ -109,26 +109,11 @@ class QueenKaylaBinKroogTarget extends TargetCard { return new QueenKaylaBinKroogTarget(this); } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) { - if (!super.canTarget(playerId, id, ability, game)) { - return false; - } - Card card = game.getCard(id); - return card != null && 1 <= card.getManaValue() && card.getManaValue() <= 3 && this - .getTargets() - .stream() - .map(game::getCard) - .filter(Objects::nonNull) - .mapToInt(MageObject::getManaValue) - .noneMatch(x -> card.getManaValue() == x); - } - - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set manaValues = this + + Set usedManaValues = this .getTargets() .stream() .map(game::getCard) @@ -137,8 +122,9 @@ class QueenKaylaBinKroogTarget extends TargetCard { .collect(Collectors.toSet()); possibleTargets.removeIf(uuid -> { Card card = game.getCard(uuid); - return card != null && manaValues.contains(card.getManaValue()); + return card == null || usedManaValues.contains(card.getManaValue()); }); + return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java b/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java index f53cdc145ba..5ca5445d4bd 100644 --- a/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java +++ b/Mage.Sets/src/mage/cards/r/RaiseTheDraugr.java @@ -58,8 +58,8 @@ class RaiseTheDraugrTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (getTargets().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java b/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java index 6a1fbc46c72..ab15c13a4ae 100644 --- a/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java +++ b/Mage.Sets/src/mage/cards/r/RampagingYaoGuai.java @@ -82,8 +82,8 @@ class RampagingYaoGuaiTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, GetXValue.instance.calculate(game, source, null), game); } diff --git a/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java b/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java index d499b452b17..34bfd6ab556 100644 --- a/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java +++ b/Mage.Sets/src/mage/cards/r/RasputinDreamweaver.java @@ -102,7 +102,7 @@ class RasputinDreamweaverWatcher extends Watcher { filter.add(TappedPredicate.UNTAPPED); } - private final Set startedUntapped = new HashSet<>(0); + private final Set startedUntapped = new HashSet<>(); RasputinDreamweaverWatcher() { super(WatcherScope.GAME); diff --git a/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java b/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java index f5e141d7811..ed4bd054210 100644 --- a/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java +++ b/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java @@ -1,7 +1,6 @@ package mage.cards.r; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.TransformIntoSourceTriggeredAbility; import mage.abilities.common.WerewolfBackTriggeredAbility; @@ -15,13 +14,11 @@ import mage.constants.SubType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetOpponentOrPlaneswalker; import mage.target.targetpointer.EachTargetPointer; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -100,54 +97,30 @@ class RavagerOfTheFellsEffect extends OneShotEffect { class RavagerOfTheFellsTarget extends TargetPermanent { RavagerOfTheFellsTarget() { - super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); + super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE); } private RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (player == null) { - return false; - } - UUID firstTarget = player.getId(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/r/ReapIntellect.java b/Mage.Sets/src/mage/cards/r/ReapIntellect.java index 1e9f975dbfc..028cca76fd4 100644 --- a/Mage.Sets/src/mage/cards/r/ReapIntellect.java +++ b/Mage.Sets/src/mage/cards/r/ReapIntellect.java @@ -85,7 +85,7 @@ class ReapIntellectEffect extends OneShotEffect { int xCost = Math.min(CardUtil.getSourceCostsTag(game, source, "X", 0), targetPlayer.getHand().size()); TargetCard target = new TargetCard(0, xCost, Zone.HAND, filterNonLands); target.withNotTarget(true); - controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game); + controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game); for (UUID cardId : target.getTargets()) { Card chosenCard = game.getCard(cardId); if (chosenCard != null) { diff --git a/Mage.Sets/src/mage/cards/r/Reciprocate.java b/Mage.Sets/src/mage/cards/r/Reciprocate.java index 500e2f03c4c..ddb05c1c5ad 100644 --- a/Mage.Sets/src/mage/cards/r/Reciprocate.java +++ b/Mage.Sets/src/mage/cards/r/Reciprocate.java @@ -1,20 +1,15 @@ package mage.cards.r; -import mage.MageObject; -import mage.abilities.Ability; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; import mage.target.TargetPermanent; -import mage.watchers.common.PlayerDamagedBySourceWatcher; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; /** @@ -22,12 +17,18 @@ import java.util.UUID; */ public final class Reciprocate extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn"); + + static { + filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU)); + } + public Reciprocate(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); // Exile target creature that dealt damage to you this turn. this.getSpellAbility().addEffect(new ExileTargetEffect()); - this.getSpellAbility().addTarget(new ReciprocateTarget()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); } private Reciprocate(final Reciprocate card) { @@ -40,66 +41,3 @@ public final class Reciprocate extends CardImpl { } } - -class ReciprocateTarget extends TargetPermanent { - - public ReciprocateTarget() { - super(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); - targetName = "creature that dealt damage to you this turn"; - } - - private ReciprocateTarget(final ReciprocateTarget target) { - super(target); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, source.getControllerId()); - if (watcher != null && watcher.hasSourceDoneDamage(id, game)) { - return super.canTarget(id, source, game); - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && watcher != null && watcher.hasSourceDoneDamage(targetId, game)) { - possibleTargets.add(targetId); - } - } - return possibleTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets == 0) { - return true; - } - int count = 0; - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) - && watcher != null && watcher.hasSourceDoneDamage(permanent.getId(), game)) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - } - return false; - } - - @Override - public ReciprocateTarget copy() { - return new ReciprocateTarget(this); - } -} diff --git a/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java b/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java index 0d616b47e77..72f208a3d11 100644 --- a/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java +++ b/Mage.Sets/src/mage/cards/r/ReckonerShakedown.java @@ -7,6 +7,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; @@ -68,7 +69,7 @@ class ReckonerShakedownEffect extends OneShotEffect { return false; } player.revealCards(source, player.getHand(), game); - TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_A_NON_LAND); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); controller.choose(Outcome.Discard, player.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card != null) { diff --git a/Mage.Sets/src/mage/cards/r/Retether.java b/Mage.Sets/src/mage/cards/r/Retether.java index 97db1ae084e..23315d003bd 100644 --- a/Mage.Sets/src/mage/cards/r/Retether.java +++ b/Mage.Sets/src/mage/cards/r/Retether.java @@ -87,7 +87,7 @@ class RetetherEffect extends OneShotEffect { for (Ability ability : aura.getAbilities()) { if (ability instanceof SpellAbility) { for (Target abilityTarget : ability.getTargets()) { - if (abilityTarget.possibleTargets(controller.getId(), game).contains(permanent.getId())) { + if (abilityTarget.possibleTargets(controller.getId(), source, game).contains(permanent.getId())) { target = abilityTarget.copy(); break auraLegalitySearch; } diff --git a/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java b/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java index 7c77ece8f41..2e00840a16d 100644 --- a/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java +++ b/Mage.Sets/src/mage/cards/r/ReturnFromExtinction.java @@ -58,8 +58,8 @@ class ReturnFromExtinctionTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (getTargets().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java index 50f9f129c53..8309d525b8d 100644 --- a/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java +++ b/Mage.Sets/src/mage/cards/r/ReunionOfTheHouse.java @@ -61,8 +61,8 @@ class ReunionOfTheHouseTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, m -> m.getPower().getValue(), 10, game); } diff --git a/Mage.Sets/src/mage/cards/r/RevealingEye.java b/Mage.Sets/src/mage/cards/r/RevealingEye.java index cbd392e737c..284c7eee69b 100644 --- a/Mage.Sets/src/mage/cards/r/RevealingEye.java +++ b/Mage.Sets/src/mage/cards/r/RevealingEye.java @@ -11,6 +11,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -82,7 +83,7 @@ class RevealingEyeEffect extends OneShotEffect { if (opponent.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1) { return true; } - TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_NON_LAND); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_NON_LAND); controller.choose(outcome, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/r/RevivalExperiment.java b/Mage.Sets/src/mage/cards/r/RevivalExperiment.java index bf79a994bd5..caa7c28ec3f 100644 --- a/Mage.Sets/src/mage/cards/r/RevivalExperiment.java +++ b/Mage.Sets/src/mage/cards/r/RevivalExperiment.java @@ -120,7 +120,7 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/r/RiftElemental.java b/Mage.Sets/src/mage/cards/r/RiftElemental.java index cb9a06e5890..dab8891b0cb 100644 --- a/Mage.Sets/src/mage/cards/r/RiftElemental.java +++ b/Mage.Sets/src/mage/cards/r/RiftElemental.java @@ -99,7 +99,7 @@ class RiftElementalCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { Target target = new TargetPermanentOrSuspendedCard(filter, true); - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/r/RivalsDuel.java b/Mage.Sets/src/mage/cards/r/RivalsDuel.java index 8e4851faec7..1f31733aa21 100644 --- a/Mage.Sets/src/mage/cards/r/RivalsDuel.java +++ b/Mage.Sets/src/mage/cards/r/RivalsDuel.java @@ -90,8 +90,8 @@ class RivalsDuelTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/r/RoguesGallery.java b/Mage.Sets/src/mage/cards/r/RoguesGallery.java index e7769a5831e..2dad3fa0abd 100644 --- a/Mage.Sets/src/mage/cards/r/RoguesGallery.java +++ b/Mage.Sets/src/mage/cards/r/RoguesGallery.java @@ -84,7 +84,7 @@ class RoguesGalleryTarget extends TargetCardInYourGraveyard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/r/RoleReversal.java b/Mage.Sets/src/mage/cards/r/RoleReversal.java index 61a31de577e..12d30093ed1 100644 --- a/Mage.Sets/src/mage/cards/r/RoleReversal.java +++ b/Mage.Sets/src/mage/cards/r/RoleReversal.java @@ -53,8 +53,8 @@ class TargetPermanentsThatShareCardType extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { if (!getTargets().isEmpty()) { Permanent targetOne = game.getPermanent(getTargets().get(0)); Permanent targetTwo = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/r/RunAwayTogether.java b/Mage.Sets/src/mage/cards/r/RunAwayTogether.java index d5107d33568..62ab7a5d2f7 100644 --- a/Mage.Sets/src/mage/cards/r/RunAwayTogether.java +++ b/Mage.Sets/src/mage/cards/r/RunAwayTogether.java @@ -59,8 +59,8 @@ class RunAwayTogetherTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java b/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java index 44072938fec..ffc40661fda 100644 --- a/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java +++ b/Mage.Sets/src/mage/cards/s/ScoutForSurvivors.java @@ -96,8 +96,8 @@ class ScoutForSurvivorsTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 3, game ); diff --git a/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java b/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java index 6037655954e..a2276bf5f14 100644 --- a/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java +++ b/Mage.Sets/src/mage/cards/s/ScreamsFromWithin.java @@ -77,7 +77,7 @@ class ScreamsFromWithinEffect extends OneShotEffect { if (aura != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD && player != null && player.getGraveyard().contains(source.getSourceId())) { for (Permanent creaturePermanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, source.getControllerId(), source, game)) { for (Target target : aura.getSpellAbility().getTargets()) { - if (target.canTarget(creaturePermanent.getId(), game)) { + if (target.canTarget(creaturePermanent.getId(), source, game)) { return player.moveCards(aura, Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/cards/s/SearingBlaze.java b/Mage.Sets/src/mage/cards/s/SearingBlaze.java index 9c76774aa3e..113afd02074 100644 --- a/Mage.Sets/src/mage/cards/s/SearingBlaze.java +++ b/Mage.Sets/src/mage/cards/s/SearingBlaze.java @@ -1,6 +1,5 @@ package mage.cards.s; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -11,19 +10,16 @@ import mage.constants.Outcome; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.watchers.common.LandfallWatcher; -import java.util.HashSet; import java.util.Set; import java.util.UUID; /** - * @author BetaSteward_at_googlemail.com - * @author North + * @author BetaSteward_at_googlemail.com, North */ public final class SearingBlaze extends CardImpl { @@ -111,21 +107,21 @@ class SearingBlazeTarget extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } + return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/s/SeverancePriest.java b/Mage.Sets/src/mage/cards/s/SeverancePriest.java index ff17d0efc7b..6f040691e1d 100644 --- a/Mage.Sets/src/mage/cards/s/SeverancePriest.java +++ b/Mage.Sets/src/mage/cards/s/SeverancePriest.java @@ -13,6 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.token.SpiritXXToken; @@ -86,7 +87,7 @@ class SeverancePriestEffect extends OneShotEffect { return false; } opponent.revealCards(source, opponent.getHand(), game); - TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_NON_LAND); + TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_NON_LAND); controller.choose(Outcome.Discard, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); return card != null && controller.moveCardsToExile( diff --git a/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java b/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java index 40772d276f3..e7983619e12 100644 --- a/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java +++ b/Mage.Sets/src/mage/cards/s/ShiftingLoyalties.java @@ -53,8 +53,8 @@ class TargetPermanentsThatShareCardType extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { if (!getTargets().isEmpty()) { Permanent targetOne = game.getPermanent(getTargets().get(0)); Permanent targetTwo = game.getPermanent(id); diff --git a/Mage.Sets/src/mage/cards/s/ShimianSpecter.java b/Mage.Sets/src/mage/cards/s/ShimianSpecter.java index 408f6f34638..d79a441e3d7 100644 --- a/Mage.Sets/src/mage/cards/s/ShimianSpecter.java +++ b/Mage.Sets/src/mage/cards/s/ShimianSpecter.java @@ -88,9 +88,7 @@ class ShimianSpecterEffect extends SearchTargetGraveyardHandLibraryForCardNameAn // You choose a nonland card from it TargetCard target = new TargetCard(Zone.HAND, new FilterNonlandCard()); - target.withNotTarget(true); - if (target.canChoose(controller.getId(), source, game) - && controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { return applySearchAndExile(game, source, CardUtil.getCardNameForSameNameSearch(game.getCard(target.getFirstTarget())), getTargetPointer().getFirst(game, source)); } } diff --git a/Mage.Sets/src/mage/cards/s/SirenStormtamer.java b/Mage.Sets/src/mage/cards/s/SirenStormtamer.java index 4800139e41d..55612b969cc 100644 --- a/Mage.Sets/src/mage/cards/s/SirenStormtamer.java +++ b/Mage.Sets/src/mage/cards/s/SirenStormtamer.java @@ -1,9 +1,6 @@ package mage.cards.s; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -15,19 +12,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.filter.Filter; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.Spell; -import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.target.Target; -import mage.target.TargetObject; +import mage.target.TargetStackObject; import mage.target.Targets; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author spjspj */ public final class SirenStormtamer extends CardImpl { @@ -46,7 +42,7 @@ public final class SirenStormtamer extends CardImpl { // {U}, Sacrifice Siren Stormtamer: Counter target spell or ability that targets you or a creature you control. Ability ability = new SimpleActivatedAbility(new CounterTargetEffect(), new ManaCostsImpl<>("{U}")); - ability.addTarget(new SirenStormtamerTargetObject()); + ability.addTarget(new SirenStormtamerTarget()); ability.addCost(new SacrificeSourceCost()); this.addAbility(ability); @@ -62,97 +58,45 @@ public final class SirenStormtamer extends CardImpl { } } -class SirenStormtamerTargetObject extends TargetObject { +class SirenStormtamerTarget extends TargetStackObject { - public SirenStormtamerTargetObject() { - this.minNumberOfTargets = 1; - this.maxNumberOfTargets = 1; - this.zone = Zone.STACK; - this.targetName = "spell or ability that targets you or a creature you control"; + public SirenStormtamerTarget() { + super(); + withTargetName("spell or ability that targets you or a creature you control"); } - private SirenStormtamerTargetObject(final SirenStormtamerTargetObject target) { + private SirenStormtamerTarget(final SirenStormtamerTarget target) { super(target); } @Override - public boolean canTarget(UUID id, Ability source, Game game) { - StackObject stackObject = game.getStack().getStackObject(id); - return (stackObject instanceof Spell) || (stackObject instanceof StackAbility); - } + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return canChoose(sourceControllerId, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { + Set targetsMe = new HashSet<>(); for (StackObject stackObject : game.getStack()) { - if ((stackObject instanceof Spell) || (stackObject instanceof StackAbility)) { - Targets objectTargets = stackObject.getStackAbility().getTargets(); - if (!objectTargets.isEmpty()) { - for (Target target : objectTargets) { - for (UUID targetId : target.getTargets()) { - Permanent targetedPermanent = game.getPermanentOrLKIBattlefield(targetId); - if (targetedPermanent != null - && targetedPermanent.isControlledBy(sourceControllerId) - && targetedPermanent.isCreature(game)) { - return true; - } - - if (sourceControllerId.equals(targetId)) { - return true; - } - - } + Targets objectTargets = stackObject.getStackAbility().getTargets(); + for (Target target : objectTargets) { + for (UUID targetId : target.getTargets()) { + Permanent targetedPermanent = game.getPermanentOrLKIBattlefield(targetId); + if (targetedPermanent != null + && targetedPermanent.isControlledBy(sourceControllerId) + && targetedPermanent.isCreature(game)) { + targetsMe.add(stackObject.getId()); + } + if (sourceControllerId.equals(targetId)) { + targetsMe.add(stackObject.getId()); } } } } - return false; - } + possibleTargets.removeIf(id -> !targetsMe.contains(id)); - @Override - public Set possibleTargets(UUID sourceControllerId, - Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (StackObject stackObject : game.getStack()) { - if ((stackObject instanceof Spell) || (stackObject instanceof StackAbility)) { - Targets objectTargets = stackObject.getStackAbility().getTargets(); - if (!objectTargets.isEmpty()) { - for (Target target : objectTargets) { - for (UUID targetId : target.getTargets()) { - Permanent targetedPermanent = game.getPermanentOrLKIBattlefield(targetId); - if (targetedPermanent != null - && targetedPermanent.isControlledBy(sourceControllerId) - && targetedPermanent.isCreature(game)) { - possibleTargets.add(stackObject.getId()); - } - - if (sourceControllerId.equals(targetId)) { - possibleTargets.add(stackObject.getId()); - } - } - } - } - } - } return possibleTargets; } @Override - public SirenStormtamerTargetObject copy() { - return new SirenStormtamerTargetObject(this); - } - - @Override - public Filter getFilter() { - throw new UnsupportedOperationException("Not supported yet."); + public SirenStormtamerTarget copy() { + return new SirenStormtamerTarget(this); } } diff --git a/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java b/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java index c26c14ca77b..f18b7cf1613 100644 --- a/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java +++ b/Mage.Sets/src/mage/cards/s/SoulOfShandalar.java @@ -1,11 +1,7 @@ package mage.cards.s; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.ExileSourceFromGraveCost; @@ -15,17 +11,19 @@ import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetPlayerOrPlaneswalker; +import java.util.Set; +import java.util.UUID; + /** * @author noxx */ @@ -96,54 +94,30 @@ class SoulOfShandalarEffect extends OneShotEffect { class SoulOfShandalarTarget extends TargetPermanent { public SoulOfShandalarTarget() { - super(0, 1, new FilterCreaturePermanent("creature that the targeted player or planeswalker's controller controls"), false); + super(0, 1, new FilterCreaturePermanent("creature that the targeted player or planeswalker's controller controls")); } private SoulOfShandalarTarget(final SoulOfShandalarTarget target) { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (player == null) { - return false; - } - UUID firstTarget = player.getId(); - Permanent permanent = game.getPermanent(id); - if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) { - return super.canTarget(id, source, game); - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); - Set possibleTargets = new HashSet<>(); - MageObject object = game.getObject(source); + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - for (StackObject item : game.getState().getStack()) { - if (item.getId().equals(source.getSourceId())) { - object = item; - } - if (item.getSourceId().equals(source.getSourceId())) { - object = item; - } + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); } - if (object instanceof StackObject) { - UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget(); - Player player = game.getPlayerOrPlaneswalkerController(playerId); - if (player != null) { - for (UUID targetId : availablePossibleTargets) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.isControlledBy(player.getId())) { - possibleTargets.add(targetId); - } - } - } - } return possibleTargets; } diff --git a/Mage.Sets/src/mage/cards/s/SoulSearch.java b/Mage.Sets/src/mage/cards/s/SoulSearch.java index 75da014f391..8d53266e750 100644 --- a/Mage.Sets/src/mage/cards/s/SoulSearch.java +++ b/Mage.Sets/src/mage/cards/s/SoulSearch.java @@ -69,7 +69,7 @@ class SoulSearchEffect extends OneShotEffect { if (opponent.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1) { return true; } - TargetCard target = new TargetCardInHand(StaticFilters.FILTER_CARD_NON_LAND); + TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_NON_LAND); controller.choose(Outcome.Discard, opponent.getHand(), target, source, game); Card card = game.getCard(target.getFirstTarget()); if (card == null) { diff --git a/Mage.Sets/src/mage/cards/s/Spawnbroker.java b/Mage.Sets/src/mage/cards/s/Spawnbroker.java index 476dc520868..bad7f039c65 100644 --- a/Mage.Sets/src/mage/cards/s/Spawnbroker.java +++ b/Mage.Sets/src/mage/cards/s/Spawnbroker.java @@ -1,18 +1,19 @@ - package mage.cards.s; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; -import mage.target.common.TargetControlledPermanent; import java.util.HashSet; import java.util.Set; @@ -33,8 +34,8 @@ public final class Spawnbroker extends CardImpl { this.toughness = new MageInt(1); // When Spawnbroker enters the battlefield, you may exchange control of target creature you control and target creature with power less than or equal to that creature's power an opponent controls. - Ability ability = new EntersBattlefieldTriggeredAbility(new ExchangeControlTargetEffect(Duration.Custom, rule, false, true), true); - ability.addTarget(new TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent()); + Ability ability = new EntersBattlefieldTriggeredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame, rule, false, true), true); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_CREATURE)); ability.addTarget(new SpawnbrokerSecondTarget()); this.addAbility(ability); @@ -50,89 +51,53 @@ public final class Spawnbroker extends CardImpl { } } -class TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent extends TargetControlledPermanent { - - public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent() { - super(); - this.filter = this.filter.copy(); - filter.add(CardType.CREATURE.getPredicate()); - withTargetName("creature you control"); - } - - private TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent(final TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent target) { - super(target); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - } - return possibleTargets; - } - - @Override - public TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent copy() { - return new TargetControlledCreatureWithPowerGreaterOrLessThanOpponentPermanent(this); - } -} - class SpawnbrokerSecondTarget extends TargetPermanent { - private Permanent firstTarget = null; - public SpawnbrokerSecondTarget() { - super(); - this.filter = this.filter.copy(); - filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(CardType.CREATURE.getPredicate()); + super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE); withTargetName("creature with power less than or equal to that creature's power an opponent controls"); } private SpawnbrokerSecondTarget(final SpawnbrokerSecondTarget target) { super(target); - this.firstTarget = target.firstTarget; } @Override public boolean canTarget(UUID id, Ability source, Game game) { - if (super.canTarget(id, source, game)) { - Permanent target1 = game.getPermanent(source.getFirstTarget()); - Permanent opponentPermanent = game.getPermanent(id); - if (target1 != null && opponentPermanent != null) { - return target1.getPower().getValue() >= opponentPermanent.getPower().getValue(); - } + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + Permanent possiblePermanent = game.getPermanent(id); + if (ownPermanent == null || possiblePermanent == null) { + return false; } - return false; + return super.canTarget(id, source, game) && ownPermanent.getPower().getValue() >= possiblePermanent.getPower().getValue(); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - if (firstTarget != null) { - MageObject targetSource = game.getObject(source); - if (targetSource != null) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - if (firstTarget.getPower().getValue() >= permanent.getPower().getValue()) { - possibleTargets.add(permanent.getId()); - } - } + + Permanent ownPermanent = game.getPermanent(source.getFirstTarget()); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { + if (ownPermanent == null) { + // playable or first target not yet selected + // use all + possibleTargets.add(permanent.getId()); + } else { + // real + // filter by power + if (ownPermanent.getPower().getValue() >= permanent.getPower().getValue()) { + possibleTargets.add(permanent.getId()); } } } - return possibleTargets; + possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id)); + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { - firstTarget = game.getPermanent(source.getFirstTarget()); + // AI hint with better outcome return super.chooseTarget(Outcome.GainControl, playerId, source, game); } diff --git a/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java b/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java index 85de10c7bbd..015a0bc0950 100644 --- a/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java +++ b/Mage.Sets/src/mage/cards/s/SpearOfHeliod.java @@ -18,7 +18,6 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate; import mage.target.Target; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -27,8 +26,7 @@ import java.util.UUID; */ public final class SpearOfHeliod extends CardImpl { - private static final FilterCreaturePermanent filter - = new FilterCreaturePermanent("creature that dealt damage to you this turn"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn"); static { filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU)); @@ -38,15 +36,13 @@ public final class SpearOfHeliod extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.ARTIFACT}, "{1}{W}{W}"); this.supertype.add(SuperType.LEGENDARY); - // Creatures you control get +1/+1. this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield))); // {1}{W}{W}, {T}: Destroy target creature that dealt damage to you this turn. Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new ManaCostsImpl<>("{1}{W}{W}")); ability.addCost(new TapSourceCost()); - Target target = new TargetPermanent(filter); - ability.addTarget(target); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SpectersShriek.java b/Mage.Sets/src/mage/cards/s/SpectersShriek.java index 0374dacc8f7..e50892777cc 100644 --- a/Mage.Sets/src/mage/cards/s/SpectersShriek.java +++ b/Mage.Sets/src/mage/cards/s/SpectersShriek.java @@ -77,8 +77,7 @@ class SpectersShriekEffect extends OneShotEffect { return false; } TargetCard target = new TargetCard(0, 1, Zone.HAND, new FilterNonlandCard()); - target.withNotTarget(true); - if (!controller.chooseTarget(Outcome.Benefit, player.getHand(), target, source, game)) { + if (!controller.choose(Outcome.Benefit, player.getHand(), target, source, game)) { return false; } Card card = game.getCard(target.getFirstTarget()); diff --git a/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java b/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java index 7d5070a531b..f36c37b1baa 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java +++ b/Mage.Sets/src/mage/cards/s/SphinxOfTheChimes.java @@ -9,7 +9,6 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.*; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.mageobject.NamePredicate; @@ -82,27 +81,22 @@ class TargetTwoNonLandCardsWithSameNameInHand extends TargetCardInHand { } @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set newPossibleTargets = new HashSet<>(); + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); Player player = game.getPlayer(sourceControllerId); if (player == null) { - return newPossibleTargets; - } - for (Card card : player.getHand().getCards(filter, game)) { - possibleTargets.add(card.getId()); + return possibleTargets; } - Cards cardsToCheck = new CardsImpl(); - cardsToCheck.addAll(possibleTargets); + Cards cardsToCheck = new CardsImpl(player.getHand().getCards(filter, game)); if (targets.size() == 1) { - // first target is laready chosen, now only targets with the same name are selectable + // first target is already chosen, now only targets with the same name are selectable for (Map.Entry entry : targets.entrySet()) { Card chosenCard = cardsToCheck.get(entry.getKey(), game); if (chosenCard != null) { for (UUID cardToCheck : cardsToCheck) { if (!cardToCheck.equals(chosenCard.getId()) && chosenCard.getName().equals(game.getCard(cardToCheck).getName())) { - newPossibleTargets.add(cardToCheck); + possibleTargets.add(cardToCheck); } } } @@ -116,56 +110,13 @@ class TargetTwoNonLandCardsWithSameNameInHand extends TargetCardInHand { nameFilter.add(new NamePredicate(nameToSearch)); if (cardsToCheck.count(nameFilter, game) > 1) { - newPossibleTargets.add(cardToCheck); + possibleTargets.add(cardToCheck); } } } } - return newPossibleTargets; - } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - Cards cardsToCheck = new CardsImpl(); - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return false; - } - for (Card card : player.getHand().getCards(filter, game)) { - cardsToCheck.add(card.getId()); - } - int possibleCards = 0; - for (Card card : cardsToCheck.getCards(game)) { - String nameToSearch = CardUtil.getCardNameForSameNameSearch(card); - FilterCard nameFilter = new FilterCard(); - nameFilter.add(new NamePredicate(nameToSearch)); - - if (cardsToCheck.count(nameFilter, game) > 1) { - ++possibleCards; - } - } - return possibleCards > 0; - } - - @Override - public boolean canTarget(UUID id, Game game) { - if (super.canTarget(id, game)) { - Card card = game.getCard(id); - if (card != null) { - if (targets.size() == 1) { - Card card2 = game.getCard(targets.entrySet().iterator().next().getKey()); - return CardUtil.haveSameNames(card2, card); - } else { - String nameToSearch = CardUtil.getCardNameForSameNameSearch(card); - FilterCard nameFilter = new FilterCard(); - nameFilter.add(new NamePredicate(nameToSearch)); - - Player player = game.getPlayer(card.getOwnerId()); - return player != null && player.getHand().getCards(nameFilter, game).size() > 1; - } - } - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage.Sets/src/mage/cards/s/StickTogether.java b/Mage.Sets/src/mage/cards/s/StickTogether.java index 50f8d9b917c..58064f05a35 100644 --- a/Mage.Sets/src/mage/cards/s/StickTogether.java +++ b/Mage.Sets/src/mage/cards/s/StickTogether.java @@ -139,14 +139,7 @@ class StickTogetherTarget extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } - - static int checkTargetCount(Ability source, Game game) { - List permanents = game - .getBattlefield() - .getActivePermanents(filterParty, source.getControllerId(), source, game); - return subTypeAssigner.getRoleCount(new CardsImpl(permanents), game); - } } diff --git a/Mage.Sets/src/mage/cards/s/StoneGiant.java b/Mage.Sets/src/mage/cards/s/StoneGiant.java index 5a781fa56be..a0f1062dfc0 100644 --- a/Mage.Sets/src/mage/cards/s/StoneGiant.java +++ b/Mage.Sets/src/mage/cards/s/StoneGiant.java @@ -68,13 +68,13 @@ class StoneGiantTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent sourceCreature = game.getPermanent(source.getSourceId()); Permanent targetCreature = game.getPermanent(id); if (targetCreature != null && sourceCreature != null && targetCreature.getToughness().getValue() < sourceCreature.getPower().getValue()) { - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SynthesisPod.java b/Mage.Sets/src/mage/cards/s/SynthesisPod.java index b668b9fe5bc..d1d90bedd00 100644 --- a/Mage.Sets/src/mage/cards/s/SynthesisPod.java +++ b/Mage.Sets/src/mage/cards/s/SynthesisPod.java @@ -93,7 +93,7 @@ class SynthesisPodCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage.Sets/src/mage/cards/t/Technomancer.java b/Mage.Sets/src/mage/cards/t/Technomancer.java index 830c673ba3d..1db84f1f7b8 100644 --- a/Mage.Sets/src/mage/cards/t/Technomancer.java +++ b/Mage.Sets/src/mage/cards/t/Technomancer.java @@ -107,8 +107,8 @@ class TechnomancerTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 6, game); } diff --git a/Mage.Sets/src/mage/cards/t/TemptingWurm.java b/Mage.Sets/src/mage/cards/t/TemptingWurm.java index 1d808530d7e..aeafeff497c 100644 --- a/Mage.Sets/src/mage/cards/t/TemptingWurm.java +++ b/Mage.Sets/src/mage/cards/t/TemptingWurm.java @@ -1,34 +1,30 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; import mage.target.Target; -import mage.target.common.TargetCardInHand; +import mage.target.TargetCard; + +import java.util.UUID; /** - * * @author Eirkei */ public final class TemptingWurm extends CardImpl { public TemptingWurm(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); this.subtype.add(SubType.WURM); this.power = new MageInt(5); this.toughness = new MageInt(5); @@ -59,43 +55,36 @@ class TemptingWurmEffect extends OneShotEffect { CardType.LAND.getPredicate() )); } - + TemptingWurmEffect() { super(Outcome.Detriment); this.staticText = "each opponent may put any number of artifact, creature, enchantment, and/or land cards from their hand onto the battlefield."; } - + @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - + if (controller != null) { Cards cards = new CardsImpl(); - + for (UUID playerId : game.getOpponents(controller.getId())) { Player opponent = game.getPlayer(playerId); - - if (opponent != null){ - Target target = new TargetCardInHand(0, Integer.MAX_VALUE, filter); - - if (target.canChoose(opponent.getId(), source, game)) { - if (opponent.chooseUse(Outcome.PutCardInPlay , "Put any artifact, creature, enchantment, and/or land cards cards from your hand onto the battlefield?", source, game)) { - if (target.chooseTarget(Outcome.PutCardInPlay, opponent.getId(), source, game)) { - for (UUID cardId: target.getTargets()){ - Card card = game.getCard(cardId); - - if (card != null) { - cards.add(card); - } - } + if (opponent != null) { + Target target = new TargetCard(0, Integer.MAX_VALUE, Zone.HAND, filter).withChooseHint("put from hand to battlefield"); + if (target.chooseTarget(Outcome.PutCardInPlay, opponent.getId(), source, game)) { + for (UUID cardId : target.getTargets()) { + Card card = game.getCard(cardId); + if (card != null) { + cards.add(card); } } } } } - + controller.moveCards(cards.getCards(game), Zone.BATTLEFIELD, source, game, false, false, true, null); - + return true; } @@ -105,7 +94,7 @@ class TemptingWurmEffect extends OneShotEffect { private TemptingWurmEffect(final TemptingWurmEffect effect) { super(effect); } - + @Override public TemptingWurmEffect copy() { return new TemptingWurmEffect(this); diff --git a/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java b/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java index 1b00eec46b6..107603aedee 100644 --- a/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java +++ b/Mage.Sets/src/mage/cards/t/TheCapitolineTriad.java @@ -1,9 +1,7 @@ package mage.cards.t; import java.awt.*; -import java.util.Collection; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import mage.MageInt; import mage.MageObject; @@ -170,7 +168,10 @@ class TheCapitolineTriadTarget extends TargetCardInYourGraveyard { return false; } // Check that exiling all the possible cards would have >= 4 different card types - return metCondition(this.possibleTargets(sourceControllerId, source, game), game); + Set idsToCheck = new HashSet<>(); + idsToCheck.addAll(this.getTargets()); + idsToCheck.addAll(this.possibleTargets(sourceControllerId, source, game)); + return metCondition(idsToCheck, game); } private static int manaValueOfSelection(Collection cardsIds, Game game) { diff --git a/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java b/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java index a4eb35e9231..e6cfac11e2b 100644 --- a/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java +++ b/Mage.Sets/src/mage/cards/t/TheTricksterGodsHeist.java @@ -95,8 +95,8 @@ class TheTricksterGodsHeistTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (getTargets().isEmpty()) { @@ -118,7 +118,7 @@ class TheTricksterGodsHeistTarget extends TargetPermanent { return false; } for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { + if (!isNotTarget() && !permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { continue; } for (CardType cardType : permanent.getCardType(game)) { diff --git a/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java b/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java index b8e1ebd88f9..6f628a029f1 100644 --- a/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java +++ b/Mage.Sets/src/mage/cards/t/TheWarInHeaven.java @@ -128,8 +128,8 @@ class TheWarInHeavenTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 8, game); } diff --git a/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java b/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java index 6c2c76bd899..51e50cd3a1e 100644 --- a/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java +++ b/Mage.Sets/src/mage/cards/t/TocasiaDigSiteMentor.java @@ -93,8 +93,8 @@ class TocasiaDigSiteMentorTarget extends TargetCardInYourGraveyard { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - return super.canTarget(controllerId, id, source, game) + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) && CardUtil.checkCanTargetTotalValueLimit( this.getTargets(), id, MageObject::getManaValue, 10, game); } diff --git a/Mage.Sets/src/mage/cards/v/VATS.java b/Mage.Sets/src/mage/cards/v/VATS.java index ebf4b169f15..057affe65ab 100644 --- a/Mage.Sets/src/mage/cards/v/VATS.java +++ b/Mage.Sets/src/mage/cards/v/VATS.java @@ -62,8 +62,8 @@ class VATSTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (this.getTargets().isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java index 667cebb3c60..fb5f2ea73d9 100644 --- a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java +++ b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java @@ -123,8 +123,7 @@ class ValkiGodOfLiesRevealExileEffect extends OneShotEffect { TargetCard targetToExile = new TargetCard(Zone.HAND, StaticFilters.FILTER_CARD_CREATURE); targetToExile.withChooseHint("card to exile"); targetToExile.withNotTarget(true); - if (opponent.getHand().count(StaticFilters.FILTER_CARD_CREATURE, game) > 0 && - controller.choose(Outcome.Exile, opponent.getHand(), targetToExile, source, game)) { + if (controller.choose(Outcome.Exile, opponent.getHand(), targetToExile, source, game)) { Card targetedCardToExile = game.getCard(targetToExile.getFirstTarget()); if (targetedCardToExile != null && game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { diff --git a/Mage.Sets/src/mage/cards/w/WeightOfConscience.java b/Mage.Sets/src/mage/cards/w/WeightOfConscience.java index 063c4dec9a6..222e01d0130 100644 --- a/Mage.Sets/src/mage/cards/w/WeightOfConscience.java +++ b/Mage.Sets/src/mage/cards/w/WeightOfConscience.java @@ -1,6 +1,5 @@ package mage.cards.w; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; @@ -11,11 +10,13 @@ import mage.abilities.effects.common.combat.CantAttackAttachedEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicate; import mage.filter.predicate.mageobject.SharesCreatureTypePredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; @@ -25,7 +26,10 @@ import mage.target.TargetPermanent; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetCreaturePermanent; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; /** * @author emerald000 @@ -66,7 +70,6 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { static { filterUntapped.add(TappedPredicate.UNTAPPED); - filterUntapped.add(WeightOfConsciencePredicate.instance); } WeightOfConscienceTarget() { @@ -79,13 +82,15 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = new HashSet<>(); + Player player = game.getPlayer(sourceControllerId); - Set possibleTargets = new HashSet<>(0); if (player == null) { return possibleTargets; } - // Choosing first target + if (this.getTargets().isEmpty()) { + // choosing first target - use any permanent with shared types List permanentList = game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, source, game); if (permanentList.size() < 2) { return possibleTargets; @@ -101,8 +106,8 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { possibleTargets.add(permanent.getId()); } } - } // Choosing second target - else { + } else { + // choosing second target - must have shared type with first target Permanent firstTargetCreature = game.getPermanent(this.getFirstTarget()); if (firstTargetCreature == null) { return possibleTargets; @@ -115,50 +120,8 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { } } } - return possibleTargets; - } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - for (Permanent permanent1 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, source, game)) { - for (Permanent permanent2 : game.getBattlefield().getActivePermanents(filterUntapped, sourceControllerId, source, game)) { - if (!Objects.equals(permanent1, permanent2) && permanent1.shareCreatureTypes(game, permanent2)) { - return true; - } - } - } - return false; - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (!super.canTarget(id, game)) { - return false; - } - Permanent targetPermanent = game.getPermanent(id); - if (targetPermanent == null) { - return false; - } - if (this.getTargets().isEmpty()) { - List permanentList = game.getBattlefield().getActivePermanents(filterUntapped, source.getControllerId(), source, game); - if (permanentList.size() < 2) { - return false; - } - for (Permanent permanent : permanentList) { - if (permanent.isAllCreatureTypes(game)) { - return true; - } - FilterPermanent filter = filterUntapped.copy(); - filter.add(new SharesCreatureTypePredicate(permanent)); - if (game.getBattlefield().count(filter, source.getControllerId(), source, game) > 1) { - return true; - } - } - } else { - Permanent firstTarget = game.getPermanent(this.getTargets().get(0)); - return firstTarget != null && firstTarget.shareCreatureTypes(game, targetPermanent); - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -166,17 +129,3 @@ class WeightOfConscienceTarget extends TargetControlledPermanent { return new WeightOfConscienceTarget(this); } } - -enum WeightOfConsciencePredicate implements Predicate { - instance; - - @Override - public boolean apply(MageObject input, Game game) { - return input.isAllCreatureTypes(game) - || input - .getSubtype(game) - .stream() - .map(SubType::getSubTypeSet) - .anyMatch(SubTypeSet.CreatureType::equals); - } -} diff --git a/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java b/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java index e246f1e5933..52d8f8d66df 100644 --- a/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java +++ b/Mage.Sets/src/mage/cards/w/WhimsOfTheFates.java @@ -175,20 +175,17 @@ class TargetSecondPilePermanent extends TargetPermanent { this.firstPile = firstPile; } - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game, boolean flag) { - if (firstPile.contains(id)) { - return false; - } - return super.canTarget(controllerId, id, source, game, flag); + public TargetSecondPilePermanent(final TargetSecondPilePermanent target) { + super(target); + this.firstPile = new HashSet<>(target.firstPile); } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { if (firstPile.contains(id)) { return false; } - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } @Override @@ -199,4 +196,8 @@ class TargetSecondPilePermanent extends TargetPermanent { return super.canTarget(id, source, game); } + @Override + public TargetSecondPilePermanent copy() { + return new TargetSecondPilePermanent(this); + } } diff --git a/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java b/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java index c65013cc80d..f31607ad1d3 100644 --- a/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java +++ b/Mage.Sets/src/mage/cards/y/YorionSkyNomad.java @@ -52,8 +52,7 @@ public final class YorionSkyNomad extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - // When Yorion enters the battlefield, exile any number of other nonland permanents you own - // and control. Return those cards to the battlefield at the beginning of the next end step. + // When Yorion enters the battlefield, exile any number of other nonland permanents you own and control. Return those cards to the battlefield at the beginning of the next end step. this.addAbility(new EntersBattlefieldTriggeredAbility(new OneShotNonTargetEffect( new ExileReturnBattlefieldNextEndStepTargetEffect().setText(ruleText), new TargetPermanent(0, Integer.MAX_VALUE, filter, true)))); diff --git a/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java b/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java index dab9297934f..1393a2a67bf 100644 --- a/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java +++ b/Mage.Sets/src/mage/cards/y/YoseiTheMorningStar.java @@ -69,12 +69,12 @@ class YoseiTheMorningStarTarget extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Player player = game.getPlayer(source.getFirstTarget()); if (player != null) { this.filter = filterTemplate.copy(); this.filter.add(new ControllerIdPredicate(player.getId())); - return super.canTarget(controllerId, id, source, game); + return super.canTarget(playerId, id, source, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/y/YunasDecision.java b/Mage.Sets/src/mage/cards/y/YunasDecision.java index a65e84c1b05..c8b11ef013c 100644 --- a/Mage.Sets/src/mage/cards/y/YunasDecision.java +++ b/Mage.Sets/src/mage/cards/y/YunasDecision.java @@ -127,7 +127,7 @@ class YunasDecisionTarget extends TargetCardInHand { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game)); + possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); return possibleTargets; } } diff --git a/Mage.Sets/src/mage/cards/z/ZamWesell.java b/Mage.Sets/src/mage/cards/z/ZamWesell.java index 297c533e777..e2c5ad1166d 100644 --- a/Mage.Sets/src/mage/cards/z/ZamWesell.java +++ b/Mage.Sets/src/mage/cards/z/ZamWesell.java @@ -74,7 +74,7 @@ class ZamWesselEffect extends OneShotEffect { if (targetPlayer != null && you != null) { if (!targetPlayer.getHand().isEmpty()) { TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_CREATURE); - if (you.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { + if (you.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { ContinuousEffect copyEffect = new CopyEffect(Duration.EndOfGame, card.getMainCard(), source.getSourceId()); diff --git a/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java b/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java index 31d9612fb30..b1abd5885c2 100644 --- a/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java +++ b/Mage.Sets/src/mage/cards/z/ZaraRenegadeRecruiter.java @@ -18,6 +18,7 @@ import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInHand; import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.targetpointer.FixedTarget; @@ -83,10 +84,8 @@ class ZaraRenegadeRecruiterEffect extends OneShotEffect { if (controller == null || player == null || player.getHand().isEmpty()) { return false; } - TargetCardInHand targetCard = new TargetCardInHand( - 0, 1, StaticFilters.FILTER_CARD_CREATURE - ); - controller.choose(outcome, player.getHand(), targetCard, source, game); + TargetCard targetCard = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_CREATURE); + controller.choose(Outcome.Detriment, player.getHand(), targetCard, source, game); Card card = game.getCard(targetCard.getFirstTarget()); if (card == null) { return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java index a7840516228..07204312358 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CopyAITest.java @@ -116,7 +116,7 @@ public class CopyAITest extends CardTestPlayerBaseWithAIHelps { // addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 1); // 2/2 - // copy (AI must choose most valueable permanent - own) + // copy (AI must choose most valuable permanent - own) aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java index d1aa17af189..4ee5d9fbf8e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/SimulationStabilityAITest.java @@ -28,6 +28,8 @@ public class SimulationStabilityAITest extends CardTestPlayerBaseWithAIHelps { @Before public void prepare() { + // WARNING, for some reason java 8 sometime can't compile and run test with updated AI settings, so it's ok to freeze on it + // comment it to enable AI code debug Assert.assertFalse("AI stability tests must be run under release config", ComputerPlayer.COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java index d9bea5640c2..247e30aef40 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/ValakutTheMoltenPinnacleTest.java @@ -69,6 +69,7 @@ public class ValakutTheMoltenPinnacleTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scapeshift"); setChoice(playerA, "Forest^Forest^Forest^Forest^Forest^Forest"); // to sac + setChoice(playerA, TestPlayer.CHOICE_SKIP); addTarget(playerA, "Mountain^Mountain^Mountain^Mountain^Mountain^Mountain"); // to search setChoice(playerA, "Whenever a Mountain", 6 - 1); // x6 triggers from valakut addTarget(playerA, playerB, 6); // to deal damage diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java index 457ecad62cf..9eed0be4e7a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/CumulativeUpkeepTest.java @@ -61,32 +61,37 @@ public class CumulativeUpkeepTest extends CardTestPlayerBase { // Whenever Kor Celebrant or another creature you control enters, you gain 1 life. addCard(Zone.HAND, playerB, "Kor Celebrant", 1); // Creature {2}{W} addCard(Zone.BATTLEFIELD, playerB, "Plains", 3); - - addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // // Cumulative upkeep {2} - // When Illusions of Grandeur enters the battlefield, you gain 20 life. + // At the beginning of your upkeep, put an age counter on this permanent, + // then sacrifice it unless you pay its upkeep cost for each age counter on it. // When Illusions of Grandeur leaves the battlefield, you lose 20 life. addCard(Zone.HAND, playerA, "Illusions of Grandeur"); // Enchantment {3}{U} - - // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // + // At the beginning of your upkeep, you may exchange control of target nonland permanent you control + // and target nonland permanent an opponent controls with an equal or lesser converted mana cost. addCard(Zone.HAND, playerA, "Puca's Mischief"); // Enchantment {3}{U} - + // turn 1, 2 - prepare cumulative upkeep and gain life triggers castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Illusions of Grandeur"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Kor Celebrant"); - - // Illusions of Grandeur - CumulativeUpkeepAbility: Cumulative upkeep {2} - setChoice(playerA, true); // Pay {2}? - + + // turn 3 - upkeep trigger and prepare control card + setChoice(playerA, true); // use upkeep cost {2} castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Puca's Mischief"); - - setChoice(playerA, "Cumulative upkeep"); // Triggered list (total 2) which trigger goes first on the stack - addTarget(playerA, "Illusions of Grandeur"); // Own target permanent of Puca's Mischief - addTarget(playerA, "Kor Celebrant"); // Opponent's target permanent of Puca's Mischief - - setChoice(playerA, true); // At the beginning of your upkeep, you may exchange control of target nonland permanent you control and target nonland permanent an opponent controls with an equal or lesser converted mana cost. - setChoice(playerA, false); // Pay {2}{2}? + + // turn 5 - multiple upkeep triggers + // first put upkeep, then put change control - so control will be resolved before upkeep cost + setChoice(playerA, "Cumulative upkeep"); + + // resolve change control first + addTarget(playerA, "Illusions of Grandeur"); // own target + addTarget(playerA, "Kor Celebrant"); // opponent target + setChoice(playerA, true); // yes, resolve change control + + // resolve upkeep + setChoice(playerA, false); // no, do not pay upkeep cost checkPermanentCounters("Age counters", 5, PhaseStep.PRECOMBAT_MAIN, playerB, "Illusions of Grandeur", CounterType.AGE, 2); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java index ce3c5451784..875883533cc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EscalateTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -27,7 +28,9 @@ public class EscalateTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Savage Alliance", "mode=2Silvercoat Lion"); setModeChoice(playerA, "2"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -52,7 +55,9 @@ public class EscalateTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", playerB); setModeChoice(playerA, "3"); // deal 3 dmg to opponent + setModeChoice(playerA, TestPlayer.MODE_SKIP); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -78,10 +83,12 @@ public class EscalateTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Collective Defiance"); // {1}{R}{R} sorcery addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", "mode=2Gaddock Teeg"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", "mode=2Gaddock Teeg^mode=3targetPlayer=PlayerB"); setModeChoice(playerA, "2"); // deal 4 dmg to target creature (gaddock teeg) setModeChoice(playerA, "3"); // deal 3 dmg to opponent + setModeChoice(playerA, TestPlayer.MODE_SKIP); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -109,8 +116,6 @@ public class EscalateTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Collective Defiance"); // {1}{R}{R} sorcery addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); - setStrictChooseMode(true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Collective Defiance", "mode=2Wall of Omens"); setModeChoice(playerA, "1"); // opponent discards hand and draws that many setModeChoice(playerA, "2"); // deal 4 dmg to target creature (Wall of Omens) @@ -120,6 +125,8 @@ public class EscalateTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Spell Queller"); addTarget(playerB, "Collective Defiance"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java index d42dd419e14..63bf73b6070 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithAnyNumberModesAbilityTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -50,6 +51,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters @@ -79,6 +81,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters @@ -108,6 +111,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters @@ -139,6 +143,7 @@ public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase { setChoice(playerA, true); // use kicker setModeChoice(playerA, "2"); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, playerA); // gain x life addTarget(playerA, "Balduvian Bears"); // get counters diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java index fd5b5811759..f52e53f7474 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/LandfallTest.java @@ -1,15 +1,12 @@ - package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.IntimidateAbility; import mage.constants.PhaseStep; import mage.constants.Zone; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author LevelX2 */ public class LandfallTest extends CardTestPlayerBase { @@ -26,9 +23,12 @@ public class LandfallTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary"); + addTarget(playerA, playerA); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary"); + addTarget(playerA, playerA); + setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); @@ -43,7 +43,6 @@ public class LandfallTest extends CardTestPlayerBase { * If you Hive Mind an opponent's Rest for the Weary and redirect its target * to yourself when it's not your turn, the game spits out this message and * rolls back to before Rest for the Weary was cast. - * */ @Test public void testHiveMind() { @@ -57,15 +56,17 @@ public class LandfallTest extends CardTestPlayerBase { // Landfall - If you had a land enter the battlefield under your control this turn, that player gains 8 life instead. addCard(Zone.HAND, playerA, "Rest for the Weary", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest for the Weary", playerA); + setChoice(playerB, true); // change target of copied spell + addTarget(playerB, playerB); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerA, "Rest for the Weary", 1); assertLife(playerA, 24); assertLife(playerB, 24); - } @Test @@ -76,6 +77,7 @@ public class LandfallTest extends CardTestPlayerBase { playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -104,10 +106,14 @@ public class LandfallTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + // prepare landfall playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Searing Blaze"); + addTarget(playerA, playerB); + addTarget(playerA, "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -133,6 +139,7 @@ public class LandfallTest extends CardTestPlayerBase { attack(2, playerB, "Silvercoat Lion"); castSpell(2, PhaseStep.DECLARE_ATTACKERS, playerB, "Groundswell", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_COMBAT); execute(); @@ -179,6 +186,7 @@ public class LandfallTest extends CardTestPlayerBase { attack(2, playerB, "Silvercoat Lion"); castSpell(2, PhaseStep.DECLARE_ATTACKERS, playerB, "Groundswell", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(2, PhaseStep.END_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java index 26edf9e5db8..fc9123a883e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java @@ -16,6 +16,7 @@ import mage.filter.predicate.mageobject.*; import mage.game.permanent.Permanent; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -319,6 +320,7 @@ public class PrototypeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, epiphany); setModeChoice(playerA, "3"); // Return target nonland permanent to its owner's hand. setModeChoice(playerA, "4"); // Create a token that's a copy of target creature you control. + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, automaton); addTarget(playerA, automaton); @@ -340,6 +342,7 @@ public class PrototypeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, epiphany); setModeChoice(playerA, "3"); // Return target nonland permanent to its owner's hand. setModeChoice(playerA, "4"); // Create a token that's a copy of target creature you control. + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, automaton); addTarget(playerA, automaton); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java index 33051479103..ab6a016413a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SaddleTest.java @@ -8,6 +8,7 @@ import mage.game.permanent.Permanent; import mage.watchers.common.SaddledMountWatcher; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -90,6 +91,7 @@ public class SaddleTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); setChoice(playerA, bear); // to saddle cost + setChoice(playerA, TestPlayer.CHOICE_SKIP); attack(1, playerA, possum, playerB); setChoice(playerA, bear); // to return @@ -116,6 +118,7 @@ public class SaddleTest extends CardTestPlayerBase { // turn 1 - saddle x2 and trigger on attack activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Saddle"); setChoice(playerA, bear + "^" + lion); + setChoice(playerA, TestPlayer.CHOICE_SKIP); attack(1, playerA, possum, playerB); setChoice(playerA, elf); // to return (try to choose a wrong creature, so game must not allow to choose it) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java index d15f224a4a2..97c0b402a5b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpreeTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -24,8 +25,9 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, bear, 1); addCard(Zone.HAND, playerA, accident); - setModeChoice(playerA, "1"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -41,8 +43,9 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1 + 1); addCard(Zone.HAND, playerA, accident); - setModeChoice(playerA, "2"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident); + setModeChoice(playerA, "2"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -58,9 +61,10 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, bear, 1); addCard(Zone.HAND, playerA, accident); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); setModeChoice(playerA, "1"); setModeChoice(playerA, "2"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -80,9 +84,10 @@ public class SpreeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, electromancer, 1); addCard(Zone.HAND, playerA, accident); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); setModeChoice(playerA, "1"); setModeChoice(playerA, "2"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, accident, bear); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java index 0475b151603..95f1e8ceb06 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/OneShotNonTargetTest.java @@ -5,22 +5,31 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; + /** - * * @author notgreat */ public class OneShotNonTargetTest extends CardTestPlayerBase { + @Test public void YorionChooseAfterTriggerTest() { + // When Yorion enters the battlefield, exile any number of other nonland permanents you own and control. + // Return those cards to the battlefield at the beginning of the next end step. addCard(Zone.HAND, playerA, "Yorion, Sky Nomad"); addCard(Zone.BATTLEFIELD, playerA, "Plains", 7); + // + // When Resolute Reinforcements enters the battlefield, create a 1/1 white Soldier creature token. addCard(Zone.HAND, playerA, "Resolute Reinforcements"); + // prepare yori and put trigger on stack castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Yorion, Sky Nomad"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); checkPermanentCount("Yorion on battlefield", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Yorion, Sky Nomad", 1); + + // prepare another create before yori trigger resolve castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Resolute Reinforcements"); - setChoice(playerA, "Resolute Reinforcements"); + setChoice(playerA, "Resolute Reinforcements"); // 1 of 2 target for yori trigger + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -30,6 +39,7 @@ public class OneShotNonTargetTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Resolute Reinforcements", 1); assertTappedCount("Plains", true, 7); } + @Test public void NonTargetAdjusterTest() { addCard(Zone.HAND, playerA, "Temporal Firestorm"); @@ -52,21 +62,30 @@ public class OneShotNonTargetTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Python", 0); assertGraveyardCount(playerA, "Watchwolf", 1); } + @Test public void ModeSelectionTest() { - addCard(Zone.HAND, playerA, "SOLDIER Military Program"); + // At the beginning of combat on your turn, choose one. If you control a commander, you may choose both instead. + // * Create a 1/1 white Soldier creature token. + // * Put a +1/+1 counter on each of up to two Soldiers you control. + addCard(Zone.HAND, playerA, "SOLDIER Military Program"); // {2}{W} addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); - addCard(Zone.BATTLEFIELD, playerA, "Squire", 1); + addCard(Zone.BATTLEFIELD, playerA, "Squire", 1); // 1/2 + // prepare mode ability castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "SOLDIER Military Program"); + + // turn 1 combat - put counter setModeChoice(playerA, "2"); setChoice(playerA, "Squire"); - setChoice(playerA, TestPlayer.CHOICE_SKIP); + // turn 3 combat - create token setModeChoice(playerA, "1"); + // turn 5 combat - put counter setModeChoice(playerA, "2"); - setChoice(playerA, "Squire^Soldier Token"); + setChoice(playerA, "Squire"); + setChoice(playerA, "Soldier Token"); setStrictChooseMode(true); setStopAt(5, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java index 47aa71944e6..c59ff09ec30 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/OracleEnVecNextTurnTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.continuous; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -22,7 +23,9 @@ public class OracleEnVecNextTurnTest extends CardTestPlayerBase { // 1 - activate activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target opponent", playerB); - setChoice(playerB, "Balduvian Bears^Angelic Wall"); + setChoice(playerB, "Balduvian Bears"); + setChoice(playerB, "Angelic Wall"); + setChoice(playerB, TestPlayer.CHOICE_SKIP); // checkLife("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, 20); checkPermanentCount("turn 1", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java index 6abb3d2a9ac..f1ae5bc4a96 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/TakeControlWhileSearchingLibraryTest.java @@ -235,7 +235,7 @@ public class TakeControlWhileSearchingLibraryTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Buried Alive"); setChoice(playerB, true); // continue after new control addTarget(playerB, "Balduvian Bears"); - addTarget(playerB, TestPlayer.TARGET_SKIP); + //addTarget(playerB, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkGraveyardCount("after grave a", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 0); checkGraveyardCount("after grave b", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java index 1d6ba250fcb..7cec4f73b4f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RemoveCounterCostTest.java @@ -23,7 +23,6 @@ public class RemoveCounterCostTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Remove two +1/+1 counters"); setChoice(playerA, "Novijen Sages"); // counters to remove - setChoice(playerA, TestPlayer.CHOICE_SKIP); setChoice(playerA, "X=2"); // counters to remove setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java index 10f2bc5eb3c..bf0e4d40959 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/CastFromHandWithoutPayingManaCostTest.java @@ -427,18 +427,20 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { String savageBeating = "Savage Beating"; skipInitShuffling(); - addCard(Zone.LIBRARY, playerA, savageBeating, 2); + addCard(Zone.LIBRARY, playerA, savageBeating, 2); // to free cast addCard(Zone.HAND, playerA, jeleva); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Island", 3); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // prepare and exile cards from libs castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, jeleva); + // attack and use free cast from exile attack(3, playerA, jeleva); - setChoice(playerA, true); // opt to use Jeleva ability - setChoice(playerA, savageBeating); // choose to cast Savage Beating for free - setChoice(playerA, false); // opt not to pay entwine cost + setChoice(playerA, savageBeating); // to free cast + setChoice(playerA, true); // confirm free cast + setChoice(playerA, false); // ignore entwine cost setModeChoice(playerA, "1"); // use first mode of Savage Beating granting double strike setStopAt(3, PhaseStep.END_COMBAT); @@ -448,6 +450,5 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase { assertTapped(jeleva, true); assertLife(playerB, 18); assertAbility(playerA, jeleva, DoubleStrikeAbility.getInstance(), true); - } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java index a229bb39472..257cae4f572 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java @@ -945,7 +945,7 @@ public class ModalDoubleFacedCardsTest extends CardTestPlayerBase { // cast as copy of mdf card castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Zam Wesell"); addTarget(playerA, playerB); // target opponent - addTarget(playerA, "Akoum Warrior"); // creature card to copy + setChoice(playerA, "Akoum Warrior"); // creature card to copy waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java index 548ecea0a7c..126f783ade5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/dungeons/DungeonTest.java @@ -20,7 +20,7 @@ import java.util.stream.Stream; */ public class DungeonTest extends CardTestPlayerBase { - private static final String TOMB_OF_ANNIHILATION = "Tomb of Annihilation"; + private static final String TOMB_OF_ANNIHILATION = "Tomb of Annihilation"; // TODO: miss test private static final String LOST_MINE_OF_PHANDELVER = "Lost Mine of Phandelver"; private static final String DUNGEON_OF_THE_MAD_MAGE = "Dungeon of the Mad Mage"; private static final String FLAMESPEAKER_ADEPT = "Flamespeaker Adept"; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java index 9ea9a2ee029..c9dcc5a9b48 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrBothTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.modal; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -25,7 +26,7 @@ public class OneOrBothTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); setModeChoice(playerA, "1"); - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -47,7 +48,7 @@ public class OneOrBothTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Subtle Strike", "Pillarfield Ox"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java index 8d65219d629..dd02ce056d6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/OneOrMoreTest.java @@ -6,6 +6,7 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -39,7 +40,7 @@ public class OneOrMoreTest extends CardTestPlayerBase { addTarget(playerA, "Silvercoat Lion"); // for 3 addTarget(playerA, "Silvercoat Lion"); // for 4 addTarget(playerA, playerB); // for 5 - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); @@ -76,7 +77,7 @@ public class OneOrMoreTest extends CardTestPlayerBase { addTarget(playerA, "Silvercoat Lion"); // for 3 addTarget(playerA, "Silvercoat Lion"); // for 4 addTarget(playerA, playerB); // for 5 - setModeChoice(playerA, null); + setModeChoice(playerA, TestPlayer.MODE_SKIP); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java index b302b9cea0d..1e7f01f5e0c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/PawPrintsTest.java @@ -3,7 +3,9 @@ package org.mage.test.cards.modal; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; +import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -61,25 +63,17 @@ public class PawPrintsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Season of Gathering"); setModeChoice(playerA, "1"); setModeChoice(playerA, "2"); - setModeChoice(playerA, "3"); // Will be unused + setModeChoice(playerA, "3"); // no more paws for mode 3, see exception below addTarget(playerA, "Memnite"); // for 1 setChoice(playerA, "Enchantment"); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); - execute(); - - // Add one more counter from Hardened Scales, which was still on the battlefield when the counter placing effect triggered - assertPowerToughness(playerA, "Memnite", 3, 3); - assertCounterCount("Memnite", CounterType.P1P1, 2); - - // But not anymore... - assertPermanentCount(playerA, "Hardened Scales", 0); - assertGraveyardCount(playerA, "Hardened Scales", 1); - - // Draw effect didnt trigger - assertHandCount(playerA, 0); - + try { + execute(); + } catch (AssertionError e) { + Assert.assertTrue("mode 3 must not be able to choose due total paws cost", e.getMessage().contains("Can't use mode: 3")); + } } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java index 7da50462f5d..8f2c735588f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/AttachmentTest.java @@ -282,9 +282,9 @@ public class AttachmentTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Island", 3); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Show and Tell"); - setChoice(playerA, true); - setChoice(playerB, false); + setChoice(playerA, true); // yes, put from hand to battle addTarget(playerA, "Aether Tunnel"); + setChoice(playerB, false); // no, do not put setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java index e425d65dc53..5245a558120 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/MantleOfTheAncientsTest.java @@ -50,6 +50,9 @@ public class MantleOfTheAncientsTest extends CardTestPlayerBase { addTarget(playerA, "Gate Smasher^Aether Tunnel"); addTarget(playerA, TestPlayer.TARGET_SKIP); + + showBattlefield("after", 1, PhaseStep.END_TURN, playerA); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java index 844ca5cfb0a..b8140e785d9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afc/ShareTheSpoilsTest.java @@ -314,15 +314,14 @@ public class ShareTheSpoilsTest extends CardTestCommander4Players { // Cast an adventure card from hand castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Dizzying Swoop"); addTarget(playerA, "Prosper, Tome-Bound"); - addTarget(playerA, TestPlayer.TARGET_SKIP); // tap 1 of 2 targets + //addTarget(playerA, TestPlayer.TARGET_SKIP); // only 1 creature, so tap 1 of 2, no need in skip waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN); // Make sure the creature card can't be played from exile since there isn't the {W}{W} for it checkPlayableAbility("creature cast", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ardenvale Tactician", false); - setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); - setStrictChooseMode(true); + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); execute(); // 1 exiled with Share the Spoils diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java index bbc8a10ac93..de0b7d560c5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/ApproachOfTheSecondSunTest.java @@ -189,9 +189,9 @@ public class ApproachOfTheSecondSunTest extends CardTestPlayerBase { // first may have been cast from anywhere. castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Finale of Promise", true); setChoice(playerA, "X=7"); // each with mana value X or less + addTarget(playerA, TARGET_SKIP); // skip instant target + addTarget(playerA, "Approach of the Second Sun"); // use sorcery target setChoice(playerA, "Yes"); // You may cast - addTarget(playerA, TARGET_SKIP); // up to one target instant card - addTarget(playerA, "Approach of the Second Sun"); // and/or up to one target sorcery card from your graveyard checkLife("Approach of the Second Sun cast from graveyard gains life", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 27); checkLibraryCount("Approach of the Second Sun cast from graveyard goes to library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Approach of the Second Sun", 1); @@ -200,9 +200,9 @@ public class ApproachOfTheSecondSunTest extends CardTestPlayerBase { // The second Approach of the Second Sun that you cast must be cast from your hand, castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Finale of Promise", true); setChoice(playerA, "X=7"); // each with mana value X or less - setChoice(playerA, "Yes"); // You may cast addTarget(playerA, TARGET_SKIP); // up to one target instant card addTarget(playerA, "Approach of the Second Sun"); // and/or up to one target sorcery card from your graveyard + setChoice(playerA, "Yes"); // You may cast checkLife("Approach of the Second Sun cast from graveyard gains life", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 34); checkLibraryCount("Approach of the Second Sun cast from graveyard goes to library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Approach of the Second Sun", 2); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java index cbaf8d3479d..41701e21958 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/bfz/BrutalExpulsionTest.java @@ -4,6 +4,7 @@ package org.mage.test.cards.single.bfz; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -27,7 +28,7 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=2Augmenting Automaton"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); // ignore last one mode + setModeChoice(playerA, TestPlayer.MODE_SKIP); // ignore last one mode //addTarget(playerA, "mode=2Augmenting Automaton"); // doesn't work with mode setStopAt(1, PhaseStep.END_COMBAT); @@ -56,7 +57,7 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=2Kiora, the Crashing Wave"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); // ignore last one mode + setModeChoice(playerA, TestPlayer.MODE_SKIP); // ignore last one mode setStopAt(1, PhaseStep.END_COMBAT); execute(); @@ -116,7 +117,7 @@ public class BrutalExpulsionTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=2Gideon, Ally of Zendikar"); setModeChoice(playerA, "2"); - setModeChoice(playerA, null); // ignore last one mode + setModeChoice(playerA, TestPlayer.MODE_SKIP); // ignore last one mode castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", "Gideon, Ally of Zendikar"); setStopAt(1, PhaseStep.END_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java index fe28717b08c..a15c9f866aa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java @@ -27,8 +27,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // attack and cast first time (free) attack(1, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Grizzly Bears"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(1, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Grizzly Bears", 1); checkPermanentTapped("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Forest", true, 0); @@ -40,8 +40,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 3 - second cast (1x tax) attack(3, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Grizzly Bears"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(3, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Grizzly Bears", 1); checkPermanentTapped("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Forest", true, 2); // 1x tax @@ -53,8 +53,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 5 - third cast (2x tax) attack(5, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Grizzly Bears"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(5, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Grizzly Bears", 1); checkPermanentTapped("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Forest", true, 2 * 2); // 2x tax @@ -85,8 +85,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // attack and cast first time (free) attack(1, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Akoum Warrior"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(1, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Akoum Warrior", 1); checkPermanentTapped("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Mountain", true, 0); @@ -98,8 +98,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 3 - second cast (1x tax) attack(3, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Akoum Warrior"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(3, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Akoum Warrior", 1); checkPermanentTapped("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Mountain", true, 2); // 1x tax @@ -111,8 +111,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 5 - third cast (2x tax) attack(5, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Akoum Warrior"); // commander choice + setChoice(playerA, true); // cast commander waitStackResolved(5, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Akoum Warrior", 1); checkPermanentTapped("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Mountain", true, 2 * 2); // 2x tax @@ -144,8 +144,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // attack and cast first time (free) attack(1, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Birgi, God of Storytelling"); // spell choice waitStackResolved(1, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 1", 1, PhaseStep.COMBAT_DAMAGE, playerA, "Birgi, God of Storytelling", 1); @@ -158,8 +158,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 3 - second cast, LEFT side (1x tax) attack(3, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Birgi, God of Storytelling"); // spell choice waitStackResolved(3, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 2", 3, PhaseStep.COMBAT_DAMAGE, playerA, "Birgi, God of Storytelling", 1); @@ -172,8 +172,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 5 - third cast, RIGHT side (2x tax) attack(5, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Harnfel, Horn of Bounty"); // spell choice waitStackResolved(5, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 3", 5, PhaseStep.COMBAT_DAMAGE, playerA, "Harnfel, Horn of Bounty", 1); @@ -186,8 +186,8 @@ public class GeodeGolemTest extends CardTestCommanderDuelBase { // turn 7 - fourth cast, RIGHT side (3x tax) attack(7, playerA, "Geode Golem"); - setChoice(playerA, true); // cast commander setChoice(playerA, "Birgi, God of Storytelling"); // commander choice + setChoice(playerA, true); // cast commander setChoice(playerA, "Cast Harnfel, Horn of Bounty"); // spell choice waitStackResolved(7, PhaseStep.COMBAT_DAMAGE); checkPermanentCount("after 4", 7, PhaseStep.COMBAT_DAMAGE, playerA, "Harnfel, Horn of Bounty", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java index 9b22b8e5954..67d4275faee 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clu/SludgeTitanTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.single.clu; import mage.constants.PhaseStep; @@ -6,6 +5,7 @@ import mage.constants.Zone; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -41,7 +41,6 @@ public class SludgeTitanTest extends CardTestPlayerBase { addCard(Zone.LIBRARY, playerA, divination, 5); attack(1, playerA, titan, playerB); - setChoice(playerA, playerA.CHOICE_SKIP); setStopAt(1, PhaseStep.DECLARE_BLOCKERS); execute(); @@ -49,29 +48,6 @@ public class SludgeTitanTest extends CardTestPlayerBase { assertGraveyardCount(playerA, divination, 5); } - @Test - public void testNoValidChoiceInvalid() { - setStrictChooseMode(true); - skipInitShuffling(); - - addCard(Zone.BATTLEFIELD, playerA, titan); - addCard(Zone.LIBRARY, playerA, divination, 5); - - attack(1, playerA, titan, playerB); - setChoice(playerA, divination); // invalid, titan doesn't allow for choosing sorcery - - setStopAt(1, PhaseStep.DECLARE_BLOCKERS); - - try { - execute(); - Assert.fail("must throw exception on execute"); - } catch (Throwable e) { - if (!e.getMessage().startsWith("Missing CHOICE def")) { - Assert.fail("Unexpected exception " + e.getMessage()); - } - } - } - @Test public void testCreatureOnly_ChooseNone() { setStrictChooseMode(true); @@ -81,7 +57,7 @@ public class SludgeTitanTest extends CardTestPlayerBase { addCard(Zone.LIBRARY, playerA, piker, 5); attack(1, playerA, titan, playerB); - setChoice(playerA, playerA.CHOICE_SKIP); + setChoice(playerA, TestPlayer.CHOICE_SKIP); setStopAt(1, PhaseStep.DECLARE_BLOCKERS); execute(); @@ -207,6 +183,8 @@ public class SludgeTitanTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, titan); addCard(Zone.LIBRARY, playerA, brownscale, 5); + // must able to select only 1 creature, so after first choice it must auto-finish + // if you see miss choice here then something broken in TestPlayer's choose method attack(1, playerA, titan, playerB); setChoice(playerA, brownscale); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java index 8f5316c9411..31a24c63200 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/TamiyoFieldResearcherTest.java @@ -78,7 +78,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); - addTarget(playerA, TestPlayer.TARGET_SKIP); + //addTarget(playerA, TestPlayer.TARGET_SKIP); // there are only 1 creature, so choose 1 of 2, no need in skip attack(1, playerA, "Bronze Sable"); @@ -240,7 +240,7 @@ public class TamiyoFieldResearcherTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tamiyo, Field Researcher", true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Choose up to two"); addTarget(playerA, "Bronze Sable"); - addTarget(playerA, TestPlayer.TARGET_SKIP); + //addTarget(playerA, TestPlayer.TARGET_SKIP); // there are only 1 creature, so choose 1 of 2, no need in skip attack(2, playerB, "Bronze Sable"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/GarnetPrincessOfAlexandriaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/GarnetPrincessOfAlexandriaTest.java new file mode 100644 index 00000000000..9f308e91789 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/GarnetPrincessOfAlexandriaTest.java @@ -0,0 +1,91 @@ +package org.mage.test.cards.single.fin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class GarnetPrincessOfAlexandriaTest extends CardTestPlayerBase { + + @Test + public void test_Targets_0() { + // 2/2 + // Whenever Garnet attacks, you may remove a lore counter from each of any number of Sagas you control. + // Put a +1/+1 counter on Garnet for each lore counter removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Garnet, Princess of Alexandria"); + + // nothing to select after attack + attack(1, playerA, "Garnet, Princess of Alexandria", playerB); + //setChoice(playerA, TestPlayer.CHOICE_SKIP); // no valid choices, so do not show choose dialog + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + } + + @Test + public void test_Targets_1() { + // 2/2 + // Whenever Garnet attacks, you may remove a lore counter from each of any number of Sagas you control. + // Put a +1/+1 counter on Garnet for each lore counter removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Garnet, Princess of Alexandria"); + // + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + // I, II, III, IV -- Stampede! -- Other creatures you control get +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Summon: Choco/Mog"); // {2}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + + // prepare x1 saga + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summon: Choco/Mog"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // can select 1 target only + attack(1, playerA, "Garnet, Princess of Alexandria", playerB); + setChoice(playerA, "Summon: Choco/Mog"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + // 2/2 attack + 1/1 saga's boost + 1/0 counter remove boost + assertCounterCount(playerA, "Summon: Choco/Mog", CounterType.LORE, 0); + assertLife(playerB, 20 - 2 - 1 - 1); + } + + @Test + public void test_Targets_2() { + // 2/2 + // Whenever Garnet attacks, you may remove a lore counter from each of any number of Sagas you control. + // Put a +1/+1 counter on Garnet for each lore counter removed this way. + addCard(Zone.BATTLEFIELD, playerA, "Garnet, Princess of Alexandria"); + // + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + // I, II, III, IV -- Stampede! -- Other creatures you control get +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Summon: Choco/Mog", 2); // {2}{W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3 * 2); + + // prepare x2 sagas + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summon: Choco/Mog"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Summon: Choco/Mog"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + // can select 2 targets + attack(1, playerA, "Garnet, Princess of Alexandria", playerB); + setChoice(playerA, "Summon: Choco/Mog", 2); // x2 targets + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + // 2/2 attack + x2 1/1 saga's boost + x2 1/0 counter remove boost + assertCounterCount(playerA, "Summon: Choco/Mog", CounterType.LORE, 0); + assertLife(playerB, 20 - 2 - 2 - 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java index 66824c5bb01..be2d8afaacb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/grn/WandOfVertebraeTest.java @@ -27,13 +27,13 @@ public class WandOfVertebraeTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, wandOfVertebrae); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.GRAVEYARD, playerA, lavaCoil); - - setStrictChooseMode(true); + addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears", 10); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}"); addTarget(playerA, lavaCoil); addTarget(playerA, TestPlayer.TARGET_SKIP); // must choose 1 of 5 + setStrictChooseMode(true); setStopAt(1, PhaseStep.PRECOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java index 8d8353cf3a1..cb5d923bdd2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/gtc/DiluvianPrimordialTest.java @@ -20,9 +20,9 @@ public class DiluvianPrimordialTest extends CardTestPlayerBase { */ private static final String primordial = "Diluvian Primordial"; - // Bug: NPE on casting Valakut Awakening @Test public void test_MDFC() { + // possible bug: NPE on casting Valakut Awakening setStrictChooseMode(true); addCard(Zone.HAND, playerA, primordial); @@ -32,7 +32,7 @@ public class DiluvianPrimordialTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, primordial); addTarget(playerA, "Valakut Awakening"); setChoice(playerA, true); // Yes to "You may" - setChoice(playerA, TestPlayer.CHOICE_SKIP); // No choice for Awakening's effect + //setChoice(playerA, TestPlayer.CHOICE_SKIP); // no need in skip, cause no valid choices for Awakening's effect setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java index 15473da96c3..fc073e3f4b8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lcc/RipplesOfPotentialTest.java @@ -4,6 +4,7 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -32,9 +33,10 @@ public class RipplesOfPotentialTest extends CardTestPlayerBase { setChoice(playerA, "Blast Zone^Arcbound Javelineer^Chandra, Pyromaster^Atraxa's Skitterfang"); // Phase out choice setChoice(playerA, "Blast Zone^Arcbound Javelineer^Chandra, Pyromaster"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); // keep Atraxa's Skitterfang + setChoice(playerA, false); // Atraxa's Skitterfang ability - do not remove oil setStrictChooseMode(true); - setChoice(playerA, false); // Atraxa's Skitterfang ability setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/BreakingOfTheFellowshipTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/BreakingOfTheFellowshipTest.java new file mode 100644 index 00000000000..6abe5a0d95a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/BreakingOfTheFellowshipTest.java @@ -0,0 +1,83 @@ +package org.mage.test.cards.single.ltr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ + +public class BreakingOfTheFellowshipTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_PossibleTargets() { + // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. + addCard(Zone.HAND, playerA, "Breaking of the Fellowship"); // {1}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + addCard(Zone.HAND, playerB, "Grizzly Bears"); // 2/2, {1}{G} + addCard(Zone.HAND, playerB, "Spectral Bears"); // 3/3, {1}{G} + addCard(Zone.BATTLEFIELD, playerB, "Forest", 2 * 2); + + // turn 1 - no targets, can't play + checkPlayableAbility("no targets - can't cast", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Breaking of the Fellowship", false); + + // turn 3 - 1 of 2 targets, can't play + castSpell(3 - 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Grizzly Bears"); + checkPlayableAbility("1 of 2 targets - can't cast", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Breaking of the Fellowship", false); + + // turn 5 - 2 of 2 targets, can play + castSpell(5 - 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Spectral Bears"); + checkPlayableAbility("2 of 2 targets - can cast", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Breaking of the Fellowship", true); + + castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Breaking of the Fellowship", "Grizzly Bears^Spectral Bears"); + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerB, "Spectral Bears", 2); + } + + @Test + public void test_Normal_AI() { + // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. + addCard(Zone.HAND, playerA, "Breaking of the Fellowship"); // {1}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears"); // 3/3 + + // AI must choose 3/3 to kill 2/2 + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Grizzly Bears", 1); + } + + @Test + public void test_Protection_AI() { + // Target creature an opponent controls deals damage equal to its power to another target creature that player controls. + addCard(Zone.HAND, playerA, "Breaking of the Fellowship"); // {1}{R} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerB, "Spectral Bears"); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Lavinia of the Tenth"); // 4/4, Protection from red + + // can't choose 4/4 to kill 3/3 due protection from red + // AI must choose 3/3 to kill 2/2 + aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Grizzly Bears", 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java index 030bdfe7d6b..a469a2d31c6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/GlamdringTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.single.ltr; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -29,14 +30,16 @@ public class GlamdringTest extends CardTestPlayerBase { attack(1, playerA, "Blur Sliver"); setChoice(playerA, "In Garruk's Wake"); // 9 mana, so we shouldn't be able to choose it - setChoice(playerA, "Yes"); try { setStopAt(1, PhaseStep.FIRST_COMBAT_DAMAGE); execute(); } catch (AssertionError e) { - assert(e.getMessage().contains("Missing CHOICE def for turn 1, step FIRST_COMBAT_DAMAGE, PlayerA")); + Assert.assertTrue( + "catch wrong exception: " + e.getMessage(), + e.getMessage().contains("Found wrong choice command") && e.getMessage().contains("In Garruk's Wake") + ); return; } fail("Was able to pick [[In Garruk's Wake]] but it costs more than 2"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java index 1d135208a16..a1dd9da6925 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/EnthrallingHoldTest.java @@ -10,6 +10,14 @@ public class EnthrallingHoldTest extends CardTestPlayerBase { @Test public void testTappedTarget_untapped_doesNotFizzle() { // Traxos, Scourge of Kroog enters the battlefield tapped and doesn't untap during your untap step. + + // The middle ability of Enthralling Hold affects only the choice of target as the spell is cast. + // If the creature becomes untapped before the spell resolves, it still resolves. If a player is + // allowed to change the spell's target while it's on the stack, they may choose an untapped + // creature. If you put Enthralling Hold onto the battlefield without casting it, you may attach + // it to an untapped creature. + // (2020-06-23) + addCard(Zone.BATTLEFIELD, playerB, "Traxos, Scourge of Kroog"); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); @@ -26,10 +34,11 @@ public class EnthrallingHoldTest extends CardTestPlayerBase { */ addCard(Zone.HAND, playerA, "Twiddle"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Enthralling Hold", "Traxos, Scourge of Kroog"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twiddle", "Traxos, Scourge of Kroog"); - - setChoice(playerA, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Enthralling Hold"); + addTarget(playerA, "Traxos, Scourge of Kroog"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twiddle"); + addTarget(playerA, "Traxos, Scourge of Kroog"); + setChoice(playerA, true); // untap traxos setStrictChooseMode(true); setStopAt(1, PhaseStep.END_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java index e561f89d04e..755799fee58 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m3c/BarrowgoyfTest.java @@ -100,7 +100,6 @@ public class BarrowgoyfTest extends CardTestPlayerBase { attack(1, playerA, barrowgoyf, playerB); setChoice(playerA, true); - setChoice(playerA, TestPlayer.CHOICE_SKIP); // decide to not return. There was no choice anyway. setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java index 4141d0e485d..2eb0d77ac76 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/IzzetGeneratoriumTest.java @@ -6,6 +6,7 @@ import mage.counters.CounterType; import mage.players.Player; import org.junit.Assert; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -82,6 +83,7 @@ public class IzzetGeneratoriumTest extends CardTestPlayerBase { checkPlayableAbility("1: condition not met before losing counters", 2, PhaseStep.UPKEEP, playerA, "{T}: Draw", false); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Final Act"); setModeChoice(playerB, "5"); // each opponent loses all counters. + setModeChoice(playerB, TestPlayer.MODE_SKIP); waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); runCode("energy counter is 0", 2, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> checkEnergyCount(info, player, 0)); checkPlayableAbility("2: condition met after losing counters", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Draw", true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java index 658445c4daa..1edd03bd76b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/NethergoyfTest.java @@ -183,6 +183,7 @@ public class NethergoyfTest extends CardTestPlayerBaseWithAIHelps { // The same is true for permanent spells you control and nonland permanent cards you own that aren’t on the battlefield. addCard(Zone.HAND, playerA, "Encroaching Mycosynth"); + // AI must be able to choose good targets combination aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA); setStopAt(1, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java index 20cc546706d..119db90c629 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/KayaSpiritsJusticeTest.java @@ -1,9 +1,13 @@ package org.mage.test.cards.single.mkm; +import mage.abilities.Mode; +import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.filter.StaticFilters; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -21,6 +25,11 @@ public class KayaSpiritsJusticeTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, "Fyndhorn Elves"); addCard(Zone.HAND, playerA, "Thraben Inspector"); addCard(Zone.HAND, playerA, "Astrid Peth"); + // Choose one or more — + // • Exile all artifacts. + // • Exile all creatures. + // • Exile all enchantments. + // • Exile all graveyards. addCard(Zone.HAND, playerA, "Farewell"); // Creates a Clue token @@ -35,6 +44,7 @@ public class KayaSpiritsJusticeTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Farewell"); setModeChoice(playerA, "2"); setModeChoice(playerA, "4"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); // Kaya's first ability triggers twice, so choose which is put on the stack: diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java index e312c636ce9..88a9e75abd7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mom/InvasionOfFioraTest.java @@ -1,6 +1,7 @@ package org.mage.test.cards.single.mom; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; import mage.constants.PhaseStep; @@ -47,6 +48,7 @@ public class InvasionOfFioraTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, invasion); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); checkPermanentCount("Battle on battlefield", 1, PhaseStep.PRECOMBAT_MAIN, playerA, invasion, 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java index 7681ef14fd4..2863182baec 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/targets/TargetsSelectionBaseTest.java @@ -54,11 +54,10 @@ public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { checkHandCardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", availableCardsCount); checkExileCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", 0); + // cause minTarget = 0, so ability can be activated at any time + checkPlayableAbility("can activate on any targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); if (availableCardsCount > 0) { - checkPlayableAbility("can activate on non-zero targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText); - } else { - checkPlayableAbility("can't activate on zero targets", 1, PhaseStep.PRECOMBAT_MAIN, playerA, startingText, false); } if (chooseCardsCount > 0) { @@ -74,7 +73,8 @@ public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { // - 2 of 3 - yes // - 3 of 3 - no, it's auto-finish on last select // - 3 of 5 - no, it's auto-finish on last select - if (chooseCardsCount < maxTarget) { + int maxPossibleChoose = Math.min(availableCardsCount, maxTarget); + if (chooseCardsCount < maxPossibleChoose) { addTarget(playerA, TestPlayer.TARGET_SKIP); } } else { @@ -128,20 +128,18 @@ public class TargetsSelectionBaseTest extends CardTestPlayerBaseWithAIHelps { targetCards.add("Swamp"); }); setChoice(playerA, String.join("^", targetCards)); - } else { - // need skip - // on 0 cards there are must be dialog with done button anyway - - // end selection: - // - x of 0 - yes - // - 1 of 3 - yes - // - 2 of 3 - yes - // - 3 of 3 - no, it's auto-finish on last select - // - 3 of 5 - no, it's auto-finish on last select - if (chooseCardsCount < maxTarget) { - setChoice(playerA, TestPlayer.CHOICE_SKIP); - } + } + // choose skip + // end selection condition: + // - x of 0 - yes + // - 1 of 3 - yes + // - 2 of 3 - yes + // - 3 of 3 - no, it's auto-finish on last select + // - 3 of 5 - no, it's auto-finish on last select and nothing to choose + int canSelectCount = Math.min(maxTarget, availableCardsCount); + if (canSelectCount > 0 && chooseCardsCount < canSelectCount) { + setChoice(playerA, TestPlayer.CHOICE_SKIP); } if (DEBUG_ENABLE_DETAIL_LOGS) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java index cdaed27a013..47f8a695997 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesTargetTriggerTest.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.Filter; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** @@ -478,6 +479,7 @@ public class BecomesTargetTriggerTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hostility); setModeChoice(playerA, "1"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, protector); setStrictChooseMode(true); @@ -502,6 +504,7 @@ public class BecomesTargetTriggerTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hostility); setModeChoice(playerA, "2"); + setModeChoice(playerA, TestPlayer.MODE_SKIP); addTarget(playerA, dragon); setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java index 2217cb4ee82..2837a7e6380 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java @@ -24,7 +24,6 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angel of Serenity"); addTarget(playerA, "Silvercoat Lion^Pillarfield Ox"); - addTarget(playerA, TestPlayer.TARGET_SKIP); setChoice(playerA, true); setStrictChooseMode(true); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 50340177526..479fe01ef6a 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1227,7 +1227,7 @@ public abstract class AbilityImpl implements Ability { boolean validTargets = true; for (Target target : mode.getTargets()) { UUID abilityControllerId = target.getAffectedAbilityControllerId(controllerId); - if (!target.canChoose(abilityControllerId, ability, game)) { + if (!target.canChooseOrAlreadyChosen(abilityControllerId, ability, game)) { validTargets = false; break; } diff --git a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java index fc11c56c0e7..1ab07eda011 100644 --- a/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/AttachableToRestrictedAbility.java @@ -1,33 +1,43 @@ package mage.abilities.common; -import mage.abilities.Ability; import mage.abilities.effects.common.InfoEffect; +import mage.constants.TargetController; import mage.constants.Zone; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.target.Target; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** - * * @author LevelX2 */ public class AttachableToRestrictedAbility extends SimpleStaticAbility { + private final Target attachable; + public AttachableToRestrictedAbility(Target target) { super(Zone.BATTLEFIELD, new InfoEffect("{this} can be attached only to a " + target.getTargetName())); this.attachable = target.copy(); + + // verify check: make sure filter don't have controller predicate cause it used in code without controller info + List list = new ArrayList<>(); + Predicates.collectAllComponents(target.getFilter().getPredicates(), target.getFilter().getExtraPredicates(), list); + if (list.stream().anyMatch(TargetController.ControllerPredicate.class::isInstance)) { + throw new IllegalArgumentException("Wrong code usage: attachable restriction filter must not contain controller predicate"); + } } private AttachableToRestrictedAbility(AttachableToRestrictedAbility ability) { super(ability); - this.attachable = ability.attachable; // Since we never modify the target, we don't need to re-copy it + this.attachable = ability.attachable.copy(); } - public boolean canEquip(UUID toEquip, Ability source, Game game) { - if (source == null) { - return attachable.canTarget(toEquip, game); - } else return attachable.canTarget(toEquip, source, game); + public boolean canEquip(UUID toEquip, Game game) { + return attachable.canTarget(toEquip, null, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/Cost.java b/Mage/src/main/java/mage/abilities/costs/Cost.java index 9aecb8937f3..8a70a010b2c 100644 --- a/Mage/src/main/java/mage/abilities/costs/Cost.java +++ b/Mage/src/main/java/mage/abilities/costs/Cost.java @@ -19,9 +19,20 @@ public interface Cost extends Serializable, Copyable { /** * Check is it possible to pay * For mana it checks only single color and amount available, not total mana cost + *

+ * Warning, if you want to use canChoose, then don't forget about already selected targets (important for AI sims). */ boolean canPay(Ability ability, Ability source, UUID controllerId, Game game); + /** + * Simple canPay logic implementation with targets - cost has possible targets or already selected it, e.g. by AI sims + *

+ * Do not override + */ + default boolean canChooseOrAlreadyChosen(Ability ability, Ability source, UUID controllerId, Game game) { + return this.getTargets().stream().allMatch(target -> target.isChosen(game) || target.canChoose(controllerId, source, game)); + } + boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana); boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay); diff --git a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java index 83b12475790..1316d2daf35 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/DiscardTargetCost.java @@ -70,7 +70,7 @@ public class DiscardTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java index 552ab89af2d..6565f9e164d 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileFromGraveCost.java @@ -120,7 +120,7 @@ public class ExileFromGraveCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java index 9a9aee8454e..4b1466742d2 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileFromHandCost.java @@ -82,7 +82,7 @@ public class ExileFromHandCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java index c71f98d225b..07cf32a50c9 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileTargetCost.java @@ -68,7 +68,7 @@ public class ExileTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java b/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java index 565bb66c1b0..890ece07a43 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PutCardFromHandOnTopOfLibraryCost.java @@ -32,8 +32,7 @@ public class PutCardFromHandOnTopOfLibraryCost extends CostImpl { TargetCardInHand targetCardInHand = new TargetCardInHand(); targetCardInHand.setRequired(false); Card card; - if (targetCardInHand.canChoose(controllerId, source, game) - && controller.choose(Outcome.PreventDamage, targetCardInHand, source, game)) { + if (controller.choose(Outcome.PreventDamage, targetCardInHand, source, game)) { card = game.getCard(targetCardInHand.getFirstTarget()); paid = card != null && controller.moveCardToLibraryWithInfo(card, source, game, Zone.HAND, true, true); } diff --git a/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java index 1baac83c362..cb3cbdb36be 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PutCountersTargetCost.java @@ -58,6 +58,6 @@ public class PutCountersTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } } diff --git a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java index 03c1607a1fb..6264bd10aac 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RemoveCounterCost.java @@ -159,7 +159,7 @@ public class RemoveCounterCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } private String setText() { diff --git a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java index 640f9c7b0d3..48489cdbc0c 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandChosenControlledPermanentCost.java @@ -71,7 +71,7 @@ public class ReturnToHandChosenControlledPermanentCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java index 3efc8aec28f..bc0df6c3a52 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ReturnToHandFromGraveyardCost.java @@ -52,7 +52,7 @@ public class ReturnToHandFromGraveyardCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java index 6ce286b3e5e..bb1dcffa446 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/RevealTargetFromHandCost.java @@ -89,7 +89,7 @@ public class RevealTargetFromHandCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return allowNoReveal || this.getTargets().canChoose(controllerId, source, game); + return allowNoReveal || canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java index ea468535e43..761f22b5dc2 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/TapTargetCost.java @@ -69,7 +69,7 @@ public class TapTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } public TargetControlledPermanent getTarget() { diff --git a/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java index 8e0506c2f5c..2ba5fa58762 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/UntapTargetCost.java @@ -63,7 +63,7 @@ public class UntapTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java index 4af0a7d1837..bb091c2cf19 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/ConvokedSourceCount.java @@ -20,7 +20,7 @@ public enum ConvokedSourceCount implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return CardUtil.getSourceCostsTag(game, sourceAbility, ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)).size(); + return CardUtil.getSourceCostsTag(game, sourceAbility, ConvokeAbility.convokingCreaturesKey, new HashSet<>()).size(); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java index 63bca73a3b5..5eecee67a66 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java @@ -74,8 +74,7 @@ public class SacrificeOpponentsUnlessPayEffect extends OneShotEffect { if (game.getBattlefield().count(TargetSacrifice.makeFilter(filter), player.getId(), source, game) > 0) { TargetSacrifice target = new TargetSacrifice(1, filter); - if (target.canChoose(player.getId(), source, game)) { - player.choose(Outcome.Sacrifice, target, source, game); + if (player.choose(Outcome.Sacrifice, target, source, game)) { permsToSacrifice.addAll(target.getTargets()); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java index 7cacad39c33..a6a958121c2 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java @@ -7,6 +7,7 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.cards.CardsImpl; import mage.constants.Outcome; +import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.game.Game; @@ -60,7 +61,7 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect { } return true; } - TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, filter); + TargetCard target = new TargetCard(upTo ? 0 : num, num, Zone.HAND, filter); if (controller.choose(Outcome.Discard, player.getHand(), target, source, game)) { // TODO: must fizzle discard effect on not full choice // - tests: affected (allow to choose and discard 1 instead 2) diff --git a/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java b/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java index d6baf07740f..5ef72d017fc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ChampionAbility.java @@ -160,7 +160,7 @@ class ChampionExileCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java b/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java index 377f7743323..3ba9690fb4d 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java @@ -115,7 +115,7 @@ class CraftCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return target.canChoose(controllerId, source, game); + return target.canChooseOrAlreadyChosen(controllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java b/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java index 32422330d34..7410e49ad31 100644 --- a/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java @@ -171,7 +171,7 @@ class ReturnAttackerToHandTargetCost extends CostImpl { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - return this.getTargets().canChoose(controllerId, source, game); + return canChooseOrAlreadyChosen(ability, source, controllerId, game); } @Override diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java index cbb2b853a42..09ead62f82e 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ConvokedSourcePredicate.java @@ -17,7 +17,7 @@ public enum ConvokedSourcePredicate implements ObjectSourcePlayerPredicate input, Game game) { - HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0)); + HashSet set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>()); return set.contains(new MageObjectReference(input.getObject(), game)); } } diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index ad0cd9232d4..ea21c5102b5 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -162,7 +162,7 @@ public interface Game extends MageItem, Serializable, Copyable { // Result must be checked for null. Possible errors search pattern: (\S*) = game.getPlayer.+\n(?!.+\1 != null) Player getPlayer(UUID playerId); - Player getPlayerOrPlaneswalkerController(UUID playerId); + Player getPlayerOrPlaneswalkerController(UUID targetId); /** * Static players list from start of the game. Use it to find player by ID or in game engine. diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index e3f59becee3..978171210d6 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -412,12 +412,12 @@ public abstract class GameImpl implements Game { } @Override - public Player getPlayerOrPlaneswalkerController(UUID playerId) { - Player player = getPlayer(playerId); + public Player getPlayerOrPlaneswalkerController(UUID targetId) { + Player player = getPlayer(targetId); if (player != null) { return player; } - Permanent permanent = getPermanent(playerId); + Permanent permanent = getPermanent(targetId); if (permanent == null) { return null; } @@ -1841,6 +1841,7 @@ public abstract class GameImpl implements Game { boolean wasError = false; try { top = state.getStack().peek(); + DataCollectorServices.getInstance().onTestsStackResolve(this); top.resolve(this); resetControlAfterSpellResolve(top.getId()); } catch (Throwable e) { @@ -2819,7 +2820,7 @@ public abstract class GameImpl implements Game { if (attachedTo != null) { for (Ability ability : perm.getAbilities(this)) { if (ability instanceof AttachableToRestrictedAbility) { - if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), null, this)) { + if (!((AttachableToRestrictedAbility) ability).canEquip(attachedTo.getId(), this)) { attachedTo = null; break; } diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index 809fc545a32..d6aa18cd37c 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -253,9 +253,9 @@ public final class ZonesHandler { spell = new Spell(card, card.getSpellAbility().copy(), card.getOwnerId(), event.getFromZone(), game); } spell.syncZoneChangeCounterOnStack(card, game); - game.getStack().push(spell); game.getState().setZone(spell.getId(), Zone.STACK); game.getState().setZone(card.getId(), Zone.STACK); + game.getStack().push(game, spell); } break; case BATTLEFIELD: diff --git a/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java b/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java index af928e4acfb..a9416f76d98 100644 --- a/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java +++ b/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilationDungeon.java @@ -188,28 +188,22 @@ class OublietteTarget extends TargetSacrifice { return new OublietteTarget(this); } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) { - if (!super.canTarget(playerId, id, ability, game)) { - return false; - } - Permanent permanent = game.getPermanent(id); - if (permanent == null) { - return false; - } - if (this.getTargets().isEmpty()) { - return true; - } - Cards cards = new CardsImpl(this.getTargets()); - cards.add(permanent); - return cardTypeAssigner.getRoleCount(cards, game) >= cards.size(); - } - - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game)); + + // only valid roles + Cards existingTargets = new CardsImpl(this.getTargets()); + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + if (permanent == null) { + return true; + } + Cards newTargets = existingTargets.copy(); + newTargets.add(permanent); + return cardTypeAssigner.getRoleCount(newTargets, game) < newTargets.size(); + }); + return possibleTargets; } diff --git a/Mage/src/main/java/mage/game/events/TargetEvent.java b/Mage/src/main/java/mage/game/events/TargetEvent.java index f2c90ebc87b..f9b9f96484f 100644 --- a/Mage/src/main/java/mage/game/events/TargetEvent.java +++ b/Mage/src/main/java/mage/game/events/TargetEvent.java @@ -12,24 +12,21 @@ import java.util.UUID; public class TargetEvent extends GameEvent { /** - * @param target - * @param sourceId * @param sourceControllerId can be different from real controller (example: ability instructs another player to targeting) */ public TargetEvent(Card target, UUID sourceId, UUID sourceControllerId) { - super(GameEvent.EventType.TARGET, target.getId(), null, sourceControllerId); - this.setSourceId(sourceId); + this(target.getId(), sourceId, sourceControllerId); } public TargetEvent(Player target, UUID sourceId, UUID sourceControllerId) { - super(GameEvent.EventType.TARGET, target.getId(), null, sourceControllerId); + this(target.getId(), sourceId, sourceControllerId); + } + + public TargetEvent(UUID targetId, UUID sourceId, UUID sourceControllerId) { + super(GameEvent.EventType.TARGET, targetId, null, sourceControllerId); this.setSourceId(sourceId); } - /** - * @param targetId - * @param source - */ public TargetEvent(UUID targetId, Ability source) { super(GameEvent.EventType.TARGET, targetId, source, source.getControllerId()); } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 7cc905df6b6..2b1ed1937c6 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -42,7 +42,11 @@ public class Spell extends StackObjectImpl implements Card { private final List spellAbilities = new ArrayList<>(); + // this.card.getSpellAbility() - blueprint ability with zero selected targets + // this.ability - real casting ability with selected targets + // so if you need to check targets on cast then try to use "Ability source" param first (e.g. aura legality on etb) private final Card card; + private ManaCosts manaCost; private final ObjectColor color; private final ObjectColor frameColor; @@ -1157,7 +1161,7 @@ public class Spell extends StackObjectImpl implements Card { applier.modifySpell(spellCopy, game); } spellCopy.setZone(Zone.STACK, game); // required for targeting ex: Nivmagus Elemental - game.getStack().push(spellCopy); + game.getStack().push(game, spellCopy); // new targets if (newTargetFilterPredicate != null) { diff --git a/Mage/src/main/java/mage/game/stack/SpellStack.java b/Mage/src/main/java/mage/game/stack/SpellStack.java index 83458c5980b..598c67c366e 100644 --- a/Mage/src/main/java/mage/game/stack/SpellStack.java +++ b/Mage/src/main/java/mage/game/stack/SpellStack.java @@ -3,6 +3,7 @@ package mage.game.stack; import mage.MageObject; import mage.abilities.Ability; +import mage.collectors.DataCollectorServices; import mage.constants.PutCards; import mage.constants.Zone; import mage.game.Game; @@ -136,11 +137,18 @@ public class SpellStack extends ArrayDeque { } @Override + @Deprecated // must use only full version with game param public void push(StackObject e) { super.push(e); this.dateLastAdded = new Date(); } + public void push(Game game, StackObject e) { + super.push(e); + this.dateLastAdded = new Date(); + DataCollectorServices.getInstance().onTestsStackPush(game); + } + public Date getDateLastAdded() { return dateLastAdded; } diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index f83a90a5697..a6f80c416c1 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -744,7 +744,7 @@ public class StackAbility extends StackObjectImpl implements Ability { newAbility.setControllerId(newControllerId); StackAbility newStackAbility = new StackAbility(newAbility, newControllerId); - game.getStack().push(newStackAbility); + game.getStack().push(game, newStackAbility); // new targets if (newTargetFilterPredicate != null) { diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 5e3e89f8a8d..bf3c4bba966 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -1231,10 +1231,6 @@ public interface Player extends MageItem, Copyable { /** * Only used for test player for pre-setting targets - * - * @param ability - * @param game - * @return */ boolean addTargets(Ability ability, Game game); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 94d126e11f5..e35f1d43034 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -101,7 +101,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected Cards sideboard; protected Cards hand; protected Graveyard graveyard; - protected Set commandersIds = new HashSet<>(0); + protected Set commandersIds = new HashSet<>(); protected Abilities abilities; protected Counters counters; @@ -1546,7 +1546,7 @@ public abstract class PlayerImpl implements Player, Serializable { setStoredBookmark(bookmark); // move global bookmark to current state (if you activated mana before then you can't rollback it) ability.newId(); ability.setControllerId(playerId); - game.getStack().push(new StackAbility(ability, playerId)); + game.getStack().push(game, new StackAbility(ability, playerId)); if (ability.activate(game, false)) { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY, ability.getId(), ability, playerId)); @@ -1711,7 +1711,7 @@ public abstract class PlayerImpl implements Player, Serializable { ability.adjustTargets(game); if (ability.canChooseTarget(game, playerId)) { if (ability.isUsesStack()) { - game.getStack().push(new StackAbility(ability, playerId)); + game.getStack().push(game, new StackAbility(ability, playerId)); } if (ability.activate(game, false)) { if ((ability.isUsesStack() @@ -4561,29 +4561,46 @@ public abstract class PlayerImpl implements Player, Serializable { } /** - * AI related code + * AI related code, generate all possible usage use cases for activating ability (all possible targets combination) */ protected void addTargetOptions(List options, Ability option, int targetNum, Game game) { // TODO: target options calculated for triggered ability too, but do not used in real game // TODO: there are rare errors with wrong targetNum - maybe multiple game sims can change same target object somehow? // do not hide NullPointError here, research instead - for (Target target : option.getTargets().getNextUnchosen(game, targetNum).getTargetOptions(option, game)) { + + if (targetNum >= option.getTargets().size()) { + return; + } + + // already selected for some reason (TODO: is it possible?) + Target currentTarget = option.getTargets().get(targetNum); + if (currentTarget.isChoiceSelected()) { + return; + } + + // analyse all possible use cases + for (Target targetOption : currentTarget.getTargetOptions(option, game)) { + // fill target Ability newOption = option.copy(); - if (target instanceof TargetAmount) { - for (UUID targetId : target.getTargets()) { - int amount = target.getTargetAmount(targetId); + if (targetOption instanceof TargetAmount) { + for (UUID targetId : targetOption.getTargets()) { + int amount = targetOption.getTargetAmount(targetId); newOption.getTargets().get(targetNum).addTarget(targetId, amount, newOption, game, true); } } else { - for (UUID targetId : target.getTargets()) { + for (UUID targetId : targetOption.getTargets()) { newOption.getTargets().get(targetNum).addTarget(targetId, newOption, game, true); } } - if (targetNum < option.getTargets().size() - 2) { // wtf + + if (targetNum + 1 < option.getTargets().size()) { + // fill more targets addTargetOptions(options, newOption, targetNum + 1, game); } else if (!option.getCosts().getTargets().isEmpty()) { + // fill cost addCostTargetOptions(options, newOption, 0, game); } else { + // all filled, ability ready with all targets and costs options.add(newOption); } } @@ -4938,6 +4955,7 @@ public abstract class PlayerImpl implements Player, Serializable { List infoList = new ArrayList<>(); for (Card card : cards) { fromZone = game.getState().getZone(card.getId()); + // 712.14a. If a spell or ability puts a transforming double-faced card onto the battlefield "transformed" // or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card // that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in @@ -4946,15 +4964,29 @@ public abstract class PlayerImpl implements Player, Serializable { if (enterTransformed != null && enterTransformed && !card.isTransformable()) { continue; } + // 303.4g. If an Aura is entering the battlefield and there is no legal object or player for it to enchant, // the Aura remains in its current zone, unless that zone is the stack. In that case, the Aura is put into // its owner's graveyard instead of entering the battlefield. If the Aura is a token, it isn't created. - if (card.hasSubtype(SubType.AURA, game) - && card.getSpellAbility() != null - && !card.getSpellAbility().getTargets().isEmpty() - && !card.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).canChoose(byOwner ? card.getOwnerId() : getId(), game)) { - continue; + if (card.hasSubtype(SubType.AURA, game) && !(source instanceof BestowAbility)) { + SpellAbility auraSpellAbility; + if (source instanceof SpellAbility && card.getAbilities(game).contains(source)) { + // cast aura - use source ability + auraSpellAbility = (SpellAbility) source; + } else { + // put to battlefield by another effect - use default spell + auraSpellAbility = card.getSpellAbility(); + } + if (auraSpellAbility != null) { + if (auraSpellAbility.getTargets().isEmpty()) { + throw new IllegalArgumentException("Something wrong, found etb aura with empty spell ability or without any targets: " + card + ", source: " + source); + } + if (!auraSpellAbility.getTargets().get(0).copy().withNotTarget(true).canChooseOrAlreadyChosen(byOwner ? card.getOwnerId() : getId(), source, game)) { + continue; + } + } } + ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source, byOwner ? card.getOwnerId() : getId(), fromZone, Zone.BATTLEFIELD, appliedEffects); infoList.add(new ZoneChangeInfo.Battlefield(event, faceDown, tapped, source)); diff --git a/Mage/src/main/java/mage/players/StubPlayer.java b/Mage/src/main/java/mage/players/StubPlayer.java index d39f65a1444..b5efae370e3 100644 --- a/Mage/src/main/java/mage/players/StubPlayer.java +++ b/Mage/src/main/java/mage/players/StubPlayer.java @@ -43,7 +43,7 @@ public class StubPlayer extends PlayerImpl { if (target instanceof TargetPlayer) { for (Player player : game.getPlayers().values()) { if (player.getId().equals(getId()) - && target.canTarget(getId(), game) + && target.canTarget(getId(), source, game) && !target.contains(getId())) { target.add(player.getId(), game); return true; diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java index c04395b2cc5..89b9b113f04 100644 --- a/Mage/src/main/java/mage/target/Target.java +++ b/Mage/src/main/java/mage/target/Target.java @@ -1,11 +1,13 @@ package mage.target; +import mage.MageObject; import mage.abilities.Ability; import mage.cards.Cards; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.Filter; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.Copyable; @@ -27,10 +29,24 @@ public interface Target extends Copyable, Serializable { * Warning, for "up to" targets it will return true all the time, so make sure your dialog * use do-while logic and call "choose" one time min or use isChoiceCompleted */ - @Deprecated // TODO: replace with UUID abilityControllerId, Ability source, Game game + @Deprecated + // TODO: replace with UUID abilityControllerId, Ability source, Game game boolean isChosen(Game game); - boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game); + /** + * Checking target complete and nothing to choose (X=0, all selected, all possible selected, etc) + * + * @param fromCards can be null for non cards selection + */ + boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards); + + /** + * Temporary status to work with "up to" targets (mark target that it was skip on selection) + * TODO: remove after target.chooseXXX remove + */ + boolean isSkipChoice(); + + void setSkipChoice(boolean isSkipChoice); void clearChosen(); @@ -51,16 +67,50 @@ public interface Target extends Copyable, Serializable { */ Target withNotTarget(boolean notTarget); - // methods for targets + /** + * Checks if there are enough targets the player can choose from among them + * or if they are autochosen since there are fewer than the minimum number. + *

+ * Implement as return canChooseFromPossibleTargets(sourceControllerId, source, game); + * TODO: remove after all canChoose replaced with default + * + * @param sourceControllerId - controller of the target event source + * @param source - can be null + * @param game + * @return - true if enough valid choices exist + */ boolean canChoose(UUID sourceControllerId, Ability source, Game game); + /** + * Make sure target can be fully selected or already selected, e.g. by AI sims + *

+ * Do not override + */ + default boolean canChooseOrAlreadyChosen(UUID sourceControllerId, Ability source, Game game) { + return this.isChosen(game) || this.canChoose(sourceControllerId, source, game); + } + + default boolean canChooseFromPossibleTargets(UUID sourceControllerId, Ability source, Game game) { + // TODO: replace all canChoose override methods by that code call + if (getMinNumberOfTargets() == 0) { + return true; + } + + int selectedCount = getSize(); + int moreSelectCount = possibleTargets(sourceControllerId, source, game).size(); + + if (selectedCount >= getMaxNumberOfTargets()) { + return false; + } + + return moreSelectCount > 0 && selectedCount + moreSelectCount >= getMinNumberOfTargets(); + } + /** * Returns a set of all possible targets that match the criteria of the implemented Target class. + * WARNING, it must filter already selected targets by keepValidPossibleTargets call at the end * - * @param sourceControllerId UUID of the ability's controller - * @param source Ability which requires the targets - * @param game Current game - * @return Set of the UUIDs of possible targets + * @param source - can be null */ Set possibleTargets(UUID sourceControllerId, Ability source, Game game); @@ -71,6 +121,42 @@ public interface Target extends Copyable, Serializable { .collect(Collectors.toSet()); } + /** + * Keep only valid and not selected targets - must be used inside any possibleTargets implementation + */ + default Set keepValidPossibleTargets(Set possibleTargets, UUID sourceControllerId, Ability source, Game game) { + // TODO: check target amount in human dialogs - is it allow to select it again + // do not override + // keep only valid and not selected targets list + return possibleTargets.stream() + .filter(this::notContains) + .filter(targetId -> { + // non-target allow any + if (source == null || source.getSourceId() == null || isNotTarget()) { + return true; + } + MageObject sourceObject = game.getObject(source); + if (sourceObject == null) { + return true; + } + + // target allow non-protected + Player targetPlayer = game.getPlayer(targetId); + if (targetPlayer != null) { + return !targetPlayer.hasLeft() + && canTarget(sourceControllerId, targetId, source, game) + && targetPlayer.canBeTargetedBy(sourceObject, sourceControllerId, source, game); + } + Permanent targetPermanent = game.getPermanent(targetId); + if (targetPermanent != null) { + return canTarget(sourceControllerId, targetId, source, game) + && targetPermanent.canBeTargetedBy(sourceObject, sourceControllerId, source, game); + } + return true; + }) + .collect(Collectors.toSet()); + } + /** * Priority method to make a choice from cards and other places, not a player.chooseXXX */ @@ -87,8 +173,6 @@ public interface Target extends Copyable, Serializable { void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent); - boolean canTarget(UUID id, Game game); - /** * @param id * @param source WARNING, it can be null for AI or other calls from events @@ -108,11 +192,12 @@ public interface Target extends Copyable, Serializable { */ List getTargetOptions(Ability source, Game game); - boolean canChoose(UUID sourceControllerId, Game game); + default boolean canChoose(UUID sourceControllerId, Game game) { + return canChoose(sourceControllerId, null, game); + } - Set possibleTargets(UUID sourceControllerId, Game game); - - @Deprecated // TODO: need replace to source only version? + @Deprecated + // TODO: need replace to source only version? boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game); /** @@ -236,6 +321,11 @@ public interface Target extends Copyable, Serializable { boolean contains(UUID targetId); + default boolean notContains(UUID targetId) { + // for better usage in streams + return !contains(targetId); + } + /** * This function tries to auto-choose the next target. *

diff --git a/Mage/src/main/java/mage/target/TargetAmount.java b/Mage/src/main/java/mage/target/TargetAmount.java index d28b1852459..bcc4b84bc4e 100644 --- a/Mage/src/main/java/mage/target/TargetAmount.java +++ b/Mage/src/main/java/mage/target/TargetAmount.java @@ -5,6 +5,7 @@ import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.cards.Card; +import mage.cards.Cards; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; @@ -68,7 +69,7 @@ public abstract class TargetAmount extends TargetImpl { } @Override - public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards) { // make sure target request called one time minimum (for "up to" targets) // choice is selected after any addTarget call (by test, AI or human players) if (!isChoiceSelected()) { @@ -80,6 +81,11 @@ public abstract class TargetAmount extends TargetImpl { return false; } + // already selected + if (this.getSize() >= getMaxNumberOfTargets()) { + return true; + } + // TODO: need auto-choose here? See super // all other use cases are fine @@ -178,7 +184,7 @@ public abstract class TargetAmount extends TargetImpl { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(targetController.getId(), source, game)) { + if (isChoiceCompleted(targetController.getId(), source, game, null)) { break; } diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index 3abc11591dc..af7b05fc114 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -1,6 +1,5 @@ package mage.target; -import mage.MageItem; import mage.abilities.Ability; import mage.cards.Card; import mage.cards.Cards; @@ -56,196 +55,9 @@ public class TargetCard extends TargetObject { return this; } - /** - * Checks if there are enough {@link Card} that can be selected. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Card} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); - } - - /** - * Checks if there are enough {@link Card cards} in the appropriate zone that the player can choose from among them - * or if they are autochosen since there are fewer than the minimum number. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Card} exist - */ @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int possibleTargets = 0; - if (getMinNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true - return true; - } - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null) { - if (this.minNumberOfTargets == 0) { - return true; - } - switch (zone) { - case HAND: - possibleTargets += countPossibleTargetInHand(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case GRAVEYARD: - possibleTargets += countPossibleTargetInGraveyard(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case LIBRARY: - possibleTargets += countPossibleTargetInLibrary(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case EXILED: - possibleTargets += countPossibleTargetInExile(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case COMMAND: - possibleTargets += countPossibleTargetInCommandZone(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - case ALL: - possibleTargets += countPossibleTargetInAnyZone(game, player, sourceControllerId, source, - filter, isNotTarget(), this.minNumberOfTargets - possibleTargets); - break; - default: - throw new IllegalArgumentException("Unsupported TargetCard zone: " + zone); - } - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - /** - * count up to N possible target cards in a player's hand matching the filter - */ - protected static int countPossibleTargetInHand(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's graveyard matching the filter - */ - protected static int countPossibleTargetInGraveyard(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's library matching the filter - */ - protected static int countPossibleTargetInLibrary(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : player.getLibrary().getUniqueCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, game)) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's exile matching the filter - */ - protected static int countPossibleTargetInExile(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : game.getExile().getPermanentExile().getCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, player.getId(), source, game)) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in a player's command zone matching the filter - */ - protected static int countPossibleTargetInCommandZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - List possibleCards = game.getCommandersIds(player, CommanderCardType.ANY, false).stream() - .map(game::getCard) - .filter(Objects::nonNull) - .filter(card -> game.getState().getZone(card.getId()).equals(Zone.COMMAND)) - .filter(card -> filter.match(card, sourceControllerId, source, game)) - .collect(Collectors.toList()); - for (Card card : possibleCards) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - return possibleTargets; - } - - /** - * count up to N possible target cards in ANY zone - */ - protected static int countPossibleTargetInAnyZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget, int countUpTo) { - UUID sourceId = source != null ? source.getSourceId() : null; - int possibleTargets = 0; - for (Card card : game.getCards()) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, game)) { - possibleTargets++; - if (possibleTargets >= countUpTo) { - return possibleTargets; // early return for faster computation. - } - } - } - } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return possibleTargets(sourceControllerId, null, game); - } - - public Set possibleTargets(UUID sourceControllerId, Cards cards, Ability source, Game game) { - return cards.getCards(filter, sourceControllerId, source, game).stream().map(MageItem::getId).collect(Collectors.toSet()); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override @@ -278,7 +90,7 @@ public class TargetCard extends TargetObject { } } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } /** @@ -286,12 +98,8 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInHand(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - // TODO: Why for sourceId == null? - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } return possibleTargets; } @@ -301,11 +109,8 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInGraveyard(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } return possibleTargets; } @@ -315,12 +120,9 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInLibrary(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : player.getLibrary().getUniqueCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, sourceControllerId, source, game)) { - possibleTargets.add(card.getId()); - } + if (filter.match(card, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); } } return possibleTargets; @@ -332,11 +134,9 @@ public class TargetCard extends TargetObject { protected static Set getAllPossibleTargetInExile(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); UUID sourceId = source != null ? source.getSourceId() : null; - for (Card card : game.getExile().getPermanentExile().getCards(game)) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, sourceControllerId, source, game)) { - possibleTargets.add(card.getId()); - } + for (Card card : game.getExile().getAllCardsByRange(game, sourceControllerId)) { + if (filter.match(card, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); } } return possibleTargets; @@ -367,21 +167,16 @@ public class TargetCard extends TargetObject { */ protected static Set getAllPossibleTargetInAnyZone(Game game, Player player, UUID sourceControllerId, Ability source, FilterCard filter, boolean isNotTarget) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; for (Card card : game.getCards()) { - if (sourceId == null || isNotTarget || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - if (filter.match(card, sourceControllerId, source, game)) { - possibleTargets.add(card.getId()); - } + if (filter.match(card, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); } } return possibleTargets; } - // TODO: check all class targets, if it override canTarget then make sure it override ALL 3 METHODS with canTarget and possibleTargets (method with cards doesn't need) - @Override - public boolean canTarget(UUID id, Game game) { + public boolean canTarget(UUID id, Ability source, Game game) { // copy-pasted from super but with card instead object Card card = game.getCard(id); return card != null @@ -389,11 +184,6 @@ public class TargetCard extends TargetObject { && getFilter() != null && getFilter().match(card, game); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(id, game); - } - @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Card card = game.getCard(id); diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index 48f6f4bf73f..1fed07fc4a4 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -3,6 +3,7 @@ package mage.target; import mage.MageObject; import mage.abilities.Ability; import mage.cards.Card; +import mage.cards.Cards; import mage.constants.AbilityType; import mage.constants.Outcome; import mage.constants.Zone; @@ -41,6 +42,8 @@ public abstract class TargetImpl implements Target { */ protected boolean chosen = false; + protected boolean isSkipChoice = false; + // is the target handled as targeted spell/ability (notTarget = true is used for not targeted effects like e.g. sacrifice) protected boolean notTarget = false; protected boolean atRandom = false; // for inner choose logic @@ -70,6 +73,7 @@ public abstract class TargetImpl implements Target { this.required = target.required; this.requiredExplicitlySet = target.requiredExplicitlySet; this.chosen = target.chosen; + this.isSkipChoice = target.isSkipChoice; this.targets.putAll(target.targets); this.zoneChangeCounters.putAll(target.zoneChangeCounters); this.atRandom = target.atRandom; @@ -163,6 +167,16 @@ public abstract class TargetImpl implements Target { return min == 0 && max == Integer.MAX_VALUE; } + @Override + public boolean isSkipChoice() { + return this.isSkipChoice; + } + + @Override + public void setSkipChoice(boolean isSkipChoice) { + this.isSkipChoice = isSkipChoice; + } + @Override public String getMessage(Game game) { // UI choose message @@ -261,7 +275,7 @@ public abstract class TargetImpl implements Target { } @Override - public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards) { // make sure target request called one time minimum (for "up to" targets) // choice is selected after any addTarget call (by test, AI or human players) if (!isChoiceSelected()) { @@ -273,22 +287,23 @@ public abstract class TargetImpl implements Target { return false; } + // already selected + if (this.getSize() >= getMaxNumberOfTargets()) { + return true; + } + // make sure to auto-finish on all targets selection // - human player can select and deselect targets until fill all targets amount or press done button // - AI player can select all new targets as much as possible if (getMaxNumberOfTargets() > 0) { - if (getMaxNumberOfTargets() == Integer.MAX_VALUE) { - if (abilityControllerId != null && source != null) { - // any amount - nothing to choose - return this.getSize() >= this.possibleTargets(abilityControllerId, source, game).size(); - } else { - // any amount - any selected - return this.getSize() > 0; - } - } else { - // check selected limit - return this.getSize() >= getMaxNumberOfTargets(); + // full selection + if (this.getSize() >= getMaxNumberOfTargets()) { + return true; } + + // partly selection + int moreSelectCount = this.possibleTargets(abilityControllerId, source, game, fromCards).size(); + return moreSelectCount == 0 || isSkipChoice(); } // all other use cases are fine @@ -300,6 +315,7 @@ public abstract class TargetImpl implements Target { targets.clear(); zoneChangeCounters.clear(); chosen = false; + isSkipChoice = false; } @Override @@ -423,7 +439,7 @@ public abstract class TargetImpl implements Target { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(abilityControllerId, source, game)) { + if (isChoiceCompleted(abilityControllerId, source, game, null)) { break; } @@ -503,7 +519,7 @@ public abstract class TargetImpl implements Target { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(abilityControllerId, source, game)) { + if (isChoiceCompleted(abilityControllerId, source, game, null)) { break; } @@ -570,9 +586,7 @@ public abstract class TargetImpl implements Target { @Override public List getTargetOptions(Ability source, Game game) { List options = new ArrayList<>(); - List possibleTargets = new ArrayList<>(); - possibleTargets.addAll(possibleTargets(source.getControllerId(), source, game)); - possibleTargets.removeAll(getTargets()); + List possibleTargets = new ArrayList<>(possibleTargets(source.getControllerId(), source, game)); // get the length of the array // e.g. for {'A','B','C','D'} => N = 4 diff --git a/Mage/src/main/java/mage/target/TargetObject.java b/Mage/src/main/java/mage/target/TargetObject.java index 1cce9447696..46918cfd479 100644 --- a/Mage/src/main/java/mage/target/TargetObject.java +++ b/Mage/src/main/java/mage/target/TargetObject.java @@ -50,27 +50,17 @@ public abstract class TargetObject extends TargetImpl { /** * Warning, don't use with non card objects here like commanders/emblems/etc. If you want it then * override canTarget in your own target. - * - * @param id - * @param game - * @return */ @Override - public boolean canTarget(UUID id, Game game) { + public boolean canTarget(UUID id, Ability source, Game game) { MageObject object = game.getObject(id); return object != null && zone != null && zone.match(game.getState().getZone(id)) && getFilter() != null && getFilter().match(object, game); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(id, game); - } - @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { return canTarget(id, source, game); } - } diff --git a/Mage/src/main/java/mage/target/TargetPermanent.java b/Mage/src/main/java/mage/target/TargetPermanent.java index c97ea339e01..16cf4a9c1fb 100644 --- a/Mage/src/main/java/mage/target/TargetPermanent.java +++ b/Mage/src/main/java/mage/target/TargetPermanent.java @@ -51,11 +51,11 @@ public class TargetPermanent extends TargetObject { @Override public boolean canTarget(UUID id, Ability source, Game game) { - return canTarget(source.getControllerId(), id, source, game); + return canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (permanent == null) { return false; @@ -67,19 +67,14 @@ public class TargetPermanent extends TargetObject { // first for protection from spells or abilities (e.g. protection from colored spells, r1753) // second for protection from sources (e.g. protection from artifacts + equip ability) if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) - || !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game) + || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) { return false; } } } - return filter.match(permanent, controllerId, source, game); - } - - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game, boolean flag) { - Permanent permanent = game.getPermanent(id); - return filter.match(permanent, controllerId, source, game); + return filter.match(permanent, playerId, source, game); } @Override @@ -87,91 +82,19 @@ public class TargetPermanent extends TargetObject { return this.filter; } - /** - * Checks if there are enough {@link Permanent} that can be chosen. - *

- * Takes into account notTarget parameter, in case it's true doesn't check - * for protection, shroud etc. - * - * @param sourceControllerId controller of the target event source - * @param source - * @param game - * @return true if enough valid {@link Permanent} exist - */ @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets <= 0) { - return true; - } - int count = 0; - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId())) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - } - return false; - } - - /** - * Checks if there are enough {@link Permanent} that can be selected. Should - * not be used for Ability targets since this does not check for protection, - * shroud etc. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Permanent} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int remainingTargets = this.minNumberOfTargets - targets.size(); - if (remainingTargets == 0) { - // if we return true, then AnowonTheRuinSage will hang for AI when no targets in play - // TODO: retest Anowon the Ruin Sage - return true; - } - int count = 0; - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { - if (!targets.containsKey(permanent.getId())) { - count++; - if (count >= remainingTargets) { - return true; - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { // TODO: check if possible targets works with setTargetController from some cards like Nicol Bolas, Dragon-God Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { - if (!targets.containsKey(permanent.getId())) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } + possibleTargets.add(permanent.getId()); } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { - if (!targets.containsKey(permanent.getId())) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/TargetPlayer.java b/Mage/src/main/java/mage/target/TargetPlayer.java index 9c1c13ad722..ef0696597b7 100644 --- a/Mage/src/main/java/mage/target/TargetPlayer.java +++ b/Mage/src/main/java/mage/target/TargetPlayer.java @@ -51,82 +51,21 @@ public class TargetPlayer extends TargetImpl { return filter; } - /** - * Checks if there are enough {@link Player} that can be chosen. Should only - * be used for Ability targets since this checks for protection, shroud etc. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Player} exist - */ @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, sourceControllerId, source, game)) { - if (player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; - } - - /** - * Checks if there are enough {@link Player} that can be selected. Should - * not be used for Ability targets since this does not check for protection, - * shroud etc. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Player} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - int count = 0; - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - MageObject targetSource = game.getObject(source); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, sourceControllerId, source, game)) { - if (isNotTarget() || player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(playerId); - } - } - } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player != null && !player.hasLeft() && filter.match(player, game)) { + if (player != null && filter.match(player, sourceControllerId, source, game)) { possibleTargets.add(playerId); } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -138,12 +77,6 @@ public class TargetPlayer extends TargetImpl { return targets.keySet().stream().anyMatch(playerId -> canTarget(playerId, source, game)); } - @Override - public boolean canTarget(UUID id, Game game) { - Player player = game.getPlayer(id); - return filter.match(player, game); - } - @Override public boolean canTarget(UUID id, Ability source, Game game) { Player player = game.getPlayer(id); diff --git a/Mage/src/main/java/mage/target/TargetSource.java b/Mage/src/main/java/mage/target/TargetSource.java index 171e6b88236..5ffbc7639f2 100644 --- a/Mage/src/main/java/mage/target/TargetSource.java +++ b/Mage/src/main/java/mage/target/TargetSource.java @@ -89,63 +89,9 @@ public class TargetSource extends TargetObject { return true; } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, (Ability) null, game); - } - @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filter.match(stackObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - for (Player player : game.getPlayers().values()) { - for (Card card : player.getGraveyard().getCards(game)) { - if (filter.match(card, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - } - for (Card card : game.getExile().getAllCards(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++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return possibleTargets(sourceControllerId, (Ability) null, game); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override @@ -179,7 +125,8 @@ public class TargetSource extends TargetObject { possibleTargets.add(commandObject.getId()); } } - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/TargetSpell.java b/Mage/src/main/java/mage/target/TargetSpell.java index 4ded8d97a8d..45d11a6a6c9 100644 --- a/Mage/src/main/java/mage/target/TargetSpell.java +++ b/Mage/src/main/java/mage/target/TargetSpell.java @@ -64,41 +64,16 @@ public class TargetSpell extends TargetObject { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (this.minNumberOfTargets == 0) { - return true; - } - int count = 0; - for (StackObject stackObject : game.getStack()) { - // rule 114.4. A spell or ability on the stack is an illegal target for itself. - if (source.getSourceId() != null && source.getSourceId().equals(stackObject.getSourceId())) { - continue; - } - if (canBeChosen(stackObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return game.getStack().stream() + Set possibleTargets = game.getStack().stream() .filter(stackObject -> canBeChosen(stackObject, sourceControllerId, source, game)) .map(StackObject::getId) .collect(Collectors.toSet()); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, null, game); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -107,7 +82,11 @@ public class TargetSpell extends TargetObject { } private boolean canBeChosen(StackObject stackObject, UUID sourceControllerId, Ability source, Game game) { + // rule 114.4. A spell or ability on the stack is an illegal target for itself. + boolean isSelfTarget = source.getSourceId() != null && source.getSourceId().equals(stackObject.getSourceId()); + return stackObject instanceof Spell + && !isSelfTarget && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) && canTarget(sourceControllerId, stackObject.getId(), source, game); } diff --git a/Mage/src/main/java/mage/target/TargetStackObject.java b/Mage/src/main/java/mage/target/TargetStackObject.java index 2b7840436c2..42df9dc7ec3 100644 --- a/Mage/src/main/java/mage/target/TargetStackObject.java +++ b/Mage/src/main/java/mage/target/TargetStackObject.java @@ -56,22 +56,7 @@ public class TargetStackObject extends TargetObject { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int count = 0; - for (StackObject stackObject : game.getStack()) { - if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filter.match(stackObject, sourceControllerId, source, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override @@ -83,12 +68,7 @@ public class TargetStackObject extends TargetObject { possibleTargets.add(stackObject.getId()); } } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, null, game); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/Targets.java b/Mage/src/main/java/mage/target/Targets.java index cba1f867cef..10e6cba05ab 100644 --- a/Mage/src/main/java/mage/target/Targets.java +++ b/Mage/src/main/java/mage/target/Targets.java @@ -2,6 +2,7 @@ package mage.target; import mage.MageObject; import mage.abilities.Ability; +import mage.cards.Cards; import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; @@ -63,8 +64,8 @@ public class Targets extends ArrayList implements Copyable { return unchosenIndex < res.size() ? res.get(unchosenIndex) : null; } - public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game) { - return stream().allMatch(t -> t.isChoiceCompleted(abilityControllerId, source, game)); + public boolean isChoiceCompleted(UUID abilityControllerId, Ability source, Game game, Cards fromCards) { + return stream().allMatch(t -> t.isChoiceCompleted(abilityControllerId, source, game, fromCards)); } public void clearChosen() { @@ -78,99 +79,64 @@ public class Targets extends ArrayList implements Copyable { } public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game) { - Player player = game.getPlayer(playerId); - if (player == null) { - return false; - } - - // in test mode some targets can be predefined already, e.g. by cast/activate command - // so do not clear chosen status here - - if (this.size() > 0) { - do { - // stop on disconnect or nothing to choose - if (!player.canRespond() || !canChoose(playerId, source, game)) { - break; - } - - // stop on complete - Target target = this.getNextUnchosen(game); - if (target == null) { - break; - } - - // stop on cancel/done - if (!target.choose(outcome, playerId, sourceId, source, game)) { - break; - } - - // target done, can take next one - } while (true); - } - - if (DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS && !game.isSimulation()) { - printDebugTargets("choose finish", this, source, game); - } - - return isChosen(game); + return makeChoice(false, outcome, playerId, source, false, game, false); } public boolean chooseTargets(Outcome outcome, UUID playerId, Ability source, boolean noMana, Game game, boolean canCancel) { - Player player = game.getPlayer(playerId); - if (player == null) { - return false; - } + return makeChoice(true, outcome, playerId, source, noMana, game, canCancel); + } + private boolean makeChoice(boolean isTargetChoice, Outcome outcome, UUID playerId, Ability source, boolean noMana, Game game, boolean canCancel) { // in test mode some targets can be predefined already, e.g. by cast/activate command // so do not clear chosen status here - if (this.size() > 0) { - do { - // stop on disconnect or nothing to choose - if (!player.canRespond() || !canChoose(playerId, source, game)) { - break; - } + // there are possible multiple targets, so must check per target, not whole list + // good example: cast Scatter to the Winds with awaken + for (Target target : this) { + UUID abilityControllerId = target.getAffectedAbilityControllerId(playerId); - // stop on complete - Target target = this.getNextUnchosen(game); - if (target == null) { - break; - } + // stop on disconnect + Player player = game.getPlayer(abilityControllerId); + if (player == null || !player.canRespond()) { + return false; + } - // some targets can have controller different than ability controller - UUID targetController = playerId; - if (target.getTargetController() != null) { - targetController = target.getTargetController(); - } + // continue on nothing to choose or complete + if (target.isChoiceSelected() || !target.canChoose(abilityControllerId, source, game)) { + continue; + } - // disable cancel button - if cast without mana (e.g. by suspend you may not be able to cancel the casting if you are able to cast it - if (noMana) { - target.setRequired(true); - } - // enable cancel button - if (canCancel) { - target.setRequired(false); - } + // TODO: need research and remove or re-implement for other choices + // disable cancel button - if cast without mana (e.g. by Suspend) you may not be able to cancel the casting if you are able to cast it + if (noMana) { + target.setRequired(true); + } + // enable cancel button + if (canCancel) { + target.setRequired(false); + } - // stop on cancel/done - if (!target.chooseTarget(outcome, targetController, source, game)) { - if (!target.isChosen(game)) { - break; - } - } + // continue on cancel/skip one of the target + boolean choiceRes; + if (isTargetChoice) { + choiceRes = target.chooseTarget(outcome, abilityControllerId, source, game); + } else { + choiceRes = target.choose(outcome, abilityControllerId, source, game); + } + if (!choiceRes) { + //break; // do not stop targeting, example: two "or" targets from Finale of Promise + } + } - // reset on wrong restrictions and start from scratch - if (this.getNextUnchosen(game) == null - && game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source, source.getControllerId()), source)) { - clearChosen(); - } - - // target done, can take next one - } while (true); + // TODO: need research or wait bug reports - old version was able to continue selection from scratch, + // current version just clear the chosen, but do not start selection again + // reset on wrong restrictions and start from scratch + if (isTargetChoice && isChosen(game) && game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source, source.getControllerId()), source)) { + clearChosen(); } if (DebugUtil.GAME_SHOW_CHOOSE_TARGET_LOGS && !game.isSimulation()) { - printDebugTargets("chooseTargets finish", this, source, game); + printDebugTargets(isTargetChoice ? "target finish" : "choose finish", this, source, game); } return isChosen(game); diff --git a/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java b/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java index 17e7067d386..87ab3a0454d 100644 --- a/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java +++ b/Mage/src/main/java/mage/target/common/TargetActivatedAbility.java @@ -54,38 +54,21 @@ public class TargetActivatedAbility extends TargetObject { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return canChoose(sourceControllerId, game); - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - for (StackObject stackObject : game.getStack()) { - if (stackObject.getStackAbility() != null - && stackObject.getStackAbility().isActivatedAbility() - && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getStackAbility().getControllerId()) - ) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { Set possibleTargets = new HashSet<>(); for (StackObject stackObject : game.getStack()) { if (stackObject.getStackAbility().isActivatedAbility() && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getStackAbility().getControllerId()) - && filter.match(stackObject, game)) { + && filter.match(stackObject,sourceControllerId, source, game) + && this.notContains(stackObject.getId())) { possibleTargets.add(stackObject.getStackAbility().getId()); } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java index d5c01e5f4b7..4d542757de9 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCard.java @@ -12,6 +12,7 @@ import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.target.TargetCard; import mage.util.CardUtil; @@ -70,48 +71,23 @@ public class TargetCardAndOrCard extends TargetCard { return new TargetCardAndOrCard(this); } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { - if (!super.canTarget(playerId, id, source, game)) { - return false; - } - Card card = game.getCard(id); - if (card == null) { - return false; - } - if (this.getTargets().isEmpty()) { - return true; - } - Cards cards = new CardsImpl(this.getTargets()); - cards.add(card); - return assignment.getRoleCount(cards, game) >= cards.size(); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - // assuming max targets = 2, need to expand this code if not - Card card = game.getCard(this.getFirstTarget()); - if (card == null) { - return possibleTargets; // no further restriction if no target yet chosen - } - Cards cards = new CardsImpl(card); - if (assignment.getRoleCount(cards, game) == 2) { - // if the first chosen target is both types, no further restriction - return possibleTargets; - } - Set leftPossibleTargets = new HashSet<>(); - for (UUID possibleId : possibleTargets) { - Card possibleCard = game.getCard(possibleId); - Cards checkCards = cards.copy(); - checkCards.add(possibleCard); - if (assignment.getRoleCount(checkCards, game) == 2) { - // if the possible target and the existing target have both types, it's legal - // but this prevents the case of both targets with the same type - leftPossibleTargets.add(possibleId); + + // only valid roles + Cards existingTargets = new CardsImpl(this.getTargets()); + possibleTargets.removeIf(id -> { + Card card = game.getCard(id); + if (card == null) { + return true; } - } - return leftPossibleTargets; + Cards newTargets = existingTargets.copy(); + newTargets.add(card); + return assignment.getRoleCount(newTargets, game) < newTargets.size(); + }); + + return possibleTargets; } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java index ff4235ce33f..3b5b3461715 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java @@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.NamePredicate; import mage.game.Game; import mage.util.CardUtil; -import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -71,48 +70,23 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { return new TargetCardAndOrCardInLibrary(this); } - @Override - public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { - if (!super.canTarget(playerId, id, source, game)) { - return false; - } - Card card = game.getCard(id); - if (card == null) { - return false; - } - if (this.getTargets().isEmpty()) { - return true; - } - Cards cards = new CardsImpl(this.getTargets()); - cards.add(card); - return assignment.getRoleCount(cards, game) >= cards.size(); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - // assuming max targets = 2, need to expand this code if not - Card card = game.getCard(this.getFirstTarget()); - if (card == null) { - return possibleTargets; // no further restriction if no target yet chosen - } - Cards cards = new CardsImpl(card); - if (assignment.getRoleCount(cards, game) == 2) { - // if the first chosen target is both types, no further restriction - return possibleTargets; - } - Set leftPossibleTargets = new HashSet<>(); - for (UUID possibleId : possibleTargets) { - Card possibleCard = game.getCard(possibleId); - Cards checkCards = cards.copy(); - checkCards.add(possibleCard); - if (assignment.getRoleCount(checkCards, game) == 2) { - // if the possible target and the existing target have both types, it's legal - // but this prevents the case of both targets with the same type - leftPossibleTargets.add(possibleId); + + // only valid roles + Cards existingTargets = new CardsImpl(this.getTargets()); + possibleTargets.removeIf(id -> { + Card card = game.getCard(id); + if (card == null) { + return true; } - } - return leftPossibleTargets; + Cards newTargets = existingTargets.copy(); + newTargets.add(card); + return assignment.getRoleCount(newTargets, game) < newTargets.size(); + }); + + return possibleTargets; } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java index 38176140196..b0e98188d5a 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInASingleGraveyard.java @@ -5,7 +5,6 @@ import mage.cards.Card; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; -import mage.game.events.TargetEvent; import mage.players.Player; import mage.target.TargetCard; @@ -28,71 +27,35 @@ public class TargetCardInASingleGraveyard extends TargetCard { super(target); } - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - UUID firstTarget = this.getFirstTarget(); - - // If a card is already targeted, ensure that this new target has the same owner as currently chosen target - if (firstTarget != null) { - Card card = game.getCard(firstTarget); - Card targetCard = game.getCard(id); - if (card == null || targetCard == null || !card.isOwnedBy(targetCard.getOwnerId())) { - return false; - } - } - - // If it has the same owner (or no target picked) check that it's a valid target with super - return super.canTarget(id, source, game); - } - - /** - * Set of UUIDs of all possible targets - * - * @param sourceControllerId UUID of the ability's controller - * @param source Ability which requires the targets - * @param game Current game - * @return Set of the UUIDs of possible targets - */ @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - UUID sourceId = source != null ? source.getSourceId() : null; - UUID controllerOfFirstTarget = null; - - // If any targets have been chosen, get the UUID of the owner in order to limit the targets to that owner's graveyard - if (!targets.isEmpty()) { - for (UUID cardInGraveyardId : targets.keySet()) { - Card targetCard = game.getCard(cardInGraveyardId); - if (targetCard == null) { - continue; - } - - controllerOfFirstTarget = targetCard.getOwnerId(); - break; // Only need the first UUID since they will all be the same - } - } + Card firstTarget = game.getCard(source.getFirstTarget()); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - // If the playerId of this iteration is not the same as that of any existing target, then continue - // All cards must be from the same player's graveyard. - if (controllerOfFirstTarget != null && !playerId.equals(controllerOfFirstTarget)) { - continue; - } - Player player = game.getPlayer(playerId); if (player == null) { continue; } - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - // TODO: Why for sourceId == null? - if (sourceId == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { - possibleTargets.add(card.getId()); + if (firstTarget == null) { + // playable or not selected + // use any player + } else { + // already selected + // use from same player + if (!playerId.equals(firstTarget.getOwnerId())) { + continue; } } + + for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { + possibleTargets.add(card.getId()); + } } - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java b/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java index 46a8988d0e1..a06ea169ab5 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInCommandZone.java @@ -46,7 +46,7 @@ public class TargetCardInCommandZone extends TargetCard { @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -59,30 +59,13 @@ public class TargetCardInCommandZone extends TargetCard { Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY)); for (Card card : cards.getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return false; - } - - int possibletargets = 0; - Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY)); - for (Card card : cards.getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibletargets++; - if (possibletargets >= this.minNumberOfTargets) { - return true; - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/target/common/TargetCardInExile.java b/Mage/src/main/java/mage/target/common/TargetCardInExile.java index 2c8cba1d09a..292e958cb1c 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInExile.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInExile.java @@ -12,34 +12,22 @@ import java.util.HashSet; import java.util.Set; import java.util.UUID; - /** * @author BetaSteward_at_googlemail.com */ public class TargetCardInExile extends TargetCard { - // If null, can target any card in exile matching [filter] - // If non-null, can only target - private final UUID zoneId; + private final UUID zoneId; // use null to target any exile zone or only specific - /** - * @param filter filter for the card to be a target - */ public TargetCardInExile(FilterCard filter) { this(1, 1, filter); } - /** - * @param minNumTargets minimum number of targets - * @param maxNumTargets maximum number of targets - * @param filter filter for the card to be a target - */ public TargetCardInExile(int minNumTargets, int maxNumTargets, FilterCard filter) { this(minNumTargets, maxNumTargets, filter, null); } /** - * @param filter filter for the card to be a target * @param zoneId if non-null can only target cards in that exileZone. if null card can be in ever exile zone. */ public TargetCardInExile(FilterCard filter, UUID zoneId) { @@ -47,10 +35,7 @@ public class TargetCardInExile extends TargetCard { } /** - * @param minNumTargets minimum number of targets - * @param maxNumTargets maximum number of targets - * @param filter filter for the card to be a target - * @param zoneId if non-null can only target cards in that exileZone. if null card can be in ever exile zone. + * @param zoneId if non-null can only target cards in that exileZone. if null card can be in ever exile zone. */ public TargetCardInExile(int minNumTargets, int maxNumTargets, FilterCard filter, UUID zoneId) { super(minNumTargets, maxNumTargets, Zone.EXILED, filter); @@ -65,6 +50,7 @@ public class TargetCardInExile extends TargetCard { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); + if (zoneId == null) { // no specific exile zone for (Card card : game.getExile().getAllCardsByRange(game, sourceControllerId)) { if (filter.match(card, sourceControllerId, source, game)) { @@ -81,40 +67,8 @@ public class TargetCardInExile extends TargetCard { } } } - return possibleTargets; - } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (zoneId == null) { // no specific exile zone - int numberTargets = 0; - for (ExileZone exileZone : game.getExile().getExileZones()) { - numberTargets += exileZone.count(filter, sourceControllerId, source, game); - if (numberTargets >= this.minNumberOfTargets) { - return true; - } - } - } else { - ExileZone exileZone = game.getExile().getExileZone(zoneId); - return exileZone != null && exileZone.count(filter, sourceControllerId, source, game) >= this.minNumberOfTargets; - - } - return false; - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - Card card = game.getCard(id); - if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED) { - if (zoneId == null) { // no specific exile zone - return filter.match(card, source.getControllerId(), source, game); - } - ExileZone exile = game.getExile().getExileZone(zoneId); - if (exile != null && exile.contains(id)) { - return filter.match(card, source.getControllerId(), source, game); - } - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java index b86014f5cac..368978950c8 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java @@ -1,6 +1,5 @@ package mage.target.common; -import mage.MageObject; import mage.abilities.Ability; import mage.constants.ComparisonType; import mage.constants.Zone; @@ -18,7 +17,6 @@ import java.util.Set; import java.util.UUID; /** - * * @author LevelX2 */ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { @@ -59,28 +57,12 @@ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (super.canChoose(sourceControllerId, source, game)) { - return true; - } - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, source, game)) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - return true; - } - } - for (StackObject stackObject : game.getStack()) { - if (stackObject instanceof Spell - && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filterSpell.match(stackObject, sourceControllerId, source, game)) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -90,31 +72,22 @@ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { } Permanent permanent = game.getPermanent(id); if (permanent != null) { - return filterPermanent.match(permanent, playerId, source, game); + return playerId == null ? filterPermanent.match(permanent, game) : filterPermanent.match(permanent, playerId, source, game); } Spell spell = game.getSpell(id); - return spell != null && filterSpell.match(spell, playerId, source, game); - } - - @Override - public boolean canTarget(UUID id, Game game) { - return this.canTarget(null, id, null, game); // wtf - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, (Ability) null, game); + return spell != null && (playerId == null ? filter.match(spell, game) : filterSpell.match(spell, playerId, source, game)); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); // in graveyard first - MageObject targetSource = game.getObject(source); + + // from battlefield for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, source, game)) { - if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } + possibleTargets.add(permanent.getId()); } + + // from stack for (StackObject stackObject : game.getStack()) { if (stackObject instanceof Spell && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) @@ -122,7 +95,8 @@ public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { possibleTargets.add(stackObject.getId()); } } - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInHand.java b/Mage/src/main/java/mage/target/common/TargetCardInHand.java index 25dd47d6bb1..2057ca9126e 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInHand.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInHand.java @@ -1,7 +1,9 @@ package mage.target.common; +import mage.MageObject; import mage.abilities.Ability; import mage.cards.Card; +import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; @@ -53,7 +55,7 @@ public class TargetCardInHand extends TargetCard { @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -61,30 +63,16 @@ public class TargetCardInHand extends TargetCard { Set possibleTargets = new HashSet<>(); Player player = game.getPlayer(sourceControllerId); if (player != null) { - for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } - } - } - return possibleTargets; + player.getHand().getCards(filter, sourceControllerId, source, game).stream() + .map(MageObject::getId) + .forEach(possibleTargets::add); + }; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int possibleTargets = 0; - Player player = game.getPlayer(sourceControllerId); - if (player != null) { - for (Card card : player.getHand().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java index a6c111bca01..30475de61c2 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInLibrary.java @@ -97,7 +97,7 @@ public class TargetCardInLibrary extends TargetCard { chosen = isChosen(game); // stop by full complete - if (isChoiceCompleted(abilityControllerId, source, game)) { + if (isChoiceCompleted(abilityControllerId, source, game, null)) { break; } diff --git a/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java index e7f81c810ef..9968cd74126 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInOpponentsGraveyard.java @@ -70,50 +70,6 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { return false; } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return canChoose(sourceControllerId, null, game); - } - - /** - * Checks if there are enough {@link Card} that can be chosen. - * - * @param sourceControllerId - controller of the target event source - * @param source - * @param game - * @return - true if enough valid {@link Card} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - int possibleTargets = 0; - if (getMinNumberOfTargets() == 0) { // if 0 target is valid, the canChoose is always true - return true; - } - Player sourceController = game.getPlayer(sourceControllerId); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - if (!sourceController.hasOpponent(playerId, game)) { - continue; - } - if (this.allFromOneOpponent) { - possibleTargets = 0; - } - if (!playerId.equals(sourceControllerId)) { - Player player = game.getPlayer(playerId); - if (player != null) { - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - } - } - } - return false; - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); @@ -126,9 +82,7 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { if (player != null) { Set targetsInThisGraveyeard = new HashSet<>(); for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - targetsInThisGraveyeard.add(card.getId()); - } + targetsInThisGraveyeard.add(card.getId()); } // if there is not enough possible targets, the can't be any if (this.allFromOneOpponent && targetsInThisGraveyeard.size() < this.minNumberOfTargets) { @@ -137,7 +91,7 @@ public class TargetCardInOpponentsGraveyard extends TargetCard { possibleTargets.addAll(targetsInThisGraveyeard); } } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java index 5920ba3717d..53264f40a7e 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyard.java @@ -80,59 +80,9 @@ public class TargetCardInYourGraveyard extends TargetCard { Set possibleTargets = new HashSet<>(); Player player = game.getPlayer(sourceControllerId); for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets.add(card.getId()); - } + possibleTargets.add(card.getId()); } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Cards cards, Ability source, Game game) { - Set possibleTargets = new HashSet<>(); - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return possibleTargets; - } - - for (Card card : cards.getCards(filter, sourceControllerId, source, game)) { - if (player.getGraveyard().getCards(game).contains(card)) { - possibleTargets.add(card.getId()); - } - } - return possibleTargets; - } - - /** - * Checks if there are enough {@link Card} that can be selected. - * - * @param sourceControllerId - controller of the select event - * @param game - * @return - true if enough valid {@link Card} exist - */ - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getPlayer(sourceControllerId).getGraveyard().count(filter, game) >= this.minNumberOfTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Player player = game.getPlayer(sourceControllerId); - if (player != null) { - if (this.minNumberOfTargets == 0) { - return true; - } - int possibleTargets = 0; - for (Card card : player.getGraveyard().getCards(filter, sourceControllerId, source, game)) { - if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) { - possibleTargets++; - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - } - } - } - return false; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java index fd5b6816627..c36eb9e0a13 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java +++ b/Mage/src/main/java/mage/target/common/TargetCardInYourGraveyardOrExile.java @@ -33,47 +33,26 @@ public class TargetCardInYourGraveyardOrExile extends TargetCard { this.filterExile = target.filterExile; } - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - Player player = game.getPlayer(sourceControllerId); - if (player == null) { - return false; - } - int possibleTargets = 0; - // in your graveyard: - possibleTargets += countPossibleTargetInGraveyard(game, player, sourceControllerId, source, - filterGraveyard, isNotTarget(), this.minNumberOfTargets - possibleTargets); - if (possibleTargets >= this.minNumberOfTargets) { - return true; - } - // in exile: - possibleTargets += countPossibleTargetInExile(game, player, sourceControllerId, source, - filterExile, isNotTarget(), this.minNumberOfTargets - possibleTargets); - return possibleTargets >= this.minNumberOfTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, (Ability) null, game); - } - @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); + Player player = game.getPlayer(sourceControllerId); if (player == null) { return possibleTargets; } - // in your graveyard: + + // in your graveyard possibleTargets.addAll(getAllPossibleTargetInGraveyard(game, player, sourceControllerId, source, filterGraveyard, isNotTarget())); - // in exile: + // in exile possibleTargets.addAll(getAllPossibleTargetInExile(game, player, sourceControllerId, source, filterExile, isNotTarget())); - return possibleTargets; + + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canTarget(UUID id, Ability source, Game game) { - return this.canTarget(source.getControllerId(), id, source, game); + return this.canTarget(source == null ? null : source.getControllerId(), id, source, game); } @Override @@ -84,19 +63,14 @@ public class TargetCardInYourGraveyardOrExile extends TargetCard { } switch (game.getState().getZone(id)) { case GRAVEYARD: - return filterGraveyard.match(card, playerId, source, game); + return playerId == null ? filterGraveyard.match(card, game) : filterGraveyard.match(card, playerId, source, game); case EXILED: - return filterExile.match(card, playerId, source, game); + return playerId == null ? filterExile.match(card, game) : filterExile.match(card, playerId, source, game); default: return false; } } - @Override - public boolean canTarget(UUID id, Game game) { - return this.canTarget(null, id, null, game); - } - @Override public TargetCardInYourGraveyardOrExile copy() { return new TargetCardInYourGraveyardOrExile(this); diff --git a/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java b/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java index 121b2e8ca52..64689ae5288 100644 --- a/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java +++ b/Mage/src/main/java/mage/target/common/TargetCreaturesWithDifferentPowers.java @@ -34,8 +34,8 @@ public class TargetCreaturesWithDifferentPowers extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } Permanent creature = game.getPermanent(id); diff --git a/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java b/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java index a3ddb8a237d..10a9db39355 100644 --- a/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java +++ b/Mage/src/main/java/mage/target/common/TargetOpponentsChoicePermanent.java @@ -12,6 +12,8 @@ import mage.target.TargetPermanent; import java.util.UUID; /** + * TODO: rework to support possible targets + * * @author Mael */ public class TargetOpponentsChoicePermanent extends TargetPermanent { @@ -28,20 +30,15 @@ public class TargetOpponentsChoicePermanent extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game, boolean flag) { - return opponentId != null && super.canTarget(opponentId, id, source, game, flag); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (opponentId != null) { if (permanent != null) { if (source != null) { boolean canSourceControllerTarget = true; if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) - || !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game) + || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) { canSourceControllerTarget = false; } } diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java b/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java index 9525f7b11cc..2b963c75377 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentAmount.java @@ -77,17 +77,8 @@ public class TargetPermanentAmount extends TargetAmount { return this.filter; } - @Override - public boolean canTarget(UUID objectId, Game game) { - Permanent permanent = game.getPermanent(objectId); - return filter.match(permanent, game); - } - @Override public boolean canTarget(UUID objectId, Ability source, Game game) { - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets().contains(objectId); - } Permanent permanent = game.getPermanent(objectId); if (permanent == null) { return false; @@ -95,8 +86,7 @@ public class TargetPermanentAmount extends TargetAmount { if (source == null) { return filter.match(permanent, game); } - MageObject targetSource = source.getSourceObject(game); - return (notTarget || permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game)) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } @@ -107,47 +97,18 @@ public class TargetPermanentAmount extends TargetAmount { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game).size() - >= this.minNumberOfTargets; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return game.getBattlefield().getActivePermanents(filter, sourceControllerId, game).size() - >= this.minNumberOfTargets; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets() - .stream() - .collect(Collectors.toSet()); - } - MageObject targetSource = game.getObject(source); - return game + Set possibleTargets = game .getBattlefield() .getActivePermanents(filter, sourceControllerId, source, game) .stream() - .filter(Objects::nonNull) - .filter(permanent -> notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) - .map(Permanent::getId) - .collect(Collectors.toSet()); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets() - .stream() - .collect(Collectors.toSet()); - } - return game - .getBattlefield() - .getActivePermanents(filter, sourceControllerId, game) - .stream() .map(Permanent::getId) .collect(Collectors.toSet()); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java index 6cc048b56ff..af75f3eda94 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayer.java @@ -61,16 +61,6 @@ public class TargetPermanentOrPlayer extends TargetImpl { return filter; } - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Player player = game.getPlayer(id); - return filter.match(player, game); - } - @Override public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { return canTarget(id, source, game); @@ -183,33 +173,16 @@ public class TargetPermanentOrPlayer extends TargetImpl { MageObject targetSource = game.getObject(source); for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { Player player = game.getPlayer(playerId); - if (player != null && (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) && filter.match(player, sourceControllerId, source, game)) { + if (player != null && filter.match(player, sourceControllerId, source, game)) { possibleTargets.add(playerId); } } for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) && filter.match(permanent, sourceControllerId, source, game)) { + if (filter.match(permanent, sourceControllerId, source, game)) { possibleTargets.add(permanent.getId()); } } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (filter.match(player, game)) { - possibleTargets.add(playerId); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, null, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java index a1e91b817cc..eee75f2dd54 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java @@ -36,49 +36,30 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { return this.filter; } - @Override - public boolean canTarget(UUID objectId, Game game) { - - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets().contains(objectId); - } - - Permanent permanent = game.getPermanent(objectId); - if (permanent != null) { - return filter.match(permanent, game); - } - Player player = game.getPlayer(objectId); - return filter.match(player, game); - } - @Override public boolean canTarget(UUID objectId, Ability source, Game game) { - - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - return getTargets().contains(objectId); - } - Permanent permanent = game.getPermanent(objectId); Player player = game.getPlayer(objectId); if (source != null) { - MageObject targetSource = source.getSourceObject(game); if (permanent != null) { - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } if (player != null) { - return player.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || player.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(player, game); } + } else { + if (permanent != null) { + return filter.match(permanent, game); + } + if (player != null) { + return filter.match(player, game); + } } - if (permanent != null) { - return filter.match(permanent, game); - } - return filter.match(player, game); + return false; } @Override @@ -88,100 +69,13 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - // no max targets limit here - int count = 0; - MageObject targetSource = game.getObject(source); - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player == null - || !player.canBeTargetedBy(targetSource, sourceControllerId, source, game) - || !filter.match(player, game)) { - continue; - } - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (!permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { - continue; - } - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - return false; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - // no max targets limit here - int count = 0; - for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { - Player player = game.getPlayer(playerId); - if (player == null || !filter.match(player, game)) { - continue; - } - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - count++; - if (count >= this.minNumberOfTargets) { - return true; - } - } - return false; + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { Set possibleTargets = new HashSet<>(); - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - possibleTargets.addAll(getTargets()); - return possibleTargets; - } - - MageObject targetSource = game.getObject(source); - - game.getState() - .getPlayersInRange(sourceControllerId, game) - .stream() - .map(game::getPlayer) - .filter(Objects::nonNull) - .filter(player -> player.canBeTargetedBy(targetSource, sourceControllerId, source, game) - && filter.match(player, game) - ) - .map(Player::getId) - .forEach(possibleTargets::add); - - game.getBattlefield() - .getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game) - .stream() - .filter(Objects::nonNull) - .filter(permanent -> permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) - .map(Permanent::getId) - .forEach(possibleTargets::add); - - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - - // max targets limit reached (only selected can be chosen again) - if (getMaxNumberOfTargets() > 0 && getTargets().size() >= getMaxNumberOfTargets()) { - possibleTargets.addAll(getTargets()); - return possibleTargets; - } - game.getState() .getPlayersInRange(sourceControllerId, game) .stream() @@ -194,10 +88,11 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { game.getBattlefield() .getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game) .stream() + .filter(Objects::nonNull) .map(Permanent::getId) .forEach(possibleTargets::add); - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java index 0f00929e453..c9c60a399c2 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrSuspendedCard.java @@ -71,10 +71,9 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = new HashSet<>(20); - MageObject sourceObject = game.getObject(source); + Set possibleTargets = new HashSet<>(); for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(sourceObject, sourceControllerId, source, game) && filter.match(permanent, sourceControllerId, source, game)) { + if (filter.match(permanent, sourceControllerId, source, game)) { possibleTargets.add(permanent.getId()); } } @@ -83,17 +82,7 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { possibleTargets.add(card.getId()); } } - return possibleTargets; - } - - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Card card = game.getExile().getCard(id, game); - return filter.match(card, game); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override @@ -101,8 +90,7 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { Permanent permanent = game.getPermanent(id); if (permanent != null) { if (source != null) { - MageObject targetSource = game.getObject(source); - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } else { return filter.match(permanent, game); @@ -117,16 +105,6 @@ public class TargetPermanentOrSuspendedCard extends TargetImpl { return this.canTarget(id, source, game); } - @Override - public boolean canChoose(UUID sourceControllerId, Game game) { - return this.canChoose(sourceControllerId, null, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - return this.possibleTargets(sourceControllerId, null, game); - } - @Override public String getTargetedName(Game game) { StringBuilder sb = new StringBuilder(); diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java b/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java index 9dd16fff7e8..b6c1898fc72 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentSameController.java @@ -28,8 +28,8 @@ public class TargetPermanentSameController extends TargetPermanent { } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (!super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { return false; } if (this.getTargets().isEmpty()) { diff --git a/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java b/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java index 5122e3d5763..b8a432dab60 100644 --- a/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java +++ b/Mage/src/main/java/mage/target/common/TargetSpellOrPermanent.java @@ -73,23 +73,12 @@ public class TargetSpellOrPermanent extends TargetImpl { this.filter = filter; } - @Override - public boolean canTarget(UUID id, Game game) { - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filter.match(permanent, game); - } - Spell spell = game.getStack().getSpell(id); - return filter.match(spell, game); - } - @Override public boolean canTarget(UUID id, Ability source, Game game) { Permanent permanent = game.getPermanent(id); if (permanent != null) { if (source != null) { - MageObject targetSource = game.getObject(source); - return permanent.canBeTargetedBy(targetSource, source.getControllerId(), source, game) + return (isNotTarget() || permanent.canBeTargetedBy(game.getObject(source), source.getControllerId(), source, game)) && filter.match(permanent, source.getControllerId(), source, game); } else { return filter.match(permanent, game); @@ -183,35 +172,18 @@ public class TargetSpellOrPermanent extends TargetImpl { for (StackObject stackObject : game.getStack()) { Spell spell = game.getStack().getSpell(stackObject.getId()); if (spell != null - && !source.getSourceId().equals(spell.getSourceId()) + && targetSource != null + && !targetSource.getId().equals(spell.getSourceId()) && filter.match(spell, sourceControllerId, source, game)) { possibleTargets.add(spell.getId()); } } for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game) && filter.match(permanent, sourceControllerId, source, game)) { + if (filter.match(permanent, sourceControllerId, source, game)) { possibleTargets.add(permanent.getId()); } } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = new HashSet<>(); - for (StackObject stackObject : game.getStack()) { - Spell spell = game.getStack().getSpell(stackObject.getId()); - if (spell != null - && filter.match(spell, sourceControllerId, null, game)) { - possibleTargets.add(spell.getId()); - } - } - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) { - if (filter.match(permanent, sourceControllerId, null, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java b/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java index 4fa993cba4a..db7f03551a3 100644 --- a/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java +++ b/Mage/src/main/java/mage/target/common/TargetTappedPermanentAsYouCast.java @@ -30,21 +30,21 @@ public class TargetTappedPermanentAsYouCast extends TargetPermanent { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return game.getBattlefield().getActivePermanents(getFilter(), source.getControllerId(), source, game).stream() + Set possibleTargets = game.getBattlefield().getActivePermanents(getFilter(), sourceControllerId, source, game).stream() .filter(Permanent::isTapped) .map(Permanent::getId) .collect(Collectors.toSet()); + return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game); } @Override public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - return game.getBattlefield().getActivePermanents(getFilter(), source.getControllerId(), source, game).stream() - .anyMatch(Permanent::isTapped); + return canChooseFromPossibleTargets(sourceControllerId, source, game); } @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (super.canTarget(controllerId, id, source, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { Permanent permanent = game.getPermanent(id); return permanent != null && permanent.isTapped(); } @@ -54,6 +54,14 @@ public class TargetTappedPermanentAsYouCast extends TargetPermanent { // See ruling: https://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/253345-dream-leash @Override public boolean stillLegalTarget(UUID controllerId, UUID id, Ability source, Game game) { + // on resolve must ignore tapped status + + // The middle ability of Enthralling Hold affects only the choice of target as the spell is cast. + // If the creature becomes untapped before the spell resolves, it still resolves. If a player is allowed + // to change the spell's target while it's on the stack, they may choose an untapped creature. If you put + // Enthralling Hold onto the battlefield without casting it, you may attach it to an untapped creature. + // (2020-06-23) + Permanent permanent = game.getPermanent(id); return permanent != null && getFilter().match(permanent, game) diff --git a/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java b/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java index 585af8abdd0..b771bb39943 100644 --- a/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java +++ b/Mage/src/main/java/mage/target/common/TargetTriggeredAbility.java @@ -65,14 +65,10 @@ public class TargetTriggeredAbility extends TargetObject { @Override public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - return possibleTargets(sourceControllerId, game); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { return game.getStack().stream() .filter(TargetTriggeredAbility::isTriggeredAbility) .map(stackObject -> stackObject.getStackAbility().getId()) + .filter(this::notContains) .collect(Collectors.toSet()); } diff --git a/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java b/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java index b6f0c6583e1..e5d49b85a47 100644 --- a/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/RevoltWatcher.java @@ -17,7 +17,7 @@ import java.util.UUID; */ public class RevoltWatcher extends Watcher { - private final Set revoltActivePlayerIds = new HashSet<>(0); + private final Set revoltActivePlayerIds = new HashSet<>(); public RevoltWatcher() { super(WatcherScope.GAME);