Merge branch 'master' into feature/implement-spdr-piloted-by-peni

This commit is contained in:
theelk801 2025-09-05 18:15:31 -04:00
commit 04bc7b8b2a
1185 changed files with 32207 additions and 8890 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

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

View file

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

@ -28,6 +28,8 @@ public class ScryfallImageSupportCards {
add("2ED"); // Unlimited Edition add("2ED"); // Unlimited Edition
//add("CEI"); // Intl. Collectors Edition //add("CEI"); // Intl. Collectors Edition
//add("CED"); // Collectors Edition //add("CED"); // Collectors Edition
add("RIN"); // Rinascimento
add("REN"); // Renaissance
add("ARN"); // Arabian Nights add("ARN"); // Arabian Nights
add("ATQ"); // Antiquities add("ATQ"); // Antiquities
//add("FBB"); // Foreign Black Border //add("FBB"); // Foreign Black Border
@ -465,6 +467,7 @@ public class ScryfallImageSupportCards {
// add("MD1"); // Modern Event Deck // add("MD1"); // Modern Event Deck
// add("DD3"); // Duel Decks Anthology // add("DD3"); // Duel Decks Anthology
// add("PZ1"); // Legendary Cube // add("PZ1"); // Legendary Cube
add("PLG20"); // Love Your LGS 2020
add("IKO"); // Ikoria: Lair of Behemoths add("IKO"); // Ikoria: Lair of Behemoths
add("C20"); // Commander 2020 add("C20"); // Commander 2020
add("M21"); // Core Set 2021 add("M21"); // Core Set 2021
@ -514,10 +517,13 @@ public class ScryfallImageSupportCards {
add("NCC"); // New Capenna Commander add("NCC"); // New Capenna Commander
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("PLG22"); // Love Your LGS 2022
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
add("PRCQ"); // Regional Championship Qualifiers 2022
add("40K"); // Warhammer 40,000 Commander add("40K"); // Warhammer 40,000 Commander
add("UNF"); // Unfinity add("UNF"); // Unfinity
add("GN3"); // Game Night: Free-for-All add("GN3"); // Game Night: Free-for-All
@ -545,9 +551,11 @@ public class ScryfallImageSupportCards {
add("30A"); // 30th Anniversary Edition add("30A"); // 30th Anniversary Edition
add("P30A"); // 30th Anniversary Play Promos add("P30A"); // 30th Anniversary Play Promos
add("P30M"); // 30th Anniversary Misc Promos add("P30M"); // 30th Anniversary Misc Promos
add("P30H"); // 30th Anniversary History Promos
add("PEWK"); // Eternal Weekend add("PEWK"); // Eternal Weekend
add("LTR"); // The Lord of the Rings: Tales of Middle-Earth add("LTR"); // The Lord of the Rings: Tales of Middle-Earth
add("LTC"); // Tales of Middle-Earth Commander add("LTC"); // Tales of Middle-Earth Commander
add("PF23"); // MagicFest 2023
add("CMM"); // Commander Masters add("CMM"); // Commander Masters
add("WHO"); // Doctor Who add("WHO"); // Doctor Who
add("WOE"); // Wilds of Eldraine add("WOE"); // Wilds of Eldraine
@ -558,10 +566,13 @@ public class ScryfallImageSupportCards {
add("REX"); // Jurassic World Collection add("REX"); // Jurassic World Collection
add("SPG"); // Special Guests add("SPG"); // Special Guests
add("PW24"); // Wizards Play Network 2024 add("PW24"); // Wizards Play Network 2024
add("PF24"); // MagicFest 2024
add("RVR"); // Ravnica Remastered add("RVR"); // Ravnica Remastered
add("PL24"); // Year of the Dragon 2024
add("PIP"); // Fallout add("PIP"); // Fallout
add("MKM"); // Murders at Karlov Manor add("MKM"); // Murders at Karlov Manor
add("MKC"); // Murders at Karlov Manor Commander add("MKC"); // Murders at Karlov Manor Commander
add("PSS4"); // MKM Standard Showdown
add("CLU"); // Ravnica: Clue Edition add("CLU"); // Ravnica: Clue Edition
add("OTJ"); // Outlaws of Thunder Junction add("OTJ"); // Outlaws of Thunder Junction
add("OTC"); // Outlaws of Thunder Junction Commander add("OTC"); // Outlaws of Thunder Junction Commander
@ -569,9 +580,12 @@ 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("PLG24"); // Love Your LGS 2024
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 +593,26 @@ 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("PL25"); // Year of the Snake 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

@ -1,10 +1,10 @@
package org.mage.plugins.card.dl.sources; package org.mage.plugins.card.dl.sources;
import mage.cards.repository.TokenRepository;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import mage.cards.repository.TokenRepository;
/** /**
* @author JayDi85 * @author JayDi85
*/ */
@ -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");
@ -2787,6 +2789,7 @@ public class ScryfallImageSupportTokens {
// EOE // EOE
put("EOE/Drone", "https://api.scryfall.com/cards/teoe/3?format=image"); put("EOE/Drone", "https://api.scryfall.com/cards/teoe/3?format=image");
put("EOE/Emblem Tezzeret", "https://api.scryfall.com/cards/teoe/11?format=image");
put("EOE/Human Soldier", "https://api.scryfall.com/cards/teoe/2?format=image"); put("EOE/Human Soldier", "https://api.scryfall.com/cards/teoe/2?format=image");
put("EOE/Lander/1", "https://api.scryfall.com/cards/teoe/4?format=image"); put("EOE/Lander/1", "https://api.scryfall.com/cards/teoe/4?format=image");
put("EOE/Lander/2", "https://api.scryfall.com/cards/teoe/5?format=image"); put("EOE/Lander/2", "https://api.scryfall.com/cards/teoe/5?format=image");
@ -2928,6 +2931,12 @@ public class ScryfallImageSupportTokens {
// PL23 // PL23
put("PL23/Food", "https://api.scryfall.com/cards/pl23/2?format=image"); put("PL23/Food", "https://api.scryfall.com/cards/pl23/2?format=image");
// PL24
put("PL24/Dragon", "https://api.scryfall.com/cards/pl24/3?format=image");
// PL25
put("PL25/Snake", "https://api.scryfall.com/cards/pl25/2?format=image");
// generate supported sets // generate supported sets
supportedSets.clear(); supportedSets.clear();
for (String cardName : this.keySet()) { for (String cardName : this.keySet()) {

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

@ -13,17 +13,20 @@ public class DuelCommander extends Commander {
banned.add("Balance"); banned.add("Balance");
banned.add("Bazaar of Baghdad"); banned.add("Bazaar of Baghdad");
banned.add("Black Lotus"); banned.add("Black Lotus");
banned.add("Blood Moon");
banned.add("Capture of Jingzhou"); banned.add("Capture of Jingzhou");
banned.add("Cavern of Souls"); banned.add("Cavern of Souls");
banned.add("Channel"); banned.add("Channel");
banned.add("Chrome Mox"); banned.add("Chrome Mox");
banned.add("Comet, Stellar Pup"); banned.add("Comet, Stellar Pup");
banned.add("Dark Ritual");
banned.add("Deadly Rollick"); banned.add("Deadly Rollick");
banned.add("Deflecting Swat"); banned.add("Deflecting Swat");
banned.add("Dig Through Time"); banned.add("Dig Through Time");
banned.add("Emrakul, the Aeons Torn"); banned.add("Emrakul, the Aeons Torn");
banned.add("Entomb"); banned.add("Entomb");
banned.add("Fastbond"); banned.add("Fastbond");
banned.add("Force of Will");
banned.add("Field of the Dead"); banned.add("Field of the Dead");
banned.add("Fierce Guardianship"); banned.add("Fierce Guardianship");
banned.add("Flawless Maneuver"); banned.add("Flawless Maneuver");
@ -82,6 +85,7 @@ public class DuelCommander extends Commander {
banned.add("Tolarian Academy"); banned.add("Tolarian Academy");
banned.add("Trazyn The Infinite"); banned.add("Trazyn The Infinite");
banned.add("Treasure Cruise"); banned.add("Treasure Cruise");
banned.add("Underworld Breach");
banned.add("Uro, Titan of Nature's Wrath"); banned.add("Uro, Titan of Nature's Wrath");
banned.add("Vampiric Tutor"); banned.add("Vampiric Tutor");
banned.add("Wasteland"); banned.add("Wasteland");

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
} }
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,13 +813,11 @@ 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)) { if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
return true; return true;
} }
} }
}
} else { } else {
// done or cancel button pressed // done or cancel button pressed
if (target.isChosen(game)) { if (target.isChosen(game)) {
@ -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);
} }
} }

View file

@ -0,0 +1,54 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangAirNomad extends CardImpl {
public AangAirNomad(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.AVATAR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(5);
this.toughness = new MageInt(4);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Other creatures you control have vigilance.
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
VigilanceAbility.getInstance(), Duration.WhileOnBattlefield,
StaticFilters.FILTER_PERMANENT_CREATURES, true
)));
}
private AangAirNomad(final AangAirNomad card) {
super(card);
}
@Override
public AangAirNomad copy() {
return new AangAirNomad(this);
}
}

View file

@ -0,0 +1,67 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.OneOrMoreLeaveWithoutDyingTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CountersControllerCount;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.counter.AddCountersPlayersEffect;
import mage.abilities.effects.keyword.AirbendTargetEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
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 mage.filter.StaticFilters;
import mage.game.permanent.token.AllyToken;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangAirbendingMaster extends CardImpl {
private static final DynamicValue xValue = new CountersControllerCount(CounterType.EXPERIENCE);
public AangAirbendingMaster(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.AVATAR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// When Aang enters, airbend another target creature.
Ability ability = new EntersBattlefieldTriggeredAbility(new AirbendTargetEffect());
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE));
this.addAbility(ability);
// Whenever one or more creatures you control leave the battlefield without dying, you get an experience counter.
this.addAbility(new OneOrMoreLeaveWithoutDyingTriggeredAbility(
new AddCountersPlayersEffect(CounterType.EXPERIENCE.createInstance(), TargetController.YOU),
StaticFilters.FILTER_CONTROLLED_CREATURES
));
// At the beginning of your upkeep, create a 1/1 white Ally creature token for each experience counter you have.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CreateTokenEffect(new AllyToken(), xValue)
.setText("create a 1/1 white Ally creature token for each experience counter you have")));
}
private AangAirbendingMaster(final AangAirbendingMaster card) {
super(card);
}
@Override
public AangAirbendingMaster copy() {
return new AangAirbendingMaster(this);
}
}

