Merge pull request 'master' (#35) from External/mage:master into master
Some checks failed
/ build_release (push) Has been cancelled

Reviewed-on: #35
This commit is contained in:
Failure 2025-08-12 23:42:37 -07:00
commit e176b4a5bd
379 changed files with 5417 additions and 4608 deletions

View file

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

View file

@ -1,5 +1,6 @@
package mage.client.deckeditor;
import mage.cards.decks.importer.MtgaImporter;
import mage.client.MageFrame;
import mage.client.dialog.MageDialog;
import mage.util.DeckUtil;
@ -20,11 +21,18 @@ import java.util.Optional;
public class DeckImportClipboardDialog extends MageDialog {
private static final String FORMAT_TEXT =
"// Example:\n" +
"//1 Library of Congress\n" +
"//1 Cryptic Gateway\n" +
"//1 Azami, Lady of Scrolls\n" +
"// NB: This is slow as, and will lock your screen :)\n" +
"// MTGO format example:\n" +
"// 1 Library of Congress\n" +
"// 3 Cryptic Gateway\n" +
"//\n" +
"// MTGA, moxfield, archidekt format example:\n" +
"// Deck\n" +
"// 4 Accumulated Knowledge (A25) 40\n" +
"// 2 Adarkar Wastes (EOC) 147\n" +
"// Commander\n" +
"// 1 Grizzly Bears\n" +
"//\n" +
"// Importing deck can take some time to finish\n" +
"\n" +
"// Your current clipboard:\n" +
"\n";
@ -68,17 +76,19 @@ public class DeckImportClipboardDialog extends MageDialog {
}
private void onOK() {
String decklist = editData.getText();
decklist = decklist.replace(FORMAT_TEXT, "");
String importData = editData.getText();
importData = importData.replace(FORMAT_TEXT, "").trim();
// find possible data format
String tempDeckPath;
// This dialog also accepts a paste in .mtga format
if (decklist.startsWith("Deck\n")) { // An .mtga list always starts with the first line being "Deck". This kind of paste is processed as .mtga
tempDeckPath = DeckUtil.writeTextToTempFile("cbimportdeck", ".mtga", decklist);
if (MtgaImporter.isMTGA(importData)) {
// MTGA or Moxfield
tempDeckPath = DeckUtil.writeTextToTempFile("cbimportdeck", ".mtga", importData);
} else {
// If the paste is not .mtga format, it's processed as plaintext
tempDeckPath = DeckUtil.writeTextToTempFile(decklist);
// text
tempDeckPath = DeckUtil.writeTextToTempFile(importData);
}
if (this.callback != null) {
callback.onImportDone(tempDeckPath);
}

View file

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

View file

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

View file

@ -10,10 +10,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
@ -93,7 +90,9 @@ public class XmageURLConnection {
initDefaultProxy();
try {
URL url = new URL(this.url);
// convert utf8 url to ascii format (e.g. url encode)
URI uri = new URI(this.url);
URL url = new URL(uri.toASCIIString());
// proxy settings
if (this.proxy != null) {
@ -107,7 +106,7 @@ public class XmageURLConnection {
this.connection.setReadTimeout(CONNECTION_READING_TIMEOUT_MS);
initDefaultHeaders();
} catch (IOException e) {
} catch (IOException | URISyntaxException e) {
this.connection = null;
}
}

View file

@ -107,7 +107,7 @@ public class GathererSets implements Iterable<DownloadJob> {
"JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA",
"C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID",
"NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3",
"UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC",
"UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC", "SCH",
"MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT",
"WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ",
"OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB", "BLC", "DSK", "DSC",

View file

@ -515,6 +515,7 @@ public class ScryfallImageSupportCards {
add("SLX"); // Universes Within
add("CLB"); // Commander Legends: Battle for Baldur's Gate
add("2X2"); // Double Masters 2022
add("SCH"); // Store Championships
add("DMU"); // Dominaria United
add("DMC"); // Dominaria United Commander
add("YDMU"); // Alchemy: Dominaria
@ -569,9 +570,11 @@ public class ScryfallImageSupportCards {
add("BIG"); // The Big Score
add("MH3"); // Modern Horizons 3
add("M3C"); // Modern Horizons 3 Commander
add("H2R"); // Modern Horizons 2 Timeshifts
add("ACR"); // Assassin's Creed
add("BLB"); // Bloomburrow
add("BLC"); // Bloomburrow Commander
add("PCBB"); // Cowboy Bebop
add("MB2"); // Mystery Booster 2
add("DSK"); // Duskmourn: House of Horror
add("DSC"); // Duskmourn: House of Horror Commander
@ -579,21 +582,25 @@ public class ScryfallImageSupportCards {
add("J25"); // Foundations Jumpstart
add("PIO"); // Pioneer Masters
add("PW25"); // Wizards Play Network 2025
add("PSPL"); // Spotlight Series
add("INR"); // Innistrad Remastered
add("PF25"); // MagicFest 2025
add("DFT"); // Aetherdrift
add("DRC"); // Aetherdrift Commander
add("PLG25"); // Love Your LGS 2025
add("TDM"); // Tarkir: Dragonstorm
add("TDC"); // Tarkir: Dragonstorm Commander
add("FIN"); // Final Fantasy
add("FIC"); // Final Fantasy Commander
add("FCA"); // Final Fantasy: Through the Ages
add("PSS5"); // FIN Standard Showdown
add("EOE"); // Edge of Eternities
add("EOC"); // Edge of Eternities Commander
add("EOS"); // Edge of Eternities: Stellar Sights
add("SPM"); // Marvel's Spider-Man
add("SPE"); // Marvel's Spider-Man Eternal
add("TLA"); // Avatar: The Last Airbender
add("TLE"); // Avatar: The Last Airbender Eternal
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
add("CALC"); // Custom Alchemized versions of existing cards

View file

@ -837,6 +837,8 @@ public class ScryfallImageSupportTokens {
put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?format=image");
put("SLD/Icingdeath, Frost Tongue", "https://api.scryfall.com/cards/sld/1018?format=image");
put("SLD/Marit Lage", "https://api.scryfall.com/cards/sld/1681?format=image");
put("SLD/Mechtitan/1", "https://api.scryfall.com/cards/sld/1969?format=image");
put("SLD/Mechtitan/2", "https://api.scryfall.com/cards/sld/1969/en?format=image&face=back");
put("SLD/Mechtitan", "https://api.scryfall.com/cards/sld/1969?format=image");
put("SLD/Myr", "https://api.scryfall.com/cards/sld/2101?format=image");
put("SLD/Saproling", "https://api.scryfall.com/cards/sld/1139?format=image");

View file

@ -43,6 +43,12 @@ public class DownloaderTest {
Assert.assertTrue("must have text data (redirect to login page)", s.contains("Sign in to GitHub"));
}
@Test
public void test_DownloadText_ScryfallUtf8() {
String s = XmageURLConnection.downloadText("https://api.scryfall.com/cards/sld/379★/en");
Assert.assertTrue("must have text data (utf8 url must work)", s.contains("Zndrsplt, Eye of Wisdom"));
}
@Test
public void test_DownloadFile_ByHttp() throws IOException {
// use any public image here

View file

@ -785,7 +785,7 @@ public final class SystemUtil {
* @return added cards list
*/
private static Set<Card> addNewCardsToGame(Game game, String cardName, String setCode, int amount, Player owner) {
CardInfo cardInfo = CardLookup.instance.lookupCardInfo(cardName, setCode).orElse(null);
CardInfo cardInfo = CardLookup.instance.lookupCardInfo(cardName, setCode, null);
if (cardInfo == null || amount <= 0) {
return null;
}

View file

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

View file

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

View file

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

View file

@ -219,6 +219,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
// Condition to stop deeper simulation
if (SimulationNode2.nodeCount > MAX_SIMULATED_NODES_PER_ERROR) {
// how-to fix: make sure you are disabled debug mode by COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false
throw new IllegalStateException("AI ERROR: too much nodes (possible actions)");
}
if (depth <= 0
@ -398,20 +399,23 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
protected void resolve(SimulationNode2 node, int depth, Game game) {
StackObject stackObject = game.getStack().getFirst();
StackObject stackObject = game.getStack().getFirstOrNull();
if (stackObject == null) {
throw new IllegalStateException("Catch empty stack on resolve (something wrong with sim code)");
}
if (stackObject instanceof StackAbility) {
// AI hint for search effects (calc all possible cards for best score)
SearchEffect effect = getSearchEffect((StackAbility) stackObject);
if (effect != null
&& stackObject.getControllerId().equals(playerId)) {
Target target = effect.getTarget();
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game, null)) {
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
Game sim = game.createSimulationForAI();
StackAbility newAbility = (StackAbility) stackObject.copy();
SearchEffect newEffect = getSearchEffect(newAbility);
newEffect.getTarget().addTarget(targetId, newAbility, sim);
sim.getStack().push(newAbility);
sim.getStack().push(sim, newAbility);
SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId());
node.children.add(newNode);
newNode.getTargets().add(targetId);
@ -498,7 +502,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
logger.warn("Possible freeze chain:");
if (root != null && chain.isEmpty()) {
logger.warn(" - unknown use case"); // maybe can't finish any calc, maybe related to target options, I don't know
logger.warn(" - unknown use case (too many possible targets?)"); // maybe can't finish any calc, maybe related to target options, I don't know
}
chain.forEach(s -> {
logger.warn(" - " + s);
@ -639,7 +643,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
return "unknown";
})
.collect(Collectors.joining(", "));
logger.info(String.format("Sim Prio [%d] -> with choices (TODO): [%d]<diff %s> (%s)",
logger.info(String.format("Sim Prio [%d] -> with possible choices: [%d]<diff %s> (%s)",
depth,
currentNode.getDepth(),
printDiffScore(currentScore - prevScore),
@ -648,14 +652,18 @@ public class ComputerPlayer6 extends ComputerPlayer {
} else if (!currentNode.getChoices().isEmpty()) {
// ON CHOICES
String choicesInfo = String.join(", ", currentNode.getChoices());
logger.info(String.format("Sim Prio [%d] -> with choices (TODO): [%d]<diff %s> (%s)",
logger.info(String.format("Sim Prio [%d] -> with possible choices (must not see that code): [%d]<diff %s> (%s)",
depth,
currentNode.getDepth(),
printDiffScore(currentScore - prevScore),
choicesInfo)
);
} else {
throw new IllegalStateException("AI CALC ERROR: unknown calculation result (no abilities, no targets, no choices)");
logger.info(String.format("Sim Prio [%d] -> with do nothing: [%d]<diff %s>",
depth,
currentNode.getDepth(),
printDiffScore(currentScore - prevScore))
);
}
}
}
@ -886,10 +894,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
for (UUID targetId : targets) {
target.addTarget(targetId, source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
targets.clear();
return true;
}
@ -906,10 +914,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
for (UUID targetId : targets) {
target.add(targetId, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
targets.clear();
return true;
}

View file

@ -105,7 +105,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
return list;
}
protected void simulateOptions(Game game) {
private void simulateOptions(Game game) {
List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
for (ActivatedAbility ability : playables) {
if (ability.isManaAbility()) {
@ -176,6 +176,10 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
return options;
}
// remove invalid targets
// TODO: is it useless cause it already filtered before?
options.removeIf(option -> !option.getTargets().isChosen(game));
if (AI_SIMULATE_ALL_BAD_AND_GOOD_TARGETS) {
return options;
}
@ -314,14 +318,17 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
Ability ability = source.copy();
List<Ability> options = getPlayableOptions(ability, game);
if (options.isEmpty()) {
// no options - activate as is
logger.debug("simulating -- triggered ability:" + ability);
game.getStack().push(new StackAbility(ability, playerId));
game.getStack().push(game, new StackAbility(ability, playerId));
if (ability.activate(game, false) && ability.isUsesStack()) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
}
game.applyEffects();
game.getPlayers().resetPassed();
} else {
// many options - activate and add to sims tree
// TODO: AI run all sims, but do not use best option for triggers yet
SimulationNode2 parent = (SimulationNode2) game.getCustomData();
int depth = parent.getDepth() - 1;
if (depth == 0) {
@ -337,16 +344,16 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
protected void addAbilityNode(SimulationNode2 parent, Ability ability, int depth, Game game) {
Game sim = game.createSimulationForAI();
sim.getStack().push(new StackAbility(ability, playerId));
sim.getStack().push(sim, new StackAbility(ability, playerId));
if (ability.activate(sim, false) && ability.isUsesStack()) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
sim.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
}
sim.applyEffects();
SimulationNode2 newNode = new SimulationNode2(parent, sim, depth, playerId);
logger.debug("simulating -- node #:" + SimulationNode2.getCount() + " triggered ability option");
for (Target target : ability.getTargets()) {
for (UUID targetId : target.getTargets()) {
newNode.getTargets().add(targetId); // save for info only (real targets in newNode.ability already)
newNode.getTargets().add(targetId); // save for info only (real targets in newNode.game.stack already)
}
}
parent.children.add(newNode);

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,58 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ExileUntilSourceLeavesEffect;
import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.keyword.FlashAbility;
import mage.abilities.keyword.WaterbendAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangsIceberg extends CardImpl {
private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent");
static {
filter.add(AnotherPredicate.instance);
}
public AangsIceberg(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
// Flash
this.addAbility(FlashAbility.getInstance());
// When this enchantment enters, exile up to one other target nonland permanent until this enchantment leaves the battlefield.
Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect());
ability.addTarget(new TargetPermanent(0, 1, filter));
this.addAbility(ability);
// Waterbend {3}: Sacrifice this enchantment. If you do, scry 2.
this.addAbility(new WaterbendAbility(new DoIfCostPaid(
new ScryEffect(2), new SacrificeSourceCost(), null, false
), new GenericManaCost(3)));
}
private AangsIceberg(final AangsIceberg card) {
super(card);
}
@Override
public AangsIceberg copy() {
return new AangsIceberg(this);
}
}

View file

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

View file

@ -12,6 +12,8 @@ import mage.filter.common.FilterCreaturePlayerOrPlaneswalker;
import mage.filter.common.FilterPermanentOrPlayer;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetPermanentOrPlayer;
import java.util.UUID;
@ -68,6 +70,16 @@ class AllWillBeOneTriggeredAbility extends TriggeredAbilityImpl {
if (!isControlledBy(event.getPlayerId())) {
return false;
}
Player player = game.getPlayer(event.getTargetId());
if (player == null) {
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent == null) {
permanent = game.getPermanentEntering(event.getTargetId());
if (permanent == null) {
return false;
}
}
}
getEffects().setValue("damage", event.getAmount());
return true;
}

View file

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

View file

@ -1,23 +1,16 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.Filter;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
@ -39,9 +32,7 @@ public final class AngelOfJubilation extends CardImpl {
this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES_NON_BLACK, true)));
// Players can't pay life or sacrifice creatures to cast spells or activate abilities.
Ability ability = new SimpleStaticAbility(new AngelOfJubilationEffect());
ability.addEffect(new AngelOfJubilationSacrificeFilterEffect());
this.addAbility(ability);
this.addAbility(new CantPayLifeOrSacrificeAbility(StaticFilters.FILTER_PERMANENT_CREATURES));
}
private AngelOfJubilation(final AngelOfJubilation card) {
@ -53,66 +44,3 @@ public final class AngelOfJubilation extends CardImpl {
return new AngelOfJubilation(this);
}
}
class AngelOfJubilationEffect extends ContinuousEffectImpl {
AngelOfJubilationEffect() {
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment);
staticText = "Players can't pay life or sacrifice creatures to cast spells";
}
private AngelOfJubilationEffect(final AngelOfJubilationEffect effect) {
super(effect);
}
@Override
public AngelOfJubilationEffect copy() {
return new AngelOfJubilationEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
player.setPayLifeCostLevel(Player.PayLifeCostLevel.nonSpellnonActivatedAbilities);
player.setCanPaySacrificeCostFilter(new FilterCreaturePermanent());
}
return true;
}
}
class AngelOfJubilationSacrificeFilterEffect extends CostModificationEffectImpl {
AngelOfJubilationSacrificeFilterEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.SET_COST);
staticText = "or activate abilities";
}
protected AngelOfJubilationSacrificeFilterEffect(AngelOfJubilationSacrificeFilterEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
for (Cost cost : abilityToModify.getCosts()) {
if (cost instanceof SacrificeTargetCost) {
SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost;
Filter filter = sacrificeCost.getTargets().get(0).getFilter();
filter.add(Predicates.not(CardType.CREATURE.getPredicate()));
}
}
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return (abilityToModify.isActivatedAbility() || abilityToModify.getAbilityType() == AbilityType.SPELL)
&& game.getState().getPlayersInRange(source.getControllerId(), game).contains(abilityToModify.getControllerId());
}
@Override
public AngelOfJubilationSacrificeFilterEffect copy() {
return new AngelOfJubilationSacrificeFilterEffect(this);
}
}

View file

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

View file

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

View file

@ -0,0 +1,70 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.keyword.AirbendTargetEffect;
import mage.abilities.keyword.FlashAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.permanent.token.AllyToken;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AppaSteadfastGuardian extends CardImpl {
private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanents you control");
static {
filter.add(AnotherPredicate.instance);
filter.add(TargetController.YOU.getControllerPredicate());
}
public AppaSteadfastGuardian(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.BISON);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(3);
this.toughness = new MageInt(4);
// Flash
this.addAbility(FlashAbility.getInstance());
// Flying
this.addAbility(FlyingAbility.getInstance());
// When Appa enters, airbend any number of other target nonland permanents you control.
Ability ability = new EntersBattlefieldTriggeredAbility(new AirbendTargetEffect());
ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, filter));
this.addAbility(ability);
// Whenever you cast a spell from exile, create a 1/1 white Ally creature token.
this.addAbility(new SpellCastControllerTriggeredAbility(
Zone.BATTLEFIELD, new CreateTokenEffect(new AllyToken()), StaticFilters.FILTER_SPELL_A,
false, SetTargetPointer.NONE, Zone.EXILED
));
}
private AppaSteadfastGuardian(final AppaSteadfastGuardian card) {
super(card);
}
@Override
public AppaSteadfastGuardian copy() {
return new AppaSteadfastGuardian(this);
}
}

View file

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

View file

@ -68,10 +68,7 @@ class AstralDriftTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (game.getState().getStack().isEmpty()) {
return false;
}
StackObject item = game.getState().getStack().getFirst();
StackObject item = game.getState().getStack().getFirstOrNull();
if (!(item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility)) {
return false;

View file

@ -0,0 +1,51 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AvatarEnthusiasts extends CardImpl {
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ALLY, "another Ally you control");
static {
filter.add(AnotherPredicate.instance);
}
public AvatarEnthusiasts(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.PEASANT);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Whenever another Ally you control enters, put a +1/+1 counter on this creature.
this.addAbility(new EntersBattlefieldAllTriggeredAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter
));
}
private AvatarEnthusiasts(final AvatarEnthusiasts card) {
super(card);
}
@Override
public AvatarEnthusiasts copy() {
return new AvatarEnthusiasts(this);
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -12,11 +12,9 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.util.CardUtil;
import java.util.UUID;
@ -65,7 +63,7 @@ enum BiteDownOnCrimeAdjuster implements CostAdjuster {
@Override
public void reduceCost(Ability ability, Game game) {
if (CollectedEvidenceCondition.instance.apply(game, ability)
|| (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, null, ability.getControllerId(), game))) {
|| (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, ability, ability.getControllerId(), game))) {
CardUtil.reduceCost(ability, 2);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -78,7 +78,6 @@ class CobraTrapWatcher extends Watcher {
if (event.getType() == GameEvent.EventType.DESTROYED_PERMANENT) {
Permanent perm = game.getPermanentOrLKIBattlefield(event.getTargetId()); // can regenerate or be indestructible
if (perm != null && !perm.isCreature(game)) {
if (!game.getStack().isEmpty()) {
StackObject spell = game.getStack().getStackObject(event.getSourceId());
if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) {
players.add(perm.getControllerId());
@ -86,7 +85,6 @@ class CobraTrapWatcher extends Watcher {
}
}
}
}
@Override
public void reset() {

View file

@ -119,8 +119,7 @@ class ConspiracyTheoristEffect extends OneShotEffect {
if (controller != null) {
CardsImpl cards = new CardsImpl(discardedCards);
TargetCard target = new TargetCard(Zone.GRAVEYARD, new FilterCard("card to exile"));
boolean validTarget = cards.stream()
.anyMatch(card -> target.canTarget(card, game));
boolean validTarget = cards.stream().anyMatch(card -> target.canTarget(card, source, game));
if (validTarget && controller.chooseUse(Outcome.Benefit, "Exile a card?", source, game)) {
if (controller.choose(Outcome.Benefit, cards, target, source, game)) {
Card card = cards.get(target.getFirstTarget(), game);

View file

@ -9,6 +9,7 @@ import mage.abilities.keyword.InspiredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -35,8 +36,12 @@ public final class DaringThief extends CardImpl {
this.toughness = new MageInt(3);
// Inspired - Whenever Daring Thief becomes untapped, you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it.
Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame,
"you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it", false, true), true);
Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(
Duration.EndOfGame,
"you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it",
false,
true
), true);
ability.addTarget(new TargetControlledPermanentSharingOpponentPermanentCardType());
ability.addTarget(new DaringThiefSecondTarget());
this.addAbility(ability);
@ -66,9 +71,10 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
}
@Override
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
if (super.canTarget(controllerId, id, source, game)) {
Set<CardType> cardTypes = getOpponentPermanentCardTypes(controllerId, game);
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
Set<CardType> cardTypes = getOpponentPermanentCardTypes(playerId, game);
if (super.canTarget(playerId, id, source, game)) {
Permanent permanent = game.getPermanent(id);
for (CardType type : permanent.getCardType(game)) {
if (cardTypes.contains(type)) {
@ -81,13 +87,12 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
// get all cardtypes from opponents permanents
Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
Set<UUID> possibleTargets = new HashSet<>();
MageObject targetSource = game.getObject(source);
if (targetSource != null) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
for (CardType type : permanent.getCardType(game)) {
if (cardTypes.contains(type)) {
possibleTargets.add(permanent.getId());
@ -96,8 +101,7 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
}
}
}
}
return possibleTargets;
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override
@ -119,58 +123,54 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
}
}
class DaringThiefSecondTarget extends TargetPermanent {
private Permanent firstTarget = null;
public DaringThiefSecondTarget() {
super();
this.filter = this.filter.copy();
filter.add(TargetController.OPPONENT.getControllerPredicate());
super(StaticFilters.FILTER_OPPONENTS_PERMANENT);
withTargetName("permanent an opponent controls that shares a card type with it");
}
private DaringThiefSecondTarget(final DaringThiefSecondTarget target) {
super(target);
this.firstTarget = target.firstTarget;
}
@Override
public boolean canTarget(UUID id, Ability source, Game game) {
if (super.canTarget(id, source, game)) {
Permanent target1 = game.getPermanent(source.getFirstTarget());
Permanent opponentPermanent = game.getPermanent(id);
if (target1 != null && opponentPermanent != null) {
return target1.shareTypes(opponentPermanent, game);
}
}
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
Permanent possiblePermanent = game.getPermanent(id);
if (ownPermanent == null || possiblePermanent == null) {
return false;
}
return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
MageObject targetSource = game.getObject(source);
if (targetSource != null) {
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
if (permanent.shareTypes(firstTarget, game)) {
if (ownPermanent == null) {
// playable or first target not yet selected
// use all
possibleTargets.add(permanent.getId());
} else {
// real
// filter by shared type
if (permanent.shareTypes(ownPermanent, game)) {
possibleTargets.add(permanent.getId());
}
}
}
}
}
return possibleTargets;
possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id));
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
firstTarget = game.getPermanent(source.getFirstTarget());
return super.chooseTarget(Outcome.Damage, playerId, source, game);
// AI hint with better outcome
return super.chooseTarget(Outcome.GainControl, playerId, source, game);
}
@Override

View file

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

View file

@ -2,35 +2,32 @@ package mage.cards.d;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.effects.common.SacrificeSourceEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterOpponentsCreaturePermanent;
import mage.game.Controllable;
import mage.game.Game;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author Plopman
*/
public final class DefenseOfTheHeart extends CardImpl {
private static final Condition condition = new PermanentsOnTheBattlefieldCondition(
new FilterOpponentsCreaturePermanent("an opponent controls three or more creatures"),
ComparisonType.MORE_THAN, 2
);
public DefenseOfTheHeart(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}");
// At the beginning of your upkeep, if an opponent controls three or more creatures, sacrifice Defense of the Heart, search your library for up to two creature cards, and put those cards onto the battlefield. Then shuffle your library.
Ability ability = new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceEffect()).withInterveningIf(condition);
Ability ability = new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceEffect())
.withInterveningIf(DefenseOfTheHeartCondition.instance);
ability.addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(
0, 2, StaticFilters.FILTER_CARD_CREATURES
)).concatBy(","));
@ -46,3 +43,28 @@ public final class DefenseOfTheHeart extends CardImpl {
return new DefenseOfTheHeart(this);
}
}
enum DefenseOfTheHeartCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game
.getBattlefield()
.getActivePermanents(
StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE,
source.getControllerId(), source, game
)
.stream()
.map(Controllable::getControllerId)
.collect(Collectors.toMap(Function.identity(), x -> 1, Integer::sum))
.values()
.stream()
.anyMatch(x -> x >= 3);
}
@Override
public String toString() {
return "an opponent controls three or more creatures";
}
}

View file

@ -137,49 +137,12 @@ class TargetControlledSource extends TargetSource {
}
@Override
public boolean canChoose(UUID sourceControllerId, Game game) {
int count = 0;
for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& Objects.equals(stackObject.getControllerId(), sourceControllerId)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (Objects.equals(permanent.getControllerId(), sourceControllerId)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
for (Player player : game.getPlayers().values()) {
if (Objects.equals(player, game.getPlayer(sourceControllerId))) {
for (Card card : player.getGraveyard().getCards(game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
// 108.4a If anything asks for the controller of a card that doesn't have one (because it's not a permanent or spell), use its owner instead.
for (Card card : game.getExile().getAllCards(game)) {
if (Objects.equals(card.getOwnerId(), sourceControllerId)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
}
}
return false;
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
return canChooseFromPossibleTargets(sourceControllerId, source, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
@ -205,7 +168,7 @@ class TargetControlledSource extends TargetSource {
}
}
}
return possibleTargets;
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override

View file

@ -90,10 +90,10 @@ class DistendedMindbenderEffect extends OneShotEffect {
TargetCard targetThreeOrLess = new TargetCard(1, Zone.HAND, filterThreeOrLess);
TargetCard targetFourOrGreater = new TargetCard(1, Zone.HAND, filterFourOrGreater);
Cards toDiscard = new CardsImpl();
if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) {
if (controller.choose(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) {
toDiscard.addAll(targetThreeOrLess.getTargets());
}
if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) {
if (controller.choose(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) {
toDiscard.addAll(targetFourOrGreater.getTargets());
}
opponent.discard(toDiscard, false, source, game);

View file

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

View file

@ -72,7 +72,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect {
filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate()));
TargetCard target = new TargetCard(Zone.HAND, filter);
target.withNotTarget(true);
controller.chooseTarget(Outcome.Discard, opponent.getHand(), target, source, game);
controller.choose(Outcome.Discard, opponent.getHand(), target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
toExile.add(card);
@ -81,7 +81,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect {
filter.setMessage("artifact or creature card from " + opponent.getName() + "'s graveyard");
target = new TargetCard(Zone.GRAVEYARD, filter);
target.withNotTarget(true);
controller.chooseTarget(Outcome.Exile, opponent.getGraveyard(), target, source, game);
controller.choose(Outcome.Exile, opponent.getGraveyard(), target, source, game);
card = game.getCard(target.getFirstTarget());
if (card != null) {
toExile.add(card);

View file

@ -105,11 +105,10 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard {
return cardTypeAssigner.getRoleCount(cards, game) >= cards.size();
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game));
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
return possibleTargets;
}
}

View file

@ -0,0 +1,36 @@
package mage.cards.e;
import mage.abilities.effects.keyword.EarthbendTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class EarthbendingLesson extends CardImpl {
public EarthbendingLesson(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}");
this.subtype.add(SubType.LESSON);
// Earthbend 4.
this.getSpellAbility().addEffect(new EarthbendTargetEffect(4));
this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND));
}
private EarthbendingLesson(final EarthbendingLesson card) {
super(card);
}
@Override
public EarthbendingLesson copy() {
return new EarthbendingLesson(this);
}
}

View file

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

View file

@ -129,7 +129,7 @@ class EnchantmentAlterationEffect extends OneShotEffect {
if (oldPermanent != null
&& !oldPermanent.equals(permanentToBeAttachedTo)) {
Target auraTarget = aura.getSpellAbility().getTargets().get(0);
if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), game)) {
if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), source, game)) {
game.informPlayers(aura.getLogName() + " was not attched to " + permanentToBeAttachedTo.getLogName() + " because it's no legal target for the aura");
} else if (oldPermanent.removeAttachment(aura.getId(), source, game)) {
game.informPlayers(aura.getLogName() + " was unattached from " + oldPermanent.getLogName() + " and attached to " + permanentToBeAttachedTo.getLogName());

View file

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

View file

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

View file

@ -0,0 +1,36 @@
package mage.cards.f;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.keyword.FlashbackAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.game.permanent.token.SoldierFirebendingToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class FireNationAttacks extends CardImpl {
public FireNationAttacks(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}");
// Create two 2/2 red Soldier creature tokens with firebending 1.
this.getSpellAbility().addEffect(new CreateTokenEffect(new SoldierFirebendingToken(), 2));
// Flashback {8}{R}
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{R}")));
}
private FireNationAttacks(final FireNationAttacks card) {
super(card);
}
@Override
public FireNationAttacks copy() {
return new FireNationAttacks(this);
}
}

View file

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

View file

@ -85,7 +85,7 @@ class FrogkinKidnapperEffect extends OneShotEffect {
opponent.revealCards(source, opponent.getHand(), game);
TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
Cards toRansom = new CardsImpl();
if (controller.chooseTarget(outcome, opponent.getHand(), target, source, game)) {
if (controller.choose(outcome, opponent.getHand(), target, source, game)) {
toRansom.addAll(target.getTargets());
String exileName = "Ransomed (owned by " + opponent.getName() + ")";

View file

@ -9,13 +9,12 @@ import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.ToughnessPredicate;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import java.util.UUID;

View file

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

View file

@ -1,16 +1,14 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.SupportAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility;
import mage.filter.StaticFilters;
import java.util.UUID;
@ -19,13 +17,6 @@ import java.util.UUID;
*/
public final class GenerousPatron extends CardImpl {
private static final FilterPermanent filter
= new FilterCreaturePermanent("creature you don't control");
static {
filter.add(TargetController.NOT_YOU.getControllerPredicate());
}
public GenerousPatron(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
@ -38,7 +29,8 @@ public final class GenerousPatron extends CardImpl {
this.addAbility(new SupportAbility(this, 2));
// Whenever you put one or more counters on a creature you don't control, draw a card.
this.addAbility(new PutCounterOnCreatureTriggeredAbility(new DrawCardSourceControllerEffect(1), filter));
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new DrawCardSourceControllerEffect(1),
null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL));
}
private GenerousPatron(final GenerousPatron card) {

View file

@ -82,7 +82,7 @@ class GhastlordOfFugueEffect extends OneShotEffect {
TargetCard target = new TargetCard(Zone.HAND, new FilterCard());
target.withNotTarget(true);
Card chosenCard = null;
if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
chosenCard = game.getCard(target.getFirstTarget());
}
if (chosenCard != null) {

View file

@ -2,7 +2,6 @@
package mage.cards.g;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
@ -12,15 +11,11 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.constants.TargetController;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate;
import mage.target.TargetPermanent;
import mage.watchers.common.PlayerDamagedBySourceWatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
@ -28,6 +23,12 @@ import java.util.UUID;
*/
public final class GiltspireAvenger extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn");
static {
filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU));
}
public GiltspireAvenger(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
this.subtype.add(SubType.HUMAN);
@ -41,7 +42,7 @@ public final class GiltspireAvenger extends CardImpl {
// {T}: Destroy target creature that dealt damage to you this turn.
Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new TapSourceCost());
ability.addTarget(new GiltspireAvengerTarget());
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}
@ -55,64 +56,3 @@ public final class GiltspireAvenger extends CardImpl {
return new GiltspireAvenger(this);
}
}
class GiltspireAvengerTarget extends TargetPermanent {
public GiltspireAvengerTarget() {
super(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false);
targetName = "creature that dealt damage to you this turn";
}
private GiltspireAvengerTarget(final GiltspireAvengerTarget target) {
super(target);
}
@Override
public boolean canTarget(UUID id, Ability source, Game game) {
PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, source.getControllerId());
if (watcher != null && watcher.hasSourceDoneDamage(id, game)) {
return super.canTarget(id, source, game);
}
return false;
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId);
for (UUID targetId : availablePossibleTargets) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null && watcher != null && watcher.hasSourceDoneDamage(targetId, game)) {
possibleTargets.add(targetId);
}
}
return possibleTargets;
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int remainingTargets = this.minNumberOfTargets - targets.size();
if (remainingTargets == 0) {
return true;
}
int count = 0;
MageObject targetSource = game.getObject(source);
PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId);
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)
&& watcher != null && watcher.hasSourceDoneDamage(permanent.getId(), game)) {
count++;
if (count >= remainingTargets) {
return true;
}
}
}
return false;
}
@Override
public GiltspireAvengerTarget copy() {
return new GiltspireAvengerTarget(this);
}
}

View file

@ -1,9 +1,7 @@
package mage.cards.g;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.SourceHasCounterCondition;
import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect;
@ -11,11 +9,14 @@ import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DontUntapInControllersUntapStepSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -59,8 +60,6 @@ public final class GlyphOfDelusion extends CardImpl {
class GlyphOfDelusionSecondTarget extends TargetPermanent {
private Permanent firstTarget = null;
public GlyphOfDelusionSecondTarget() {
super();
withTargetName("target creature that target Wall blocked this turn");
@ -68,33 +67,39 @@ class GlyphOfDelusionSecondTarget extends TargetPermanent {
private GlyphOfDelusionSecondTarget(final GlyphOfDelusionSecondTarget target) {
super(target);
this.firstTarget = target.firstTarget;
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
if (firstTarget != null) {
BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class);
if (watcher != null) {
MageObject targetSource = game.getObject(source);
if (targetSource != null) {
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, source, game)) {
if (!targets.containsKey(creature.getId()) && creature.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game))) {
possibleTargets.add(creature.getId());
}
}
}
}
}
}
if (watcher == null) {
return possibleTargets;
}
Permanent targetWall = game.getPermanent(source.getFirstTarget());
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
if (targetWall == null) {
// playable or first target not yet selected
// use all
possibleTargets.add(permanent.getId());
} else {
// real
// filter by blocked
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(permanent, game), new MageObjectReference(targetWall, game))) {
possibleTargets.add(permanent.getId());
}
}
}
possibleTargets.removeIf(id -> targetWall != null && targetWall.getId().equals(id));
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
}
@Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
firstTarget = game.getPermanent(source.getFirstTarget());
// AI hint with better outcome
return super.chooseTarget(Outcome.Tap, playerId, source, game);
}
@ -133,8 +138,7 @@ class GlyphOfDelusionEffect extends OneShotEffect {
effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), game));
game.addEffect(effect, source);
BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance())
);
BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance()));
GainAbilityTargetEffect effect2 = new GainAbilityTargetEffect(ability2, Duration.Custom);
effect2.setTargetPointer(new FixedTarget(targetPermanent.getId(), game));
game.addEffect(effect2, source);

