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;
This commit is contained in:
Oleg Agafonov 2025-08-04 23:51:01 +04:00
parent e866707912
commit c7a485b728
240 changed files with 1652 additions and 3096 deletions

View file

@ -18,10 +18,8 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.ArrayList; import java.util.*;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -258,7 +256,7 @@ public class CardArea extends JPanel implements CardEventProducer {
this.reloaded = false; this.reloaded = false;
} }
public void selectCards(List<UUID> selected) { public void selectCards(Set<UUID> selected) {
for (Component component : cardArea.getComponents()) { for (Component component : cardArea.getComponents()) {
if (component instanceof MageCard) { if (component instanceof MageCard) {
MageCard mageCard = (MageCard) component; 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()) { for (Component component : cardArea.getComponents()) {
if (component instanceof MageCard) { if (component instanceof MageCard) {
MageCard mageCard = (MageCard) component; MageCard mageCard = (MageCard) component;

View file

@ -100,11 +100,11 @@
cardArea.loadCards(showCards, bigCard, gameId); cardArea.loadCards(showCards, bigCard, gameId);
if (options != null) { if (options != null) {
if (options.containsKey("chosenTargets")) { 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); cardArea.selectCards(chosenCards);
} }
if (options.containsKey("possibleTargets")) { 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); cardArea.markCards(choosableCards);
} }
if (options.containsKey("queryType") && options.get("queryType") == QueryType.PICK_ABILITY) { 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() { public Set<UUID> getChosenTargets() {
if (options != null && options.containsKey("chosenTargets")) { if (options != null && options.containsKey("chosenTargets")) {
return new HashSet<>((List<UUID>) options.get("chosenTargets")); return (Set<UUID>) options.get("chosenTargets");
} else { } else {
return Collections.emptySet(); return Collections.emptySet();
} }

View file

@ -3,7 +3,6 @@ package mage.player.human;
import mage.MageIdentifier; import mage.MageIdentifier;
import mage.MageObject; import mage.MageObject;
import mage.abilities.*; import mage.abilities.*;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
@ -697,7 +696,7 @@ public class HumanPlayer extends PlayerImpl {
} }
// stop on completed, e.g. X=0 // stop on completed, e.g. X=0
if (target.isChoiceCompleted(abilityControllerId, source, game)) { if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return false; 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) // continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards)
} else { } else {
// manual choose // manual choose
options.put("chosenTargets", (Serializable) target.getTargets()); options.put("chosenTargets", new HashSet<>(target.getTargets()));
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
@ -747,9 +746,9 @@ public class HumanPlayer extends PlayerImpl {
continue; continue;
} }
if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, game)) { if (possibleTargets.contains(responseId)) {
target.add(responseId, game); target.add(responseId, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) { if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
break; break;
} }
} }
@ -794,7 +793,7 @@ public class HumanPlayer extends PlayerImpl {
// manual choice // manual choice
if (responseId == null) { if (responseId == null) {
options.put("chosenTargets", (Serializable) target.getTargets()); options.put("chosenTargets", new HashSet<>(target.getTargets()));
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
@ -814,11 +813,9 @@ public class HumanPlayer extends PlayerImpl {
// add new target // add new target
if (possibleTargets.contains(responseId)) { if (possibleTargets.contains(responseId)) {
if (target.canTarget(abilityControllerId, responseId, source, game)) { target.addTarget(responseId, source, game);
target.addTarget(responseId, source, game); if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
if (target.isChoiceCompleted(abilityControllerId, source, game)) { return true;
return true;
}
} }
} }
} else { } else {
@ -864,13 +861,7 @@ public class HumanPlayer extends PlayerImpl {
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId()); UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
while (canRespond()) { while (canRespond()) {
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards);
List<UUID> possibleTargets = new ArrayList<>();
for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
possibleTargets.add(cardId);
}
}
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
int count = cards.count(target.getFilter(), abilityControllerId, source, 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) // 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()) { if (required && possibleTargets.isEmpty()) {
required = false; required = false;
} }
@ -894,7 +885,7 @@ public class HumanPlayer extends PlayerImpl {
} else { } else {
// manual choose // manual choose
Map<String, Serializable> options = getOptions(target, null); Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) target.getTargets()); options.put("chosenTargets", new HashSet<>(target.getTargets()));
if (!possibleTargets.isEmpty()) { if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets); options.put("possibleTargets", (Serializable) possibleTargets);
} }
@ -916,9 +907,9 @@ public class HumanPlayer extends PlayerImpl {
continue; continue;
} }
if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, cards, game)) { if (possibleTargets.contains(responseId)) {
target.add(responseId, game); target.add(responseId, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) { if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
return true; return true;
} }
} }
@ -962,12 +953,8 @@ public class HumanPlayer extends PlayerImpl {
required = false; required = false;
} }
List<UUID> possibleTargets = new ArrayList<>(); Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards);
for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
possibleTargets.add(cardId);
}
}
// if nothing to choose then show dialog (user must see non-selectable items and click on any of them) // if nothing to choose then show dialog (user must see non-selectable items and click on any of them)
if (possibleTargets.isEmpty()) { if (possibleTargets.isEmpty()) {
required = false; required = false;
@ -977,7 +964,7 @@ public class HumanPlayer extends PlayerImpl {
if (responseId == null) { if (responseId == null) {
Map<String, Serializable> options = getOptions(target, null); Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) target.getTargets()); options.put("chosenTargets", new HashSet<>(target.getTargets()));
if (!possibleTargets.isEmpty()) { if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets); options.put("possibleTargets", (Serializable) possibleTargets);
@ -995,9 +982,9 @@ public class HumanPlayer extends PlayerImpl {
if (responseId != null) { if (responseId != null) {
if (target.contains(responseId)) { // if already included remove it if (target.contains(responseId)) { // if already included remove it
target.remove(responseId); target.remove(responseId);
} else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) { } else if (possibleTargets.contains(responseId)) {
target.addTarget(responseId, source, game); target.addTarget(responseId, source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) { if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
return true; return true;
} }
} }
@ -1054,9 +1041,9 @@ public class HumanPlayer extends PlayerImpl {
// 1. Select targets // 1. Select targets
// TODO: rework to use existing chooseTarget instead custom select? // TODO: rework to use existing chooseTarget instead custom select?
while (canRespond()) { 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); boolean required = target.isRequired(source.getSourceId(), game);
if (possibleTargetIds.isEmpty() if (possibleTargets.isEmpty()
|| target.getSize() >= target.getMinNumberOfTargets()) { || target.getSize() >= target.getMinNumberOfTargets()) {
required = false; required = false;
} }
@ -1065,12 +1052,6 @@ public class HumanPlayer extends PlayerImpl {
// responseId is null if a choice couldn't be automatically made // responseId is null if a choice couldn't be automatically made
if (responseId == null) { 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 nothing to choose then show dialog (user must see non selectable items and click on any of them)
if (required && possibleTargets.isEmpty()) { if (required && possibleTargets.isEmpty()) {
required = false; required = false;
@ -1078,7 +1059,7 @@ public class HumanPlayer extends PlayerImpl {
// selected // selected
Map<String, Serializable> options = getOptions(target, null); Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) target.getTargets()); options.put("chosenTargets", new HashSet<>(target.getTargets()));
if (!possibleTargets.isEmpty()) { if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets); options.put("possibleTargets", (Serializable) possibleTargets);
} }
@ -1087,7 +1068,7 @@ public class HumanPlayer extends PlayerImpl {
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters"; String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters";
String message = target.getMessage(game) + String.format(multiType, amountTotal); 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); waitForResponse(game);
@ -1098,9 +1079,7 @@ public class HumanPlayer extends PlayerImpl {
if (target.contains(responseId)) { if (target.contains(responseId)) {
// unselect // unselect
target.remove(responseId); target.remove(responseId);
} else if (possibleTargetIds.contains(responseId) } else if (possibleTargets.contains(responseId) && target.getSize() < amountTotal) {
&& target.canTarget(abilityControllerId, responseId, source, game)
&& target.getSize() < amountTotal) {
// select // select
target.addTarget(responseId, source, game); target.addTarget(responseId, source, game);
} }
@ -2094,32 +2073,33 @@ public class HumanPlayer extends PlayerImpl {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return; return;
} }
TargetAttackingCreature target = new TargetAttackingCreature();
// TODO: add canRespond cycle? // no need in cycle, cause parent selectBlockers used it already
if (!canRespond()) { if (!canRespond()) {
return; return;
} }
UUID responseId = null; UUID responseId = null;
prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
// possible attackers to block // possible attackers to block
Set<UUID> attackers = target.possibleTargets(playerId, null, game); TargetAttackingCreature target = new TargetAttackingCreature();
Permanent blocker = game.getPermanent(blockerId); Permanent blocker = game.getPermanent(blockerId);
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> allAttackers = target.possibleTargets(playerId, null, game);
for (UUID attackerId : attackers) { Set<UUID> possibleAttackersToBlock = new HashSet<>();
for (UUID attackerId : allAttackers) {
CombatGroup group = game.getCombat().findGroup(attackerId); CombatGroup group = game.getCombat().findGroup(attackerId);
if (group != null && blocker != null && group.canBlock(blocker, game)) { if (group != null && blocker != null && group.canBlock(blocker, game)) {
possibleTargets.add(attackerId); possibleAttackersToBlock.add(attackerId);
} }
} }
if (possibleTargets.size() == 1) { if (possibleAttackersToBlock.size() == 1) {
responseId = possibleTargets.stream().iterator().next(); // auto-choice
responseId = possibleAttackersToBlock.stream().iterator().next();
} else { } else {
prepareForResponse(game);
game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, 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); waitForResponse(game);
} }
} }
@ -2174,7 +2154,7 @@ public class HumanPlayer extends PlayerImpl {
break; break;
} }
return xValue; return xValue;
} }
@Override @Override

