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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,6 +28,8 @@ public class ScryfallImageSupportCards {
add("2ED"); // Unlimited Edition
//add("CEI"); // Intl. Collectors Edition
//add("CED"); // Collectors Edition
add("RIN"); // Rinascimento
add("REN"); // Renaissance
add("ARN"); // Arabian Nights
add("ATQ"); // Antiquities
//add("FBB"); // Foreign Black Border
@ -465,6 +467,7 @@ public class ScryfallImageSupportCards {
// add("MD1"); // Modern Event Deck
// add("DD3"); // Duel Decks Anthology
// add("PZ1"); // Legendary Cube
add("PLG20"); // Love Your LGS 2020
add("IKO"); // Ikoria: Lair of Behemoths
add("C20"); // Commander 2020
add("M21"); // Core Set 2021
@ -514,10 +517,13 @@ public class ScryfallImageSupportCards {
add("NCC"); // New Capenna Commander
add("SLX"); // Universes Within
add("CLB"); // Commander Legends: Battle for Baldur's Gate
add("PLG22"); // Love Your LGS 2022
add("2X2"); // Double Masters 2022
add("SCH"); // Store Championships
add("DMU"); // Dominaria United
add("DMC"); // Dominaria United Commander
add("YDMU"); // Alchemy: Dominaria
add("PRCQ"); // Regional Championship Qualifiers 2022
add("40K"); // Warhammer 40,000 Commander
add("UNF"); // Unfinity
add("GN3"); // Game Night: Free-for-All
@ -545,9 +551,11 @@ public class ScryfallImageSupportCards {
add("30A"); // 30th Anniversary Edition
add("P30A"); // 30th Anniversary Play Promos
add("P30M"); // 30th Anniversary Misc Promos
add("P30H"); // 30th Anniversary History Promos
add("PEWK"); // Eternal Weekend
add("LTR"); // The Lord of the Rings: Tales of Middle-Earth
add("LTC"); // Tales of Middle-Earth Commander
add("PF23"); // MagicFest 2023
add("CMM"); // Commander Masters
add("WHO"); // Doctor Who
add("WOE"); // Wilds of Eldraine
@ -558,10 +566,13 @@ public class ScryfallImageSupportCards {
add("REX"); // Jurassic World Collection
add("SPG"); // Special Guests
add("PW24"); // Wizards Play Network 2024
add("PF24"); // MagicFest 2024
add("RVR"); // Ravnica Remastered
add("PL24"); // Year of the Dragon 2024
add("PIP"); // Fallout
add("MKM"); // Murders at Karlov Manor
add("MKC"); // Murders at Karlov Manor Commander
add("PSS4"); // MKM Standard Showdown
add("CLU"); // Ravnica: Clue Edition
add("OTJ"); // Outlaws of Thunder Junction
add("OTC"); // Outlaws of Thunder Junction Commander
@ -569,9 +580,12 @@ public class ScryfallImageSupportCards {
add("BIG"); // The Big Score
add("MH3"); // Modern Horizons 3
add("M3C"); // Modern Horizons 3 Commander
add("H2R"); // Modern Horizons 2 Timeshifts
add("ACR"); // Assassin's Creed
add("BLB"); // Bloomburrow
add("BLC"); // Bloomburrow Commander
add("PLG24"); // Love Your LGS 2024
add("PCBB"); // Cowboy Bebop
add("MB2"); // Mystery Booster 2
add("DSK"); // Duskmourn: House of Horror
add("DSC"); // Duskmourn: House of Horror Commander
@ -579,21 +593,26 @@ public class ScryfallImageSupportCards {
add("J25"); // Foundations Jumpstart
add("PIO"); // Pioneer Masters
add("PW25"); // Wizards Play Network 2025
add("PSPL"); // Spotlight Series
add("INR"); // Innistrad Remastered
add("PF25"); // MagicFest 2025
add("PL25"); // Year of the Snake 2025
add("DFT"); // Aetherdrift
add("DRC"); // Aetherdrift Commander
add("PLG25"); // Love Your LGS 2025
add("TDM"); // Tarkir: Dragonstorm
add("TDC"); // Tarkir: Dragonstorm Commander
add("FIN"); // Final Fantasy
add("FIC"); // Final Fantasy Commander
add("FCA"); // Final Fantasy: Through the Ages
add("PSS5"); // FIN Standard Showdown
add("EOE"); // Edge of Eternities
add("EOC"); // Edge of Eternities Commander
add("EOS"); // Edge of Eternities: Stellar Sights
add("SPM"); // Marvel's Spider-Man
add("SPE"); // Marvel's Spider-Man Eternal
add("TLA"); // Avatar: The Last Airbender
add("TLE"); // Avatar: The Last Airbender Eternal
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
add("CALC"); // Custom Alchemized versions of existing cards

View file

@ -1,10 +1,10 @@
package org.mage.plugins.card.dl.sources;
import mage.cards.repository.TokenRepository;
import java.util.HashMap;
import java.util.Map;
import mage.cards.repository.TokenRepository;
/**
* @author JayDi85
*/
@ -837,6 +837,8 @@ public class ScryfallImageSupportTokens {
put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?format=image");
put("SLD/Icingdeath, Frost Tongue", "https://api.scryfall.com/cards/sld/1018?format=image");
put("SLD/Marit Lage", "https://api.scryfall.com/cards/sld/1681?format=image");
put("SLD/Mechtitan/1", "https://api.scryfall.com/cards/sld/1969?format=image");
put("SLD/Mechtitan/2", "https://api.scryfall.com/cards/sld/1969/en?format=image&face=back");
put("SLD/Mechtitan", "https://api.scryfall.com/cards/sld/1969?format=image");
put("SLD/Myr", "https://api.scryfall.com/cards/sld/2101?format=image");
put("SLD/Saproling", "https://api.scryfall.com/cards/sld/1139?format=image");
@ -2787,6 +2789,7 @@ public class ScryfallImageSupportTokens {
// EOE
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/Lander/1", "https://api.scryfall.com/cards/teoe/4?format=image");
put("EOE/Lander/2", "https://api.scryfall.com/cards/teoe/5?format=image");
@ -2928,6 +2931,12 @@ public class ScryfallImageSupportTokens {
// PL23
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
supportedSets.clear();
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"));
}
@Test
public void test_DownloadText_ScryfallUtf8() {
String s = XmageURLConnection.downloadText("https://api.scryfall.com/cards/sld/379★/en");
Assert.assertTrue("must have text data (utf8 url must work)", s.contains("Zndrsplt, Eye of Wisdom"));
}
@Test
public void test_DownloadFile_ByHttp() throws IOException {
// use any public image here

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 {
private static final FilterCard filter = new FilterCard("spells");
public AccursedWitch(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
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.
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.

View file

@ -13,10 +13,8 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterBasicLandCard;
import mage.filter.common.FilterLandPermanent;
import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate;
import mage.game.Controllable;
@ -32,8 +30,6 @@ import java.util.stream.Collectors;
*/
public final class AerialSurveyor extends CardImpl {
private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS);
public AerialSurveyor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}");
@ -45,7 +41,7 @@ public final class AerialSurveyor extends CardImpl {
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.
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)
.addHint(LandsYouControlHint.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());
if (oldCreature != null) {
boolean canAttach = enchantment.getSpellAbility() == null
|| (!enchantment.getSpellAbility().getTargets().isEmpty() && enchantment.getSpellAbility().getTargets().get(0).canTarget(tokenPermanent.getId(), game));
|| (!enchantment.getSpellAbility().getTargets().isEmpty() && enchantment.getSpellAbility().getTargets().get(0).canTarget(tokenPermanent.getId(), source, game));
if (canAttach && controller.chooseUse(Outcome.Neutral, "Attach " + enchantment.getName() + " to the token ?", source, game)) {
if (oldCreature.removeAttachment(enchantment.getId(), source, game)) {
tokenPermanent.addAttachment(enchantment.getId(), source, game);

View file

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

View file

@ -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.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterBySubtypeCard;
import mage.filter.common.FilterControlledPermanent;
import mage.target.common.TargetCardInLibrary;
@ -24,7 +23,7 @@ import java.util.UUID;
*/
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");
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 "
+ "lose all land types and abilities, "
+ "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) {
@ -84,9 +90,6 @@ class AlpineMoonEffect extends ContinuousEffectImpl {
for (Permanent land : game.getBattlefield().getActivePermanents(filter2, source.getControllerId(), game)) {
switch (layer) {
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);
break;
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.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
@ -25,13 +24,6 @@ import java.util.UUID;
*/
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) {
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.
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.

View file

@ -14,7 +14,6 @@ import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterControlledPermanent;
import mage.game.permanent.token.custom.CreatureToken;
import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
@ -42,7 +41,11 @@ public final class AmbushCommander extends CardImpl {
ContinuousEffect effect = new BecomesCreatureAllEffect(
new CreatureToken(1, 1, "1/1 green Elf creatures").withColor("G").withSubType(SubType.ELF),
"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));
// {1}{G}, Sacrifice an Elf: Target creature gets +3/+3 until end of turn.

View file

@ -1,7 +1,6 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.ObjectColor;
@ -16,6 +15,8 @@ import mage.filter.predicate.mageobject.ColorPredicate;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author fireshoes
@ -29,7 +30,7 @@ public final class AnHavvaConstable extends CardImpl {
this.toughness = new MageInt(1);
// 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) {
@ -72,7 +73,7 @@ class AnHavvaConstableEffect extends ContinuousEffectImpl {
FilterCreaturePermanent filter = new FilterCreaturePermanent("green creatures");
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);

View file

@ -1,11 +1,9 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.DisturbAbility;
import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
@ -32,8 +30,7 @@ public final class AncestorsEmbrace extends CardImpl {
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget);
this.addAbility(ability);
this.addAbility(new EnchantAbility(auraTarget));
// Enchanted creature has lifelink.
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.
this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead")));
this.addAbility(DisturbAbility.makeBackAbility());
}
private AncestorsEmbrace(final AncestorsEmbrace card) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.filter.FilterCard;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterLandPermanent;
@ -26,13 +23,10 @@ import java.util.UUID;
*/
public final class ArchaeomancersMap extends CardImpl {
private static final FilterCard filter = new FilterCard("basic Plains cards");
private static final FilterPermanent filter2 = new FilterLandPermanent("a land an opponent controls");
private static final FilterPermanent filter = new FilterLandPermanent("a land an opponent controls");
static {
filter.add(SubType.PLAINS.getPredicate());
filter.add(SuperType.BASIC.getPredicate());
filter2.add(TargetController.OPPONENT.getControllerPredicate());
filter.add(TargetController.OPPONENT.getControllerPredicate());
}
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.
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.
this.addAbility(new EntersBattlefieldAllTriggeredAbility(
new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter2
new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter
).withInterveningIf(ArchaeomancersMapCondition.instance));
}

View file

@ -1,21 +1,18 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.costs.common.SacrificeTargetCost;
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.TrampleAbility;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
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;
@ -24,11 +21,7 @@ import java.util.UUID;
*/
public final class ArchdemonOfGreed extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent("Human");
static {
filter.add(SubType.HUMAN.getPredicate());
}
private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.HUMAN, "Human");
public ArchdemonOfGreed(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
@ -44,7 +37,10 @@ public final class ArchdemonOfGreed extends CardImpl {
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.
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) {
@ -55,48 +51,4 @@ public final class ArchdemonOfGreed extends CardImpl {
public ArchdemonOfGreed copy() {
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.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.effects.common.DrawDiscardControllerEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.keyword.DisturbAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -35,7 +34,7 @@ public final class ArchiveHaunt extends CardImpl {
this.addAbility(new AttacksTriggeredAbility(new DrawDiscardControllerEffect(1, 1)));
// 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) {

View file

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

View file

@ -1,9 +1,7 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.GetEmblemEffect;
import mage.abilities.effects.common.TransformSourceEffect;
@ -21,8 +19,9 @@ import mage.filter.StaticFilters;
import mage.game.command.emblems.ArlinnEmbracedByTheMoonEmblem;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class ArlinnEmbracedByTheMoon extends CardImpl {
@ -37,12 +36,14 @@ public final class ArlinnEmbracedByTheMoon extends CardImpl {
this.nightCard = true;
// +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);
effect.setText("Creatures you control get +1/+1");
LoyaltyAbility ability = new LoyaltyAbility(effect, 1);
effect = new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE);
effect.setText("and gain trample until end of turn");
ability.addEffect(effect);
Ability ability = new LoyaltyAbility(new BoostControlledEffect(
1, 1, Duration.EndOfTurn,
StaticFilters.FILTER_PERMANENT_CREATURE
).setText("Creatures you control get +1/+1"), 1);
ability.addEffect(new GainAbilityControlledEffect(
TrampleAbility.getInstance(), Duration.EndOfTurn,
StaticFilters.FILTER_PERMANENT_CREATURE
).setText("and gain trample until end of turn"));
this.addAbility(ability);
// -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;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
@ -20,14 +18,15 @@ import mage.constants.SuperType;
import mage.game.permanent.token.WolfToken;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class ArlinnKord extends CardImpl {
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.subtype.add(SubType.ARLINN);
@ -36,15 +35,15 @@ public final class ArlinnKord extends CardImpl {
this.setStartingLoyalty(3);
// +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);
effect.setText("Until end of turn, up to one target creature gets +2/+2");
LoyaltyAbility ability = new LoyaltyAbility(effect, 1);
effect = new GainAbilityTargetEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn);
effect.setText("and gains vigilance");
ability.addEffect(effect);
effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn);
effect.setText("and haste");
ability.addEffect(effect);
Ability ability = new LoyaltyAbility(new BoostTargetEffect(
2, 2, Duration.EndOfTurn
).setText("until end of turn, up to one target creature gets +2/+2"), 1);
ability.addEffect(new GainAbilityTargetEffect(
VigilanceAbility.getInstance(), Duration.EndOfTurn
).setText("and gains vigilance"));
ability.addEffect(new GainAbilityTargetEffect(
HasteAbility.getInstance(), Duration.EndOfTurn
).setText("and haste"));
ability.addTarget(new TargetCreaturePermanent(0, 1));
this.addAbility(ability);

View file

@ -18,7 +18,7 @@ import mage.cards.CardSetInfo;
import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.common.FilterBySubtypeCard;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.util.CardUtil;
@ -30,6 +30,8 @@ import java.util.UUID;
*/
public final class ArmMountedAnchor extends CardImpl {
private static final FilterCard filter = new FilterCard(SubType.PIRATE);
public ArmMountedAnchor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
this.subtype.add(SubType.EQUIPMENT);
@ -42,11 +44,12 @@ public final class ArmMountedAnchor extends CardImpl {
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.
Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility(new DrawCardSourceControllerEffect(2), "equipped creature", false);
DiscardCardCost cost = new DiscardCardCost(new FilterBySubtypeCard(SubType.PIRATE));
cost.setText("Discard a Pirate card instead of discarding two cards");
Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility(
new DrawCardSourceControllerEffect(2), "equipped creature", false
);
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"));
this.addAbility(drawAbility);

View file

@ -1,7 +1,6 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
@ -9,11 +8,16 @@ import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.ProtectionAbility;
import mage.cards.CardImpl;
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.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author MarcoMarin
@ -33,7 +37,7 @@ public final class ArtifactWard extends CardImpl {
// Enchanted creature can't be blocked by artifact creatures.
// 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.
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)));
}

View file

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

View file

@ -90,7 +90,7 @@ class AshioksErasureExileEffect extends OneShotEffect {
|| spell == null) {
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());
}
}
@ -126,7 +126,7 @@ class AshioksErasureReplacementEffect extends ContinuousRuleModifyingEffectImpl
|| card == null) {
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);
if (exile == null) {

View file

@ -1,44 +1,40 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.Ability;
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.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class AshmouthBlade extends CardImpl {
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 card is the second face of double-faced card
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.addEffect(new GainAbilityAttachedEffect(
FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT
).setText("and has first strike"));
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}
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3), new TargetControlledCreaturePermanent(), false));
this.addAbility(new EquipAbility(3, false));
}
private AshmouthBlade(final AshmouthBlade card) {

View file

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

View file

@ -73,7 +73,7 @@ class AuratouchedMageEffect extends OneShotEffect {
Card aura = game.getCard(target.getFirstTarget());
Permanent auratouchedMage = source.getSourcePermanentIfItStillExists(game);
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);
if (controller.moveCards(aura, Zone.BATTLEFIELD, source, game)) {
auratouchedMage.addAttachment(aura.getId(), source, game);

View file

@ -1,22 +1,21 @@
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.condition.common.DeliriumCondition;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
import mage.abilities.effects.common.MillCardsControllerEffect;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.keyword.TransformAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.AbilityWord;
import mage.constants.CardType;
import mage.constants.TargetController;
import java.util.UUID;
/**
* @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.
this.addAbility(new TransformAbility());
Ability ability = new BeginningOfEndStepTriggeredAbility(TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance);
ability.setAbilityWord(AbilityWord.DELIRIUM);
ability.addHint(CardTypesInGraveyardCount.YOU.getHint());
this.addAbility(ability);
this.addAbility(new BeginningOfEndStepTriggeredAbility(
TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance
).setAbilityWord(AbilityWord.DELIRIUM).addHint(CardTypesInGraveyardCount.YOU.getHint()));
}
private AutumnalGloom(final AutumnalGloom card) {

View file

@ -2,10 +2,10 @@ package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.DayboundAbility;
import mage.abilities.keyword.HexproofAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
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.
Ability ability = new BeginningOfCombatTriggeredAbility(
new AddCountersTargetEffect(
CounterType.P1P1.createInstance(2)
)
new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))
);
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL));
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;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.decorator.ConditionalOneShotEffect;
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.UntapSourceEffect;
import mage.abilities.keyword.TransformAbility;
@ -19,11 +22,10 @@ import mage.constants.SuperType;
import mage.game.ExileZone;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInHand;
import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Set;
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,
// you gain 5 life, untap Azor's Gateway, and transform it.
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.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);
}
@ -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 {
AzorsGatewayEffect() {
super(Outcome.Benefit);
this.staticText = "Draw a card, 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";
this.staticText = ", then exile a card from your hand";
}
private AzorsGatewayEffect(final AzorsGatewayEffect effect) {
@ -76,37 +98,18 @@ class AzorsGatewayEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
Player player = game.getPlayer(source.getControllerId());
if (player == null || player.getHand().isEmpty()) {
return false;
}
MageObject sourceObject = source.getSourceObject(game);
if (sourceObject == null) {
return false;
}
UUID exileId = CardUtil.getCardExileZoneId(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;
TargetCard target = new TargetCardInHand();
target.withChooseHint("to exile");
player.choose(outcome, player.getHand(), target, source, game);
Card card = game.getCard(target.getFirstTarget());
return card != null && player.moveCardsToExile(
card, source, game, true,
CardUtil.getExileZoneId(game, source),
CardUtil.getSourceLogName(game, source)
);
}
}

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

View file

@ -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;
}
ExileZone exileZone = game.getExile().getExileZone(
CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter())
CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC())
);
if (exileZone == null) {
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.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.CantBlockTargetEffect;
@ -72,15 +71,15 @@ class BallistaWielderEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getFirstTarget());
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
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;
}
if (permanent.damage(1, source, game) > 0) {
game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn), source);
return true;
if (permanent.damage(1, source, game) <= 0) {
return false;
}
return false;
game.addEffect(new CantBlockTargetEffect(Duration.EndOfTurn), source);
return true;
}
}

View file

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

View file

@ -1,7 +1,5 @@
package mage.cards.b;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
@ -11,11 +9,15 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.mana.ColorlessManaAbility;
import mage.cards.CardImpl;
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.predicate.Predicates;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
* @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");
static {
filter.add(CardType.LAND.getPredicate());
filter.add(SuperType.BASIC.getPredicate());
filter.add(Predicates.or(
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
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
Set<String> names = this.getTargets()
.stream()
.map(game::getPermanent)
@ -159,6 +160,7 @@ class BattleForBretagardTarget extends TargetPermanent {
Permanent permanent = game.getPermanent(uuid);
return permanent == null || names.contains(permanent.getName());
});
return possibleTargets;
}
}

View file

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

View file

@ -121,11 +121,11 @@ class BeamsplitterMageTriggeredAbility extends TriggeredAbilityImpl {
private boolean checkNotSource(Permanent permanent, Game game) {
// 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())
|| permanent.getZoneChangeCounter(game) != this.getSourceObjectZoneChangeCounter();
|| permanent.getZoneChangeCounter(game) != this.getStackMomentSourceZCC();
}
@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.addTarget(new TargetCreaturePermanent());
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);
}

View file

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

View file

@ -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;
import mage.MageInt;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.CantBeCounteredControlledEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.keyword.DisturbAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
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.
this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead")));
this.addAbility(DisturbAbility.makeBackAbility());
}
private BenevolentGeist(final BenevolentGeist card) {

View file

@ -13,7 +13,6 @@ import mage.constants.SagaChapter;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterBySubtypeCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary;
@ -24,7 +23,7 @@ import java.util.UUID;
*/
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) {
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.)
SagaAbility sagaAbility = new SagaAbility(this);
// I Destroy target nonland permanent an opponent controls.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I,
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.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II,
new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)
);
// III Creatures you control gain deathtouch until end of turn.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III,
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.Outcome;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.ExileZone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.util.CardUtil;
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
@ -87,7 +85,7 @@ class BishopOfBindingExileEffect extends OneShotEffect {
// the target creature won't be exiled.
if (permanent != null) {
new ExileTargetEffect(
CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()), permanent.getIdName()
CardUtil.getExileZoneId(game, source.getSourceId(), source.getStackMomentSourceZCC()), permanent.getIdName()
).apply(game, source);
game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source);
return true;
@ -101,7 +99,7 @@ enum BishopOfBindingValue implements DynamicValue {
@Override
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) {
Card exiledCard = exileZone.getRandom(game);
if (exiledCard != null) {

View file

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

View file

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

View file

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

View file

@ -51,7 +51,7 @@ public final class Blink extends CardImpl {
this, SagaChapter.CHAPTER_IV,
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) {

View file

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

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