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

View file

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

View file

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

View file

@ -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() { public Set<UUID> getChosenTargets() {
if (options != null && options.containsKey("chosenTargets")) { if (options != null && options.containsKey("chosenTargets")) {
return new HashSet<>((List<UUID>) options.get("chosenTargets")); return (Set<UUID>) options.get("chosenTargets");
} else { } else {
return Collections.emptySet(); return Collections.emptySet();
} }

View file

@ -10,10 +10,7 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
@ -93,7 +90,9 @@ public class XmageURLConnection {
initDefaultProxy(); initDefaultProxy();
try { 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 // proxy settings
if (this.proxy != null) { if (this.proxy != null) {
@ -107,7 +106,7 @@ public class XmageURLConnection {
this.connection.setReadTimeout(CONNECTION_READING_TIMEOUT_MS); this.connection.setReadTimeout(CONNECTION_READING_TIMEOUT_MS);
initDefaultHeaders(); initDefaultHeaders();
} catch (IOException e) { } catch (IOException | URISyntaxException e) {
this.connection = null; 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", "JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA",
"C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID", "C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID",
"NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3", "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", "MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT",
"WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ", "WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ",
"OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB", "BLC", "DSK", "DSC", "OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB", "BLC", "DSK", "DSC",

View file

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

View file

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

View file

@ -7,6 +7,7 @@ import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.TargetPlayer;
import mage.target.common.TargetPermanentOrPlayer; import mage.target.common.TargetPermanentOrPlayer;
/** /**
@ -76,6 +77,10 @@ abstract class BaseTestableDialog implements TestableDialog {
return createAnyTarget(min, max, false); 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) { private static Target createAnyTarget(int min, int max, boolean notTarget) {
return new TargetPermanentOrPlayer(min, max).withNotTarget(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 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 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)); 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.game.Game;
import mage.players.Player; import mage.players.Player;
import java.util.Collection; import java.util.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -120,10 +117,8 @@ public class TestableDialogsRunner {
choice = prepareSelectDialogChoice(needGroup); choice = prepareSelectDialogChoice(needGroup);
player.choose(Outcome.Benefit, choice, game); player.choose(Outcome.Benefit, choice, game);
if (choice.getChoiceKey() != null) { if (choice.getChoiceKey() != null) {
int needIndex = Integer.parseInt(choice.getChoiceKey()); int needRegNumber = Integer.parseInt(choice.getChoiceKey());
if (needIndex < this.dialogs.size()) { needDialog = this.dialogs.getOrDefault(needRegNumber, null);
needDialog = this.dialogs.get(needIndex);
}
} }
} }
if (needDialog == null) { if (needDialog == null) {
@ -144,15 +139,20 @@ public class TestableDialogsRunner {
Choice choice = new ChoiceImpl(false); Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose dialogs group to run"); 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 // main groups
int recNumber = 0;
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
recNumber++;
String group = groups.get(i); String group = groups.get(i);
Integer groupMinNumber = groupNumber.getOrDefault(group, 0);
choice.withItem( choice.withItem(
String.valueOf(i), String.valueOf(i),
String.format("%02d. %s", recNumber, group), String.format("%02d. %s", groupMinNumber, group),
recNumber, groupMinNumber,
ChoiceHintType.TEXT, ChoiceHintType.TEXT,
String.join("<br>", group) String.join("<br>", group)
); );
@ -186,18 +186,15 @@ public class TestableDialogsRunner {
private Choice prepareSelectDialogChoice(String needGroup) { private Choice prepareSelectDialogChoice(String needGroup) {
Choice choice = new ChoiceImpl(false); Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose game dialog to run from " + needGroup); choice.setMessage("Choose game dialog to run from " + needGroup);
int recNumber = 0; for (TestableDialog dialog : this.dialogs.values()) {
for (int i = 0; i < this.dialogs.size(); i++) {
TestableDialog dialog = this.dialogs.get(i);
if (!dialog.getGroup().equals(needGroup)) { if (!dialog.getGroup().equals(needGroup)) {
continue; continue;
} }
recNumber++;
String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription()); String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription());
choice.withItem( choice.withItem(
String.valueOf(i), String.valueOf(dialog.getRegNumber()),
String.format("%02d. %s", recNumber, info), String.format("%02d. %s", dialog.getRegNumber(), info),
recNumber, dialog.getRegNumber(),
ChoiceHintType.TEXT, ChoiceHintType.TEXT,
String.join("<br>", info) String.join("<br>", info)
); );

View file

@ -219,6 +219,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
} }
// Condition to stop deeper simulation // Condition to stop deeper simulation
if (SimulationNode2.nodeCount > MAX_SIMULATED_NODES_PER_ERROR) { 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)"); throw new IllegalStateException("AI ERROR: too much nodes (possible actions)");
} }
if (depth <= 0 if (depth <= 0
@ -398,20 +399,23 @@ public class ComputerPlayer6 extends ComputerPlayer {
} }
protected void resolve(SimulationNode2 node, int depth, Game game) { 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) { if (stackObject instanceof StackAbility) {
// AI hint for search effects (calc all possible cards for best score) // AI hint for search effects (calc all possible cards for best score)
SearchEffect effect = getSearchEffect((StackAbility) stackObject); SearchEffect effect = getSearchEffect((StackAbility) stackObject);
if (effect != null if (effect != null
&& stackObject.getControllerId().equals(playerId)) { && stackObject.getControllerId().equals(playerId)) {
Target target = effect.getTarget(); 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)) { for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
Game sim = game.createSimulationForAI(); Game sim = game.createSimulationForAI();
StackAbility newAbility = (StackAbility) stackObject.copy(); StackAbility newAbility = (StackAbility) stackObject.copy();
SearchEffect newEffect = getSearchEffect(newAbility); SearchEffect newEffect = getSearchEffect(newAbility);
newEffect.getTarget().addTarget(targetId, newAbility, sim); newEffect.getTarget().addTarget(targetId, newAbility, sim);
sim.getStack().push(newAbility); sim.getStack().push(sim, newAbility);
SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId()); SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId());
node.children.add(newNode); node.children.add(newNode);
newNode.getTargets().add(targetId); newNode.getTargets().add(targetId);
@ -498,7 +502,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
} }
logger.warn("Possible freeze chain:"); logger.warn("Possible freeze chain:");
if (root != null && chain.isEmpty()) { 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 -> { chain.forEach(s -> {
logger.warn(" - " + s); logger.warn(" - " + s);
@ -639,7 +643,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
return "unknown"; return "unknown";
}) })
.collect(Collectors.joining(", ")); .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, depth,
currentNode.getDepth(), currentNode.getDepth(),
printDiffScore(currentScore - prevScore), printDiffScore(currentScore - prevScore),
@ -648,14 +652,18 @@ public class ComputerPlayer6 extends ComputerPlayer {
} else if (!currentNode.getChoices().isEmpty()) { } else if (!currentNode.getChoices().isEmpty()) {
// ON CHOICES // ON CHOICES
String choicesInfo = String.join(", ", currentNode.getChoices()); 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, depth,
currentNode.getDepth(), currentNode.getDepth(),
printDiffScore(currentScore - prevScore), printDiffScore(currentScore - prevScore),
choicesInfo) choicesInfo)
); );
} else { } 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()); UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) { if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
for (UUID targetId : targets) { for (UUID targetId : targets) {
target.addTarget(targetId, source, game); target.addTarget(targetId, source, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) { if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
targets.clear(); targets.clear();
return true; return true;
} }
@ -906,10 +914,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
} }
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId()); UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) { if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
for (UUID targetId : targets) { for (UUID targetId : targets) {
target.add(targetId, game); target.add(targetId, game);
if (target.isChoiceCompleted(abilityControllerId, source, game)) { if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
targets.clear(); targets.clear();
return true; return true;
} }

View file

@ -105,7 +105,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
return list; return list;
} }
protected void simulateOptions(Game game) { private void simulateOptions(Game game) {
List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
for (ActivatedAbility ability : playables) { for (ActivatedAbility ability : playables) {
if (ability.isManaAbility()) { if (ability.isManaAbility()) {
@ -176,6 +176,10 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
return options; 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) { if (AI_SIMULATE_ALL_BAD_AND_GOOD_TARGETS) {
return options; return options;
} }
@ -314,14 +318,17 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
Ability ability = source.copy(); Ability ability = source.copy();
List<Ability> options = getPlayableOptions(ability, game); List<Ability> options = getPlayableOptions(ability, game);
if (options.isEmpty()) { if (options.isEmpty()) {
// no options - activate as is
logger.debug("simulating -- triggered ability:" + ability); 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()) { if (ability.activate(game, false) && ability.isUsesStack()) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId())); game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
} }
game.applyEffects(); game.applyEffects();
game.getPlayers().resetPassed(); game.getPlayers().resetPassed();
} else { } 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(); SimulationNode2 parent = (SimulationNode2) game.getCustomData();
int depth = parent.getDepth() - 1; int depth = parent.getDepth() - 1;
if (depth == 0) { if (depth == 0) {
@ -337,16 +344,16 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
protected void addAbilityNode(SimulationNode2 parent, Ability ability, int depth, Game game) { protected void addAbilityNode(SimulationNode2 parent, Ability ability, int depth, Game game) {
Game sim = game.createSimulationForAI(); 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()) { 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(); sim.applyEffects();
SimulationNode2 newNode = new SimulationNode2(parent, sim, depth, playerId); SimulationNode2 newNode = new SimulationNode2(parent, sim, depth, playerId);
logger.debug("simulating -- node #:" + SimulationNode2.getCount() + " triggered ability option"); logger.debug("simulating -- node #:" + SimulationNode2.getCount() + " triggered ability option");
for (Target target : ability.getTargets()) { for (Target target : ability.getTargets()) {
for (UUID targetId : target.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); parent.children.add(newNode);

View file

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

View file

@ -47,7 +47,6 @@ public class PossibleTargetsSelector {
// collect new valid targets // collect new valid targets
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream() List<MageItem> found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream()
.filter(id -> !target.contains(id)) .filter(id -> !target.contains(id))
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
.map(id -> { .map(id -> {
Player player = game.getPlayer(id); Player player = game.getPlayer(id);
if (player != null) { 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) { public static boolean isMyItem(UUID abilityControllerId, MageItem item) {
if (item instanceof Player) { if (item instanceof Player) {
return item.getId().equals(abilityControllerId); return item.getId().equals(abilityControllerId);
@ -181,7 +184,12 @@ public class PossibleTargetsSelector {
return false; return false;
} }
boolean hasAnyTargets() { public boolean hasAnyTargets() {
return !this.any.isEmpty(); 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()) { if (ability.isUsesStack()) {
game.getStack().push(new StackAbility(ability, playerId)); game.getStack().push(game, new StackAbility(ability, playerId));
if (ability.activate(game, false)) { if (ability.activate(game, false)) {
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId())); game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
actionCount++; actionCount++;
@ -187,8 +187,8 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
abort = true; abort = true;
} }
protected boolean chooseRandom(Target target, Game game) { private boolean chooseRandom(Target target, Ability source, Game game) {
Set<UUID> possibleTargets = target.possibleTargets(playerId, game); Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
if (possibleTargets.isEmpty()) { if (possibleTargets.isEmpty()) {
return false; return false;
} }
@ -233,7 +233,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
@Override @Override
public boolean choose(Outcome outcome, Target target, Ability source, Game game) { public boolean choose(Outcome outcome, Target target, Ability source, Game game) {
if (this.isHuman()) { if (this.isHuman()) {
return chooseRandom(target, game); return chooseRandom(target, source, game);
} }
return super.choose(outcome, target, source, game); return super.choose(outcome, target, source, game);
} }
@ -241,7 +241,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
@Override @Override
public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) { public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map<String, Serializable> options) {
if (this.isHuman()) { if (this.isHuman()) {
return chooseRandom(target, game); return chooseRandom(target, source, game);
} }
return super.choose(outcome, target, source, game, options); return super.choose(outcome, target, source, game, options);
} }
@ -252,7 +252,7 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
if (cards.isEmpty()) { if (cards.isEmpty()) {
return false; return false;
} }
Set<UUID> possibleTargets = target.possibleTargets(playerId, cards, source, game); Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game, cards);
if (possibleTargets.isEmpty()) { if (possibleTargets.isEmpty()) {
return false; return false;
} }

View file

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

View file

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

View file

@ -12,6 +12,8 @@ import mage.filter.common.FilterCreaturePlayerOrPlaneswalker;
import mage.filter.common.FilterPermanentOrPlayer; import mage.filter.common.FilterPermanentOrPlayer;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetPermanentOrPlayer; import mage.target.common.TargetPermanentOrPlayer;
import java.util.UUID; import java.util.UUID;
@ -68,6 +70,16 @@ class AllWillBeOneTriggeredAbility extends TriggeredAbilityImpl {
if (!isControlledBy(event.getPlayerId())) { if (!isControlledBy(event.getPlayerId())) {
return false; 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()); getEffects().setValue("damage", event.getAmount());
return true; return true;
} }

View file

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

View file

@ -1,23 +1,16 @@
package mage.cards.a; package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.common.CantPayLifeOrSacrificeAbility;
import mage.abilities.common.SimpleStaticAbility; 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.continuous.BoostControlledEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.filter.Filter; import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.StaticFilters; 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; 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))); 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. // Players can't pay life or sacrifice creatures to cast spells or activate abilities.
Ability ability = new SimpleStaticAbility(new AngelOfJubilationEffect()); this.addAbility(new CantPayLifeOrSacrificeAbility(StaticFilters.FILTER_PERMANENT_CREATURES));
ability.addEffect(new AngelOfJubilationSacrificeFilterEffect());
this.addAbility(ability);
} }
private AngelOfJubilation(final AngelOfJubilation card) { private AngelOfJubilation(final AngelOfJubilation card) {
@ -53,66 +44,3 @@ public final class AngelOfJubilation extends CardImpl {
return new AngelOfJubilation(this); 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 @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return this.getTargets().canChoose(controllerId, source, game); return canChooseOrAlreadyChosen(ability, source, controllerId, game);
} }
@Override @Override

View file

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

View file

@ -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 @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return this.getTargets().canChoose(controllerId, source, game); return canChooseOrAlreadyChosen(ability, source, controllerId, game);
} }
@Override @Override

View file

@ -68,10 +68,7 @@ class AstralDriftTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
if (game.getState().getStack().isEmpty()) { StackObject item = game.getState().getStack().getFirstOrNull();
return false;
}
StackObject item = game.getState().getStack().getFirst();
if (!(item instanceof StackAbility if (!(item instanceof StackAbility
&& item.getStackAbility() instanceof CyclingAbility)) { && item.getStackAbility() instanceof CyclingAbility)) {
return false; 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 @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return this.getTargets().canChoose(controllerId, source, game); return canChooseOrAlreadyChosen(ability, source, controllerId, game);
} }
@Override @Override

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,35 +2,32 @@ package mage.cards.d;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.condition.Condition; import mage.abilities.condition.Condition;
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.effects.common.SacrificeSourceEffect; import mage.abilities.effects.common.SacrificeSourceEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterOpponentsCreaturePermanent; import mage.game.Controllable;
import mage.game.Game;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* @author Plopman * @author Plopman
*/ */
public final class DefenseOfTheHeart extends CardImpl { 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) { public DefenseOfTheHeart(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); 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. // 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( ability.addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(
0, 2, StaticFilters.FILTER_CARD_CREATURES 0, 2, StaticFilters.FILTER_CARD_CREATURES
)).concatBy(",")); )).concatBy(","));
@ -46,3 +43,28 @@ public final class DefenseOfTheHeart extends CardImpl {
return new DefenseOfTheHeart(this); 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 @Override
public boolean canChoose(UUID sourceControllerId, Game game) { public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0; return canChooseFromPossibleTargets(sourceControllerId, source, game);
for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& Objects.equals(stackObject.getControllerId(), sourceControllerId)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
if (Objects.equals(permanent.getControllerId(), sourceControllerId)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
for (Player player : game.getPlayers().values()) {
if (Objects.equals(player, game.getPlayer(sourceControllerId))) {
for (Card card : player.getGraveyard().getCards(game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
// 108.4a If anything asks for the controller of a card that doesn't have one (because it's not a permanent or spell), use its owner instead.
for (Card card : game.getExile().getAllCards(game)) {
if (Objects.equals(card.getOwnerId(), sourceControllerId)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
}
}
return false;
} }
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>(); Set<UUID> possibleTargets = new HashSet<>();
for (StackObject stackObject : game.getStack()) { for (StackObject stackObject : game.getStack()) {
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
@ -205,7 +168,7 @@ class TargetControlledSource extends TargetSource {
} }
} }
} }
return possibleTargets; return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
} }
@Override @Override

View file

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

View file

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

View file

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

View file

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

View file

@ -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()) { if (controller == null || opponent == null || opponent.getHand().isEmpty()) {
return false; return false;
} }
TargetCard target = new TargetCardInHand( TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
);
controller.choose(outcome, opponent.getHand(), target, source, game); controller.choose(outcome, opponent.getHand(), target, source, game);
Card card = opponent.getHand().get(target.getFirstTarget(), game); Card card = opponent.getHand().get(target.getFirstTarget(), game);
if (card == null) { if (card == null) {

View file

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

View file

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

View file

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

View file

@ -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; continue;
} }
possibleTargets.removeAll(getTargets());
for (UUID targetId : possibleTargets) { for (UUID targetId : possibleTargets) {
TargetAnyTarget target = this.copy(); TargetAnyTarget target = this.copy();
target.clearChosen(); target.clearChosen();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -63,7 +63,7 @@ enum HamletGluttonAdjuster implements CostAdjuster {
@Override @Override
public void reduceCost(Ability ability, Game game) { public void reduceCost(Ability ability, Game game) {
if (BargainedCondition.instance.apply(game, ability) 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); CardUtil.reduceCost(ability, 2);
} }
} }

View file

@ -3,7 +3,7 @@ package mage.cards.h;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; 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.CreateTokenEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -12,6 +12,7 @@ import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.permanent.token.DeathtouchSnakeToken; import mage.game.permanent.token.DeathtouchSnakeToken;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
@ -37,7 +38,8 @@ public final class HapatraVizierOfPoisons extends CardImpl {
this.addAbility(ability); 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. // 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) { private HapatraVizierOfPoisons(final HapatraVizierOfPoisons card) {

View file

@ -54,7 +54,7 @@ enum IceOutAdjuster implements CostAdjuster {
@Override @Override
public void reduceCost(Ability ability, Game game) { public void reduceCost(Ability ability, Game game) {
if (BargainedCondition.instance.apply(game, ability) 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); CardUtil.reduceCost(ability, 1);
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -56,7 +56,7 @@ enum JohannsStopgapAdjuster implements CostAdjuster {
@Override @Override
public void reduceCost(Ability ability, Game game) { public void reduceCost(Ability ability, Game game) {
if (BargainedCondition.instance.apply(game, ability) 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); CardUtil.reduceCost(ability, 2);
} }
} }

View file

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

View file

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

View file

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

View file

@ -2,21 +2,22 @@ package mage.cards.k;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.common.ActivateAsSorceryActivatedAbility;
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
import mage.abilities.common.EntersBattlefieldTappedAbility; import mage.abilities.common.EntersBattlefieldTappedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.ExileSourceCost; import mage.abilities.costs.common.ExileSourceCost;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.filter.common.FilterNonlandPermanent; import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil; import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -33,7 +34,7 @@ public class KarnsSylex extends CardImpl {
this.addAbility(new EntersBattlefieldTappedAbility()); this.addAbility(new EntersBattlefieldTappedAbility());
// Players cant pay life to cast spells or to activate abilities that arent mana abilities. // 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. // {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}")); 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 { class KarnsSylexDestroyEffect extends OneShotEffect {
KarnsSylexDestroyEffect() { 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.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
@ -18,7 +18,6 @@ import mage.constants.*;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.SoldierToken; import mage.game.permanent.token.SoldierToken;
import java.util.UUID; import java.util.UUID;
@ -38,7 +37,8 @@ public final class KateStewart extends CardImpl {
this.toughness = new MageInt(3); 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. // 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. // 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( 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 { enum KateStewartValue implements DynamicValue {
instance; instance;
private static final Hint hint = new ValueHint("Time counters among permanents you control", 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; package mage.cards.k;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
@ -15,10 +14,8 @@ import mage.filter.FilterOpponent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.BeastToken4; import mage.game.permanent.token.BeastToken4;
import mage.players.Player;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -35,7 +32,7 @@ public final class KeeperOfTheBeasts extends CardImpl {
this.power = new MageInt(1); this.power = new MageInt(1);
this.toughness = new MageInt(2); this.toughness = new MageInt(2);
// {G}, {tap}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield. // {G}, {T}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield.
Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new BeastToken4()).setText("Choose target opponent who controlled more creatures than you did as you activated this ability. Create a 2/2 green Beast creature token."), Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new BeastToken4()).setText("Choose target opponent who controlled more creatures than you did as you activated this ability. Create a 2/2 green Beast creature token."),
new ManaCostsImpl<>("{G}")); new ManaCostsImpl<>("{G}"));
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
@ -65,42 +62,12 @@ class KeeperOfTheBeastsTarget extends TargetPlayer {
@Override @Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) { public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game); Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<UUID> possibleTargets = new HashSet<>(); int myCount = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game);
int creaturesController = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game); possibleTargets.removeIf(playerId -> game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) < myCount);
for (UUID targetId : availablePossibleTargets) {
if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, targetId, game) > creaturesController) {
possibleTargets.add(targetId);
}
}
return possibleTargets; return possibleTargets;
} }
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
int count = 0;
MageObject targetSource = game.getObject(source);
Player controller = game.getPlayer(sourceControllerId);
if (controller != null && targetSource != null) {
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
Player player = game.getPlayer(playerId);
if (player != null
&& game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game)
< game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game)
&& !player.hasLeft()
&& filter.match(player, sourceControllerId, source, game)
&& player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
count++;
if (count >= this.minNumberOfTargets) {
return true;
}
}
}
}
return false;
}
@Override @Override
public KeeperOfTheBeastsTarget copy() { public KeeperOfTheBeastsTarget copy() {
return new KeeperOfTheBeastsTarget(this); return new KeeperOfTheBeastsTarget(this);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility; import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect; import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
@ -42,7 +42,8 @@ public final class KrosDefenseContractor extends CardImpl {
this.addAbility(ability); 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. // 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) { private KrosDefenseContractor(final KrosDefenseContractor card) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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