View file

@ -77,7 +77,7 @@ class AjanisChosenEffect extends OneShotEffect {
Permanent oldCreature = game.getPermanent(enchantment.getAttachedTo()); Permanent oldCreature = game.getPermanent(enchantment.getAttachedTo());
if (oldCreature != null) { if (oldCreature != null) {
boolean canAttach = enchantment.getSpellAbility() == 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 (canAttach && controller.chooseUse(Outcome.Neutral, "Attach " + enchantment.getName() + " to the token ?", source, game)) {
if (oldCreature.removeAttachment(enchantment.getId(), source, game)) { if (oldCreature.removeAttachment(enchantment.getId(), source, game)) {
tokenPermanent.addAttachment(enchantment.getId(), source, game); tokenPermanent.addAttachment(enchantment.getId(), source, game);

View file

@ -108,8 +108,8 @@ class AncientBrassDragonTarget extends TargetCardInGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, xValue, game); this.getTargets(), id, MageObject::getManaValue, xValue, game);
} }

View file

@ -84,7 +84,7 @@ class AnuridScavengerCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -134,8 +134,8 @@ class AoTheDawnSkyTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 4, game); this.getTargets(), id, MageObject::getManaValue, 4, game);
} }

View file

@ -79,7 +79,7 @@ class ArdentDustspeakerCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -67,7 +67,7 @@ class BackFromTheBrinkCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -147,6 +147,7 @@ class BattleForBretagardTarget extends TargetPermanent {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<String> names = this.getTargets() Set<String> names = this.getTargets()
.stream() .stream()
.map(game::getPermanent) .map(game::getPermanent)
@ -159,6 +160,7 @@ class BattleForBretagardTarget extends TargetPermanent {
Permanent permanent = game.getPermanent(uuid); Permanent permanent = game.getPermanent(uuid);
return permanent == null || names.contains(permanent.getName()); return permanent == null || names.contains(permanent.getName());
}); });
return possibleTargets; return possibleTargets;
} }
} }

View file

@ -79,7 +79,7 @@ class BattlefieldScroungerCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -66,12 +66,12 @@ class BeguilerOfWillsTarget extends TargetPermanent {
} }
@Override @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); Permanent permanent = game.getPermanent(id);
int count = game.getBattlefield().countAll(this.filter, source.getControllerId(), game); int count = game.getBattlefield().countAll(this.filter, source.getControllerId(), game);
if (permanent != null && permanent.getPower().getValue() <= count) { if (permanent != null && permanent.getPower().getValue() <= count) {
return super.canTarget(controllerId, id, source, game); return super.canTarget(playerId, id, source, game);
} }
return false; return false;
} }

View file

@ -49,18 +49,18 @@ class BlazingHopeTarget extends TargetPermanent {
} }
@Override @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); Permanent permanent = game.getPermanent(id);
if (permanent != null) { if (permanent != null) {
if (!isNotTarget()) { if (!isNotTarget()) {
if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, source, game) if (!permanent.canBeTargetedBy(game.getObject(source.getId()), playerId, source, game)
|| !permanent.canBeTargetedBy(game.getObject(source), controllerId, source, game)) { || !permanent.canBeTargetedBy(game.getObject(source), playerId, source, game)) {
return false; return false;
} }
} }
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null && permanent.getPower().getValue() >= controller.getLife()) { if (controller != null && permanent.getPower().getValue() >= controller.getLife()) {
return filter.match(permanent, controllerId, source, game); return filter.match(permanent, playerId, source, game);
} }
} }
return false; return false;

View file

@ -1,6 +1,5 @@
package mage.cards.b; package mage.cards.b;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.keyword.TheRingTemptsYouEffect; import mage.abilities.effects.keyword.TheRingTemptsYouEffect;
@ -8,20 +7,20 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterOpponentsCreaturePermanent;
import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
* TODO: combine with Mutiny
*
* @author Susucr * @author Susucr
*/ */
public final class BreakingOfTheFellowship extends CardImpl { 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. // 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().addEffect(new BreakingOfTheFellowshipEffect());
this.getSpellAbility().addTarget(new BreakingOfTheFellowshipFirstTarget()); this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls"))); this.getSpellAbility().addTarget(new BreakingOfTheFellowshipSecondTarget());
// The Ring tempts you. // The Ring tempts you.
this.getSpellAbility().addEffect(new TheRingTemptsYouEffect()); this.getSpellAbility().addEffect(new TheRingTemptsYouEffect());
@ -76,74 +75,45 @@ class BreakingOfTheFellowshipEffect extends OneShotEffect {
} }
return true; return true;
} }
} }
class BreakingOfTheFellowshipFirstTarget extends TargetPermanent { class BreakingOfTheFellowshipSecondTarget extends TargetPermanent {
public BreakingOfTheFellowshipFirstTarget() { public BreakingOfTheFellowshipSecondTarget() {
super(1, 1, StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, false); super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE);
} }
private BreakingOfTheFellowshipFirstTarget(final BreakingOfTheFellowshipFirstTarget target) { private BreakingOfTheFellowshipSecondTarget(final BreakingOfTheFellowshipSecondTarget target) {
super(target); super(target);
} }
@Override @Override
public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
super.addTarget(id, source, game, skipEvent); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
// 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);
}
}
@Override Permanent firstTarget = game.getPermanent(source.getFirstTarget());
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { if (firstTarget == null) {
if (super.canTarget(controllerId, id, source, game)) { // playable or first target not yet selected
// can only target, if the controller has at least two targetable creatures // use all
UUID controllingPlayerId = game.getControllerId(id); if (possibleTargets.size() == 1) {
int possibleTargets = 0; // workaround to make 1 target invalid
MageObject sourceObject = game.getObject(source.getId()); possibleTargets.clear();
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) {
if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) {
possibleTargets++;
}
} }
return possibleTargets > 1; } else {
// real
// filter by same player
possibleTargets.removeIf(id -> {
Permanent permanent = game.getPermanent(id);
return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId());
});
} }
return false; possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id));
return possibleTargets;
} }
@Override @Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { public BreakingOfTheFellowshipSecondTarget copy() {
if (super.canChoose(sourceControllerId, source, game)) { return new BreakingOfTheFellowshipSecondTarget(this);
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);
} }
} }

View file

@ -141,8 +141,8 @@ class CallOfTheDeathDwellerTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 3, game); this.getTargets(), id, MageObject::getManaValue, 3, game);
} }

View file