View file

@ -0,0 +1,112 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.Card;
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 mage.filter.FilterCard;
import mage.game.Game;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangMasterOfElements extends CardImpl {
private static final FilterCard filter = new FilterCard("spells");
public AangMasterOfElements(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.AVATAR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(6);
this.toughness = new MageInt(6);
this.nightCard = true;
// Flying
this.addAbility(FlyingAbility.getInstance());
// Spells you cast cost {W}{U}{B}{R}{G} less to cast.
this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(
filter, new ManaCostsImpl<>("{W}{U}{B}{R}{G}"), StaticValue.get(1), true
)));
// At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
TargetController.ANY,
new DoIfCostPaid(new GainLifeEffect(4), new AangMasterOfElementsCost())
.addEffect(new DrawCardSourceControllerEffect(4).concatBy(","))
.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(4))
.setText(", put four +1/+1 counters on him"))
.addEffect(new DamagePlayersEffect(4, TargetController.OPPONENT)
.setText(", and he deals 4 damage to each opponent")),
false
));
}
private AangMasterOfElements(final AangMasterOfElements card) {
super(card);
}
@Override
public AangMasterOfElements copy() {
return new AangMasterOfElements(this);
}
}
class AangMasterOfElementsCost extends CostImpl {
AangMasterOfElementsCost() {
super();
text = "transform {this}";
}
private AangMasterOfElementsCost(final AangMasterOfElementsCost cost) {
super(cost);
}
@Override
public AangMasterOfElementsCost copy() {
return new AangMasterOfElementsCost(this);
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return Optional
.ofNullable(source.getSourcePermanentIfItStillExists(game))
.filter(Card::isTransformable)
.isPresent();
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
paid = Optional
.ofNullable(source.getSourcePermanentIfItStillExists(game))
.filter(permanent -> permanent.transform(source, game))
.isPresent();
return paid;
}
}

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.continuous.GainAbilitySourceEffect;
import mage.abilities.effects.keyword.AirbendTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.FilterSpell;
import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangTheLastAirbender extends CardImpl {
private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent");
private static final FilterSpell filter2 = new FilterSpell("a Lesson spell");
static {
filter.add(AnotherPredicate.instance);
filter2.add(SubType.LESSON.getPredicate());
}
public AangTheLastAirbender(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.AVATAR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(3);
this.toughness = new MageInt(2);
// Flying
this.addAbility(FlyingAbility.getInstance());
// When Aang enters, airbend up to one other target nonland permanent.
Ability ability = new EntersBattlefieldTriggeredAbility(new AirbendTargetEffect());
ability.addTarget(new TargetPermanent(0, 1, filter));
this.addAbility(ability);
// Whenever you cast a Lesson spell, Aang gains lifelink until end of turn.
this.addAbility(new SpellCastControllerTriggeredAbility(
new GainAbilitySourceEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn), filter2, false
));
}
private AangTheLastAirbender(final AangTheLastAirbender card) {
super(card);
}
@Override
public AangTheLastAirbender copy() {
return new AangTheLastAirbender(this);
}
}

View file

@ -0,0 +1,45 @@
package mage.cards.a;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.BlockingPredicate;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangsDefense extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("blocking creature you control");
static {
filter.add(BlockingPredicate.instance);
}
public AangsDefense(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}");
// Target blocking creature you control gets +2/+2 until end of turn.
this.getSpellAbility().addEffect(new BoostTargetEffect(2, 2));
this.getSpellAbility().addTarget(new TargetPermanent(filter));
// Draw a card.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("<br>"));
}
private AangsDefense(final AangsDefense card) {
super(card);
}
@Override
public AangsDefense copy() {
return new AangsDefense(this);
}
}

View file

@ -0,0 +1,58 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.WaterbendCost;
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.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 SimpleActivatedAbility(new DoIfCostPaid(
new ScryEffect(2), new SacrificeSourceCost(), null, false
), new WaterbendCost(3)));
}
private AangsIceberg(final AangsIceberg card) {
super(card);
}
@Override
public AangsIceberg copy() {
return new AangsIceberg(this);
}
}

View file

@ -0,0 +1,64 @@
package mage.cards.a;
import mage.abilities.condition.common.KickedCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.abilities.keyword.KickerAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.target.common.TargetCardAndOrCardInLibrary;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AangsJourney extends CardImpl {
private static final Predicate<Card> predicate = Predicates.and(
SuperType.BASIC.getPredicate(),
CardType.LAND.getPredicate()
);
public AangsJourney(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}");
this.subtype.add(SubType.LESSON);
// Kicker {2}
this.addAbility(new KickerAbility("{2}"));
// Search your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle.
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(
new SearchLibraryPutInHandEffect(new TargetCardAndOrCardInLibrary(
predicate, SubType.SHRINE.getPredicate(),
"a basic land card and/or a Shrine card"
), true),
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true),
KickedCondition.ONCE, "search your library for a basic land card. If this spell was kicked, " +
"instead search your library for a basic land card and a Shrine card. " +
"Reveal those cards, put them into your hand, then shuffle"
));
// You gain 2 life.
this.getSpellAbility().addEffect(new GainLifeEffect(2).concatBy("<br>"));
}
private AangsJourney(final AangsJourney card) {
super(card);
}
@Override
public AangsJourney copy() {
return new AangsJourney(this);
}
}

View file

@ -0,0 +1,37 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AardvarkSloth extends CardImpl {
public AardvarkSloth(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.subtype.add(SubType.SLOTH);
this.subtype.add(SubType.BEAST);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Lifelink
this.addAbility(LifelinkAbility.getInstance());
}
private AardvarkSloth(final AardvarkSloth card) {
super(card);
}
@Override
public AardvarkSloth copy() {
return new AardvarkSloth(this);
}
}

View file

@ -0,0 +1,35 @@
package mage.cards.a;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AbandonAttachments extends CardImpl {
public AbandonAttachments(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U/R}");
this.subtype.add(SubType.LESSON);
// You may discard a card. If you do, draw two cards.
this.getSpellAbility().addEffect(new DoIfCostPaid(new DrawCardSourceControllerEffect(2), new DiscardCardCost()));
}
private AbandonAttachments(final AbandonAttachments card) {
super(card);
}
@Override
public AbandonAttachments copy() {
return new AbandonAttachments(this);
}
}

View file

@ -23,6 +23,8 @@ import java.util.UUID;
*/ */
public final class AccursedWitch extends CardImpl { public final class AccursedWitch extends CardImpl {
private static final FilterCard filter = new FilterCard("spells");
public AccursedWitch(UUID ownerId, CardSetInfo setInfo) { public AccursedWitch(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.HUMAN);
@ -34,7 +36,7 @@ public final class AccursedWitch extends CardImpl {
// Spells your opponents cast that target Accursed Witch cost {1} less to cast. // Spells your opponents cast that target Accursed Witch cost {1} less to cast.
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(
new SpellsCostModificationThatTargetSourceEffect(-1, new FilterCard("Spells"), TargetController.OPPONENT)) new SpellsCostModificationThatTargetSourceEffect(-1, filter, TargetController.OPPONENT))
); );
// When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent. // When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent.

View file

@ -13,10 +13,8 @@ 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.filter.FilterCard;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterBasicLandCard;
import mage.filter.common.FilterLandPermanent; import mage.filter.common.FilterLandPermanent;
import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate; import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate;
import mage.game.Controllable; import mage.game.Controllable;
@ -32,8 +30,6 @@ import java.util.stream.Collectors;
*/ */
public final class AerialSurveyor extends CardImpl { public final class AerialSurveyor extends CardImpl {
private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS);
public AerialSurveyor(UUID ownerId, CardSetInfo setInfo) { public AerialSurveyor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}");
@ -45,7 +41,7 @@ public final class AerialSurveyor extends CardImpl {
this.addAbility(FlyingAbility.getInstance()); this.addAbility(FlyingAbility.getInstance());
// Whenever Aerial Surveyor attacks, if defending player controls more lands than you, search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. // Whenever Aerial Surveyor attacks, if defending player controls more lands than you, search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle.
this.addAbility(new AttacksTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)) this.addAbility(new AttacksTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true))
.withInterveningIf(AerialSurveyorCondition.instance) .withInterveningIf(AerialSurveyorCondition.instance)
.addHint(LandsYouControlHint.instance) .addHint(LandsYouControlHint.instance)
.addHint(AerialSurveyorHint.instance)); .addHint(AerialSurveyorHint.instance));

View file

@ -0,0 +1,66 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeSourceControllerEffect;
import mage.abilities.keyword.FlashAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AgentVenom extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another nontoken creature you control");
static {
filter.add(AnotherPredicate.instance);
filter.add(TokenPredicate.FALSE);
}
public AgentVenom(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SYMBIOTE);
this.subtype.add(SubType.SOLDIER);
this.subtype.add(SubType.HERO);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// Flash
this.addAbility(FlashAbility.getInstance());
// Menace
this.addAbility(new MenaceAbility());
// Whenever another nontoken creature you control dies, you draw a card and lose 1 life.
Ability ability = new DiesCreatureTriggeredAbility(
new DrawCardSourceControllerEffect(1, true), false, filter
);
ability.addEffect(new LoseLifeSourceControllerEffect(1).setText("and lose 1 life"));
this.addAbility(ability);
}
private AgentVenom(final AgentVenom card) {
super(card);
}
@Override
public AgentVenom copy() {
return new AgentVenom(this);
}
}

View file