View file

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

View file

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

View file

@ -129,7 +129,7 @@ class GrimeGorgerTarget extends TargetCardInGraveyard {
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game));
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
return possibleTargets;
}

View file

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

View file

@ -63,7 +63,7 @@ enum HamletGluttonAdjuster implements CostAdjuster {
@Override
public void reduceCost(Ability ability, Game game) {
if (BargainedCondition.instance.apply(game, ability)
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
CardUtil.reduceCost(ability, 2);
}
}

View file

@ -3,7 +3,7 @@ package mage.cards.h;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility;
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
@ -12,6 +12,7 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.permanent.token.DeathtouchSnakeToken;
import mage.target.common.TargetCreaturePermanent;
@ -37,7 +38,8 @@ public final class HapatraVizierOfPoisons extends CardImpl {
this.addAbility(ability);
// Whenever you put one or more -1/-1 counters on a creature, create a 1/1 green Snake creature token with deathtouch.
this.addAbility(new PutCounterOnCreatureTriggeredAbility(new CreateTokenEffect(new DeathtouchSnakeToken()), CounterType.M1M1.createInstance()));
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new CreateTokenEffect(new DeathtouchSnakeToken()),
CounterType.M1M1, StaticFilters.FILTER_PERMANENT_CREATURE));
}
private HapatraVizierOfPoisons(final HapatraVizierOfPoisons card) {

View file

@ -54,7 +54,7 @@ enum IceOutAdjuster implements CostAdjuster {
@Override
public void reduceCost(Ability ability, Game game) {
if (BargainedCondition.instance.apply(game, ability)
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
CardUtil.reduceCost(ability, 1);
}
}

View file

@ -1,7 +1,5 @@
package mage.cards.i;
import mage.MageItem;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.RevealTargetFromHandCost;
@ -19,9 +17,9 @@ import mage.game.Game;
import mage.target.common.TargetCardInHand;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author TheElk801
@ -67,51 +65,8 @@ class IlluminatedFolioTarget extends TargetCardInHand {
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
return super.canChoose(sourceControllerId, source, game)
&& !possibleTargets(sourceControllerId, source, game).isEmpty();
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
if (this.getTargets().size() == 1) {
Card card = game.getCard(this.getTargets().get(0));
possibleTargets.removeIf(
uuid -> !game
.getCard(uuid)
.getColor(game)
.shares(card.getColor(game))
);
return possibleTargets;
}
if (possibleTargets.size() < 2) {
possibleTargets.clear();
return possibleTargets;
}
Set<Card> allTargets = possibleTargets
.stream()
.map(game::getCard)
.collect(Collectors.toSet());
possibleTargets.clear();
for (ObjectColor color : ObjectColor.getAllColors()) {
Set<Card> inColor = allTargets
.stream()
.filter(card -> card.getColor(game).shares(color))
.collect(Collectors.toSet());
if (inColor.size() > 1) {
inColor.stream().map(MageItem::getId).forEach(possibleTargets::add);
}
if (possibleTargets.size() == allTargets.size()) {
break;
}
}
return possibleTargets;
}
@Override
public boolean canTarget(UUID id, Game game) {
if (!super.canTarget(id, game)) {
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
if (!super.canTarget(playerId, id, source, game)) {
return false;
}
List<UUID> targetList = this.getTargets();
@ -123,9 +78,17 @@ class IlluminatedFolioTarget extends TargetCardInHand {
&& targetList
.stream()
.map(game::getCard)
.filter(Objects::nonNull)
.anyMatch(c -> c.getColor(game).shares(card.getColor(game)));
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
return possibleTargets;
}
@Override
public IlluminatedFolioTarget copy() {
return new IlluminatedFolioTarget(this);

View file

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

View file

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

View file

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

View file

@ -56,7 +56,7 @@ enum JohannsStopgapAdjuster implements CostAdjuster {
@Override
public void reduceCost(Ability ability, Game game) {
if (BargainedCondition.instance.apply(game, ability)
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
CardUtil.reduceCost(ability, 2);
}
}

View file

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

View file

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

View file

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

View file

@ -2,21 +2,22 @@ package mage.cards.k;
import mage.abilities.Ability;
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
import mage.abilities.common.EntersBattlefieldTappedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.ExileSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
@ -33,7 +34,7 @@ public class KarnsSylex extends CardImpl {
this.addAbility(new EntersBattlefieldTappedAbility());
// Players cant pay life to cast spells or to activate abilities that arent mana abilities.
this.addAbility(new SimpleStaticAbility(new KarnsSylexEffect()));
this.addAbility(new CantPayLifeOrSacrificeAbility(true, null));
// {X}, {T}, Exile Karns Sylex: Destroy each nonland permanent with mana value X or less. Activate only as a sorcery.
Ability ability = new ActivateAsSorceryActivatedAbility(new KarnsSylexDestroyEffect(), new ManaCostsImpl<>("{X}"));
@ -52,32 +53,6 @@ public class KarnsSylex extends CardImpl {
}
}
class KarnsSylexEffect extends ContinuousEffectImpl {
KarnsSylexEffect() {
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment);
staticText = "Players can't pay life to cast spells or to activate abilities that aren't mana abilities";
}
private KarnsSylexEffect(final KarnsSylexEffect effect) {
super(effect);
}
@Override
public KarnsSylexEffect copy() {
return new KarnsSylexEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
player.setPayLifeCostLevel(Player.PayLifeCostLevel.onlyManaAbilities);
}
return true;
}
}
class KarnsSylexDestroyEffect extends OneShotEffect {
KarnsSylexDestroyEffect() {

View file

@ -0,0 +1,79 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class KataraTheFearless extends CardImpl {
public KataraTheFearless(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WARRIOR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// If a triggered ability of an Ally you control triggers, that ability triggers an additional time.
this.addAbility(new SimpleStaticAbility(new KataraTheFearlessEffect()));
}
private KataraTheFearless(final KataraTheFearless card) {
super(card);
}
@Override
public KataraTheFearless copy() {
return new KataraTheFearless(this);
}
}
class KataraTheFearlessEffect extends ReplacementEffectImpl {
KataraTheFearlessEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "if a triggered ability of an Ally you control triggers, that ability triggers an additional time";
}
private KataraTheFearlessEffect(final KataraTheFearlessEffect effect) {
super(effect);
}
@Override
public KataraTheFearlessEffect copy() {
return new KataraTheFearlessEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
return permanent != null
&& permanent.isControlledBy(source.getControllerId())
&& permanent.hasSubtype(SubType.ALLY, game);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(event.getAmount() + 1);
return false;
}
}

View file

@ -0,0 +1,60 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.condition.common.OpponentsTurnCondition;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CountersControllerCount;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.counter.AddCountersPlayersEffect;
import mage.abilities.effects.common.discard.DiscardControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.counters.CounterType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class KataraWaterbendingMaster extends CardImpl {
private static final DynamicValue xValue = new CountersControllerCount(CounterType.EXPERIENCE);
public KataraWaterbendingMaster(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WARRIOR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(1);
this.toughness = new MageInt(3);
// Whenever you cast a spell during an opponent's turn, you get an experience counter.
this.addAbility(new SpellCastControllerTriggeredAbility(new AddCountersPlayersEffect(
CounterType.EXPERIENCE.createInstance(), TargetController.YOU), false
).withTriggerCondition(OpponentsTurnCondition.instance));
// Whenever Katara attacks, you may draw a card for each experience counter you have. If you do, discard a card.
Ability ability = new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(xValue)
.setText("draw a card for each experience counter you have"));
ability.addEffect(new DiscardControllerEffect(1).concatBy("If you do,"));
this.addAbility(ability);
}
private KataraWaterbendingMaster(final KataraWaterbendingMaster card) {
super(card);
}
@Override
public KataraWaterbendingMaster copy() {
return new KataraWaterbendingMaster(this);
}
}

View file

@ -2,8 +2,8 @@ package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
@ -18,7 +18,6 @@ import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.SoldierToken;
import java.util.UUID;
@ -38,7 +37,8 @@ public final class KateStewart extends CardImpl {
this.toughness = new MageInt(3);
// Whenever you put one or more time counters on a permanent you control, create a 1/1 white Soldier creature token.
this.addAbility(new KateStewartTriggeredAbility());
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new CreateTokenEffect(new SoldierToken()),
CounterType.TIME, StaticFilters.FILTER_CONTROLLED_PERMANENT));
// Whenever Kate Stewart attacks, you may pay {8}. If you do, attacking creatures get +X/+X until end of turn, where X is the number of time counters among permanents you control.
this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid(new BoostAllEffect(
@ -57,35 +57,6 @@ public final class KateStewart extends CardImpl {
}
}
class KateStewartTriggeredAbility extends TriggeredAbilityImpl {
KateStewartTriggeredAbility() {
super(Zone.BATTLEFIELD, new CreateTokenEffect(new SoldierToken()));
setTriggerPhrase("Whenever you put one or more time counters on a permanent you control, ");
}
private KateStewartTriggeredAbility(final KateStewartTriggeredAbility ability) {
super(ability);
}
@Override
public KateStewartTriggeredAbility copy() {
return new KateStewartTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.COUNTERS_ADDED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return CounterType.TIME.getName().equals(event.getData())
&& this.isControlledBy(event.getPlayerId())
&& this.isControlledBy(game.getControllerId(event.getTargetId()));
}
}
enum KateStewartValue implements DynamicValue {
instance;
private static final Hint hint = new ValueHint("Time counters among permanents you control", instance);

View file

@ -1,7 +1,6 @@
package mage.cards.k;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
@ -15,10 +14,8 @@ import mage.filter.FilterOpponent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.token.BeastToken4;
import mage.players.Player;
import mage.target.TargetPlayer;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@ -35,7 +32,7 @@ public final class KeeperOfTheBeasts extends CardImpl {
this.power = new MageInt(1);
this.toughness = new MageInt(2);
// {G}, {tap}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield.
// {G}, {T}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield.
Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new BeastToken4()).setText("Choose target opponent who controlled more creatures than you did as you activated this ability. Create a 2/2 green Beast creature token."),
new ManaCostsImpl<>("{G}"));
ability.addCost(new TapSourceCost());
@ -65,42 +62,12 @@ class KeeperOfTheBeastsTarget extends TargetPlayer {
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>();
int creaturesController = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game);
for (UUID targetId : availablePossibleTargets) {
if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, targetId, game) > creaturesController) {
possibleTargets.add(targetId);
}
}
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
int myCount = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game);
possibleTargets.removeIf(playerId -> game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) < myCount);
return possibleTargets;
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0;
MageObject targetSource = game.getObject(source);
Player controller = game.getPlayer(sourceControllerId);
if (controller != null && targetSource != null) {
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
if (player != null
&& game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game)
< game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game)
&& !player.hasLeft()
&& filter.match(player, sourceControllerId, source, game)
&& player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
}
return false;
}
@Override
public KeeperOfTheBeastsTarget copy() {
return new KeeperOfTheBeastsTarget(this);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,144 @@
package mage.cards.k;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SagaAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.MillCardsControllerEffect;
import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SagaChapter;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class KravensLastHunt extends CardImpl {
public KravensLastHunt(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}");
this.subtype.add(SubType.SAGA);
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)
SagaAbility sagaAbility = new SagaAbility(this);
// I -- Mill five cards. When you do, this Saga deals damage equal to the greatest power among creature cards in your graveyard to target creature.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I,
new MillCardsControllerEffect(4), new KravensLastHuntEffect()
);
// II -- Target creature you control gets +2/+2 until end of turn.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_II,
new BoostTargetEffect(2, 2),
new TargetControlledCreaturePermanent()
);
// III -- Return target creature card from your graveyard to your hand.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_III, new ReturnFromGraveyardToHandTargetEffect(),
new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)
);
this.addAbility(sagaAbility.addHint(KravensLastHuntValue.getHint()));
}
private KravensLastHunt(final KravensLastHunt card) {
super(card);
}
@Override
public KravensLastHunt copy() {
return new KravensLastHunt(this);
}
}
enum KravensLastHuntValue implements DynamicValue {
instance;
private static final Hint hint = new ValueHint("Greatest power among creature cards in your graveyard", instance);
public static Hint getHint() {
return hint;
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player player = game.getPlayer(sourceAbility.getControllerId());
if (player == null) {
return 0;
}
return player
.getGraveyard()
.getCards(StaticFilters.FILTER_CARD_CREATURE, game)
.stream()
.map(MageObject::getPower)
.mapToInt(MageInt::getValue)
.max()
.orElse(0);
}
@Override
public KravensLastHuntValue copy() {
return this;
}
@Override
public String getMessage() {
return "";
}
@Override
public String toString() {
return "1";
}
}
class KravensLastHuntEffect extends OneShotEffect {
KravensLastHuntEffect() {
super(Outcome.Benefit);
staticText = "When you do, {this} deals damage equal to the greatest power among creature cards in your graveyard to target creature";
}
private KravensLastHuntEffect(final KravensLastHuntEffect effect) {
super(effect);
}
@Override
public KravensLastHuntEffect copy() {
return new KravensLastHuntEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
new DamageTargetEffect(KravensLastHuntValue.instance)
.setText("{this} deals damage equal to the greatest power " +
"among creature cards in your graveyard to target creature"), false
);
ability.addTarget(new TargetCreaturePermanent());
game.fireReflexiveTriggeredAbility(ability, source);
return true;
}
}

View file

@ -3,7 +3,7 @@ package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility;
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
@ -42,7 +42,8 @@ public final class KrosDefenseContractor extends CardImpl {
this.addAbility(ability);
// Whenever you put one or more counters on a creature you don't control, tap that creature and goad it. It gains trample until your next turn.
this.addAbility(new PutCounterOnCreatureTriggeredAbility(new KrosDefenseContractorEffect(), null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, true));
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new KrosDefenseContractorEffect(),
null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, true, false));
}
private KrosDefenseContractor(final KrosDefenseContractor card) {

View file

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

View file

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

View file

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

View file

@ -74,7 +74,7 @@ class LobotomyEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExile
TargetCard target = new TargetCard(Zone.HAND, filter);
target.withNotTarget(true);
Card chosenCard = null;
if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
chosenCard = game.getCard(target.getFirstTarget());
}

View file

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

View file

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

View file

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

View file

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

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