Merge pull request #13892 from magefree/rework-targeting-and-possible-targets

Reworked targeting, possible targets and AI

Test framework improves:
- now game logs will show stack ability on push and on resolve (before any choices);
- now game logs will show used choices made by cast/activate, setChoice, setMode and addTarget commands (not work for AI tests, part of #13832);
- improved choice logic for modes and yes/not dialogs (now it's use a more strictly checks, use TestPlayer.MODE_SKIP to stop mode selection);
- improved error logs and testable dialogs menu in cheat mode;

Reworked AI, targeting and targets logic:
- refactor: simplified target implementation from a dozen canTarget, canChoose and possibleTargets methods to canTarget/possibleTargets only (part of #13638, #13766);
- refactor: fixed wrong target implementations in many cards (example: TargetCardInHand for opponent's hand, close #6210);
- AI: now human, AI and test players -- all use possibleTargets logic in most use cases instead filters or custom validation;
- AI: improved AI sims support for multiple targets abilities;
- AI: improved AI stability, freezes and targets errors in some use cases;

Additional fixes:
- close #13840
- close #13865
- close #13857
- close #13650
This commit is contained in:
Oleg Agafonov 2025-08-05 01:44:20 +04:00 committed by GitHub
commit f5004746e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
257 changed files with 2206 additions and 3518 deletions

View file

@ -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<UUID> selected) {
public void selectCards(Set<UUID> 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<UUID> marked) {
public void markCards(Set<UUID> marked) {
for (Component component : cardArea.getComponents()) {
if (component instanceof MageCard) {
MageCard mageCard = (MageCard) component;

View file

@ -100,11 +100,11 @@
cardArea.loadCards(showCards, bigCard, gameId);
if (options != null) {
if (options.containsKey("chosenTargets")) {
java.util.List<UUID> chosenCards = (java.util.List<UUID>) options.get("chosenTargets");
java.util.Set<UUID> chosenCards = (java.util.Set<UUID>) options.get("chosenTargets");
cardArea.selectCards(chosenCards);
}
if (options.containsKey("possibleTargets")) {
java.util.List<UUID> choosableCards = (java.util.List<UUID>) options.get("possibleTargets");
java.util.Set<UUID> choosableCards = (java.util.Set<UUID>) options.get("possibleTargets");
cardArea.markCards(choosableCards);
}
if (options.containsKey("queryType") && options.get("queryType") == QueryType.PICK_ABILITY) {

View file

@ -216,17 +216,9 @@ public final class GamePanel extends javax.swing.JPanel {
});
}
public List<UUID> getPossibleTargets() {
if (options != null && options.containsKey("possibleTargets")) {
return (List<UUID>) options.get("possibleTargets");
} else {
return Collections.emptyList();
}
}
public Set<UUID> getChosenTargets() {
if (options != null && options.containsKey("chosenTargets")) {
return new HashSet<>((List<UUID>) options.get("chosenTargets"));
return (Set<UUID>) options.get("chosenTargets");
} else {
return Collections.emptySet();
}

View file

@ -7,6 +7,7 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.TargetPlayer;
import mage.target.common.TargetPermanentOrPlayer;
/**
@ -76,6 +77,10 @@ abstract class BaseTestableDialog implements TestableDialog {
return createAnyTarget(min, max, false);
}
static Target createPlayerTarget(int min, int max, boolean notTarget) {
return new TargetPlayer(min, max, notTarget);
}
private static Target createAnyTarget(int min, int max, boolean notTarget) {
return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget);
}

View file

@ -126,6 +126,19 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)).aiMustChoose(false, 0));
//
// additional tests for 2 possible options limitation
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0", createPlayerTarget(0, 0, notTarget)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-1", createPlayerTarget(0, 1, notTarget)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-2", createPlayerTarget(0, 2, notTarget)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 0-5", createPlayerTarget(0, 5, notTarget)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1", createPlayerTarget(1, 1, notTarget)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1-2", createPlayerTarget(1, 2, notTarget)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 1-5", createPlayerTarget(1, 5, notTarget)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 2", createPlayerTarget(2, 2, notTarget)).aiMustChoose(true, 2));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 2-5", createPlayerTarget(2, 5, notTarget)).aiMustChoose(true, 2));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 3", createPlayerTarget(3, 3, notTarget)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "player 3-5", createPlayerTarget(3, 5, notTarget)).aiMustChoose(false, 0));
}
}
}

View file

@ -8,10 +8,7 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -120,10 +117,8 @@ public class TestableDialogsRunner {
choice = prepareSelectDialogChoice(needGroup);
player.choose(Outcome.Benefit, choice, game);
if (choice.getChoiceKey() != null) {
int needIndex = Integer.parseInt(choice.getChoiceKey());
if (needIndex < this.dialogs.size()) {
needDialog = this.dialogs.get(needIndex);
}
int needRegNumber = Integer.parseInt(choice.getChoiceKey());
needDialog = this.dialogs.getOrDefault(needRegNumber, null);
}
}
if (needDialog == null) {
@ -144,15 +139,20 @@ public class TestableDialogsRunner {
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose dialogs group to run");
// use min reg number for groups
Map<String, Integer> groupNumber = new HashMap<>();
this.dialogs.values().forEach(dialog -> {
groupNumber.put(dialog.getGroup(), Math.min(groupNumber.getOrDefault(dialog.getGroup(), Integer.MAX_VALUE), dialog.getRegNumber()));
});
// main groups
int recNumber = 0;
for (int i = 0; i < groups.size(); i++) {
recNumber++;
String group = groups.get(i);
Integer groupMinNumber = groupNumber.getOrDefault(group, 0);
choice.withItem(
String.valueOf(i),
String.format("%02d. %s", recNumber, group),
recNumber,
String.format("%02d. %s", groupMinNumber, group),
groupMinNumber,
ChoiceHintType.TEXT,
String.join("<br>", group)
);
@ -186,18 +186,15 @@ public class TestableDialogsRunner {
private Choice prepareSelectDialogChoice(String needGroup) {
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose game dialog to run from " + needGroup);
int recNumber = 0;
for (int i = 0; i < this.dialogs.size(); i++) {
TestableDialog dialog = this.dialogs.get(i);
for (TestableDialog dialog : this.dialogs.values()) {
if (!dialog.getGroup().equals(needGroup)) {
continue;
}
recNumber++;
String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription());
choice.withItem(
String.valueOf(i),
String.format("%02d. %s", recNumber, info),
recNumber,
String.valueOf(dialog.getRegNumber()),
String.format("%02d. %s", dialog.getRegNumber(), info),
dialog.getRegNumber(),
ChoiceHintType.TEXT,
String.join("<br>", info)
);

View file

@ -405,13 +405,13 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (effect != null
&& stackObject.getControllerId().equals(playerId)) {
Target target = effect.getTarget();
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game, null)) {
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
Game sim = game.createSimulationForAI();
StackAbility newAbility = (StackAbility) stackObject.copy();
SearchEffect newEffect = getSearchEffect(newAbility);
newEffect.getTarget().addTarget(targetId, newAbility, sim);
sim.getStack().push(newAbility);
sim.getStack().push(sim, newAbility);
SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId());
node.children.add(newNode);
newNode.getTargets().add(targetId);
@ -886,10 +886,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
for (UUID targetId : targets) {
target.addTarget(targetId, source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
targets.clear();
return true;
}
@ -906,10 +906,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
for (UUID targetId : targets) {
target.add(targetId, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
targets.clear();
return true;
}

View file

@ -105,7 +105,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
return list;
}
protected void simulateOptions(Game game) {
private void simulateOptions(Game game) {
List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
for (ActivatedAbility ability : playables) {
if (ability.isManaAbility()) {
@ -176,6 +176,10 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
return options;
}
// remove invalid targets
// TODO: is it useless cause it already filtered before?
options.removeIf(option -> !option.getTargets().isChosen(game));
if (AI_SIMULATE_ALL_BAD_AND_GOOD_TARGETS) {
return options;
}
@ -315,7 +319,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
List<Ability> options = getPlayableOptions(ability, game);
if (options.isEmpty()) {
logger.debug("simulating -- triggered ability:" + ability);
game.getStack().push(new StackAbility(ability, playerId));
game.getStack().push(game, new StackAbility(ability, playerId));
if (ability.activate(game, false) && ability.isUsesStack()) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
}
@ -337,9 +341,9 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
protected void addAbilityNode(SimulationNode2 parent, Ability ability, int depth, Game game) {
Game sim = game.createSimulationForAI();
sim.getStack().push(new StackAbility(ability, playerId));
sim.getStack().push(sim, new StackAbility(ability, playerId));
if (ability.activate(sim, false) && ability.isUsesStack()) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
sim.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
}
sim.applyEffects();
SimulationNode2 newNode = new SimulationNode2(parent, sim, depth, playerId);

View file

@ -142,17 +142,27 @@ public class ComputerPlayer extends PlayerImpl {
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
// nothing to choose, e.g. X=0
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) {
return false;
}
// default logic for any targets
PossibleTargetsSelector possibleTargetsSelector = new PossibleTargetsSelector(outcome, target, abilityControllerId, source, game);
possibleTargetsSelector.findNewTargets(fromCards);
// nothing to choose, e.g. no valid targets
if (!possibleTargetsSelector.hasAnyTargets()) {
return false;
}
// can't choose
if (!possibleTargetsSelector.hasMinNumberOfTargets()) {
return false;
}
// good targets -- choose as much as possible
for (MageItem item : possibleTargetsSelector.getGoodTargets()) {
target.add(item.getId(), game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, fromCards)) {
return true;
}
}
@ -226,7 +236,7 @@ public class ComputerPlayer extends PlayerImpl {
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
// nothing to choose, e.g. X=0
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return false;
}
@ -238,6 +248,11 @@ public class ComputerPlayer extends PlayerImpl {
return false;
}
// can't choose
if (!possibleTargetsSelector.hasMinNumberOfTargets()) {
return false;
}
// KILL PRIORITY
if (outcome == Outcome.Damage) {
// opponent first
@ -251,7 +266,7 @@ public class ComputerPlayer extends PlayerImpl {
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
if (leftLife > 0 && leftLife <= target.getAmountRemaining()) {
target.addTarget(item.getId(), leftLife, source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
@ -268,7 +283,7 @@ public class ComputerPlayer extends PlayerImpl {
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
if (leftLife > 0 && leftLife <= target.getAmountRemaining()) {
target.addTarget(item.getId(), leftLife, source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
@ -283,7 +298,7 @@ public class ComputerPlayer extends PlayerImpl {
continue;
}
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
@ -303,7 +318,7 @@ public class ComputerPlayer extends PlayerImpl {
int leftLife = PossibleTargetsComparator.getLifeForDamage(item, game);
if (leftLife > 1) {
target.addTarget(item.getId(), Math.min(leftLife - 1, target.getAmountRemaining()), source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
@ -322,7 +337,7 @@ public class ComputerPlayer extends PlayerImpl {
return !target.getTargets().isEmpty();
}
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
@ -339,7 +354,7 @@ public class ComputerPlayer extends PlayerImpl {
continue;
}
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
@ -355,7 +370,7 @@ public class ComputerPlayer extends PlayerImpl {
return !target.getTargets().isEmpty();
}
target.addTarget(item.getId(), target.getAmountRemaining(), source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}

View file

@ -47,7 +47,6 @@ public class PossibleTargetsSelector {
// collect new valid targets
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream()
.filter(id -> !target.contains(id))
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
.map(id -> {
Player player = game.getPlayer(id);
if (player != null) {
@ -137,6 +136,10 @@ public class PossibleTargetsSelector {
}
}
public List<MageItem> getAny() {
return this.any;
}
public static boolean isMyItem(UUID abilityControllerId, MageItem item) {
if (item instanceof Player) {
return item.getId().equals(abilityControllerId);
@ -181,7 +184,12 @@ public class PossibleTargetsSelector {
return false;
}
boolean hasAnyTargets() {
public boolean hasAnyTargets() {
return !this.any.isEmpty();
}
public boolean hasMinNumberOfTargets() {
return this.target.getMinNumberOfTargets() == 0
|| this.any.size() >= this.target.getMinNumberOfTargets();
}
}

View file

@ -121,7 +121,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
}
}
if (ability.isUsesStack()) {
game.getStack().push(new StackAbility(ability, playerId));
game.getStack().push(game, new StackAbility(ability, playerId));
if (ability.activate(game, false)) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
actionCount++;
@ -187,8 +187,8 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
abort = true;
}
protected boolean chooseRandom(Target target, Game game) {
Set<UUID> possibleTargets = target.possibleTargets(playerId, game);
private boolean chooseRandom(Target target, Ability source, Game game) {
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
if (possibleTargets.isEmpty()) {
return false;
}
@ -233,7 +233,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
@Override
public boolean choose(Outcome outcome, Target target, Ability source, Game game) {
if (this.isHuman()) {
return chooseRandom(target, game);
return chooseRandom(target, source, game);
}
return super.choose(outcome, target, source, game);
}
@ -241,7 +241,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
@Override
public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) {
if (this.isHuman()) {
return chooseRandom(target, game);
return chooseRandom(target, source, game);
}
return super.choose(outcome, target, source, game, options);
}
@ -252,7 +252,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
if (cards.isEmpty()) {
return false;
}
Set<UUID> possibleTargets = target.possibleTargets(playerId, cards, source, game);
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game, cards);
if (possibleTargets.isEmpty()) {
return false;
}

View file

@ -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,13 +813,11 @@ 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)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true;
}
}
}
} else {
// done or cancel button pressed
if (target.isChosen(game)) {
@ -864,13 +861,7 @@ public class HumanPlayer extends PlayerImpl {
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
while (canRespond()) {
List<UUID> possibleTargets = new ArrayList<>();
for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
possibleTargets.add(cardId);
}
}
Set<UUID> 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<String, Serializable> 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<UUID> possibleTargets = new ArrayList<>();
for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
possibleTargets.add(cardId);
}
}
Set<UUID> 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<String, Serializable> 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<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game);
Set<UUID> 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<UUID> 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<String, Serializable> 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<UUID> attackers = target.possibleTargets(playerId, null, game);
TargetAttackingCreature target = new TargetAttackingCreature();
Permanent blocker = game.getPermanent(blockerId);
Set<UUID> possibleTargets = new HashSet<>();
for (UUID attackerId : attackers) {
Set<UUID> allAttackers = target.possibleTargets(playerId, null, game);
Set<UUID> 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);
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -147,6 +147,7 @@ class BattleForBretagardTarget extends TargetPermanent {
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<String> 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;
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
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();
}
} else {
// real
// filter by same player
possibleTargets.removeIf(id -> {
Permanent permanent = game.getPermanent(id);
return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId());
});
}
possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id));
return possibleTargets;
}
@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++;
}
}
return possibleTargets > 1;
}
return false;
}
@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);
}
}

View file

@ -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);
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
Set<UUID> 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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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<CardType> cardTypes = getOpponentPermanentCardTypes(controllerId, game);
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
Set<CardType> 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,13 +87,12 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
// get all cardtypes from opponents permanents
Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
Set<UUID> 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());
@ -96,8 +101,7 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
}
}
}
}
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 super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source);
if (targetSource != null) {
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
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)) {
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

View file

@ -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) {

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Game game) {
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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

View file

@ -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);

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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))) {
Player controller = game.getPlayer(sourceControllerId);
if (controller == null) {
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 possibleTargets;
});
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override

View file

@ -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);

View file

@ -105,11 +105,10 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard {
return cardTypeAssigner.getRoleCount(cards, game) >= cards.size();
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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;
}
}

View file

@ -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) {

View file

@ -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());

View file

@ -58,7 +58,7 @@ enum EverythingComesToDustPredicate implements ObjectSourcePlayerPredicate<Perma
if (!p.isCreature(game)){
return false;
}
HashSet<MageObjectReference> set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0));
HashSet<MageObjectReference> set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>());
for (MageObjectReference mor : set){
Permanent convoked = game.getPermanentOrLKIBattlefield(mor);
if (convoked.shareCreatureTypes(game, p)){

View file

@ -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);

View file

@ -134,7 +134,6 @@ class FireballTargetCreatureOrPlayer extends TargetAnyTarget {
continue;
}
possibleTargets.removeAll(getTargets());
for (UUID targetId : possibleTargets) {
TargetAnyTarget target = this.copy();
target.clearChosen();

View file

@ -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() + ")";

View file

@ -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;

View file

@ -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<CardType> cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), controllerId, game);
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
Set<CardType> 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,13 +85,12 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
// get all cardtypes from opponents permanents
Set<CardType> cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), sourceControllerId, game);
Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
Set<UUID> 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());
@ -99,8 +99,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
}
}
}
}
return possibleTargets;
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override
@ -108,7 +107,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
return new GauntletsOfChaosFirstTarget(this);
}
private EnumSet<CardType> getOpponentPermanentCardTypes(UUID sourceId, UUID sourceControllerId, Game game) {
private EnumSet<CardType> getOpponentPermanentCardTypes(UUID sourceControllerId, Game game) {
Player controller = game.getPlayer(sourceControllerId);
EnumSet<CardType> 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 super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source);
if (targetSource != null) {
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
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)) {
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

View file

@ -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) {

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> 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);
}
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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());
}
}
}
}
}
}
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());
}
}
}
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);

View file

@ -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);

View file

@ -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);

View file

@ -129,7 +129,7 @@ class GrimeGorgerTarget extends TargetCardInGraveyard {
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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;
}

View file

@ -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

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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<Card> allTargets = possibleTargets
.stream()
.map(game::getCard)
.collect(Collectors.toSet());
possibleTargets.clear();
for (ObjectColor color : ObjectColor.getAllColors()) {
Set<Card> 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<UUID> 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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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);

View file

@ -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

View file

@ -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) {

View file

@ -1,26 +1,21 @@
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 {
@ -80,18 +75,14 @@ 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)) {
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);
}
}
}
}
}
controller.moveCards(cards.getCards(game), Zone.BATTLEFIELD, source, game, false, false, true, null);
return true;
}

View file

@ -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

View file

@ -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);
// 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;
}
if (!hasJiang && name.equals(card.getName())) {
return true;
}
class JourneyForTheElixirGraveyardTarget extends TargetCardInYourGraveyard {
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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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;
}
}

View file

@ -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);
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> 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<UUID> 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);

View file

@ -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<Player> {
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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
Set<UUID> 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;
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
int lifeController = game.getPlayer(sourceControllerId).getLife();
Set<UUID> 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);
}
}
}
Player controller = game.getPlayer(sourceControllerId);
if (controller == null) {
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)) {
possibleTargets.removeIf(playerId -> {
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;
}
}
}
}
return false;
return player == null || player.getLife() >= controller.getLife();
});
return possibleTargets;
}
@Override

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -69,7 +69,7 @@ class LethalSchemeEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
HashSet<MageObjectReference> convokingCreatures = CardUtil.getSourceCostsTag(game, source,
ConvokeAbility.convokingCreaturesKey, new HashSet<>(0));
ConvokeAbility.convokingCreaturesKey, new HashSet<>());
Set<AbstractMap.SimpleEntry<UUID, Permanent>> playerPermanentsPairs =
convokingCreatures
.stream()

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
Set<Integer> manaValues = new HashSet<>();
Set<Integer> 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;
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
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();
}
} else {
// real
// filter by same player
possibleTargets.removeIf(id -> {
Permanent permanent = game.getPermanent(id);
return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId());
});
}
possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id));
return possibleTargets;
}
@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++;
}
}
return possibleTargets > 1;
}
return false;
}
@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);
}
}

View file

@ -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<UUID> idsToCheck = new HashSet<>();
idsToCheck.addAll(this.getTargets());
idsToCheck.addAll(this.possibleTargets(sourceControllerId, source, game));
return metCondition(idsToCheck, game);
}
private static Set<CardType> typesAmongSelection(Collection<UUID> cardsIds, Game game) {

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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<UUID> 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));
}
}
return super.possibleTargets(sourceControllerId, source, game);
}
Set<UUID> possibleTargets = new HashSet<>();
private Permanent getPermanentFromFirstTarget(Ability source, Game game) {
Permanent firstTarget = null;
if (source.getTargets().size() == 2) {
firstTarget = game.getPermanent(source.getTargets().get(0).getFirstTarget());
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 firstTarget;
}
}
possibleTargets.removeIf(id -> firstPermanent != null && firstPermanent.getId().equals(id));
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override

View file

@ -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) {

View file

@ -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));

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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
);

View file

@ -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

View file

@ -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);
}

View file

@ -1,29 +1,29 @@
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 {
@ -75,37 +75,20 @@ class ProtectorOfTheWastesTarget extends TargetPermanent {
return new ProtectorOfTheWastesTarget(this);
}
@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()
.map(game::getPermanent)
.filter(Objects::nonNull)
.noneMatch(perm -> !perm.getId().equals(permanent.getId())
&& perm.isControlledBy(permanent.getControllerId()));
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
MageObject targetSource = game.getObject(source);
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
boolean validTarget = this.getTargets().stream()
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> usedControllers = 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());
}
}
}
.map(Controllable::getControllerId)
.collect(Collectors.toSet());
possibleTargets.removeIf(id -> {
Permanent permanent = game.getPermanent(id);
return permanent == null || usedControllers.contains(permanent.getControllerId());
});
return possibleTargets;
}
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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 super.canTarget(id, source, game) && ownPermanent.getManaValue() >= possiblePermanent.getManaValue();
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source);
if (targetSource != null) {
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
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()) {
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);
}

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<Integer> manaValues = this
Set<Integer> 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;
}
}

View file

@ -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()) {

View file

@ -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);
}

View file

@ -102,7 +102,7 @@ class RasputinDreamweaverWatcher extends Watcher {
filter.add(TappedPredicate.UNTAPPED);
}
private final Set<UUID> startedUntapped = new HashSet<>(0);
private final Set<UUID> startedUntapped = new HashSet<>();
RasputinDreamweaverWatcher() {
super(WatcherScope.GAME);

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
Set<UUID> 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;
}

View file

@ -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) {

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> 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);
}
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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()) {

View file

@ -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);
}

Some files were not shown because too many files have changed in this diff Show more