@ -0,0 +1,39 @@
package mage.cards.a;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.keyword.AirbendTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.target.common.TargetNonlandPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AirbendingLesson extends CardImpl {
public AirbendingLesson(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}");
this.subtype.add(SubType.LESSON);
// Airbend target nonland permanent.
this.getSpellAbility().addEffect(new AirbendTargetEffect());
this.getSpellAbility().addTarget(new TargetNonlandPermanent());
// Draw a card.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("<br>"));
}
private AirbendingLesson(final AirbendingLesson card) {
super(card);
}
@Override
public AirbendingLesson copy() {
return new AirbendingLesson(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

@ -0,0 +1,101 @@
package mage.cards.a;
import mage.MageIdentifier;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author Jmlundeen
*/
public final class AlienSymbiosis extends CardImpl {
public AlienSymbiosis(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
this.addAbility(new EnchantAbility(auraTarget));
// Enchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types.
Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1));
ability.addEffect(new GainAbilityAttachedEffect(new MenaceAbility(), null).concatBy(","));
ability.addEffect(new AddCardSubtypeAttachedEffect(SubType.SYMBIOTE, AttachmentType.AURA).concatBy(",").setText("and is a Symbiote in addition to its other types"));
this.addAbility(ability);
// You may cast this card from your graveyard by discarding a card in addition to paying its other costs.
this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, new AlienSymbiosisGraveyardEffect()));
}
private AlienSymbiosis(final AlienSymbiosis card) {
super(card);
}
@Override
public AlienSymbiosis copy() {
return new AlienSymbiosis(this);
}
}
class AlienSymbiosisGraveyardEffect extends AsThoughEffectImpl {
AlienSymbiosisGraveyardEffect() {
super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay);
this.staticText = "You may cast this card from your graveyard by discarding a card in addition to paying its other costs.";
}
private AlienSymbiosisGraveyardEffect(final AlienSymbiosisGraveyardEffect effect) {
super(effect);
}
@Override
public AlienSymbiosisGraveyardEffect copy() {
return new AlienSymbiosisGraveyardEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (!objectId.equals(source.getSourceId()) || !source.isControlledBy(affectedControllerId)
|| game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) {
return false;
}
Player controller = game.getPlayer(affectedControllerId);
if (controller != null) {
Costs<Cost> costs = new CostsImpl<>();
costs.add(new DiscardCardCost());
controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{1}{B}"), costs,
MageIdentifier.AlienSymbiosisAlternateCast);
return true;
}
return false;
}
}

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

@ -0,0 +1,43 @@
package mage.cards.a;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.permanent.token.AllyToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AlliedTeamwork extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent(SubType.ALLY, "Allies");
public AlliedTeamwork(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
// When this enchantment enters, create a 1/1 white Ally creature token.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new AllyToken())));
// Allies you control get +1/+1.
this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, filter)));
}
private AlliedTeamwork(final AlliedTeamwork card) {
super(card);
}
@Override
public AlliedTeamwork copy() {
return new AlliedTeamwork(this);
}
}

View file

@ -13,7 +13,6 @@ import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.common.FilterBySubtypeCard;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
@ -24,7 +23,7 @@ import java.util.UUID;
*/ */
public final class AlpineGuide extends CardImpl { public final class AlpineGuide extends CardImpl {
private static final FilterCard filter = new FilterBySubtypeCard(SubType.MOUNTAIN); private static final FilterCard filter = new FilterCard(SubType.MOUNTAIN);
private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.MOUNTAIN, "Mountain"); private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.MOUNTAIN, "Mountain");
public AlpineGuide(UUID ownerId, CardSetInfo setInfo) { public AlpineGuide(UUID ownerId, CardSetInfo setInfo) {

View file

@ -57,6 +57,12 @@ class AlpineMoonEffect extends ContinuousEffectImpl {
this.staticText = "lands your opponents control with the chosen name " this.staticText = "lands your opponents control with the chosen name "
+ "lose all land types and abilities, " + "lose all land types and abilities, "
+ "and they gain \"{T}: Add one mana of any color.\""; + "and they gain \"{T}: Add one mana of any color.\"";
addDependedToType(DependencyType.BecomeMountain);
addDependedToType(DependencyType.BecomeForest);
addDependedToType(DependencyType.BecomeIsland);
addDependedToType(DependencyType.BecomeSwamp);
addDependedToType(DependencyType.BecomePlains);
addDependedToType(DependencyType.BecomeNonbasicLand);
} }
private AlpineMoonEffect(final AlpineMoonEffect effect) { private AlpineMoonEffect(final AlpineMoonEffect effect) {
@ -84,9 +90,6 @@ class AlpineMoonEffect extends ContinuousEffectImpl {
for (Permanent land : game.getBattlefield().getActivePermanents(filter2, source.getControllerId(), game)) { for (Permanent land : game.getBattlefield().getActivePermanents(filter2, source.getControllerId(), game)) {
switch (layer) { switch (layer) {
case TypeChangingEffects_4: case TypeChangingEffects_4:
// 305.7 Note that this doesn't remove any abilities that were granted to the land by other effects
// So the ability removing has to be done before Layer 6
land.removeAllAbilities(source.getSourceId(), game);
land.removeAllSubTypes(game, SubTypeSet.NonBasicLandType); land.removeAllSubTypes(game, SubTypeSet.NonBasicLandType);
break; break;
case AbilityAddingRemovingEffects_6: case AbilityAddingRemovingEffects_6:

View file

@ -0,0 +1,47 @@
package mage.cards.a;
import mage.abilities.Mode;
import mage.abilities.effects.common.CounterTargetEffect;
import mage.abilities.effects.common.TapTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.TargetSpell;
import java.util.UUID;
/**
*
* @author Jmlundeen
*/
public final class AmazingAcrobatics extends CardImpl {
public AmazingAcrobatics(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}{U}");
// Choose one or both --
this.getSpellAbility().getModes().setMinModes(1);
this.getSpellAbility().getModes().setMaxModes(2);
// * Counter target spell.
this.getSpellAbility().addEffect(new CounterTargetEffect());
this.getSpellAbility().addTarget(new TargetSpell());
// * Tap one or two target creatures.
Mode mode = new Mode(new TapTargetEffect());
mode.addTarget(new TargetPermanent(1, 2, StaticFilters.FILTER_PERMANENT_CREATURES));
this.getSpellAbility().getModes().addMode(mode);
}
private AmazingAcrobatics(final AmazingAcrobatics card) {
super(card);
}
@Override
public AmazingAcrobatics copy() {
return new AmazingAcrobatics(this);
}
}

View file

@ -0,0 +1,46 @@
package mage.cards.a;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.EffectKeyValue;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AmazingAlliance extends CardImpl {
private static final DynamicValue xValue = new EffectKeyValue(
AttacksWithCreaturesTriggeredAbility.VALUEKEY_NUMBER_ATTACKERS, "that much"
);
public AmazingAlliance(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}");
// Creatures you control get +1/+1.
this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield)));
// Whenever you attack with one or more legendary creatures, you gain that much life.
this.addAbility(new AttacksWithCreaturesTriggeredAbility(
new GainLifeEffect(xValue), 1, StaticFilters.FILTER_CREATURES_LEGENDARY
));
}
private AmazingAlliance(final AmazingAlliance card) {
super(card);
}
@Override
public AmazingAlliance copy() {
return new AmazingAlliance(this);
}
}

View file

@ -14,8 +14,7 @@ import mage.cards.CardSetInfo;
import mage.constants.AbilityWord; import mage.constants.AbilityWord;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.SuperType; import mage.filter.StaticFilters;
import mage.filter.FilterCard;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.UUID; import java.util.UUID;
@ -25,13 +24,6 @@ import java.util.UUID;
*/ */
public final class AmbitiousFarmhand extends CardImpl { public final class AmbitiousFarmhand extends CardImpl {
private static final FilterCard filter = new FilterCard("basic Plains card");
static {
filter.add(SuperType.BASIC.getPredicate());
filter.add(SubType.PLAINS.getPredicate());
}
public AmbitiousFarmhand(UUID ownerId, CardSetInfo setInfo) { public AmbitiousFarmhand(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
@ -43,7 +35,7 @@ public final class AmbitiousFarmhand extends CardImpl {
// When Ambitious Farmhand enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. // When Ambitious Farmhand enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle.
this.addAbility(new EntersBattlefieldTriggeredAbility( this.addAbility(new EntersBattlefieldTriggeredAbility(
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), true new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), true
)); ));
// Coven{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers. // Coven{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers.

View file

@ -14,7 +14,6 @@ import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.game.permanent.token.custom.CreatureToken; import mage.game.permanent.token.custom.CreatureToken;
import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID; import java.util.UUID;
@ -42,7 +41,11 @@ public final class AmbushCommander extends CardImpl {
ContinuousEffect effect = new BecomesCreatureAllEffect( ContinuousEffect effect = new BecomesCreatureAllEffect(
new CreatureToken(1, 1, "1/1 green Elf creatures").withColor("G").withSubType(SubType.ELF), new CreatureToken(1, 1, "1/1 green Elf creatures").withColor("G").withSubType(SubType.ELF),
"lands", filter2, Duration.WhileOnBattlefield, true); "lands", filter2, Duration.WhileOnBattlefield, true);
effect.getDependencyTypes().add(DependencyType.BecomeForest); effect.addDependedToType(DependencyType.BecomeForest);
effect.addDependedToType(DependencyType.BecomeIsland);
effect.addDependedToType(DependencyType.BecomeMountain);
effect.addDependedToType(DependencyType.BecomePlains);
effect.addDependedToType(DependencyType.BecomeSwamp);
this.addAbility(new SimpleStaticAbility(effect)); this.addAbility(new SimpleStaticAbility(effect));
// {1}{G}, Sacrifice an Elf: Target creature gets +3/+3 until end of turn. // {1}{G}, Sacrifice an Elf: Target creature gets +3/+3 until end of turn.

View file

@ -1,7 +1,6 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.ObjectColor; import mage.ObjectColor;
@ -16,6 +15,8 @@ import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import java.util.UUID;
/** /**
* *
* @author fireshoes * @author fireshoes
@ -29,7 +30,7 @@ public final class AnHavvaConstable extends CardImpl {
this.toughness = new MageInt(1); this.toughness = new MageInt(1);
// An-Havva Constable's toughness is equal to 1 plus the number of green creatures on the battlefield. // An-Havva Constable's toughness is equal to 1 plus the number of green creatures on the battlefield.
this.addAbility(new SimpleStaticAbility(new AnHavvaConstableEffect())); this.addAbility(new SimpleStaticAbility(Zone.ALL, new AnHavvaConstableEffect()));
} }
private AnHavvaConstable(final AnHavvaConstable card) { private AnHavvaConstable(final AnHavvaConstable card) {
@ -72,7 +73,7 @@ class AnHavvaConstableEffect extends ContinuousEffectImpl {
FilterCreaturePermanent filter = new FilterCreaturePermanent("green creatures"); FilterCreaturePermanent filter = new FilterCreaturePermanent("green creatures");
filter.add(new ColorPredicate(ObjectColor.GREEN)); filter.add(new ColorPredicate(ObjectColor.GREEN));
int numberOfGreenCreatures = game.getBattlefield().count(filter, source.getSourceId(), source, game); int numberOfGreenCreatures = game.getBattlefield().count(filter, source.getControllerId(), source, game);
mageObject.getToughness().setModifiedBaseValue(1 + numberOfGreenCreatures); mageObject.getToughness().setModifiedBaseValue(1 + numberOfGreenCreatures);

View file

@ -1,11 +1,9 @@
package mage.cards.a; package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.DisturbAbility;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -32,8 +30,7 @@ public final class AncestorsEmbrace extends CardImpl {
TargetPermanent auraTarget = new TargetCreaturePermanent(); TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget); this.addAbility(new EnchantAbility(auraTarget));
this.addAbility(ability);
// Enchanted creature has lifelink. // Enchanted creature has lifelink.
this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect(
@ -41,7 +38,7 @@ public final class AncestorsEmbrace extends CardImpl {
))); )));
// If Ancestor's Embrace would be put into a graveyard from anywhere, exile it instead. // If Ancestor's Embrace would be put into a graveyard from anywhere, exile it instead.
this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); this.addAbility(DisturbAbility.makeBackAbility());
} }
private AncestorsEmbrace(final AncestorsEmbrace card) { private AncestorsEmbrace(final AncestorsEmbrace card) {

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

@ -114,7 +114,7 @@ class AngelOfDestinyLoseEffect extends OneShotEffect {
return false; return false;
} }
Set<UUID> playerSet = watcher.getPlayers(new MageObjectReference( Set<UUID> playerSet = watcher.getPlayers(new MageObjectReference(
source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game source.getSourceId(), source.getStackMomentSourceZCC(), game
)); ));
if (playerSet == null) { if (playerSet == null) {
return false; return false;

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

@ -0,0 +1,89 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.CastFromEverywhereSourceCondition;
import mage.abilities.effects.PreventionEffectImpl;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AntiVenomHorrifyingHealer extends CardImpl {
public AntiVenomHorrifyingHealer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}{W}{W}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SYMBIOTE);
this.subtype.add(SubType.HERO);
this.power = new MageInt(5);
this.toughness = new MageInt(5);
// When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield.
Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect())
.withInterveningIf(CastFromEverywhereSourceCondition.instance);
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD));
this.addAbility(ability);
// If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him.
this.addAbility(new SimpleStaticAbility(new AntiVenomHorrifyingHealerEffect()));
}
private AntiVenomHorrifyingHealer(final AntiVenomHorrifyingHealer card) {
super(card);
}
@Override
public AntiVenomHorrifyingHealer copy() {
return new AntiVenomHorrifyingHealer(this);
}
}
class AntiVenomHorrifyingHealerEffect extends PreventionEffectImpl {
AntiVenomHorrifyingHealerEffect() {
super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false);
staticText = "if damage would be dealt to {this}, prevent that damage and put that many +1/+1 counters on him";
}
private AntiVenomHorrifyingHealerEffect(final AntiVenomHorrifyingHealerEffect effect) {
super(effect);
}
@Override
public AntiVenomHorrifyingHealerEffect copy() {
return new AntiVenomHorrifyingHealerEffect(this);
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return super.applies(event, source, game)
&& event.getTargetId().equals(source.getSourceId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(event.getAmount()), source.getControllerId(), source, game);
}
return super.replaceEvent(event, source, game);
}
}

View file

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

View file

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

View file

@ -0,0 +1,62 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AppaAangsCompanion extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another target attacking creature without flying");
static {
filter.add(AnotherPredicate.instance);
filter.add(AttackingPredicate.instance);
filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
}
public AppaAangsCompanion(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.BISON);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(2);
this.toughness = new MageInt(4);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever Appa attacks, another target attacking creature without flying gains flying until until end of turn.
Ability ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance()));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}
private AppaAangsCompanion(final AppaAangsCompanion card) {
super(card);
}
@Override
public AppaAangsCompanion copy() {
return new AppaAangsCompanion(this);
}
}

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

@ -0,0 +1,70 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.ChooseCardTypeEffect;
import mage.abilities.effects.common.LookAtTargetPlayerHandEffect;
import mage.abilities.effects.common.cost.SpellsCostIncreasingAllEffect;
import mage.abilities.keyword.WebSlingingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.ChosenCardTypePredicate;
import mage.target.common.TargetOpponent;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
*
* @author Jmlundeen
*/
public final class ArachnePsionicWeaver extends CardImpl {
private static final FilterCard filter = new FilterCard("spells of the chosen type");
static {
filter.add(ChosenCardTypePredicate.TRUE);
}
public ArachnePsionicWeaver(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIDER);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.HERO);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Web-slinging {W}
this.addAbility(new WebSlingingAbility(this, "{W}"));
// As Arachne enters, look at target opponent's hand, then choose a noncreature card type.
List<CardType> types = Arrays.stream(CardType.values()).filter(cardType -> cardType != CardType.CREATURE)
.collect(Collectors.toList());
Ability ability = new AsEntersBattlefieldAbility(new LookAtTargetPlayerHandEffect());
ability.addEffect(new ChooseCardTypeEffect(Outcome.Benefit, types)
.setText("choose a noncreature card type")
.concatBy(", then"));
ability.addTarget(new TargetOpponent());
this.addAbility(ability);
// Spells of the chosen type cost {1} more to cast.
this.addAbility(new SimpleStaticAbility(new SpellsCostIncreasingAllEffect(1, filter, TargetController.ANY)));
}
private ArachnePsionicWeaver(final ArachnePsionicWeaver card) {
super(card);
}
@Override
public ArachnePsionicWeaver copy() {
return new ArachnePsionicWeaver(this);
}
}

View file

@ -0,0 +1,63 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.ModifiedPredicate;
import mage.target.common.TargetAttackingCreature;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AranaHeartOfTheSpider extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("modified creature you control");
static {
filter.add(ModifiedPredicate.instance);
}
public AranaHeartOfTheSpider(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIDER);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.HERO);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Whenever you attack, put a +1/+1 counter on target attacking creature.
Ability ability = new AttacksWithCreaturesTriggeredAbility(
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1
);
ability.addTarget(new TargetAttackingCreature());
this.addAbility(ability);
// Whenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn.
this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(
new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn),
filter, false, SetTargetPointer.NONE, true
));
}
private AranaHeartOfTheSpider(final AranaHeartOfTheSpider card) {
super(card);
}
@Override
public AranaHeartOfTheSpider copy() {
return new AranaHeartOfTheSpider(this);
}
}