@ -1,8 +1,5 @@
package mage.cards.c; package mage.cards.c;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.ApprovingObject; import mage.ApprovingObject;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -17,7 +14,6 @@ import mage.filter.common.FilterCreaturePermanent;
import mage.filter.common.FilterInstantOrSorceryCard; import mage.filter.common.FilterInstantOrSorceryCard;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.TargetCard; import mage.target.TargetCard;
@ -25,6 +21,9 @@ import mage.target.TargetPermanent;
import mage.target.common.TargetPlayerOrPlaneswalker; import mage.target.common.TargetPlayerOrPlaneswalker;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.Set;
import java.util.UUID;
/** /**
* @author jeffwadsworth * @author jeffwadsworth
*/ */
@ -111,47 +110,23 @@ class ChandraPyromasterTarget extends TargetPermanent {
super(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 @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
for (StackObject item : game.getState().getStack()) { Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget());
if (item.getId().equals(source.getSourceId())) { if (needPlayer == null) {
object = item; // playable or not selected - use any
} } else {
if (item.getSourceId().equals(source.getSourceId())) { // filter by controller
object = item; 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; return possibleTargets;
} }

View file

@ -124,8 +124,8 @@ class ChaosMutationTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(id); Permanent creature = game.getPermanent(id);

View file

@ -85,8 +85,8 @@ class CloudsLimitBreakTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(id); Permanent creature = game.getPermanent(id);

View file

@ -119,8 +119,7 @@ class ConspiracyTheoristEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
CardsImpl cards = new CardsImpl(discardedCards); CardsImpl cards = new CardsImpl(discardedCards);
TargetCard target = new TargetCard(Zone.GRAVEYARD, new FilterCard("card to exile")); TargetCard target = new TargetCard(Zone.GRAVEYARD, new FilterCard("card to exile"));
boolean validTarget = cards.stream() boolean validTarget = cards.stream().anyMatch(card -> target.canTarget(card, source, game));
.anyMatch(card -> target.canTarget(card, game));
if (validTarget && controller.chooseUse(Outcome.Benefit, "Exile a card?", source, game)) { if (validTarget && controller.chooseUse(Outcome.Benefit, "Exile a card?", source, game)) {
if (controller.choose(Outcome.Benefit, cards, target, source, game)) { if (controller.choose(Outcome.Benefit, cards, target, source, game)) {
Card card = cards.get(target.getFirstTarget(), 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.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
@ -35,8 +36,12 @@ public final class DaringThief extends CardImpl {
this.toughness = new MageInt(3); 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. // 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, Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(
"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); 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 TargetControlledPermanentSharingOpponentPermanentCardType());
ability.addTarget(new DaringThiefSecondTarget()); ability.addTarget(new DaringThiefSecondTarget());
this.addAbility(ability); this.addAbility(ability);
@ -66,9 +71,10 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (super.canTarget(controllerId, id, source, game)) { Set<CardType> cardTypes = getOpponentPermanentCardTypes(playerId, game);
Set<CardType> cardTypes = getOpponentPermanentCardTypes(controllerId, game);
if (super.canTarget(playerId, id, source, game)) {
Permanent permanent = game.getPermanent(id); Permanent permanent = game.getPermanent(id);
for (CardType type : permanent.getCardType(game)) { for (CardType type : permanent.getCardType(game)) {
if (cardTypes.contains(type)) { if (cardTypes.contains(type)) {
@ -81,23 +87,21 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
// get all cardtypes from opponents permanents
Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game); Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
MageObject targetSource = game.getObject(source); MageObject targetSource = game.getObject(source);
if (targetSource != null) { if (targetSource != null) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { 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)) {
for (CardType type : permanent.getCardType(game)) { if (cardTypes.contains(type)) {
if (cardTypes.contains(type)) { possibleTargets.add(permanent.getId());
possibleTargets.add(permanent.getId()); break;
break;
}
} }
} }
} }
} }
return possibleTargets; return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
} }
@Override @Override
@ -119,58 +123,54 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
} }
} }
class DaringThiefSecondTarget extends TargetPermanent { class DaringThiefSecondTarget extends TargetPermanent {
private Permanent firstTarget = null;
public DaringThiefSecondTarget() { public DaringThiefSecondTarget() {
super(); super(StaticFilters.FILTER_OPPONENTS_PERMANENT);
this.filter = this.filter.copy();
filter.add(TargetController.OPPONENT.getControllerPredicate());
withTargetName("permanent an opponent controls that shares a card type with it"); withTargetName("permanent an opponent controls that shares a card type with it");
} }
private DaringThiefSecondTarget(final DaringThiefSecondTarget target) { private DaringThiefSecondTarget(final DaringThiefSecondTarget target) {
super(target); super(target);
this.firstTarget = target.firstTarget;
} }
@Override @Override
public boolean canTarget(UUID id, Ability source, Game game) { public boolean canTarget(UUID id, Ability source, Game game) {
if (super.canTarget(id, source, game)) { Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
Permanent target1 = game.getPermanent(source.getFirstTarget()); Permanent possiblePermanent = game.getPermanent(id);
Permanent opponentPermanent = game.getPermanent(id); if (ownPermanent == null || possiblePermanent == null) {
if (target1 != null && opponentPermanent != null) { return false;
return target1.shareTypes(opponentPermanent, game);
}
} }
return false; return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source); Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { if (ownPermanent == null) {
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { // playable or first target not yet selected
if (permanent.shareTypes(firstTarget, game)) { // use all
possibleTargets.add(permanent.getId()); 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 @Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { 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.Damage, playerId, source, game); return super.chooseTarget(Outcome.GainControl, playerId, source, game);
} }
@Override @Override

View file

@ -91,9 +91,7 @@ class DeepCaverBatEffect extends OneShotEffect {
return true; return true;
} }
TargetCard target = new TargetCardInHand( TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
);
controller.choose(outcome, opponent.getHand(), target, source, game); controller.choose(outcome, opponent.getHand(), target, source, game);
Card card = opponent.getHand().get(target.getFirstTarget(), game); Card card = opponent.getHand().get(target.getFirstTarget(), game);
if (card == null) { if (card == null) {

View file

@ -137,49 +137,12 @@ class TargetControlledSource extends TargetSource {
} }
@Override @Override
public boolean canChoose(UUID sourceControllerId, Game game) { public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0; return canChooseFromPossibleTargets(sourceControllerId, source, game);
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;
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
for (StackObject stackObject : game.getStack()) { for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) 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 @Override

View file

@ -90,10 +90,10 @@ class DistendedMindbenderEffect extends OneShotEffect {
TargetCard targetThreeOrLess = new TargetCard(1, Zone.HAND, filterThreeOrLess); TargetCard targetThreeOrLess = new TargetCard(1, Zone.HAND, filterThreeOrLess);
TargetCard targetFourOrGreater = new TargetCard(1, Zone.HAND, filterFourOrGreater); TargetCard targetFourOrGreater = new TargetCard(1, Zone.HAND, filterFourOrGreater);
Cards toDiscard = new CardsImpl(); 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()); 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()); toDiscard.addAll(targetFourOrGreater.getTargets());
} }
opponent.discard(toDiscard, false, source, game); opponent.discard(toDiscard, false, source, game);

View file

@ -1,25 +1,26 @@
package mage.cards.d; 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.Ability;
import mage.abilities.effects.OneShotEffect; 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.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.common.FilterArtifactCard; import mage.filter.common.FilterArtifactCard;
import mage.game.Game; import mage.game.Game;
import mage.game.events.TargetEvent;
import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author emerald000 * @author emerald000
*/ */
public final class DrafnasRestoration extends CardImpl { public final class DrafnasRestoration extends CardImpl {
@ -53,27 +54,33 @@ class DrafnasRestorationTarget extends TargetCardInGraveyard {
super(target); 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 @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
if (object instanceof StackObject) { Player controller = game.getPlayer(sourceControllerId);
Player targetPlayer = game.getPlayer(((StackObject) object).getStackAbility().getFirstTarget()); if (controller == null) {
if (targetPlayer != null) { return possibleTargets;
for (Card card : targetPlayer.getGraveyard().getCards(filter, sourceControllerId, source, game)) {
if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) {
possibleTargets.add(card.getId());
}
}
}
} }
return possibleTargets;
Player targetPlayer = game.getPlayer(source.getFirstTarget());
game.getState().getPlayersInRange(sourceControllerId, game, true).stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.flatMap(player -> player.getGraveyard().getCards(filter, sourceControllerId, source, game).stream())
.forEach(card -> {
if (targetPlayer == null) {
// playable or not selected - use any
possibleTargets.add(card.getId());
} else {
// selected, filter by player
if (targetPlayer.getId().equals(card.getControllerOrOwnerId())) {
possibleTargets.add(card.getId());
}
}
});
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
} }
@Override @Override

View file

@ -72,7 +72,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect {
filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate())); filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate()));
TargetCard target = new TargetCard(Zone.HAND, filter); TargetCard target = new TargetCard(Zone.HAND, filter);
target.withNotTarget(true); 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()); Card card = game.getCard(target.getFirstTarget());
if (card != null) { if (card != null) {
toExile.add(card); toExile.add(card);
@ -81,7 +81,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect {
filter.setMessage("artifact or creature card from " + opponent.getName() + "'s graveyard"); filter.setMessage("artifact or creature card from " + opponent.getName() + "'s graveyard");
target = new TargetCard(Zone.GRAVEYARD, filter); target = new TargetCard(Zone.GRAVEYARD, filter);
target.withNotTarget(true); 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()); card = game.getCard(target.getFirstTarget());
if (card != null) { if (card != null) {
toExile.add(card); toExile.add(card);

View file

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

View file

@ -79,9 +79,7 @@ class EliteSpellbinderEffect extends OneShotEffect {
if (controller == null || opponent == null || opponent.getHand().isEmpty()) { if (controller == null || opponent == null || opponent.getHand().isEmpty()) {
return false; return false;
} }
TargetCard target = new TargetCardInHand( TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
);
controller.choose(outcome, opponent.getHand(), target, source, game); controller.choose(outcome, opponent.getHand(), target, source, game);
Card card = opponent.getHand().get(target.getFirstTarget(), game); Card card = opponent.getHand().get(target.getFirstTarget(), game);
if (card == null) { if (card == null) {

View file

@ -129,7 +129,7 @@ class EnchantmentAlterationEffect extends OneShotEffect {
if (oldPermanent != null if (oldPermanent != null
&& !oldPermanent.equals(permanentToBeAttachedTo)) { && !oldPermanent.equals(permanentToBeAttachedTo)) {
Target auraTarget = aura.getSpellAbility().getTargets().get(0); 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"); 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)) { } else if (oldPermanent.removeAttachment(aura.getId(), source, game)) {
game.informPlayers(aura.getLogName() + " was unattached from " + oldPermanent.getLogName() + " and attached to " + permanentToBeAttachedTo.getLogName()); 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)){ if (!p.isCreature(game)){
return false; 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){ for (MageObjectReference mor : set){
Permanent convoked = game.getPermanentOrLKIBattlefield(mor); Permanent convoked = game.getPermanentOrLKIBattlefield(mor);
if (convoked.shareCreatureTypes(game, p)){ if (convoked.shareCreatureTypes(game, p)){

View file

@ -8,9 +8,11 @@ import mage.cards.Cards;
import mage.cards.CardsImpl; import mage.cards.CardsImpl;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInHand;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import mage.util.CardUtil; import mage.util.CardUtil;
@ -65,9 +67,7 @@ class ExtractBrainEffect extends OneShotEffect {
if (controller == null || opponent == null || opponent.getHand().isEmpty() || xValue < 1) { if (controller == null || opponent == null || opponent.getHand().isEmpty() || xValue < 1) {
return false; return false;
} }
TargetCardInHand target = new TargetCardInHand( TargetCard target = new TargetCard(Math.min(opponent.getHand().size(), xValue), Integer.MAX_VALUE, Zone.HAND, StaticFilters.FILTER_CARD);
Math.min(opponent.getHand().size(), xValue), StaticFilters.FILTER_CARD
);
opponent.choose(Outcome.Detriment, opponent.getHand(), target, source, game); opponent.choose(Outcome.Detriment, opponent.getHand(), target, source, game);
Cards cards = new CardsImpl(target.getTargets()); Cards cards = new CardsImpl(target.getTargets());
controller.lookAtCards(source, null, cards, game); controller.lookAtCards(source, null, cards, game);

View file

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

View file

@ -85,7 +85,7 @@ class FrogkinKidnapperEffect extends OneShotEffect {
opponent.revealCards(source, opponent.getHand(), game); opponent.revealCards(source, opponent.getHand(), game);
TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND); TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
Cards toRansom = new CardsImpl(); 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()); toRansom.addAll(target.getTargets());
String exileName = "Ransomed (owned by " + opponent.getName() + ")"; 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.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.ToughnessPredicate; import mage.filter.predicate.mageobject.ToughnessPredicate;
import mage.target.TargetCard;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import java.util.UUID; import java.util.UUID;

View file

@ -69,9 +69,10 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (super.canTarget(controllerId, id, source, game)) { Set<CardType> cardTypes = getOpponentPermanentCardTypes(playerId, game);
Set<CardType> cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), controllerId, game);
if (super.canTarget(playerId, id, source, game)) {
Permanent permanent = game.getPermanent(id); Permanent permanent = game.getPermanent(id);
for (CardType type : permanent.getCardType(game)) { for (CardType type : permanent.getCardType(game)) {
if (cardTypes.contains(type)) { if (cardTypes.contains(type)) {
@ -84,23 +85,21 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
// get all cardtypes from opponents permanents Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
Set<CardType> cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), sourceControllerId, game);
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
MageObject targetSource = game.getObject(source); MageObject targetSource = game.getObject(source);
if (targetSource != null) { if (targetSource != null) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { 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)) {
for (CardType type : permanent.getCardType(game)) { if (cardTypes.contains(type)) {
if (cardTypes.contains(type)) { possibleTargets.add(permanent.getId());
possibleTargets.add(permanent.getId()); break;
break;
}
} }
} }
} }
} }
return possibleTargets; return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
} }
@Override @Override
@ -108,7 +107,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
return new GauntletsOfChaosFirstTarget(this); 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); Player controller = game.getPlayer(sourceControllerId);
EnumSet<CardType> cardTypes = EnumSet.noneOf(CardType.class); EnumSet<CardType> cardTypes = EnumSet.noneOf(CardType.class);
if (controller != null) { if (controller != null) {
@ -125,8 +124,6 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
class GauntletsOfChaosSecondTarget extends TargetPermanent { class GauntletsOfChaosSecondTarget extends TargetPermanent {
private Permanent firstTarget = null;
public GauntletsOfChaosSecondTarget() { public GauntletsOfChaosSecondTarget() {
super(); super();
this.filter = this.filter.copy(); this.filter = this.filter.copy();
@ -136,44 +133,46 @@ class GauntletsOfChaosSecondTarget extends TargetPermanent {
private GauntletsOfChaosSecondTarget(final GauntletsOfChaosSecondTarget target) { private GauntletsOfChaosSecondTarget(final GauntletsOfChaosSecondTarget target) {
super(target); super(target);
this.firstTarget = target.firstTarget;
} }
@Override @Override
public boolean canTarget(UUID id, Ability source, Game game) { public boolean canTarget(UUID id, Ability source, Game game) {
if (super.canTarget(id, source, game)) { Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
Permanent target1 = game.getPermanent(source.getFirstTarget()); Permanent possiblePermanent = game.getPermanent(id);
Permanent opponentPermanent = game.getPermanent(id); if (ownPermanent == null || possiblePermanent == null) {
if (target1 != null && opponentPermanent != null) { return false;
return target1.shareTypes(opponentPermanent, game);
}
} }
return false; return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source); Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { if (ownPermanent == null) {
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { // playable or first target not yet selected
if (permanent.shareTypes(firstTarget, game)) { // use all
possibleTargets.add(permanent.getId()); 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 @Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { 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.Damage, playerId, source, game); return super.chooseTarget(Outcome.GainControl, playerId, source, game);
} }
@Override @Override

View file

@ -82,7 +82,7 @@ class GhastlordOfFugueEffect extends OneShotEffect {
TargetCard target = new TargetCard(Zone.HAND, new FilterCard()); TargetCard target = new TargetCard(Zone.HAND, new FilterCard());
target.withNotTarget(true); target.withNotTarget(true);
Card chosenCard = null; 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()); chosenCard = game.getCard(target.getFirstTarget());
} }
if (chosenCard != null) { if (chosenCard != null) {

View file

@ -2,7 +2,6 @@
package mage.cards.g; package mage.cards.g;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
@ -12,15 +11,11 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.TargetController;
import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game; import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.watchers.common.PlayerDamagedBySourceWatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
@ -28,6 +23,12 @@ import java.util.UUID;
*/ */
public final class GiltspireAvenger extends CardImpl { 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) { public GiltspireAvenger(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
this.subtype.add(SubType.HUMAN); 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. // {T}: Destroy target creature that dealt damage to you this turn.
Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new TapSourceCost()); Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new TapSourceCost());
ability.addTarget(new GiltspireAvengerTarget()); ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability); this.addAbility(ability);
} }
@ -55,64 +56,3 @@ public final class GiltspireAvenger extends CardImpl {
return new GiltspireAvenger(this); 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; package mage.cards.g;
import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.condition.common.SourceHasCounterCondition;
import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; 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.DontUntapInControllersUntapStepSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; 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.counters.CounterType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
@ -59,8 +60,6 @@ public final class GlyphOfDelusion extends CardImpl {
class GlyphOfDelusionSecondTarget extends TargetPermanent { class GlyphOfDelusionSecondTarget extends TargetPermanent {
private Permanent firstTarget = null;
public GlyphOfDelusionSecondTarget() { public GlyphOfDelusionSecondTarget() {
super(); super();
withTargetName("target creature that target Wall blocked this turn"); withTargetName("target creature that target Wall blocked this turn");
@ -68,33 +67,39 @@ class GlyphOfDelusionSecondTarget extends TargetPermanent {
private GlyphOfDelusionSecondTarget(final GlyphOfDelusionSecondTarget target) { private GlyphOfDelusionSecondTarget(final GlyphOfDelusionSecondTarget target) {
super(target); super(target);
this.firstTarget = target.firstTarget;
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class); BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class);
if (watcher != null) { if (watcher == null) {
MageObject targetSource = game.getObject(source); return possibleTargets;
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)) { Permanent targetWall = game.getPermanent(source.getFirstTarget());
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game))) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
possibleTargets.add(creature.getId()); if (targetWall == null) {
} // playable or first target not yet selected
} // use all
} possibleTargets.add(permanent.getId());
} else {
// real
// filter by blocked
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(permanent, game), new MageObjectReference(targetWall, game))) {
possibleTargets.add(permanent.getId());
} }
} }
} }
return possibleTargets; possibleTargets.removeIf(id -> targetWall != null && targetWall.getId().equals(id));
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
} }
@Override @Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { 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); return super.chooseTarget(Outcome.Tap, playerId, source, game);
} }
@ -133,8 +138,7 @@ class GlyphOfDelusionEffect extends OneShotEffect {
effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), game)); effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), game));
game.addEffect(effect, source); 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); GainAbilityTargetEffect effect2 = new GainAbilityTargetEffect(ability2, Duration.Custom);
effect2.setTargetPointer(new FixedTarget(targetPermanent.getId(), game)); effect2.setTargetPointer(new FixedTarget(targetPermanent.getId(), game));
game.addEffect(effect2, source); game.addEffect(effect2, source);

View file

@ -81,8 +81,8 @@ class GoblinArtisansTarget extends TargetSpell {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
MageObjectReference sourceRef = new MageObjectReference(source.getSourceObject(game), game); MageObjectReference sourceRef = new MageObjectReference(source.getSourceObject(game), game);

View file

@ -61,8 +61,8 @@ class GracefulTakedownTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent permanent = game.getPermanent(id); Permanent permanent = game.getPermanent(id);

View file

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

View file

@ -92,7 +92,7 @@ class GurzigostCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -1,7 +1,5 @@
package mage.cards.i; package mage.cards.i;
import mage.MageItem;
import mage.ObjectColor;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.RevealTargetFromHandCost; import mage.abilities.costs.common.RevealTargetFromHandCost;
@ -19,9 +17,9 @@ import mage.game.Game;
import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInHand;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
/** /**
* @author TheElk801 * @author TheElk801
@ -67,51 +65,8 @@ class IlluminatedFolioTarget extends TargetCardInHand {
} }
@Override @Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canChoose(sourceControllerId, source, game) if (!super.canTarget(playerId, id, 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)) {
return false; return false;
} }
List<UUID> targetList = this.getTargets(); List<UUID> targetList = this.getTargets();
@ -123,9 +78,17 @@ class IlluminatedFolioTarget extends TargetCardInHand {
&& targetList && targetList
.stream() .stream()
.map(game::getCard) .map(game::getCard)
.filter(Objects::nonNull)
.anyMatch(c -> c.getColor(game).shares(card.getColor(game))); .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 @Override
public IlluminatedFolioTarget copy() { public IlluminatedFolioTarget copy() {
return new IlluminatedFolioTarget(this); return new IlluminatedFolioTarget(this);

View file

@ -99,7 +99,7 @@ class ImpelledGiantCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return target.canChoose(controllerId, source, game); return target.canChooseOrAlreadyChosen(controllerId, source, game);
} }
@Override @Override

View file

@ -79,9 +79,7 @@ class InvasionOfGobakhanEffect extends OneShotEffect {
if (controller == null || opponent == null || opponent.getHand().isEmpty()) { if (controller == null || opponent == null || opponent.getHand().isEmpty()) {
return false; return false;
} }
TargetCard target = new TargetCardInHand( TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
);
controller.choose(outcome, opponent.getHand(), target, source, game); controller.choose(outcome, opponent.getHand(), target, source, game);
Card card = opponent.getHand().get(target.getFirstTarget(), game); Card card = opponent.getHand().get(target.getFirstTarget(), game);
if (card == null) { if (card == null) {

View file

@ -1,32 +1,27 @@
package mage.cards.i; package mage.cards.i;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.TrampleAbility;
import mage.cards.*; import mage.cards.*;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.common.TargetCardInHand; import mage.target.TargetCard;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class IwamoriOfTheOpenFist extends CardImpl { public final class IwamoriOfTheOpenFist extends CardImpl {
public IwamoriOfTheOpenFist(UUID ownerId, CardSetInfo setInfo) { public IwamoriOfTheOpenFist(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}");
this.supertype.add(SuperType.LEGENDARY); this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.MONK); this.subtype.add(SubType.MONK);
@ -80,15 +75,11 @@ class IwamoriOfTheOpenFistEffect extends OneShotEffect {
Cards cards = new CardsImpl(); Cards cards = new CardsImpl();
for (UUID playerId : game.getOpponents(controller.getId())) { for (UUID playerId : game.getOpponents(controller.getId())) {
Player opponent = game.getPlayer(playerId); Player opponent = game.getPlayer(playerId);
Target target = new TargetCardInHand(filter); Target target = new TargetCard(0, 1, Zone.HAND, filter).withChooseHint("put from hand to battlefield");
if (opponent != null && target.canChoose(opponent.getId(), source, game)) { if (target.choose(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) {
if (opponent.chooseUse(Outcome.PutCreatureInPlay, "Put a legendary creature card from your hand onto the battlefield?", source, game)) { Card card = game.getCard(target.getFirstTarget());
if (target.chooseTarget(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) { if (card != null) {
Card card = game.getCard(target.getFirstTarget()); cards.add(card);
if (card != null) {
cards.add(card);
}
}
} }
} }
} }

View file

@ -78,7 +78,7 @@ class JotunGruntCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.NamePredicate;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import java.util.Objects; import java.util.Objects;
@ -62,28 +61,31 @@ class JourneyForTheElixirEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (player == null) { if (controller == null) {
return false; return false;
} }
TargetCardInLibrary targetCardInLibrary = new JourneyForTheElixirLibraryTarget();
player.searchLibrary(targetCardInLibrary, source, game); // search your library and graveyard for 2 cards
Cards cards = new CardsImpl(targetCardInLibrary.getTargets()); Cards allCards = new CardsImpl();
TargetCard target = new JourneyForTheElixirGraveyardTarget(cards); allCards.addAll(controller.getLibrary().getCardList());
player.choose(outcome, target, source, game); allCards.addAll(controller.getGraveyard());
cards.addAll(target.getTargets()); TargetCard target = new JourneyForTheElixirTarget();
player.revealCards(source, cards, game); if (controller.choose(Outcome.Benefit, allCards, target, source, game)) {
player.moveCards(cards, Zone.HAND, source, game); Cards cards = new CardsImpl(target.getTargets());
player.shuffleLibrary(source, game); controller.revealCards(source, cards, game);
return true; 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 String name = "Jiang Yanggu";
private static final FilterCard filter private static final FilterCard filter = new FilterCard("a basic land card and a card named Jiang Yanggu");
= new FilterCard("a basic land card and a card named Jiang Yanggu");
static { static {
filter.add(Predicates.or( filter.add(Predicates.or(
@ -95,17 +97,17 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary {
)); ));
} }
JourneyForTheElixirLibraryTarget() { JourneyForTheElixirTarget() {
super(0, 2, filter); super(2, 2, Zone.ALL, filter);
} }
private JourneyForTheElixirLibraryTarget(final JourneyForTheElixirLibraryTarget target) { private JourneyForTheElixirTarget(final JourneyForTheElixirTarget target) {
super(target); super(target);
} }
@Override @Override
public JourneyForTheElixirLibraryTarget copy() { public JourneyForTheElixirTarget copy() {
return new JourneyForTheElixirLibraryTarget(this); return new JourneyForTheElixirTarget(this);
} }
@Override @Override
@ -117,95 +119,35 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary {
if (card == null) { if (card == null) {
return false; return false;
} }
if (this.getTargets().isEmpty()) {
return true;
}
Cards cards = new CardsImpl(this.getTargets()); Cards cards = new CardsImpl(this.getTargets());
if (card.isBasic(game) boolean hasLand = cards
&& card.isLand(game)
&& cards
.getCards(game) .getCards(game)
.stream() .stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(c -> c.isBasic(game)) .filter(c -> c.isBasic(game))
.anyMatch(c -> c.isLand(game))) { .anyMatch(c -> c.isLand(game));
return false; boolean hasJiang = cards
}
if (name.equals(card.getName())
&& cards
.getCards(game) .getCards(game)
.stream() .stream()
.map(MageObject::getName) .map(MageObject::getName)
.anyMatch(name::equals)) { .anyMatch(name::equals);
return false;
if (!hasLand && card.isBasic(game) && card.isLand(game)) {
return true;
} }
return true;
}
}
class JourneyForTheElixirGraveyardTarget extends TargetCardInYourGraveyard { if (!hasJiang && name.equals(card.getName())) {
return true;
}
private static final String name = "Jiang Yanggu"; return false;
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);
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Cards alreadyTargeted = new CardsImpl(this.getTargets()); possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
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());
});
return possibleTargets; return possibleTargets;
} }
} }

View file

@ -88,8 +88,8 @@ class KairiTheSwirlingSkyTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 6, game); this.getTargets(), id, MageObject::getManaValue, 6, game);
} }

View file

@ -1,7 +1,6 @@
package mage.cards.k; package mage.cards.k;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
@ -15,10 +14,8 @@ import mage.filter.FilterOpponent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.BeastToken4; import mage.game.permanent.token.BeastToken4;
import mage.players.Player;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -35,7 +32,7 @@ public final class KeeperOfTheBeasts extends CardImpl {
this.power = new MageInt(1); this.power = new MageInt(1);
this.toughness = new MageInt(2); 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."), 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}")); new ManaCostsImpl<>("{G}"));
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
@ -65,42 +62,12 @@ class KeeperOfTheBeastsTarget extends TargetPlayer {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>(); int myCount = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game);
int creaturesController = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game); possibleTargets.removeIf(playerId -> game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) < myCount);
for (UUID targetId : availablePossibleTargets) {
if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, targetId, game) > creaturesController) {
possibleTargets.add(targetId);
}
}
return possibleTargets; 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 @Override
public KeeperOfTheBeastsTarget copy() { public KeeperOfTheBeastsTarget copy() {
return new KeeperOfTheBeastsTarget(this); return new KeeperOfTheBeastsTarget(this);

View file

@ -1,10 +1,7 @@
package mage.cards.k; package mage.cards.k;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.ObjectColor;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
@ -15,21 +12,23 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterPlayer; import mage.filter.FilterPlayer;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.Set;
import java.util.UUID;
/** /**
*
* @author spjspj * @author spjspj
*/ */
public final class KeeperOfTheDead extends CardImpl { public final class KeeperOfTheDead extends CardImpl {
@ -95,48 +94,37 @@ class KeeperOfDeadPredicate implements ObjectSourcePlayerPredicate<Player> {
class KeeperOfTheDeadCreatureTarget extends TargetPermanent { 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() { public KeeperOfTheDeadCreatureTarget() {
super(1, 1, new FilterCreaturePermanent("nonblack creature that player controls"), false); super(1, 1, filter);
} }
private KeeperOfTheDeadCreatureTarget(final KeeperOfTheDeadCreatureTarget target) { private KeeperOfTheDeadCreatureTarget(final KeeperOfTheDeadCreatureTarget target) {
super(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 @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
for (StackObject item : game.getState().getStack()) { Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget());
if (item.getId().equals(source.getSourceId())) { if (needPlayer == null) {
object = item; // playable or not selected - use any
} } else {
if (item.getSourceId().equals(source.getSourceId())) { // filter by controller
object = item; 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; return possibleTargets;
} }

View file

@ -1,8 +1,6 @@
package mage.cards.k; package mage.cards.k;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
@ -12,13 +10,11 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterOpponent; import mage.filter.FilterOpponent;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -67,43 +63,19 @@ class KeeperOfTheLightTarget extends TargetPlayer {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
int lifeController = game.getPlayer(sourceControllerId).getLife();
for (UUID targetId : availablePossibleTargets) {
Player opponent = game.getPlayer(targetId);
if (opponent != null) {
int lifeOpponent = opponent.getLife();
if (lifeOpponent > lifeController) {
possibleTargets.add(targetId);
}
}
}
return possibleTargets;
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0;
MageObject targetSource = game.getObject(source);
Player controller = game.getPlayer(sourceControllerId); Player controller = game.getPlayer(sourceControllerId);
if (controller != null && targetSource != null) { if (controller == null) {
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) { return possibleTargets;
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;
possibleTargets.removeIf(playerId -> {
Player player = game.getPlayer(playerId);
return player == null || player.getLife() >= controller.getLife();
});
return possibleTargets;
} }
@Override @Override

View file

@ -105,7 +105,7 @@ class KeldonBattlewagonCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return target.canChoose(controllerId, source, game); return target.canChooseOrAlreadyChosen(controllerId, source, game);
} }
@Override @Override

View file

@ -7,15 +7,14 @@ import mage.abilities.effects.common.continuous.BoostAllEffect;
import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.*;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.TargetCard;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import java.util.UUID; import java.util.UUID;

View file

@ -15,6 +15,7 @@ import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
@ -107,9 +108,9 @@ class KotoseTheSilentSpiderEffect extends OneShotEffect {
cards.addAll(targetCardInGraveyard.getTargets()); cards.addAll(targetCardInGraveyard.getTargets());
filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s hand"); filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s hand");
TargetCardInHand targetCardInHand = new TargetCardInHand(0, Integer.MAX_VALUE, filter); TargetCard targetCard = new TargetCard(0, Integer.MAX_VALUE, Zone.HAND, filter);
controller.choose(outcome, opponent.getHand(), targetCardInHand, source, game); controller.choose(outcome, opponent.getHand(), targetCard, source, game);
cards.addAll(targetCardInHand.getTargets()); cards.addAll(targetCard.getTargets());
filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s library"); filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s library");
TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter); TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter);

View file

@ -130,8 +130,8 @@ class LagrellaTheMagpieTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(id); Permanent creature = game.getPermanent(id);

View file

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

View file

@ -103,8 +103,8 @@ class LivelyDirgeTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 4, game); this.getTargets(), id, MageObject::getManaValue, 4, game);
} }

View file

@ -74,7 +74,7 @@ class LobotomyEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExile
TargetCard target = new TargetCard(Zone.HAND, filter); TargetCard target = new TargetCard(Zone.HAND, filter);
target.withNotTarget(true); target.withNotTarget(true);
Card chosenCard = null; 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()); chosenCard = game.getCard(target.getFirstTarget());
} }