View file

@ -9,10 +9,7 @@ import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
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.SuperType;
import mage.constants.TargetController; import mage.constants.TargetController;
import mage.filter.FilterCard;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterLandPermanent; import mage.filter.common.FilterLandPermanent;
@ -26,13 +23,10 @@ import java.util.UUID;
*/ */
public final class ArchaeomancersMap extends CardImpl { public final class ArchaeomancersMap extends CardImpl {
private static final FilterCard filter = new FilterCard("basic Plains cards"); private static final FilterPermanent filter = new FilterLandPermanent("a land an opponent controls");
private static final FilterPermanent filter2 = new FilterLandPermanent("a land an opponent controls");
static { static {
filter.add(SubType.PLAINS.getPredicate()); filter.add(TargetController.OPPONENT.getControllerPredicate());
filter.add(SuperType.BASIC.getPredicate());
filter2.add(TargetController.OPPONENT.getControllerPredicate());
} }
public ArchaeomancersMap(UUID ownerId, CardSetInfo setInfo) { public ArchaeomancersMap(UUID ownerId, CardSetInfo setInfo) {
@ -40,12 +34,12 @@ public final class ArchaeomancersMap extends CardImpl {
// When Archaeomancer's Map enters the battlefield, search your library for up to two basic Plains cards, reveal them, put them into your hand, then shuffle. // When Archaeomancer's Map enters the battlefield, search your library for up to two basic Plains cards, reveal them, put them into your hand, then shuffle.
this.addAbility(new EntersBattlefieldTriggeredAbility( this.addAbility(new EntersBattlefieldTriggeredAbility(
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, filter), true) new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_PLAINS), true)
)); ));
// Whenever a land enters the battlefield under an opponent's control, if that player controls more lands than you, you may put a land card from your hand onto the battlefield. // Whenever a land enters the battlefield under an opponent's control, if that player controls more lands than you, you may put a land card from your hand onto the battlefield.
this.addAbility(new EntersBattlefieldAllTriggeredAbility( this.addAbility(new EntersBattlefieldAllTriggeredAbility(
new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter2 new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter
).withInterveningIf(ArchaeomancersMapCondition.instance)); ).withInterveningIf(ArchaeomancersMapCondition.instance));
} }

View file

@ -1,21 +1,18 @@
package mage.cards.a; package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageControllerEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.TapSourceEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.TrampleAbility;
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.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetSacrifice;
import java.util.UUID; import java.util.UUID;
@ -24,11 +21,7 @@ import java.util.UUID;
*/ */
public final class ArchdemonOfGreed extends CardImpl { public final class ArchdemonOfGreed extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent("Human"); private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "Human");
static {
filter.add(SubType.HUMAN.getPredicate());
}
public ArchdemonOfGreed(UUID ownerId, CardSetInfo setInfo) { public ArchdemonOfGreed(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
@ -44,7 +37,10 @@ public final class ArchdemonOfGreed extends CardImpl {
this.addAbility(TrampleAbility.getInstance()); this.addAbility(TrampleAbility.getInstance());
// At the beginning of your upkeep, sacrifice a Human. If you can't, tap Archdemon of Greed and it deals 9 damage to you. // At the beginning of your upkeep, sacrifice a Human. If you can't, tap Archdemon of Greed and it deals 9 damage to you.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new ArchdemonOfGreedEffect())); this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DoIfCostPaid(
null, new TapSourceEffect(), new SacrificeTargetCost(filter), false
).addOtherwiseEffect(new DamageControllerEffect(9))
.setText("sacrifice a Human. If you can't, tap {this} and it deals 9 damage to you")));
} }
private ArchdemonOfGreed(final ArchdemonOfGreed card) { private ArchdemonOfGreed(final ArchdemonOfGreed card) {
@ -55,48 +51,4 @@ public final class ArchdemonOfGreed extends CardImpl {
public ArchdemonOfGreed copy() { public ArchdemonOfGreed copy() {
return new ArchdemonOfGreed(this); return new ArchdemonOfGreed(this);
} }
static class ArchdemonOfGreedEffect extends OneShotEffect {
public ArchdemonOfGreedEffect() {
super(Outcome.Damage);
this.staticText = "sacrifice a Human. If you can't, tap {this} and it deals 9 damage to you.";
}
private ArchdemonOfGreedEffect(final ArchdemonOfGreedEffect effect) {
super(effect);
}
@Override
public ArchdemonOfGreedEffect copy() {
return new ArchdemonOfGreedEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
// create cost for sacrificing a human
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
TargetSacrifice target = new TargetSacrifice(filter);
// if they can pay the cost, then they must pay
if (target.canChoose(player.getId(), source, game)) {
player.choose(Outcome.Sacrifice, target, source, game);
Permanent humanSacrifice = game.getPermanent(target.getFirstTarget());
if (humanSacrifice != null) {
// sacrifice the chosen card
return humanSacrifice.sacrifice(source, game);
}
} else {
permanent.tap(source, game);
player.damage(9, source.getSourceId(), source, game);
}
}
return true;
}
return false;
}
}
} }

View file

@ -2,9 +2,8 @@ package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect;
import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.keyword.DisturbAbility;
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;
@ -35,7 +34,7 @@ public final class ArchiveHaunt extends CardImpl {
this.addAbility(new AttacksTriggeredAbility(new DrawDiscardControllerEffect(1, 1))); this.addAbility(new AttacksTriggeredAbility(new DrawDiscardControllerEffect(1, 1)));
// If Archive Haunt would be put into a graveyard from anywhere, exile it instead. // If Archive Haunt would be put into a graveyard from anywhere, exile it instead.
this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); this.addAbility(DisturbAbility.makeBackAbility());
} }
private ArchiveHaunt(final ArchiveHaunt card) { private ArchiveHaunt(final ArchiveHaunt card) {

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

@ -1,9 +1,7 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID; import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility; import mage.abilities.LoyaltyAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.GetEmblemEffect; import mage.abilities.effects.common.GetEmblemEffect;
import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.TransformSourceEffect;
@ -21,8 +19,9 @@ import mage.filter.StaticFilters;
import mage.game.command.emblems.ArlinnEmbracedByTheMoonEmblem; import mage.game.command.emblems.ArlinnEmbracedByTheMoonEmblem;
import mage.target.common.TargetAnyTarget; import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/** /**
*
* @author fireshoes * @author fireshoes
*/ */
public final class ArlinnEmbracedByTheMoon extends CardImpl { public final class ArlinnEmbracedByTheMoon extends CardImpl {
@ -37,12 +36,14 @@ public final class ArlinnEmbracedByTheMoon extends CardImpl {
this.nightCard = true; this.nightCard = true;
// +1: Creatures you control get +1/+1 and gain trample until end of turn. // +1: Creatures you control get +1/+1 and gain trample until end of turn.
Effect effect = new BoostControlledEffect(1, 1, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE); Ability ability = new LoyaltyAbility(new BoostControlledEffect(
effect.setText("Creatures you control get +1/+1"); 1, 1, Duration.EndOfTurn,
LoyaltyAbility ability = new LoyaltyAbility(effect, 1); StaticFilters.FILTER_PERMANENT_CREATURE
effect = new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE); ).setText("Creatures you control get +1/+1"), 1);
effect.setText("and gain trample until end of turn"); ability.addEffect(new GainAbilityControlledEffect(
ability.addEffect(effect); TrampleAbility.getInstance(), Duration.EndOfTurn,
StaticFilters.FILTER_PERMANENT_CREATURE
).setText("and gain trample until end of turn"));
this.addAbility(ability); this.addAbility(ability);
// -1: Arlinn, Embraced by the Moon deals 3 damage to any target. Transform Arlinn, Embraced by the Moon. // -1: Arlinn, Embraced by the Moon deals 3 damage to any target. Transform Arlinn, Embraced by the Moon.

View file

@ -1,9 +1,7 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID; import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility; import mage.abilities.LoyaltyAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect;
@ -20,14 +18,15 @@ import mage.constants.SuperType;
import mage.game.permanent.token.WolfToken; import mage.game.permanent.token.WolfToken;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/** /**
*
* @author fireshoes * @author fireshoes
*/ */
public final class ArlinnKord extends CardImpl { public final class ArlinnKord extends CardImpl {
public ArlinnKord(UUID ownerId, CardSetInfo setInfo) { public ArlinnKord(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.PLANESWALKER},"{2}{R}{G}"); super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{G}");
this.supertype.add(SuperType.LEGENDARY); this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.ARLINN); this.subtype.add(SubType.ARLINN);
@ -36,15 +35,15 @@ public final class ArlinnKord extends CardImpl {
this.setStartingLoyalty(3); this.setStartingLoyalty(3);
// +1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste. // +1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste.
Effect effect = new BoostTargetEffect(2, 2, Duration.EndOfTurn); Ability ability = new LoyaltyAbility(new BoostTargetEffect(
effect.setText("Until end of turn, up to one target creature gets +2/+2"); 2, 2, Duration.EndOfTurn
LoyaltyAbility ability = new LoyaltyAbility(effect, 1); ).setText("until end of turn, up to one target creature gets +2/+2"), 1);
effect = new GainAbilityTargetEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn); ability.addEffect(new GainAbilityTargetEffect(
effect.setText("and gains vigilance"); VigilanceAbility.getInstance(), Duration.EndOfTurn
ability.addEffect(effect); ).setText("and gains vigilance"));
effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); ability.addEffect(new GainAbilityTargetEffect(
effect.setText("and haste"); HasteAbility.getInstance(), Duration.EndOfTurn
ability.addEffect(effect); ).setText("and haste"));
ability.addTarget(new TargetCreaturePermanent(0, 1)); ability.addTarget(new TargetCreaturePermanent(0, 1));
this.addAbility(ability); this.addAbility(ability);

View file

@ -18,7 +18,7 @@ import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.AttachmentType;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.common.FilterBySubtypeCard; import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
import mage.util.CardUtil; import mage.util.CardUtil;
@ -30,6 +30,8 @@ import java.util.UUID;
*/ */
public final class ArmMountedAnchor extends CardImpl { public final class ArmMountedAnchor extends CardImpl {
private static final FilterCard filter = new FilterCard(SubType.PIRATE);
public ArmMountedAnchor(UUID ownerId, CardSetInfo setInfo) { public ArmMountedAnchor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
this.subtype.add(SubType.EQUIPMENT); this.subtype.add(SubType.EQUIPMENT);
@ -42,11 +44,12 @@ public final class ArmMountedAnchor extends CardImpl {
this.addAbility(firstAbility); this.addAbility(firstAbility);
// Whenever equipped creature deals combat damage to a player, draw two cards. Then discard two cards unless you discard a Pirate card. // Whenever equipped creature deals combat damage to a player, draw two cards. Then discard two cards unless you discard a Pirate card.
Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility(new DrawCardSourceControllerEffect(2), "equipped creature", false); Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility(
DiscardCardCost cost = new DiscardCardCost(new FilterBySubtypeCard(SubType.PIRATE)); new DrawCardSourceControllerEffect(2), "equipped creature", false
cost.setText("Discard a Pirate card instead of discarding two cards"); );
drawAbility.addEffect(new DoIfCostPaid( drawAbility.addEffect(new DoIfCostPaid(
null, new DiscardControllerEffect(2), cost null, new DiscardControllerEffect(2),
new DiscardCardCost(filter).setText("Discard a Pirate card instead of discarding two cards")
).setText("Then discard two cards unless you discard a Pirate card")); ).setText("Then discard two cards unless you discard a Pirate card"));
this.addAbility(drawAbility); this.addAbility(drawAbility);

View file

@ -1,7 +1,6 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
@ -9,11 +8,16 @@ import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.ProtectionAbility; import mage.abilities.keyword.ProtectionAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.common.FilterArtifactCard; import mage.filter.common.FilterArtifactCard;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/** /**
* *
* @author MarcoMarin * @author MarcoMarin
@ -33,7 +37,7 @@ public final class ArtifactWard extends CardImpl {
// Enchanted creature can't be blocked by artifact creatures. // Enchanted creature can't be blocked by artifact creatures.
// Prevent all damage that would be dealt to enchanted creature by artifact sources. // Prevent all damage that would be dealt to enchanted creature by artifact sources.
// Enchanted creature can't be the target of abilities from artifact sources. // Enchanted creature can't be the target of abilities from artifact sources.
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility( // TODO: Implement as separate abilities, this isn't quite the same as "Enchanted creature gains protection from artifacts"
new GainAbilityAttachedEffect(new ProtectionAbility(new FilterArtifactCard("artifacts")), AttachmentType.AURA))); new GainAbilityAttachedEffect(new ProtectionAbility(new FilterArtifactCard("artifacts")), AttachmentType.AURA)));
} }

View file

@ -64,6 +64,7 @@ class AshayaSoulOfTheWildEffect extends ContinuousEffectImpl {
staticText = "Nontoken creatures you control are Forest lands in addition to their other types"; staticText = "Nontoken creatures you control are Forest lands in addition to their other types";
this.dependendToTypes.add(DependencyType.BecomeCreature); this.dependendToTypes.add(DependencyType.BecomeCreature);
this.dependencyTypes.add(DependencyType.BecomeForest); this.dependencyTypes.add(DependencyType.BecomeForest);
this.dependencyTypes.add(DependencyType.BecomeNonbasicLand);
} }
private AshayaSoulOfTheWildEffect(final AshayaSoulOfTheWildEffect effect) { private AshayaSoulOfTheWildEffect(final AshayaSoulOfTheWildEffect effect) {

View file

@ -90,7 +90,7 @@ class AshioksErasureExileEffect extends OneShotEffect {
|| spell == null) { || spell == null) {
return false; return false;
} }
UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC());
return controller.moveCardsToExile(spell, source, game, true, exileId, sourceObject.getIdName()); return controller.moveCardsToExile(spell, source, game, true, exileId, sourceObject.getIdName());
} }
} }
@ -126,7 +126,7 @@ class AshioksErasureReplacementEffect extends ContinuousRuleModifyingEffectImpl
|| card == null) { || card == null) {
return false; return false;
} }
UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); UUID exileZone = CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC());
ExileZone exile = game.getExile().getExileZone(exileZone); ExileZone exile = game.getExile().getExileZone(exileZone);
if (exile == null) { if (exile == null) {

View file

@ -1,44 +1,40 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.AttachmentType;
import mage.target.common.TargetControlledCreaturePermanent; import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/** /**
*
* @author fireshoes * @author fireshoes
*/ */
public final class AshmouthBlade extends CardImpl { public final class AshmouthBlade extends CardImpl {
public AshmouthBlade(UUID ownerId, CardSetInfo setInfo) { public AshmouthBlade(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},""); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "");
this.subtype.add(SubType.EQUIPMENT); this.subtype.add(SubType.EQUIPMENT);
// this card is the second face of double-faced card // this card is the second face of double-faced card
this.nightCard = true; this.nightCard = true;
// Equipped creature gets +3/+3 // Equipped creature gets +3/+3 and has first strike.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(3, 3)); Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(3, 3));
ability.addEffect(new GainAbilityAttachedEffect(
FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT
).setText("and has first strike"));
this.addAbility(ability); this.addAbility(ability);
// and has first strike.
Effect effect = new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT);
effect.setText("and has first strike");
ability.addEffect(effect);
// Equip {3} // Equip {3}
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3), new TargetControlledCreaturePermanent(), false)); this.addAbility(new EquipAbility(3, false));
} }
private AshmouthBlade(final AshmouthBlade card) { private AshmouthBlade(final AshmouthBlade card) {

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

@ -73,7 +73,7 @@ class AuratouchedMageEffect extends OneShotEffect {
Card aura = game.getCard(target.getFirstTarget()); Card aura = game.getCard(target.getFirstTarget());
Permanent auratouchedMage = source.getSourcePermanentIfItStillExists(game); Permanent auratouchedMage = source.getSourcePermanentIfItStillExists(game);
if (aura != null && auratouchedMage != null if (aura != null && auratouchedMage != null
&& game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter()) { && game.getState().getZoneChangeCounter(source.getSourceId()) == source.getStackMomentSourceZCC()) {
game.getState().setValue("attachTo:" + aura.getId(), auratouchedMage); game.getState().setValue("attachTo:" + aura.getId(), auratouchedMage);
if (controller.moveCards(aura, Zone.BATTLEFIELD, source, game)) { if (controller.moveCards(aura, Zone.BATTLEFIELD, source, game)) {
auratouchedMage.addAttachment(aura.getId(), source, game); auratouchedMage.addAttachment(aura.getId(), source, game);

View file

@ -1,22 +1,21 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.common.DeliriumCondition; import mage.abilities.condition.common.DeliriumCondition;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
import mage.abilities.effects.common.MillCardsControllerEffect; import mage.abilities.effects.common.MillCardsControllerEffect;
import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.keyword.TransformAbility; import mage.abilities.keyword.TransformAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AbilityWord; import mage.constants.AbilityWord;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.TargetController; import mage.constants.TargetController;
import java.util.UUID;
/** /**
* @author LevelX2 * @author LevelX2
*/ */
@ -31,10 +30,9 @@ public final class AutumnalGloom extends CardImpl {
// <i>Delirium</i> &mdash; At the beginning of your end step, if there are four or more card types among cards in your graveyard, transform Autumnal Gloom. // <i>Delirium</i> &mdash; At the beginning of your end step, if there are four or more card types among cards in your graveyard, transform Autumnal Gloom.
this.addAbility(new TransformAbility()); this.addAbility(new TransformAbility());
Ability ability = new BeginningOfEndStepTriggeredAbility(TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance); this.addAbility(new BeginningOfEndStepTriggeredAbility(
ability.setAbilityWord(AbilityWord.DELIRIUM); TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance
ability.addHint(CardTypesInGraveyardCount.YOU.getHint()); ).setAbilityWord(AbilityWord.DELIRIUM).addHint(CardTypesInGraveyardCount.YOU.getHint()));
this.addAbility(ability);
} }
private AutumnalGloom(final AutumnalGloom card) { private AutumnalGloom(final AutumnalGloom card) {

View file

@ -2,10 +2,10 @@ package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.DayboundAbility; import mage.abilities.keyword.DayboundAbility;
import mage.abilities.keyword.HexproofAbility; import mage.abilities.keyword.HexproofAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
@ -35,9 +35,7 @@ public final class AvabruckCaretaker extends CardImpl {
// At the beginning of combat on your turn, put two +1/+1 counters on another target creature you control. // At the beginning of combat on your turn, put two +1/+1 counters on another target creature you control.
Ability ability = new BeginningOfCombatTriggeredAbility( Ability ability = new BeginningOfCombatTriggeredAbility(
new AddCountersTargetEffect( new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))
CounterType.P1P1.createInstance(2)
)
); );
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL));
this.addAbility(ability); this.addAbility(ability);

View file

@ -0,0 +1,156 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.keyword.FirebendingAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AvatarAang extends CardImpl {
public AvatarAang(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{G}{W}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.AVATAR);
this.subtype.add(SubType.ALLY);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
this.secondSideCardClazz = mage.cards.a.AangMasterOfElements.class;
// Flying
this.addAbility(FlyingAbility.getInstance());
// Firebending 2
this.addAbility(new FirebendingAbility(2));
// Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang.
this.addAbility(new AvatarAangTriggeredAbility());
}
private AvatarAang(final AvatarAang card) {
super(card);
}
@Override
public AvatarAang copy() {
return new AvatarAang(this);
}
}
class AvatarAangTriggeredAbility extends TriggeredAbilityImpl {
private enum AvatarAangCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return AvatarAangWatcher.checkPlayer(game, source);
}
}
AvatarAangTriggeredAbility() {
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1));
this.addEffect(new ConditionalOneShotEffect(
new TransformSourceEffect(), AvatarAangCondition.instance,
"Then if you've done all four this turn, transform {this}"
));
this.setTriggerPhrase("Whenever you waterbend, earthbend, firebend, or airbend, ");
this.addWatcher(new AvatarAangWatcher());
}
private AvatarAangTriggeredAbility(final AvatarAangTriggeredAbility ability) {
super(ability);
}
@Override
public AvatarAangTriggeredAbility copy() {
return new AvatarAangTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
switch (event.getType()) {
case EARTHBENDED:
case AIRBENDED:
case FIREBENDED:
case WATERBENDED:
return true;
default:
return false;
}
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return isControlledBy(event.getPlayerId());
}
}
class AvatarAangWatcher extends Watcher {
private final Set<UUID> earthSet = new HashSet<>();
private final Set<UUID> airSet = new HashSet<>();
private final Set<UUID> fireSet = new HashSet<>();
private final Set<UUID> waterSet = new HashSet<>();
AvatarAangWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
switch (event.getType()) {
case EARTHBENDED:
earthSet.add(event.getPlayerId());
return;
case AIRBENDED:
airSet.add(event.getPlayerId());
return;
case FIREBENDED:
fireSet.add(event.getPlayerId());
return;
case WATERBENDED:
waterSet.add(event.getPlayerId());
}
}
@Override
public void reset() {
super.reset();
earthSet.clear();
airSet.clear();
fireSet.clear();
earthSet.clear();
}
private boolean checkPlayer(UUID playerId) {
return earthSet.contains(playerId)
&& airSet.contains(playerId)
&& fireSet.contains(playerId)
&& earthSet.contains(playerId);
}
static boolean checkPlayer(Game game, Ability source) {
return game.getState().getWatcher(AvatarAangWatcher.class).checkPlayer(source.getControllerId());
}
}

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

@ -1,12 +1,15 @@
package mage.cards.a; package mage.cards.a;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.effects.common.UntapSourceEffect; import mage.abilities.effects.common.UntapSourceEffect;
import mage.abilities.keyword.TransformAbility; import mage.abilities.keyword.TransformAbility;
@ -19,11 +22,10 @@ import mage.constants.SuperType;
import mage.game.ExileZone; import mage.game.ExileZone;
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.util.CardUtil; import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
@ -41,8 +43,13 @@ public final class AzorsGateway extends CardImpl {
// If cards with five or more different converted mana costs are exiled with Azor's Gateway, // If cards with five or more different converted mana costs are exiled with Azor's Gateway,
// you gain 5 life, untap Azor's Gateway, and transform it. // you gain 5 life, untap Azor's Gateway, and transform it.
this.addAbility(new TransformAbility()); this.addAbility(new TransformAbility());
Ability ability = new SimpleActivatedAbility(new AzorsGatewayEffect(), new GenericManaCost(1)); Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new GenericManaCost(1));
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
ability.addEffect(new AzorsGatewayEffect());
ability.addEffect(new ConditionalOneShotEffect(
new GainLifeEffect(5), AzorsGatewayCondition.instance, "If cards with five or more " +
"different mana values are exiled with {this}, you gain 5 life, untap {this}, and transform it."
).addEffect(new UntapSourceEffect()).addEffect(new TransformSourceEffect()));
this.addAbility(ability); this.addAbility(ability);
} }
@ -56,13 +63,28 @@ public final class AzorsGateway extends CardImpl {
} }
} }
enum AzorsGatewayCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source));
return exileZone != null
&& exileZone
.getCards(game)
.stream()
.map(MageObject::getManaValue)
.distinct()
.mapToInt(x -> 1)
.sum() >= 5;
}
}
class AzorsGatewayEffect extends OneShotEffect { class AzorsGatewayEffect extends OneShotEffect {
AzorsGatewayEffect() { AzorsGatewayEffect() {
super(Outcome.Benefit); super(Outcome.Benefit);
this.staticText = "Draw a card, then exile a card from your hand. " + this.staticText = ", then exile a card from your hand";
"If cards with five or more different mana values are exiled with {this}, " +
"you gain 5 life, untap {this}, and transform it";
} }
private AzorsGatewayEffect(final AzorsGatewayEffect effect) { private AzorsGatewayEffect(final AzorsGatewayEffect effect) {
@ -76,37 +98,18 @@ class AzorsGatewayEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(source.getControllerId());
if (controller == null) { if (player == null || player.getHand().isEmpty()) {
return false; return false;
} }
TargetCard target = new TargetCardInHand();
MageObject sourceObject = source.getSourceObject(game); target.withChooseHint("to exile");
if (sourceObject == null) { player.choose(outcome, player.getHand(), target, source, game);
return false; Card card = game.getCard(target.getFirstTarget());
} return card != null && player.moveCardsToExile(
card, source, game, true,
UUID exileId = CardUtil.getCardExileZoneId(game, source); CardUtil.getExileZoneId(game, source),
CardUtil.getSourceLogName(game, source)
controller.drawCards(1, source, game); );
TargetCardInHand target = new TargetCardInHand();
controller.choose(outcome, target, source, game);
Card cardToExile = game.getCard(target.getFirstTarget());
if (cardToExile != null) {
controller.moveCardsToExile(cardToExile, source, game, true, exileId, sourceObject.getIdName());
}
Set<Integer> usedCMC = new HashSet<>();
ExileZone exileZone = game.getExile().getExileZone(exileId);
if (exileZone != null) {
for (Card card : exileZone.getCards(game)) {
usedCMC.add(card.getManaValue());
}
if (usedCMC.size() > 4) {
controller.gainLife(4, game, source);
new UntapSourceEffect().apply(game, source);
new TransformSourceEffect().apply(game, source);
}
}
return true;
} }
} }