View file

@ -21,7 +21,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
*
* @author weirddan455 * @author weirddan455
*/ */
public final class LongRest extends CardImpl { public final class LongRest extends CardImpl {
@ -67,19 +66,22 @@ class LongRestTarget extends TargetCardInYourGraveyard {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
Set<Integer> manaValues = new HashSet<>();
Set<Integer> usedManaValues = new HashSet<>();
for (UUID targetId : this.getTargets()) { for (UUID targetId : this.getTargets()) {
Card card = game.getCard(targetId); Card card = game.getCard(targetId);
if (card != null) { if (card != null) {
manaValues.add(card.getManaValue()); usedManaValues.add(card.getManaValue());
} }
} }
for (UUID possibleTargetId : super.possibleTargets(sourceControllerId, source, game)) { for (UUID possibleTargetId : super.possibleTargets(sourceControllerId, source, game)) {
Card card = game.getCard(possibleTargetId); Card card = game.getCard(possibleTargetId);
if (card != null && !manaValues.contains(card.getManaValue())) { if (card != null && !usedManaValues.contains(card.getManaValue())) {
possibleTargets.add(possibleTargetId); possibleTargets.add(possibleTargetId);
} }
} }
return possibleTargets; return possibleTargets;
} }
} }

View file

@ -56,8 +56,8 @@ class MarchFromTheTombTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 8, game); this.getTargets(), id, MageObject::getManaValue, 8, game);
} }