View file

@ -0,0 +1,46 @@
package mage.cards.a;
import mage.abilities.Mode;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AzulaAlwaysLies extends CardImpl {
public AzulaAlwaysLies(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{B}");
this.subtype.add(SubType.LESSON);
// Choose one or both --
this.getSpellAbility().getModes().setMinModes(1);
this.getSpellAbility().getModes().setMaxModes(2);
// * Target creature gets -1/-1 until end of turn.
this.getSpellAbility().addEffect(new BoostTargetEffect(-1, -1));
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
// * Put a +1/+1 counter on target creature.
this.getSpellAbility().addMode(new Mode(new AddCountersTargetEffect(CounterType.P1P1.createInstance()))
.addTarget(new TargetCreaturePermanent()));
}
private AzulaAlwaysLies(final AzulaAlwaysLies card) {
super(card);
}
@Override
public AzulaAlwaysLies copy() {
return new AzulaAlwaysLies(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

@ -0,0 +1,53 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
import mage.abilities.effects.keyword.EarthbendTargetEffect;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.common.TargetControlledLandPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class Badgermole extends CardImpl {
public Badgermole(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}");
this.subtype.add(SubType.BADGER);
this.subtype.add(SubType.MOLE);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// When this creature enters, earthbend 2.
Ability ability = new EntersBattlefieldTriggeredAbility(new EarthbendTargetEffect(2));
ability.addTarget(new TargetControlledLandPermanent());
this.addAbility(ability);
// Creatures you control with +1/+1 counters on them have trample.
this.addAbility(new SimpleStaticAbility(new GainAbilityAllEffect(
TrampleAbility.getInstance(), Duration.WhileOnBattlefield,
StaticFilters.FILTER_CONTROLLED_CREATURES_P1P1
)));
}
private Badgermole(final Badgermole card) {
super(card);
}
@Override
public Badgermole copy() {
return new Badgermole(this);
}
}

View file

@ -111,7 +111,7 @@ class BagOfHoldingReturnCardsEffect extends OneShotEffect {
return false; return false;
} }
ExileZone exileZone = game.getExile().getExileZone( ExileZone exileZone = game.getExile().getExileZone(
CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()) CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC())
); );
if (exileZone == null) { if (exileZone == null) {
return true; return true;

View file

@ -0,0 +1,55 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.token.FoodAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BagelAndSchmear extends CardImpl {
public BagelAndSchmear(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}");
this.subtype.add(SubType.FOOD);
// Share -- {W}, {T}, Sacrifice this artifact: Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.
Ability ability = new ActivateAsSorceryActivatedAbility(
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl<>("{W}")
);
ability.addCost(new TapSourceCost());
ability.addCost(new SacrificeSourceCost());
ability.addEffect(new DrawCardSourceControllerEffect(1));
ability.addTarget(new TargetCreaturePermanent(0, 1));
this.addAbility(ability.withFlavorWord("Share"));
// Nosh -- {2}, {T}, Sacrifice this artifact: You gain 3 life and draw a card.
ability = new FoodAbility();
ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and"));
this.addAbility(ability.withFlavorWord("Nosh"));
}
private BagelAndSchmear(final BagelAndSchmear card) {
super(card);
}
@Override
public BagelAndSchmear copy() {
return new BagelAndSchmear(this);
}
}
// where's the lox?

View file

@ -3,7 +3,6 @@ package mage.cards.b;
import mage.MageInt; import mage.MageInt;
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.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.CantBlockTargetEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect;
@ -72,15 +71,15 @@ class BallistaWielderEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getFirstTarget()); Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) { if (permanent == null) {
Player player = game.getPlayer(source.getFirstTarget()); Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
return player != null && player.damage(1, source, game) > 0; return player != null && player.damage(1, source, game) > 0;
} }
if (permanent.damage(1, source, game) > 0) { if (permanent.damage(1, source, game) <= 0) {
return false;
}
game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn), source); game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn), source);
return true; return true;
} }
return false;
}
} }