View file

@ -86,8 +86,8 @@ class ModifyMemoryTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(id); Permanent creature = game.getPermanent(id);

View file

@ -112,8 +112,8 @@ class MoorlandRescuerTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, m -> m.getPower().getValue(), xValue, game); this.getTargets(), id, m -> m.getPower().getValue(), xValue, game);
} }

View file

@ -1,6 +1,5 @@
package mage.cards.m; package mage.cards.m;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -9,18 +8,16 @@ import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent; 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.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
* TODO: combine with BreakingOfTheFellowship
*
* @author LevelX2 * @author LevelX2
*/ */
public final class Mutiny extends CardImpl { 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. // 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().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"))); this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls")));
} }
@ -72,74 +70,45 @@ class MutinyEffect extends OneShotEffect {
} }
return true; return true;
} }
} }
class MutinyFirstTarget extends TargetPermanent { class MutinySecondTarget extends TargetPermanent {
public MutinyFirstTarget(FilterCreaturePermanent filter) { public MutinySecondTarget() {
super(1, 1, filter, false); super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE);
} }
private MutinyFirstTarget(final MutinyFirstTarget target) { private MutinySecondTarget(final MutinySecondTarget target) {
super(target); super(target);
} }
@Override @Override
public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
super.addTarget(id, source, game, skipEvent); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
// 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);
}
}
@Override Permanent firstTarget = game.getPermanent(source.getFirstTarget());
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { if (firstTarget == null) {
if (super.canTarget(controllerId, id, source, game)) { // playable or first target not yet selected
// can only target, if the controller has at least two targetable creatures // use all
UUID controllingPlayerId = game.getControllerId(id); if (possibleTargets.size() == 1) {
int possibleTargets = 0; // workaround to make 1 target invalid
MageObject sourceObject = game.getObject(source.getId()); possibleTargets.clear();
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) {
if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) {
possibleTargets++;
}
} }
return possibleTargets > 1; } else {
// real
// filter by same player
possibleTargets.removeIf(id -> {
Permanent permanent = game.getPermanent(id);
return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId());
});
} }
return false; possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id));
return possibleTargets;
} }
@Override @Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { public MutinySecondTarget copy() {
if (super.canChoose(sourceControllerId, source, game)) { return new MutinySecondTarget(this);
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);
} }
} }