View file

@ -36,7 +36,7 @@ public final class BalthierAndFran extends CardImpl {
= new FilterCreaturePermanent(SubType.VEHICLE, "a Vehicle crewed by {this} this turn"); = new FilterCreaturePermanent(SubType.VEHICLE, "a Vehicle crewed by {this} this turn");
static { static {
filter.add(BalthierAndFranPredicate.instance); filter2.add(BalthierAndFranPredicate.instance);
} }
public BalthierAndFran(UUID ownerId, CardSetInfo setInfo) { public BalthierAndFran(UUID ownerId, CardSetInfo setInfo) {

View file

@ -1,7 +1,5 @@
package mage.cards.b; package mage.cards.b;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
@ -11,11 +9,15 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.ColorlessManaAbility;
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.SubType;
import mage.constants.SuperType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/** /**
* @author North * @author North
*/ */
@ -24,7 +26,6 @@ public final class BantPanorama extends CardImpl {
private static final FilterCard filter = new FilterCard("a basic Forest, Plains, or Island card"); private static final FilterCard filter = new FilterCard("a basic Forest, Plains, or Island card");
static { static {
filter.add(CardType.LAND.getPredicate());
filter.add(SuperType.BASIC.getPredicate()); filter.add(SuperType.BASIC.getPredicate());
filter.add(Predicates.or( filter.add(Predicates.or(
SubType.FOREST.getPredicate(), SubType.FOREST.getPredicate(),

View file

@ -0,0 +1,48 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.mana.AddManaOfAnyColorEffect;
import mage.abilities.mana.LimitedTimesPerTurnActivatedManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BarrelsOfBlastingJelly extends CardImpl {
public BarrelsOfBlastingJelly(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}");
// {1}: Add one mana of any color. Activate only once each turn.
this.addAbility(new LimitedTimesPerTurnActivatedManaAbility(
Zone.BATTLEFIELD, new AddManaOfAnyColorEffect(), new GenericManaCost(1)
));
// {5}, {T}, Sacrifice this artifact: It deals 5 damage to target creature.
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(5, "it"), new GenericManaCost(5));
ability.addCost(new TapSourceCost());
ability.addCost(new SacrificeSourceCost());
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
}
private BarrelsOfBlastingJelly(final BarrelsOfBlastingJelly card) {
super(card);
}
@Override
public BarrelsOfBlastingJelly copy() {
return new BarrelsOfBlastingJelly(this);
}
}

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

@ -121,11 +121,11 @@ class BeamsplitterMageTriggeredAbility extends TriggeredAbilityImpl {
private boolean checkNotSource(Permanent permanent, Game game) { private boolean checkNotSource(Permanent permanent, Game game) {
// workaround for zcc not being set before first intervening if check // workaround for zcc not being set before first intervening if check
if (this.getSourceObjectZoneChangeCounter() == 0) { if (this.getStackMomentSourceZCC() == 0) {
return !permanent.getId().equals(this.getSourceId()); return !permanent.getId().equals(this.getSourceId());
} }
return !permanent.getId().equals(this.getSourceId()) return !permanent.getId().equals(this.getSourceId())
|| permanent.getZoneChangeCounter(game) != this.getSourceObjectZoneChangeCounter(); || permanent.getZoneChangeCounter(game) != this.getStackMomentSourceZCC();
} }
@Override @Override

View file

@ -0,0 +1,46 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
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.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BeetleHeadedMerchants extends CardImpl {
public BeetleHeadedMerchants(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CITIZEN);
this.power = new MageInt(5);
this.toughness = new MageInt(4);
// Whenever this creature attacks, you may sacrifice another creature or artifact. If you do, draw a card and put a +1/+1 counter on this creature.
this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid(
new DrawCardSourceControllerEffect(1),
new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT)
).addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy("and"))));
}
private BeetleHeadedMerchants(final BeetleHeadedMerchants card) {
super(card);
}
@Override
public BeetleHeadedMerchants copy() {
return new BeetleHeadedMerchants(this);
}
}

View file

@ -44,7 +44,7 @@ public final class BeetleLegacyCriminal extends CardImpl {
ability.addCost(new ExileSourceFromGraveCost()); ability.addCost(new ExileSourceFromGraveCost());
ability.addTarget(new TargetCreaturePermanent()); ability.addTarget(new TargetCreaturePermanent());
ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()) ability.addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance())
.setText("Put a +1/+1 counter on target creature")); .setText("It gains flying until end of turn"));
this.addAbility(ability); this.addAbility(ability);
} }

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

@ -0,0 +1,91 @@
package mage.cards.b;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.common.FilterPermanentCard;
import mage.game.Game;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public final class BeholdTheSinisterSix extends CardImpl {
public BeholdTheSinisterSix(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{6}{B}");
// Return up to six target creature cards with different names from your graveyard to the battlefield.
this.getSpellAbility().addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect());
this.getSpellAbility().addTarget(new BeholdTheSinisterSixTarget());
}
private BeholdTheSinisterSix(final BeholdTheSinisterSix card) {
super(card);
}
@Override
public BeholdTheSinisterSix copy() {
return new BeholdTheSinisterSix(this);
}
}
class BeholdTheSinisterSixTarget extends TargetCardInYourGraveyard {
private static final FilterCard filter = new FilterPermanentCard("creature cards with different names");
BeholdTheSinisterSixTarget() {
super(0, 6, filter, false);
}
private BeholdTheSinisterSixTarget(final BeholdTheSinisterSixTarget target) {
super(target);
}
@Override
public BeholdTheSinisterSixTarget copy() {
return new BeholdTheSinisterSixTarget(this);
}
@Override
public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) {
if (!super.canTarget(playerId, id, ability, game)) {
return false;
}
Set<String> names = this.getTargets()
.stream()
.map(game::getCard)
.map(MageObject::getName)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Card card = game.getCard(id);
return card != null && !names.contains(card.getName());
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<String> names = this.getTargets()
.stream()
.map(game::getCard)
.map(MageObject::getName)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
possibleTargets.removeIf(uuid -> {
Card card = game.getCard(uuid);
return card != null && names.contains(card.getName());
});
return possibleTargets;
}
}

View file

@ -0,0 +1,35 @@
package mage.cards.b;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.UntapSourceDuringEachOtherPlayersUntapStepEffect;
import mage.abilities.mana.AnyColorManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BendersWaterskin extends CardImpl {
public BendersWaterskin(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
// Untap this artifact during each other player's untap step.
this.addAbility(new SimpleStaticAbility(new UntapSourceDuringEachOtherPlayersUntapStepEffect()));
// {T}: Add one mana of any color.
this.addAbility(new AnyColorManaAbility());
}
private BendersWaterskin(final BendersWaterskin card) {
super(card);
}
@Override
public BendersWaterskin copy() {
return new BendersWaterskin(this);
}
}

View file

@ -1,10 +1,9 @@
package mage.cards.b; package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.CantBeCounteredControlledEffect; import mage.abilities.effects.common.CantBeCounteredControlledEffect;
import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.keyword.DisturbAbility;
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;
@ -39,7 +38,7 @@ public final class BenevolentGeist extends CardImpl {
))); )));
// If Benevolent Geist would be put into a graveyard from anywhere, exile it instead. // If Benevolent Geist would be put into a graveyard from anywhere, exile it instead.
this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead"))); this.addAbility(DisturbAbility.makeBackAbility());
} }
private BenevolentGeist(final BenevolentGeist card) { private BenevolentGeist(final BenevolentGeist card) {

View file

@ -13,7 +13,6 @@ import mage.constants.SagaChapter;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterBySubtypeCard;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
@ -24,7 +23,7 @@ import java.util.UUID;
*/ */
public final class BindingTheOldGods extends CardImpl { public final class BindingTheOldGods extends CardImpl {
private static final FilterCard filter = new FilterBySubtypeCard(SubType.FOREST); private static final FilterCard filter = new FilterCard(SubType.FOREST);
public BindingTheOldGods(UUID ownerId, CardSetInfo setInfo) { public BindingTheOldGods(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{G}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{G}");
@ -33,15 +32,18 @@ public final class BindingTheOldGods extends CardImpl {
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)
SagaAbility sagaAbility = new SagaAbility(this); SagaAbility sagaAbility = new SagaAbility(this);
// I Destroy target nonland permanent an opponent controls. // I Destroy target nonland permanent an opponent controls.
sagaAbility.addChapterEffect( sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I, this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I,
new DestroyTargetEffect(), new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND) new DestroyTargetEffect(), new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND)
); );
// II Search your library for a Forest card, put it onto the battlefield tapped, then shuffle your library. // II Search your library for a Forest card, put it onto the battlefield tapped, then shuffle your library.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II,
new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)
); );
// III Creatures you control gain deathtouch until end of turn. // III Creatures you control gain deathtouch until end of turn.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III,
new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn, new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn,

View file

@ -0,0 +1,64 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageTriggeredAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.ModifiedPredicate;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
*
* @author Jmlundeen
*/
public final class BiorganicCarapace extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("each modified creature you control");
static {
filter.add(ModifiedPredicate.instance);
}
public BiorganicCarapace(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}{U}");
this.subtype.add(SubType.EQUIPMENT);
// When this Equipment enters, attach it to target creature you control.
Ability ability = new EntersBattlefieldAbility(new AttachEffect(Outcome.BoostCreature));
ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(ability);
// Equipped creature gets +2/+2 and has "Whenever this creature deals combat damage to a player, draw a card for each modified creature you control."
Ability gainedAbility = new DealsCombatDamageTriggeredAbility(new DrawCardSourceControllerEffect(new PermanentsOnBattlefieldCount(filter)), false);
Ability equipAbility = new SimpleStaticAbility(new BoostEquippedEffect(2, 2));
equipAbility.addEffect(new GainAbilityAttachedEffect(gainedAbility, null)
.concatBy("and"));
this.addAbility(equipAbility);
// Equip {2}
this.addAbility(new EquipAbility(2));
}
private BiorganicCarapace(final BiorganicCarapace card) {
super(card);
}
@Override
public BiorganicCarapace copy() {
return new BiorganicCarapace(this);
}
}

View file

@ -18,13 +18,11 @@ import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.util.CardUtil; import mage.util.CardUtil;
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE; import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
@ -87,7 +85,7 @@ class BishopOfBindingExileEffect extends OneShotEffect {
// the target creature won't be exiled. // the target creature won't be exiled.
if (permanent != null) { if (permanent != null) {
new ExileTargetEffect( new ExileTargetEffect(
CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), permanent.getIdName() CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), permanent.getIdName()
).apply(game, source); ).apply(game, source);
game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source);
return true; return true;
@ -101,7 +99,7 @@ enum BishopOfBindingValue implements DynamicValue {
@Override @Override
public int calculate(Game game, Ability sourceAbility, Effect effect) { public int calculate(Game game, Ability sourceAbility, Effect effect) {
ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, sourceAbility.getSourceId(), sourceAbility.getSourceObjectZoneChangeCounter())); ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, sourceAbility.getSourceId(), sourceAbility.getStackMomentSourceZCC()));
if (exileZone != null) { if (exileZone != null) {
Card exiledCard = exileZone.getRandom(game); Card exiledCard = exileZone.getRandom(game);
if (exiledCard != null) { if (exiledCard != null) {

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

@ -0,0 +1,91 @@
package mage.cards.b;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTargets;
import java.util.UUID;
import java.util.stream.Collectors;
/**
*
* @author Jmlundeen
*/
public final class BlackCatCunningThief extends CardImpl {
public BlackCatCunningThief(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.ROGUE);
this.subtype.add(SubType.VILLAIN);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// When Black Cat enters, look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way.
Ability ability = new EntersBattlefieldTriggeredAbility(new BlackCatCunningThiefEffect());
ability.addTarget(new TargetOpponent());
this.addAbility(ability);
}
private BlackCatCunningThief(final BlackCatCunningThief card) {
super(card);
}
@Override
public BlackCatCunningThief copy() {
return new BlackCatCunningThief(this);
}
}
class BlackCatCunningThiefEffect extends OneShotEffect {
BlackCatCunningThiefEffect() {
super(Outcome.Benefit);
this.staticText = "look at the top nine cards of target opponent's library, exile two of them face down, then put the rest on the bottom of their library in a random order. You may play the exiled cards for as long as they remain exiled. Mana of any type can be spent to cast spells this way";
}
private BlackCatCunningThiefEffect(final BlackCatCunningThiefEffect effect) {
super(effect);
}
@Override
public BlackCatCunningThiefEffect copy() {
return new BlackCatCunningThiefEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source));
MageObject sourceObject = source.getSourceObject(game);
if (controller == null || opponent == null || sourceObject == null) {
return false;
}
Cards topCards = new CardsImpl();
topCards.addAllCards(opponent.getLibrary().getTopCards(game, 9));
TargetCard target = new TargetCard(2, 2, Zone.LIBRARY, new FilterCard("card to exile"));
controller.choose(outcome, topCards, target, source, game);
Cards exiledCards = new CardsImpl(target.getTargets().stream()
.map(game::getCard)
.collect(Collectors.toList()));
new ExileFaceDownYouMayPlayAsLongAsExiledTargetEffect(false, CastManaAdjustment.AS_THOUGH_ANY_MANA_TYPE)
.setTargetPointer(new FixedTargets(exiledCards, game))
.apply(game, source);
topCards.retainZone(Zone.LIBRARY, game);
// then put the rest on the bottom of that library in a random order
controller.putCardsOnBottomOfLibrary(topCards, game, source, false);
return true;
}
}

View file

@ -59,7 +59,7 @@ enum BlazingEffigyCount implements DynamicValue {
if (watcher == null) { if (watcher == null) {
return 3; return 3;
} }
int effigyDamage = watcher.damageDoneTo(sourceAbility.getSourceId(), sourceAbility.getSourceObjectZoneChangeCounter() - 1, game); int effigyDamage = watcher.damageDoneTo(sourceAbility.getSourceId(), sourceAbility.getStackMomentSourceZCC() - 1, game);
return CardUtil.overflowInc(3, effigyDamage); return CardUtil.overflowInc(3, effigyDamage);
} }

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

@ -51,7 +51,7 @@ public final class Blink extends CardImpl {
this, SagaChapter.CHAPTER_IV, this, SagaChapter.CHAPTER_IV,
new CreateTokenEffect(new AlienAngelToken()) new CreateTokenEffect(new AlienAngelToken())
); );
this.addAbility(sagaAbility); this.addAbility(sagaAbility); //TODO: These should be a single AddChapterEffect, but currently XMage does not support noncontiguous Saga chapters
} }
private Blink(final Blink card) { private Blink(final Blink card) {

View file

@ -2,7 +2,6 @@ package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.Condition; import mage.abilities.condition.Condition;
import mage.abilities.condition.common.YouGainedLifeCondition; import mage.abilities.condition.common.YouGainedLifeCondition;
@ -12,10 +11,14 @@ import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect;
import mage.abilities.hint.ConditionHint; import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint; import mage.abilities.hint.Hint;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
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.SubType;
import mage.constants.TargetController;
import mage.game.permanent.token.BloodToken; import mage.game.permanent.token.BloodToken;
import mage.watchers.common.PlayerGainedLifeWatcher;
import java.util.UUID; import java.util.UUID;
@ -40,7 +43,7 @@ public final class BloodsoakedReveler extends CardImpl {
this.addAbility(new BeginningOfEndStepTriggeredAbility( this.addAbility(new BeginningOfEndStepTriggeredAbility(
TargetController.YOU, new CreateTokenEffect(new BloodToken()), TargetController.YOU, new CreateTokenEffect(new BloodToken()),
false, condition false, condition
).addHint(hint)); ).addHint(hint), new PlayerGainedLifeWatcher());
// {4}{B}: Each opponent loses 2 life and you gain 2 life. // {4}{B}: Each opponent loses 2 life and you gain 2 life.
Ability ability = new SimpleActivatedAbility( Ability ability = new SimpleActivatedAbility(

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