View file

@ -22,10 +22,7 @@ import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.awt.*; import java.awt.*;
import java.util.Collection; import java.util.*;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -113,7 +110,10 @@ class NethergoyfTarget extends TargetCardInYourGraveyard {
return false; return false;
} }
// Check that exiling all the possible cards would have >= 4 different card types // 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) { private static Set<CardType> typesAmongSelection(Collection<UUID> cardsIds, Game game) {

View file

@ -83,8 +83,8 @@ class NethroiApexOfDeathTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, m -> m.getPower().getValue(), 10, game); this.getTargets(), id, m -> m.getPower().getValue(), 10, game);
} }

View file

@ -150,7 +150,7 @@ class NicolBolasDragonGodPlusOneEffect extends OneShotEffect {
TargetPermanent targetPermanent = new TargetControlledPermanent(); TargetPermanent targetPermanent = new TargetControlledPermanent();
targetPermanent.withNotTarget(true); targetPermanent.withNotTarget(true);
targetPermanent.setTargetController(opponentId); targetPermanent.setTargetController(opponentId);
if (!targetPermanent.possibleTargets(opponentId, game).isEmpty()) { if (!targetPermanent.possibleTargets(opponentId, source, game).isEmpty()) {
possibleTargetTypes.add(targetPermanent); possibleTargetTypes.add(targetPermanent);
} }

View file

@ -91,7 +91,7 @@ class NivmagusElementalCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -1,5 +1,6 @@
package mage.cards.n; package mage.cards.n;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.common.FightTargetsEffect; import mage.abilities.effects.common.FightTargetsEffect;
import mage.cards.Card; import mage.cards.Card;
@ -15,7 +16,9 @@ import mage.game.permanent.Permanent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.watchers.common.BlockedAttackerWatcher;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -54,50 +57,27 @@ class TargetCreatureWithLessPowerPermanent extends TargetPermanent {
super(target); 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 @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Spell spell = game.getStack().getSpell(source.getSourceId()); Set<UUID> possibleTargets = new HashSet<>();
if (spell != null) {
Permanent firstTarget = getPermanentFromFirstTarget(spell.getSpellAbility(), game); Permanent firstPermanent = game.getPermanent(source.getFirstTarget());
if (firstTarget != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
int power = firstTarget.getPower().getValue(); if (firstPermanent == null) {
// overwrite the filter with the power predicate // playable or first target not yet selected
filter = new FilterCreaturePermanent("creature with power less than " + power); // use all
filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, power)); possibleTargets.add(permanent.getId());
} else {
// real
// filter by power
if (firstPermanent.getPower().getValue() > permanent.getPower().getValue()) {
possibleTargets.add(permanent.getId());
}
} }
} }
return super.possibleTargets(sourceControllerId, source, game); possibleTargets.removeIf(id -> firstPermanent != null && firstPermanent.getId().equals(id));
}
private Permanent getPermanentFromFirstTarget(Ability source, Game game) { return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
Permanent firstTarget = null;
if (source.getTargets().size() == 2) {
firstTarget = game.getPermanent(source.getTargets().get(0).getFirstTarget());
}
return firstTarget;
} }
@Override @Override

View file

@ -52,7 +52,8 @@ public final class OKagachiVengefulKami extends CardImpl {
ability.addTarget(new TargetPermanent(filter)); ability.addTarget(new TargetPermanent(filter));
ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster());
ability.withInterveningIf(KagachiVengefulKamiCondition.instance); ability.withInterveningIf(KagachiVengefulKamiCondition.instance);
this.addAbility(ability);
this.addAbility(ability, new OKagachiVengefulKamiWatcher());
} }
private OKagachiVengefulKami(final OKagachiVengefulKami card) { private OKagachiVengefulKami(final OKagachiVengefulKami card) {

View file

@ -9,12 +9,12 @@ import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate; import mage.filter.predicate.mageobject.PowerPredicate;
import mage.target.TargetCard;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import java.util.UUID; import java.util.UUID;
@ -24,7 +24,7 @@ import java.util.UUID;
*/ */
public final class ONaginata extends CardImpl { 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 { static {
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 2)); 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.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
@ -81,7 +82,7 @@ class OildeepGearhulkEffect extends OneShotEffect {
} }
controller.lookAtCards(targetPlayer.getName() + " Hand", targetPlayer.getHand(), game); 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)) { if (!controller.choose(Outcome.Discard, targetPlayer.getHand(), chosenCard, source, game)) {
return false; return false;
} }

View file

@ -119,8 +119,8 @@ class OrcusPrinceOfUndeathTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, xValue, game); this.getTargets(), id, MageObject::getManaValue, xValue, game);
} }

View file

@ -101,8 +101,8 @@ class PairODiceLostTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, xValue, game); this.getTargets(), id, MageObject::getManaValue, xValue, game);
} }

View file

@ -58,8 +58,8 @@ class PatchUpTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 3, game); this.getTargets(), id, MageObject::getManaValue, 3, game);
} }

View file

@ -105,7 +105,7 @@ class PhenomenonInvestigatorsReturnCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { 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 @Override

View file

@ -94,8 +94,8 @@ class PhoenixWardenOfFireTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 6, game this.getTargets(), id, MageObject::getManaValue, 6, game
); );

View file

@ -78,7 +78,7 @@ class PrimordialMistCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return target.canChoose(controllerId, source, game); return target.canChooseOrAlreadyChosen(controllerId, source, game);
} }
@Override @Override

View file

@ -64,8 +64,8 @@ class ProteanHulkTarget extends TargetCardInLibrary {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 6, game); this.getTargets(), id, MageObject::getManaValue, 6, game);
} }

View file

@ -1,36 +1,36 @@
package mage.cards.p; 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.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.keyword.MonstrosityAbility;
import mage.constants.SubType;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.MonstrosityAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterArtifactOrEnchantmentPermanent; import mage.filter.common.FilterArtifactOrEnchantmentPermanent;
import mage.game.Controllable;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/** /**
*
* @author Jmlundeen * @author Jmlundeen
*/ */
public final class ProtectorOfTheWastes extends CardImpl { public final class ProtectorOfTheWastes extends CardImpl {
public ProtectorOfTheWastes(UUID ownerId, CardSetInfo setInfo) { public ProtectorOfTheWastes(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}{W}");
this.subtype.add(SubType.DRAGON); this.subtype.add(SubType.DRAGON);
this.power = new MageInt(5); this.power = new MageInt(5);
this.toughness = new MageInt(5); this.toughness = new MageInt(5);
@ -76,36 +76,19 @@ class ProtectorOfTheWastesTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID id, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
if (!super.canTarget(id, source, game)) { Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
return false;
} Set<UUID> usedControllers = this.getTargets().stream()
Permanent permanent = game.getPermanent(id);
if (permanent == null) {
return false;
}
return this.getTargets().stream()
.map(game::getPermanent) .map(game::getPermanent)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.noneMatch(perm -> !perm.getId().equals(permanent.getId()) .map(Controllable::getControllerId)
&& perm.isControlledBy(permanent.getControllerId())); .collect(Collectors.toSet());
} possibleTargets.removeIf(id -> {
Permanent permanent = game.getPermanent(id);
return permanent == null || usedControllers.contains(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()
.map(game::getPermanent)
.filter(Objects::nonNull)
.noneMatch(perm -> !perm.getId().equals(permanent.getId()) && perm.isControlledBy(permanent.getControllerId()));
if (validTarget) {
if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
possibleTargets.add(permanent.getId());
}
}
}
return possibleTargets; return possibleTargets;
} }
} }

View file

@ -1,21 +1,18 @@
package mage.cards.p; package mage.cards.p;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect; import mage.abilities.effects.common.continuous.ExchangeControlTargetEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.TargetController; import mage.filter.StaticFilters;
import mage.filter.predicate.Predicates;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetControlledPermanent;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; 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. // 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 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()); ability.addTarget(new PucasMischiefSecondTarget());
this.addAbility(ability); 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 { class PucasMischiefSecondTarget extends TargetPermanent {
private Permanent firstTarget = null;
public PucasMischiefSecondTarget() { public PucasMischiefSecondTarget() {
super(); super(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND);
this.filter = this.filter.copy();
filter.add(TargetController.OPPONENT.getControllerPredicate());
filter.add(Predicates.not(CardType.LAND.getPredicate()));
withTargetName("permanent an opponent controls with an equal or lesser mana value"); withTargetName("permanent an opponent controls with an equal or lesser mana value");
} }
private PucasMischiefSecondTarget(final PucasMischiefSecondTarget target) { private PucasMischiefSecondTarget(final PucasMischiefSecondTarget target) {
super(target); super(target);
this.firstTarget = target.firstTarget;
} }
@Override @Override
public boolean canTarget(UUID id, Ability source, Game game) { public boolean canTarget(UUID id, Ability source, Game game) {
if (super.canTarget(id, source, game)) { Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
Permanent target1 = game.getPermanent(source.getFirstTarget()); Permanent possiblePermanent = game.getPermanent(id);
Permanent opponentPermanent = game.getPermanent(id); if (ownPermanent == null || possiblePermanent == null) {
if (target1 != null && opponentPermanent != null) { return false;
return target1.getManaValue() >= opponentPermanent.getManaValue();
}
} }
return false; return super.canTarget(id, source, game) && ownPermanent.getManaValue() >= possiblePermanent.getManaValue();
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source); Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
if (targetSource != null) { for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) { if (ownPermanent == null) {
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) { // playable or first target not yet selected
if (firstTarget.getManaValue() >= permanent.getManaValue()) { // use all
possibleTargets.add(permanent.getId()); 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 @Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) { 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); return super.chooseTarget(Outcome.GainControl, playerId, source, game);
} }

View file

@ -109,26 +109,11 @@ class QueenKaylaBinKroogTarget extends TargetCard {
return new QueenKaylaBinKroogTarget(this); 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 @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<Integer> manaValues = this
Set<Integer> usedManaValues = this
.getTargets() .getTargets()
.stream() .stream()
.map(game::getCard) .map(game::getCard)
@ -137,8 +122,9 @@ class QueenKaylaBinKroogTarget extends TargetCard {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
possibleTargets.removeIf(uuid -> { possibleTargets.removeIf(uuid -> {
Card card = game.getCard(uuid); Card card = game.getCard(uuid);
return card != null && manaValues.contains(card.getManaValue()); return card == null || usedManaValues.contains(card.getManaValue());
}); });
return possibleTargets; return possibleTargets;
} }
} }

View file

@ -58,8 +58,8 @@ class RaiseTheDraugrTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
if (getTargets().isEmpty()) { if (getTargets().isEmpty()) {

View file

@ -82,8 +82,8 @@ class RampagingYaoGuaiTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, GetXValue.instance.calculate(game, source, null), game); 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); filter.add(TappedPredicate.UNTAPPED);
} }
private final Set<UUID> startedUntapped = new HashSet<>(0); private final Set<UUID> startedUntapped = new HashSet<>();
RasputinDreamweaverWatcher() { RasputinDreamweaverWatcher() {
super(WatcherScope.GAME); super(WatcherScope.GAME);

View file

@ -1,7 +1,6 @@
package mage.cards.r; package mage.cards.r;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.TransformIntoSourceTriggeredAbility; import mage.abilities.common.TransformIntoSourceTriggeredAbility;
import mage.abilities.common.WerewolfBackTriggeredAbility; import mage.abilities.common.WerewolfBackTriggeredAbility;
@ -15,13 +14,11 @@ import mage.constants.SubType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetOpponentOrPlaneswalker; import mage.target.common.TargetOpponentOrPlaneswalker;
import mage.target.targetpointer.EachTargetPointer; import mage.target.targetpointer.EachTargetPointer;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -100,54 +97,30 @@ class RavagerOfTheFellsEffect extends OneShotEffect {
class RavagerOfTheFellsTarget extends TargetPermanent { class RavagerOfTheFellsTarget extends TargetPermanent {
RavagerOfTheFellsTarget() { RavagerOfTheFellsTarget() {
super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false); super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE);
} }
private RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { private RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) {
super(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 @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject object = game.getObject(source);
for (StackObject item : game.getState().getStack()) { Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget());
if (item.getId().equals(source.getSourceId())) { if (needPlayer == null) {
object = item; // playable or not selected - use any
} } else {
if (item.getSourceId().equals(source.getSourceId())) { // filter by controller
object = item; 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; 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()); int xCost = Math.min(CardUtil.getSourceCostsTag(game, source, "X", 0), targetPlayer.getHand().size());
TargetCard target = new TargetCard(0, xCost, Zone.HAND, filterNonLands); TargetCard target = new TargetCard(0, xCost, Zone.HAND, filterNonLands);
target.withNotTarget(true); 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()) { for (UUID cardId : target.getTargets()) {
Card chosenCard = game.getCard(cardId); Card chosenCard = game.getCard(cardId);
if (chosenCard != null) { if (chosenCard != null) {

View file

@ -1,20 +1,15 @@
package mage.cards.r; package mage.cards.r;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.common.ExileTargetEffect; import mage.abilities.effects.common.ExileTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.filter.StaticFilters; import mage.constants.TargetController;
import mage.game.Game; import mage.filter.common.FilterCreaturePermanent;
import mage.game.permanent.Permanent; import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.watchers.common.PlayerDamagedBySourceWatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
@ -22,12 +17,18 @@ import java.util.UUID;
*/ */
public final class Reciprocate extends CardImpl { 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) { public Reciprocate(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}");
// Exile target creature that dealt damage to you this turn. // Exile target creature that dealt damage to you this turn.
this.getSpellAbility().addEffect(new ExileTargetEffect()); this.getSpellAbility().addEffect(new ExileTargetEffect());
this.getSpellAbility().addTarget(new ReciprocateTarget()); this.getSpellAbility().addTarget(new TargetPermanent(filter));
} }
private Reciprocate(final Reciprocate card) { 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.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
@ -68,7 +69,7 @@ class ReckonerShakedownEffect extends OneShotEffect {
return false; return false;
} }
player.revealCards(source, player.getHand(), game); 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); controller.choose(Outcome.Discard, player.getHand(), target, source, game);
Card card = game.getCard(target.getFirstTarget()); Card card = game.getCard(target.getFirstTarget());
if (card != null) { if (card != null) {

View file

@ -87,7 +87,7 @@ class RetetherEffect extends OneShotEffect {
for (Ability ability : aura.getAbilities()) { for (Ability ability : aura.getAbilities()) {
if (ability instanceof SpellAbility) { if (ability instanceof SpellAbility) {
for (Target abilityTarget : ability.getTargets()) { 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(); target = abilityTarget.copy();
break auraLegalitySearch; break auraLegalitySearch;
} }

View file

@ -58,8 +58,8 @@ class ReturnFromExtinctionTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
if (getTargets().isEmpty()) { if (getTargets().isEmpty()) {

View file

@ -61,8 +61,8 @@ class ReunionOfTheHouseTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, m -> m.getPower().getValue(), 10, game); this.getTargets(), id, m -> m.getPower().getValue(), 10, game);
} }

View file

@ -11,6 +11,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
@ -82,7 +83,7 @@ class RevealingEyeEffect extends OneShotEffect {
if (opponent.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1) { if (opponent.getHand().count(StaticFilters.FILTER_CARD_NON_LAND, game) < 1) {
return true; return true;
} }
TargetCard target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_NON_LAND); TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_NON_LAND);
controller.choose(outcome, opponent.getHand(), target, source, game); controller.choose(outcome, opponent.getHand(), target, source, game);
Card card = game.getCard(target.getFirstTarget()); Card card = game.getCard(target.getFirstTarget());
if (card == null) { if (card == null) {

View file

@ -120,7 +120,7 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, 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; return possibleTargets;
} }
} }

View file

@ -99,7 +99,7 @@ class RiftElementalCost extends CostImpl {
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
Target target = new TargetPermanentOrSuspendedCard(filter, true); Target target = new TargetPermanentOrSuspendedCard(filter, true);
return target.canChoose(controllerId, source, game); return target.canChooseOrAlreadyChosen(controllerId, source, game);
} }
@Override @Override

View file

@ -90,8 +90,8 @@ class RivalsDuelTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(id); Permanent creature = game.getPermanent(id);

View file

@ -84,7 +84,7 @@ class RoguesGalleryTarget extends TargetCardInYourGraveyard {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, 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; return possibleTargets;
} }
} }

View file

@ -53,8 +53,8 @@ class TargetPermanentsThatShareCardType extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (super.canTarget(controllerId, id, source, game)) { if (super.canTarget(playerId, id, source, game)) {
if (!getTargets().isEmpty()) { if (!getTargets().isEmpty()) {
Permanent targetOne = game.getPermanent(getTargets().get(0)); Permanent targetOne = game.getPermanent(getTargets().get(0));
Permanent targetTwo = game.getPermanent(id); Permanent targetTwo = game.getPermanent(id);

View file

@ -59,8 +59,8 @@ class RunAwayTogetherTarget extends TargetPermanent {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(controllerId, id, source, game)) { if (!super.canTarget(playerId, id, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(id); Permanent creature = game.getPermanent(id);

View file

@ -96,8 +96,8 @@ class ScoutForSurvivorsTarget extends TargetCardInYourGraveyard {
} }
@Override @Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
return super.canTarget(controllerId, id, source, game) return super.canTarget(playerId, id, source, game)
&& CardUtil.checkCanTargetTotalValueLimit( && CardUtil.checkCanTargetTotalValueLimit(
this.getTargets(), id, MageObject::getManaValue, 3, game this.getTargets(), id, MageObject::getManaValue, 3, game
); );

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