forked from External/mage
Merge pull request 'master' (#35) from External/mage:master into master
Some checks failed
/ build_release (push) Has been cancelled
Some checks failed
/ build_release (push) Has been cancelled
Reviewed-on: #35
This commit is contained in:
commit
e176b4a5bd
379 changed files with 5417 additions and 4608 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -218,17 +218,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
});
|
||||
}
|
||||
|
||||
public List<UUID> getPossibleTargets() {
|
||||
if (options != null && options.containsKey("possibleTargets")) {
|
||||
return (List<UUID>) options.get("possibleTargets");
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<UUID> getChosenTargets() {
|
||||
if (options != null && options.containsKey("chosenTargets")) {
|
||||
return new HashSet<>((List<UUID>) options.get("chosenTargets"));
|
||||
return (Set<UUID>) options.get("chosenTargets");
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -515,6 +515,7 @@ public class ScryfallImageSupportCards {
|
|||
add("SLX"); // Universes Within
|
||||
add("CLB"); // Commander Legends: Battle for Baldur's Gate
|
||||
add("2X2"); // Double Masters 2022
|
||||
add("SCH"); // Store Championships
|
||||
add("DMU"); // Dominaria United
|
||||
add("DMC"); // Dominaria United Commander
|
||||
add("YDMU"); // Alchemy: Dominaria
|
||||
|
|
@ -569,9 +570,11 @@ public class ScryfallImageSupportCards {
|
|||
add("BIG"); // The Big Score
|
||||
add("MH3"); // Modern Horizons 3
|
||||
add("M3C"); // Modern Horizons 3 Commander
|
||||
add("H2R"); // Modern Horizons 2 Timeshifts
|
||||
add("ACR"); // Assassin's Creed
|
||||
add("BLB"); // Bloomburrow
|
||||
add("BLC"); // Bloomburrow Commander
|
||||
add("PCBB"); // Cowboy Bebop
|
||||
add("MB2"); // Mystery Booster 2
|
||||
add("DSK"); // Duskmourn: House of Horror
|
||||
add("DSC"); // Duskmourn: House of Horror Commander
|
||||
|
|
@ -579,21 +582,25 @@ public class ScryfallImageSupportCards {
|
|||
add("J25"); // Foundations Jumpstart
|
||||
add("PIO"); // Pioneer Masters
|
||||
add("PW25"); // Wizards Play Network 2025
|
||||
add("PSPL"); // Spotlight Series
|
||||
add("INR"); // Innistrad Remastered
|
||||
add("PF25"); // MagicFest 2025
|
||||
add("DFT"); // Aetherdrift
|
||||
add("DRC"); // Aetherdrift Commander
|
||||
add("PLG25"); // Love Your LGS 2025
|
||||
add("TDM"); // Tarkir: Dragonstorm
|
||||
add("TDC"); // Tarkir: Dragonstorm Commander
|
||||
add("FIN"); // Final Fantasy
|
||||
add("FIC"); // Final Fantasy Commander
|
||||
add("FCA"); // Final Fantasy: Through the Ages
|
||||
add("PSS5"); // FIN Standard Showdown
|
||||
add("EOE"); // Edge of Eternities
|
||||
add("EOC"); // Edge of Eternities Commander
|
||||
add("EOS"); // Edge of Eternities: Stellar Sights
|
||||
add("SPM"); // Marvel's Spider-Man
|
||||
add("SPE"); // Marvel's Spider-Man Eternal
|
||||
add("TLA"); // Avatar: The Last Airbender
|
||||
add("TLE"); // Avatar: The Last Airbender Eternal
|
||||
|
||||
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
|
||||
add("CALC"); // Custom Alchemized versions of existing cards
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
// Condition to stop deeper simulation
|
||||
if (SimulationNode2.nodeCount > MAX_SIMULATED_NODES_PER_ERROR) {
|
||||
// how-to fix: make sure you are disabled debug mode by COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false
|
||||
throw new IllegalStateException("AI ERROR: too much nodes (possible actions)");
|
||||
}
|
||||
if (depth <= 0
|
||||
|
|
@ -398,20 +399,23 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
|
||||
protected void resolve(SimulationNode2 node, int depth, Game game) {
|
||||
StackObject stackObject = game.getStack().getFirst();
|
||||
StackObject stackObject = game.getStack().getFirstOrNull();
|
||||
if (stackObject == null) {
|
||||
throw new IllegalStateException("Catch empty stack on resolve (something wrong with sim code)");
|
||||
}
|
||||
if (stackObject instanceof StackAbility) {
|
||||
// AI hint for search effects (calc all possible cards for best score)
|
||||
SearchEffect effect = getSearchEffect((StackAbility) stackObject);
|
||||
if (effect != null
|
||||
&& stackObject.getControllerId().equals(playerId)) {
|
||||
Target target = effect.getTarget();
|
||||
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
|
||||
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game, null)) {
|
||||
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
|
||||
Game sim = game.createSimulationForAI();
|
||||
StackAbility newAbility = (StackAbility) stackObject.copy();
|
||||
SearchEffect newEffect = getSearchEffect(newAbility);
|
||||
newEffect.getTarget().addTarget(targetId, newAbility, sim);
|
||||
sim.getStack().push(newAbility);
|
||||
sim.getStack().push(sim, newAbility);
|
||||
SimulationNode2 newNode = new SimulationNode2(node, sim, depth, stackObject.getControllerId());
|
||||
node.children.add(newNode);
|
||||
newNode.getTargets().add(targetId);
|
||||
|
|
@ -498,7 +502,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
logger.warn("Possible freeze chain:");
|
||||
if (root != null && chain.isEmpty()) {
|
||||
logger.warn(" - unknown use case"); // maybe can't finish any calc, maybe related to target options, I don't know
|
||||
logger.warn(" - unknown use case (too many possible targets?)"); // maybe can't finish any calc, maybe related to target options, I don't know
|
||||
}
|
||||
chain.forEach(s -> {
|
||||
logger.warn(" - " + s);
|
||||
|
|
@ -639,7 +643,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
return "unknown";
|
||||
})
|
||||
.collect(Collectors.joining(", "));
|
||||
logger.info(String.format("Sim Prio [%d] -> with choices (TODO): [%d]<diff %s> (%s)",
|
||||
logger.info(String.format("Sim Prio [%d] -> with possible choices: [%d]<diff %s> (%s)",
|
||||
depth,
|
||||
currentNode.getDepth(),
|
||||
printDiffScore(currentScore - prevScore),
|
||||
|
|
@ -648,14 +652,18 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
} else if (!currentNode.getChoices().isEmpty()) {
|
||||
// ON CHOICES
|
||||
String choicesInfo = String.join(", ", currentNode.getChoices());
|
||||
logger.info(String.format("Sim Prio [%d] -> with choices (TODO): [%d]<diff %s> (%s)",
|
||||
logger.info(String.format("Sim Prio [%d] -> with possible choices (must not see that code): [%d]<diff %s> (%s)",
|
||||
depth,
|
||||
currentNode.getDepth(),
|
||||
printDiffScore(currentScore - prevScore),
|
||||
choicesInfo)
|
||||
);
|
||||
} else {
|
||||
throw new IllegalStateException("AI CALC ERROR: unknown calculation result (no abilities, no targets, no choices)");
|
||||
logger.info(String.format("Sim Prio [%d] -> with do nothing: [%d]<diff %s>",
|
||||
depth,
|
||||
currentNode.getDepth(),
|
||||
printDiffScore(currentScore - prevScore))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -886,10 +894,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
for (UUID targetId : targets) {
|
||||
target.addTarget(targetId, source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
targets.clear();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -906,10 +914,10 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
for (UUID targetId : targets) {
|
||||
target.add(targetId, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
targets.clear();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package mage.player.human;
|
|||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.costs.VariableCost;
|
||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
|
|
@ -639,8 +638,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
// Check check if the spell being paid for cares about the color of mana being paid
|
||||
// See: https://github.com/magefree/mage/issues/9070
|
||||
boolean caresAboutManaColor = false;
|
||||
if (!game.getStack().isEmpty() && game.getStack().getFirst() instanceof Spell) {
|
||||
Spell spellBeingCast = (Spell) game.getStack().getFirst();
|
||||
if (game.getStack().getFirstOrNull() instanceof Spell) {
|
||||
Spell spellBeingCast = (Spell) game.getStack().getFirstOrNull();
|
||||
if (!spellBeingCast.isResolving() && spellBeingCast.getControllerId().equals(this.getId())) {
|
||||
CardImpl card = (CardImpl) game.getCard(spellBeingCast.getSourceId());
|
||||
caresAboutManaColor = card.caresAboutManaColor(game);
|
||||
|
|
@ -697,7 +696,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
|
||||
// stop on completed, e.g. X=0
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -729,7 +728,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
// continue to next target (example: auto-choose must fill min/max = 2 from 2 possible cards)
|
||||
} else {
|
||||
// manual choose
|
||||
options.put("chosenTargets", (Serializable) target.getTargets());
|
||||
options.put("chosenTargets", new HashSet<>(target.getTargets()));
|
||||
|
||||
prepareForResponse(game);
|
||||
if (!isExecutingMacro()) {
|
||||
|
|
@ -747,9 +746,9 @@ public class HumanPlayer extends PlayerImpl {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, game)) {
|
||||
if (possibleTargets.contains(responseId)) {
|
||||
target.add(responseId, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -794,7 +793,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
// manual choice
|
||||
if (responseId == null) {
|
||||
options.put("chosenTargets", (Serializable) target.getTargets());
|
||||
options.put("chosenTargets", new HashSet<>(target.getTargets()));
|
||||
|
||||
prepareForResponse(game);
|
||||
if (!isExecutingMacro()) {
|
||||
|
|
@ -814,13 +813,11 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
// add new target
|
||||
if (possibleTargets.contains(responseId)) {
|
||||
if (target.canTarget(abilityControllerId, responseId, source, game)) {
|
||||
target.addTarget(responseId, source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// done or cancel button pressed
|
||||
if (target.isChosen(game)) {
|
||||
|
|
@ -864,13 +861,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||
|
||||
while (canRespond()) {
|
||||
|
||||
List<UUID> possibleTargets = new ArrayList<>();
|
||||
for (UUID cardId : cards) {
|
||||
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
|
||||
possibleTargets.add(cardId);
|
||||
}
|
||||
}
|
||||
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards);
|
||||
|
||||
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
|
||||
int count = cards.count(target.getFilter(), abilityControllerId, source, game);
|
||||
|
|
@ -880,7 +871,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
|
||||
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
|
||||
// TODO: need research - is it used?
|
||||
// TODO: need research - is it used (test player and AI player don't see empty dialogs)?
|
||||
if (required && possibleTargets.isEmpty()) {
|
||||
required = false;
|
||||
}
|
||||
|
|
@ -894,7 +885,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
} else {
|
||||
// manual choose
|
||||
Map<String, Serializable> options = getOptions(target, null);
|
||||
options.put("chosenTargets", (Serializable) target.getTargets());
|
||||
options.put("chosenTargets", new HashSet<>(target.getTargets()));
|
||||
if (!possibleTargets.isEmpty()) {
|
||||
options.put("possibleTargets", (Serializable) possibleTargets);
|
||||
}
|
||||
|
|
@ -916,9 +907,9 @@ public class HumanPlayer extends PlayerImpl {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (possibleTargets.contains(responseId) && target.canTarget(getId(), responseId, source, cards, game)) {
|
||||
if (possibleTargets.contains(responseId)) {
|
||||
target.add(responseId, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -962,12 +953,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
required = false;
|
||||
}
|
||||
|
||||
List<UUID> possibleTargets = new ArrayList<>();
|
||||
for (UUID cardId : cards) {
|
||||
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
|
||||
possibleTargets.add(cardId);
|
||||
}
|
||||
}
|
||||
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game, cards);
|
||||
|
||||
// if nothing to choose then show dialog (user must see non-selectable items and click on any of them)
|
||||
if (possibleTargets.isEmpty()) {
|
||||
required = false;
|
||||
|
|
@ -977,7 +964,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
if (responseId == null) {
|
||||
Map<String, Serializable> options = getOptions(target, null);
|
||||
options.put("chosenTargets", (Serializable) target.getTargets());
|
||||
options.put("chosenTargets", new HashSet<>(target.getTargets()));
|
||||
|
||||
if (!possibleTargets.isEmpty()) {
|
||||
options.put("possibleTargets", (Serializable) possibleTargets);
|
||||
|
|
@ -995,9 +982,9 @@ public class HumanPlayer extends PlayerImpl {
|
|||
if (responseId != null) {
|
||||
if (target.contains(responseId)) { // if already included remove it
|
||||
target.remove(responseId);
|
||||
} else if (target.canTarget(abilityControllerId, responseId, source, cards, game)) {
|
||||
} else if (possibleTargets.contains(responseId)) {
|
||||
target.addTarget(responseId, source, game);
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game, cards)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1054,9 +1041,9 @@ public class HumanPlayer extends PlayerImpl {
|
|||
// 1. Select targets
|
||||
// TODO: rework to use existing chooseTarget instead custom select?
|
||||
while (canRespond()) {
|
||||
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game);
|
||||
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
|
||||
boolean required = target.isRequired(source.getSourceId(), game);
|
||||
if (possibleTargetIds.isEmpty()
|
||||
if (possibleTargets.isEmpty()
|
||||
|| target.getSize() >= target.getMinNumberOfTargets()) {
|
||||
required = false;
|
||||
}
|
||||
|
|
@ -1065,12 +1052,6 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
// responseId is null if a choice couldn't be automatically made
|
||||
if (responseId == null) {
|
||||
List<UUID> possibleTargets = new ArrayList<>();
|
||||
for (UUID targetId : possibleTargetIds) {
|
||||
if (target.canTarget(abilityControllerId, targetId, source, game)) {
|
||||
possibleTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
|
||||
if (required && possibleTargets.isEmpty()) {
|
||||
required = false;
|
||||
|
|
@ -1078,7 +1059,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
|
||||
// selected
|
||||
Map<String, Serializable> options = getOptions(target, null);
|
||||
options.put("chosenTargets", (Serializable) target.getTargets());
|
||||
options.put("chosenTargets", new HashSet<>(target.getTargets()));
|
||||
if (!possibleTargets.isEmpty()) {
|
||||
options.put("possibleTargets", (Serializable) possibleTargets);
|
||||
}
|
||||
|
|
@ -1087,7 +1068,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
if (!isExecutingMacro()) {
|
||||
String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters";
|
||||
String message = target.getMessage(game) + String.format(multiType, amountTotal);
|
||||
game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargetIds, required, options);
|
||||
game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargets, required, options);
|
||||
}
|
||||
waitForResponse(game);
|
||||
|
||||
|
|
@ -1098,9 +1079,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
if (target.contains(responseId)) {
|
||||
// unselect
|
||||
target.remove(responseId);
|
||||
} else if (possibleTargetIds.contains(responseId)
|
||||
&& target.canTarget(abilityControllerId, responseId, source, game)
|
||||
&& target.getSize() < amountTotal) {
|
||||
} else if (possibleTargets.contains(responseId) && target.getSize() < amountTotal) {
|
||||
// select
|
||||
target.addTarget(responseId, source, game);
|
||||
}
|
||||
|
|
@ -2094,32 +2073,33 @@ public class HumanPlayer extends PlayerImpl {
|
|||
if (!canCallFeedback(game)) {
|
||||
return;
|
||||
}
|
||||
TargetAttackingCreature target = new TargetAttackingCreature();
|
||||
|
||||
// TODO: add canRespond cycle?
|
||||
// no need in cycle, cause parent selectBlockers used it already
|
||||
if (!canRespond()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID responseId = null;
|
||||
|
||||
prepareForResponse(game);
|
||||
if (!isExecutingMacro()) {
|
||||
// possible attackers to block
|
||||
Set<UUID> attackers = target.possibleTargets(playerId, null, game);
|
||||
TargetAttackingCreature target = new TargetAttackingCreature();
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
for (UUID attackerId : attackers) {
|
||||
Set<UUID> allAttackers = target.possibleTargets(playerId, null, game);
|
||||
Set<UUID> possibleAttackersToBlock = new HashSet<>();
|
||||
for (UUID attackerId : allAttackers) {
|
||||
CombatGroup group = game.getCombat().findGroup(attackerId);
|
||||
if (group != null && blocker != null && group.canBlock(blocker, game)) {
|
||||
possibleTargets.add(attackerId);
|
||||
possibleAttackersToBlock.add(attackerId);
|
||||
}
|
||||
}
|
||||
if (possibleTargets.size() == 1) {
|
||||
responseId = possibleTargets.stream().iterator().next();
|
||||
if (possibleAttackersToBlock.size() == 1) {
|
||||
// auto-choice
|
||||
responseId = possibleAttackersToBlock.stream().iterator().next();
|
||||
} else {
|
||||
prepareForResponse(game);
|
||||
game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)),
|
||||
possibleTargets, false, getOptions(target, null));
|
||||
possibleAttackersToBlock, false, getOptions(target, null));
|
||||
waitForResponse(game);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
Mage.Sets/src/mage/cards/a/AangsIceberg.java
Normal file
58
Mage.Sets/src/mage/cards/a/AangsIceberg.java
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.common.DoIfCostPaid;
|
||||
import mage.abilities.effects.common.ExileUntilSourceLeavesEffect;
|
||||
import mage.abilities.effects.keyword.ScryEffect;
|
||||
import mage.abilities.keyword.FlashAbility;
|
||||
import mage.abilities.keyword.WaterbendAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterNonlandPermanent;
|
||||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AangsIceberg extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent");
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
}
|
||||
|
||||
public AangsIceberg(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
|
||||
|
||||
// Flash
|
||||
this.addAbility(FlashAbility.getInstance());
|
||||
|
||||
// When this enchantment enters, exile up to one other target nonland permanent until this enchantment leaves the battlefield.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect());
|
||||
ability.addTarget(new TargetPermanent(0, 1, filter));
|
||||
this.addAbility(ability);
|
||||
|
||||
// Waterbend {3}: Sacrifice this enchantment. If you do, scry 2.
|
||||
this.addAbility(new WaterbendAbility(new DoIfCostPaid(
|
||||
new ScryEffect(2), new SacrificeSourceCost(), null, false
|
||||
), new GenericManaCost(3)));
|
||||
}
|
||||
|
||||
private AangsIceberg(final AangsIceberg card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AangsIceberg copy() {
|
||||
return new AangsIceberg(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
70
Mage.Sets/src/mage/cards/a/AppaSteadfastGuardian.java
Normal file
70
Mage.Sets/src/mage/cards/a/AppaSteadfastGuardian.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
51
Mage.Sets/src/mage/cards/a/AvatarEnthusiasts.java
Normal file
51
Mage.Sets/src/mage/cards/a/AvatarEnthusiasts.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.keyword.TheRingTemptsYouEffect;
|
||||
|
|
@ -8,20 +7,20 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
||||
import mage.filter.predicate.permanent.PermanentIdPredicate;
|
||||
import mage.filter.common.FilterOpponentsCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TODO: combine with Mutiny
|
||||
*
|
||||
* @author Susucr
|
||||
*/
|
||||
public final class BreakingOfTheFellowship extends CardImpl {
|
||||
|
|
@ -31,8 +30,8 @@ public final class BreakingOfTheFellowship extends CardImpl {
|
|||
|
||||
// Target creature an opponent controls deals damage equal to its power to another target creature that player controls.
|
||||
this.getSpellAbility().addEffect(new BreakingOfTheFellowshipEffect());
|
||||
this.getSpellAbility().addTarget(new BreakingOfTheFellowshipFirstTarget());
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(new FilterCreaturePermanent("another target creature that player controls")));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
|
||||
this.getSpellAbility().addTarget(new BreakingOfTheFellowshipSecondTarget());
|
||||
|
||||
// The Ring tempts you.
|
||||
this.getSpellAbility().addEffect(new TheRingTemptsYouEffect());
|
||||
|
|
@ -76,74 +75,45 @@ class BreakingOfTheFellowshipEffect extends OneShotEffect {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BreakingOfTheFellowshipFirstTarget extends TargetPermanent {
|
||||
class BreakingOfTheFellowshipSecondTarget extends TargetPermanent {
|
||||
|
||||
public BreakingOfTheFellowshipFirstTarget() {
|
||||
super(1, 1, StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE, false);
|
||||
public BreakingOfTheFellowshipSecondTarget() {
|
||||
super(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE);
|
||||
}
|
||||
|
||||
private BreakingOfTheFellowshipFirstTarget(final BreakingOfTheFellowshipFirstTarget target) {
|
||||
private BreakingOfTheFellowshipSecondTarget(final BreakingOfTheFellowshipSecondTarget target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) {
|
||||
super.addTarget(id, source, game, skipEvent);
|
||||
// Update the second target
|
||||
UUID firstController = game.getControllerId(id);
|
||||
if (firstController != null && source.getTargets().size() > 1) {
|
||||
Player controllingPlayer = game.getPlayer(firstController);
|
||||
TargetCreaturePermanent targetCreaturePermanent = (TargetCreaturePermanent) source.getTargets().get(1);
|
||||
// Set a new filter to the second target with the needed restrictions
|
||||
FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature that player " + controllingPlayer.getName() + " controls");
|
||||
filter.add(new ControllerIdPredicate(firstController));
|
||||
filter.add(Predicates.not(new PermanentIdPredicate(id)));
|
||||
targetCreaturePermanent.replaceFilter(filter);
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
|
||||
Permanent firstTarget = game.getPermanent(source.getFirstTarget());
|
||||
if (firstTarget == null) {
|
||||
// playable or first target not yet selected
|
||||
// use all
|
||||
if (possibleTargets.size() == 1) {
|
||||
// workaround to make 1 target invalid
|
||||
possibleTargets.clear();
|
||||
}
|
||||
} else {
|
||||
// real
|
||||
// filter by same player
|
||||
possibleTargets.removeIf(id -> {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
return permanent == null || !permanent.isControlledBy(firstTarget.getControllerId());
|
||||
});
|
||||
}
|
||||
possibleTargets.removeIf(id -> firstTarget != null && firstTarget.getId().equals(id));
|
||||
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (super.canTarget(controllerId, id, source, game)) {
|
||||
// can only target, if the controller has at least two targetable creatures
|
||||
UUID controllingPlayerId = game.getControllerId(id);
|
||||
int possibleTargets = 0;
|
||||
MageObject sourceObject = game.getObject(source.getId());
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllingPlayerId, game)) {
|
||||
if (permanent.canBeTargetedBy(sourceObject, controllerId, source, game)) {
|
||||
possibleTargets++;
|
||||
}
|
||||
}
|
||||
return possibleTargets > 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
if (super.canChoose(sourceControllerId, source, game)) {
|
||||
UUID controllingPlayerId = game.getControllerId(source.getSourceId());
|
||||
for (UUID playerId : game.getOpponents(controllingPlayerId)) {
|
||||
int possibleTargets = 0;
|
||||
MageObject sourceObject = game.getObject(source);
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, playerId, game)) {
|
||||
if (permanent.canBeTargetedBy(sourceObject, controllingPlayerId, source, game)) {
|
||||
possibleTargets++;
|
||||
}
|
||||
}
|
||||
if (possibleTargets > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BreakingOfTheFellowshipFirstTarget copy() {
|
||||
return new BreakingOfTheFellowshipFirstTarget(this);
|
||||
public BreakingOfTheFellowshipSecondTarget copy() {
|
||||
return new BreakingOfTheFellowshipSecondTarget(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ class CallOfTheDeathDwellerTarget extends TargetCardInYourGraveyard {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(controllerId, id, source, game)
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(playerId, id, source, game)
|
||||
&& CardUtil.checkCanTargetTotalValueLimit(
|
||||
this.getTargets(), id, MageObject::getManaValue, 3, game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.ApprovingObject;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -17,7 +14,6 @@ import mage.filter.common.FilterCreaturePermanent;
|
|||
import mage.filter.common.FilterInstantOrSorceryCard;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetCard;
|
||||
|
|
@ -25,6 +21,9 @@ import mage.target.TargetPermanent;
|
|||
import mage.target.common.TargetPlayerOrPlaneswalker;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author jeffwadsworth
|
||||
*/
|
||||
|
|
@ -111,47 +110,23 @@ class ChandraPyromasterTarget extends TargetPermanent {
|
|||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
Player player = game.getPlayerOrPlaneswalkerController(source.getFirstTarget());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
UUID firstTarget = player.getId();
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) {
|
||||
return super.canTarget(id, source, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject object = game.getObject(source);
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
|
||||
for (StackObject item : game.getState().getStack()) {
|
||||
if (item.getId().equals(source.getSourceId())) {
|
||||
object = item;
|
||||
}
|
||||
if (item.getSourceId().equals(source.getSourceId())) {
|
||||
object = item;
|
||||
}
|
||||
Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget());
|
||||
if (needPlayer == null) {
|
||||
// playable or not selected - use any
|
||||
} else {
|
||||
// filter by controller
|
||||
possibleTargets.removeIf(id -> {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
return permanent == null
|
||||
|| permanent.getId().equals(source.getFirstTarget())
|
||||
|| !permanent.isControlledBy(needPlayer.getId());
|
||||
});
|
||||
}
|
||||
|
||||
if (object instanceof StackObject) {
|
||||
UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget();
|
||||
Player player = game.getPlayerOrPlaneswalkerController(playerId);
|
||||
if (player != null) {
|
||||
for (UUID targetId : availablePossibleTargets) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null && permanent.isControlledBy(player.getId())) {
|
||||
possibleTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,8 +124,8 @@ class ChaosMutationTarget extends TargetPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(controllerId, id, source, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent creature = game.getPermanent(id);
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ class CloudsLimitBreakTarget extends TargetPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(controllerId, id, source, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent creature = game.getPermanent(id);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ class CobraTrapWatcher extends Watcher {
|
|||
if (event.getType() == GameEvent.EventType.DESTROYED_PERMANENT) {
|
||||
Permanent perm = game.getPermanentOrLKIBattlefield(event.getTargetId()); // can regenerate or be indestructible
|
||||
if (perm != null && !perm.isCreature(game)) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
StackObject spell = game.getStack().getStackObject(event.getSourceId());
|
||||
if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) {
|
||||
players.add(perm.getControllerId());
|
||||
|
|
@ -86,7 +85,6 @@ class CobraTrapWatcher extends Watcher {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
|
|
|
|||
|
|
@ -119,8 +119,7 @@ class ConspiracyTheoristEffect extends OneShotEffect {
|
|||
if (controller != null) {
|
||||
CardsImpl cards = new CardsImpl(discardedCards);
|
||||
TargetCard target = new TargetCard(Zone.GRAVEYARD, new FilterCard("card to exile"));
|
||||
boolean validTarget = cards.stream()
|
||||
.anyMatch(card -> target.canTarget(card, game));
|
||||
boolean validTarget = cards.stream().anyMatch(card -> target.canTarget(card, source, game));
|
||||
if (validTarget && controller.chooseUse(Outcome.Benefit, "Exile a card?", source, game)) {
|
||||
if (controller.choose(Outcome.Benefit, cards, target, source, game)) {
|
||||
Card card = cards.get(target.getFirstTarget(), game);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import mage.abilities.keyword.InspiredAbility;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
|
@ -35,8 +36,12 @@ public final class DaringThief extends CardImpl {
|
|||
this.toughness = new MageInt(3);
|
||||
|
||||
// Inspired - Whenever Daring Thief becomes untapped, you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it.
|
||||
Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(Duration.EndOfGame,
|
||||
"you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it", false, true), true);
|
||||
Ability ability = new InspiredAbility(new ExchangeControlTargetEffect(
|
||||
Duration.EndOfGame,
|
||||
"you may exchange control of target nonland permanent you control and target permanent an opponent controls that shares a card type with it",
|
||||
false,
|
||||
true
|
||||
), true);
|
||||
ability.addTarget(new TargetControlledPermanentSharingOpponentPermanentCardType());
|
||||
ability.addTarget(new DaringThiefSecondTarget());
|
||||
this.addAbility(ability);
|
||||
|
|
@ -66,9 +71,10 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (super.canTarget(controllerId, id, source, game)) {
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(controllerId, game);
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(playerId, game);
|
||||
|
||||
if (super.canTarget(playerId, id, source, game)) {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
for (CardType type : permanent.getCardType(game)) {
|
||||
if (cardTypes.contains(type)) {
|
||||
|
|
@ -81,13 +87,12 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
|
|||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
// get all cardtypes from opponents permanents
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
|
||||
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject targetSource = game.getObject(source);
|
||||
if (targetSource != null) {
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
for (CardType type : permanent.getCardType(game)) {
|
||||
if (cardTypes.contains(type)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
|
|
@ -96,8 +101,7 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -119,58 +123,54 @@ class TargetControlledPermanentSharingOpponentPermanentCardType extends TargetCo
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class DaringThiefSecondTarget extends TargetPermanent {
|
||||
|
||||
private Permanent firstTarget = null;
|
||||
|
||||
public DaringThiefSecondTarget() {
|
||||
super();
|
||||
this.filter = this.filter.copy();
|
||||
filter.add(TargetController.OPPONENT.getControllerPredicate());
|
||||
super(StaticFilters.FILTER_OPPONENTS_PERMANENT);
|
||||
withTargetName("permanent an opponent controls that shares a card type with it");
|
||||
}
|
||||
|
||||
private DaringThiefSecondTarget(final DaringThiefSecondTarget target) {
|
||||
super(target);
|
||||
this.firstTarget = target.firstTarget;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
if (super.canTarget(id, source, game)) {
|
||||
Permanent target1 = game.getPermanent(source.getFirstTarget());
|
||||
Permanent opponentPermanent = game.getPermanent(id);
|
||||
if (target1 != null && opponentPermanent != null) {
|
||||
return target1.shareTypes(opponentPermanent, game);
|
||||
}
|
||||
}
|
||||
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
|
||||
Permanent possiblePermanent = game.getPermanent(id);
|
||||
if (ownPermanent == null || possiblePermanent == null) {
|
||||
return false;
|
||||
}
|
||||
return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
if (firstTarget != null) {
|
||||
MageObject targetSource = game.getObject(source);
|
||||
if (targetSource != null) {
|
||||
|
||||
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
if (permanent.shareTypes(firstTarget, game)) {
|
||||
if (ownPermanent == null) {
|
||||
// playable or first target not yet selected
|
||||
// use all
|
||||
possibleTargets.add(permanent.getId());
|
||||
} else {
|
||||
// real
|
||||
// filter by shared type
|
||||
if (permanent.shareTypes(ownPermanent, game)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id));
|
||||
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||
firstTarget = game.getPermanent(source.getFirstTarget());
|
||||
return super.chooseTarget(Outcome.Damage, playerId, source, game);
|
||||
// AI hint with better outcome
|
||||
return super.chooseTarget(Outcome.GainControl, playerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -91,9 +91,7 @@ class DeepCaverBatEffect extends OneShotEffect {
|
|||
return true;
|
||||
}
|
||||
|
||||
TargetCard target = new TargetCardInHand(
|
||||
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
|
||||
);
|
||||
TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
|
||||
controller.choose(outcome, opponent.getHand(), target, source, game);
|
||||
Card card = opponent.getHand().get(target.getFirstTarget(), game);
|
||||
if (card == null) {
|
||||
|
|
|
|||
|
|
@ -2,35 +2,32 @@ package mage.cards.d;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||
import mage.abilities.effects.common.SacrificeSourceEffect;
|
||||
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterOpponentsCreaturePermanent;
|
||||
import mage.game.Controllable;
|
||||
import mage.game.Game;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Plopman
|
||||
*/
|
||||
public final class DefenseOfTheHeart extends CardImpl {
|
||||
|
||||
private static final Condition condition = new PermanentsOnTheBattlefieldCondition(
|
||||
new FilterOpponentsCreaturePermanent("an opponent controls three or more creatures"),
|
||||
ComparisonType.MORE_THAN, 2
|
||||
);
|
||||
|
||||
public DefenseOfTheHeart(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}");
|
||||
|
||||
// At the beginning of your upkeep, if an opponent controls three or more creatures, sacrifice Defense of the Heart, search your library for up to two creature cards, and put those cards onto the battlefield. Then shuffle your library.
|
||||
Ability ability = new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceEffect()).withInterveningIf(condition);
|
||||
Ability ability = new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceEffect())
|
||||
.withInterveningIf(DefenseOfTheHeartCondition.instance);
|
||||
ability.addEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(
|
||||
0, 2, StaticFilters.FILTER_CARD_CREATURES
|
||||
)).concatBy(","));
|
||||
|
|
@ -46,3 +43,28 @@ public final class DefenseOfTheHeart extends CardImpl {
|
|||
return new DefenseOfTheHeart(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum DefenseOfTheHeartCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return game
|
||||
.getBattlefield()
|
||||
.getActivePermanents(
|
||||
StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE,
|
||||
source.getControllerId(), source, game
|
||||
)
|
||||
.stream()
|
||||
.map(Controllable::getControllerId)
|
||||
.collect(Collectors.toMap(Function.identity(), x -> 1, Integer::sum))
|
||||
.values()
|
||||
.stream()
|
||||
.anyMatch(x -> x >= 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "an opponent controls three or more creatures";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,49 +137,12 @@ class TargetControlledSource extends TargetSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Game game) {
|
||||
int count = 0;
|
||||
for (StackObject stackObject : game.getStack()) {
|
||||
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
|
||||
&& Objects.equals(stackObject.getControllerId(), sourceControllerId)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(sourceControllerId, game)) {
|
||||
if (Objects.equals(permanent.getControllerId(), sourceControllerId)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Player player : game.getPlayers().values()) {
|
||||
if (Objects.equals(player, game.getPlayer(sourceControllerId))) {
|
||||
for (Card card : player.getGraveyard().getCards(game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 108.4a If anything asks for the controller of a card that doesn't have one (because it's not a permanent or spell), use its owner instead.
|
||||
for (Card card : game.getExile().getAllCards(game)) {
|
||||
if (Objects.equals(card.getOwnerId(), sourceControllerId)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
return canChooseFromPossibleTargets(sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
for (StackObject stackObject : game.getStack()) {
|
||||
if (game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
|
||||
|
|
@ -205,7 +168,7 @@ class TargetControlledSource extends TargetSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -90,10 +90,10 @@ class DistendedMindbenderEffect extends OneShotEffect {
|
|||
TargetCard targetThreeOrLess = new TargetCard(1, Zone.HAND, filterThreeOrLess);
|
||||
TargetCard targetFourOrGreater = new TargetCard(1, Zone.HAND, filterFourOrGreater);
|
||||
Cards toDiscard = new CardsImpl();
|
||||
if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) {
|
||||
if (controller.choose(Outcome.Benefit, opponent.getHand(), targetThreeOrLess, source, game)) {
|
||||
toDiscard.addAll(targetThreeOrLess.getTargets());
|
||||
}
|
||||
if (controller.chooseTarget(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) {
|
||||
if (controller.choose(Outcome.Benefit, opponent.getHand(), targetFourOrGreater, source, game)) {
|
||||
toDiscard.addAll(targetFourOrGreater.getTargets());
|
||||
}
|
||||
opponent.discard(toDiscard, false, source, game);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
|
||||
package mage.cards.d;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.*;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.common.FilterArtifactCard;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.TargetEvent;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCardInGraveyard;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author emerald000
|
||||
*/
|
||||
public final class DrafnasRestoration extends CardImpl {
|
||||
|
|
@ -53,27 +54,33 @@ class DrafnasRestorationTarget extends TargetCardInGraveyard {
|
|||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
Player targetPlayer = game.getPlayer(source.getFirstTarget());
|
||||
return targetPlayer != null && targetPlayer.getGraveyard().contains(id) && super.canTarget(id, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject object = game.getObject(source);
|
||||
if (object instanceof StackObject) {
|
||||
Player targetPlayer = game.getPlayer(((StackObject) object).getStackAbility().getFirstTarget());
|
||||
if (targetPlayer != null) {
|
||||
for (Card card : targetPlayer.getGraveyard().getCards(filter, sourceControllerId, source, game)) {
|
||||
if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) {
|
||||
|
||||
Player controller = game.getPlayer(sourceControllerId);
|
||||
if (controller == null) {
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
Player targetPlayer = game.getPlayer(source.getFirstTarget());
|
||||
game.getState().getPlayersInRange(sourceControllerId, game, true).stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(player -> player.getGraveyard().getCards(filter, sourceControllerId, source, game).stream())
|
||||
.forEach(card -> {
|
||||
if (targetPlayer == null) {
|
||||
// playable or not selected - use any
|
||||
possibleTargets.add(card.getId());
|
||||
} else {
|
||||
// selected, filter by player
|
||||
if (targetPlayer.getId().equals(card.getControllerOrOwnerId())) {
|
||||
possibleTargets.add(card.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
});
|
||||
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect {
|
|||
filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), CardType.CREATURE.getPredicate()));
|
||||
TargetCard target = new TargetCard(Zone.HAND, filter);
|
||||
target.withNotTarget(true);
|
||||
controller.chooseTarget(Outcome.Discard, opponent.getHand(), target, source, game);
|
||||
controller.choose(Outcome.Discard, opponent.getHand(), target, source, game);
|
||||
Card card = game.getCard(target.getFirstTarget());
|
||||
if (card != null) {
|
||||
toExile.add(card);
|
||||
|
|
@ -81,7 +81,7 @@ class DreamsOfSteelAndOilEffect extends OneShotEffect {
|
|||
filter.setMessage("artifact or creature card from " + opponent.getName() + "'s graveyard");
|
||||
target = new TargetCard(Zone.GRAVEYARD, filter);
|
||||
target.withNotTarget(true);
|
||||
controller.chooseTarget(Outcome.Exile, opponent.getGraveyard(), target, source, game);
|
||||
controller.choose(Outcome.Exile, opponent.getGraveyard(), target, source, game);
|
||||
card = game.getCard(target.getFirstTarget());
|
||||
if (card != null) {
|
||||
toExile.add(card);
|
||||
|
|
|
|||
|
|
@ -105,11 +105,10 @@ class RevivalExperimentTarget extends TargetCardInYourGraveyard {
|
|||
return cardTypeAssigner.getRoleCount(cards, game) >= cards.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game));
|
||||
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
|
||||
return possibleTargets;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
Mage.Sets/src/mage/cards/e/EarthbendingLesson.java
Normal file
36
Mage.Sets/src/mage/cards/e/EarthbendingLesson.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package mage.cards.e;
|
||||
|
||||
import mage.abilities.effects.keyword.EarthbendTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class EarthbendingLesson extends CardImpl {
|
||||
|
||||
public EarthbendingLesson(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}");
|
||||
|
||||
this.subtype.add(SubType.LESSON);
|
||||
|
||||
// Earthbend 4.
|
||||
this.getSpellAbility().addEffect(new EarthbendTargetEffect(4));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND));
|
||||
}
|
||||
|
||||
private EarthbendingLesson(final EarthbendingLesson card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EarthbendingLesson copy() {
|
||||
return new EarthbendingLesson(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -79,9 +79,7 @@ class EliteSpellbinderEffect extends OneShotEffect {
|
|||
if (controller == null || opponent == null || opponent.getHand().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
TargetCard target = new TargetCardInHand(
|
||||
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
|
||||
);
|
||||
TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
|
||||
controller.choose(outcome, opponent.getHand(), target, source, game);
|
||||
Card card = opponent.getHand().get(target.getFirstTarget(), game);
|
||||
if (card == null) {
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class EnchantmentAlterationEffect extends OneShotEffect {
|
|||
if (oldPermanent != null
|
||||
&& !oldPermanent.equals(permanentToBeAttachedTo)) {
|
||||
Target auraTarget = aura.getSpellAbility().getTargets().get(0);
|
||||
if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), game)) {
|
||||
if (!auraTarget.canTarget(permanentToBeAttachedTo.getId(), source, game)) {
|
||||
game.informPlayers(aura.getLogName() + " was not attched to " + permanentToBeAttachedTo.getLogName() + " because it's no legal target for the aura");
|
||||
} else if (oldPermanent.removeAttachment(aura.getId(), source, game)) {
|
||||
game.informPlayers(aura.getLogName() + " was unattached from " + oldPermanent.getLogName() + " and attached to " + permanentToBeAttachedTo.getLogName());
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ enum EverythingComesToDustPredicate implements ObjectSourcePlayerPredicate<Perma
|
|||
if (!p.isCreature(game)){
|
||||
return false;
|
||||
}
|
||||
HashSet<MageObjectReference> set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>(0));
|
||||
HashSet<MageObjectReference> set = CardUtil.getSourceCostsTag(game, input.getSource(), ConvokeAbility.convokingCreaturesKey, new HashSet<>());
|
||||
for (MageObjectReference mor : set){
|
||||
Permanent convoked = game.getPermanentOrLKIBattlefield(mor);
|
||||
if (convoked.shareCreatureTypes(game, p)){
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import mage.cards.Cards;
|
|||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
import mage.target.common.TargetOpponent;
|
||||
import mage.util.CardUtil;
|
||||
|
|
@ -65,9 +67,7 @@ class ExtractBrainEffect extends OneShotEffect {
|
|||
if (controller == null || opponent == null || opponent.getHand().isEmpty() || xValue < 1) {
|
||||
return false;
|
||||
}
|
||||
TargetCardInHand target = new TargetCardInHand(
|
||||
Math.min(opponent.getHand().size(), xValue), StaticFilters.FILTER_CARD
|
||||
);
|
||||
TargetCard target = new TargetCard(Math.min(opponent.getHand().size(), xValue), Integer.MAX_VALUE, Zone.HAND, StaticFilters.FILTER_CARD);
|
||||
opponent.choose(Outcome.Detriment, opponent.getHand(), target, source, game);
|
||||
Cards cards = new CardsImpl(target.getTargets());
|
||||
controller.lookAtCards(source, null, cards, game);
|
||||
|
|
|
|||
36
Mage.Sets/src/mage/cards/f/FireNationAttacks.java
Normal file
36
Mage.Sets/src/mage/cards/f/FireNationAttacks.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package mage.cards.f;
|
||||
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.keyword.FlashbackAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.game.permanent.token.SoldierFirebendingToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class FireNationAttacks extends CardImpl {
|
||||
|
||||
public FireNationAttacks(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}");
|
||||
|
||||
// Create two 2/2 red Soldier creature tokens with firebending 1.
|
||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new SoldierFirebendingToken(), 2));
|
||||
|
||||
// Flashback {8}{R}
|
||||
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{8}{R}")));
|
||||
}
|
||||
|
||||
private FireNationAttacks(final FireNationAttacks card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FireNationAttacks copy() {
|
||||
return new FireNationAttacks(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +134,6 @@ class FireballTargetCreatureOrPlayer extends TargetAnyTarget {
|
|||
continue;
|
||||
}
|
||||
|
||||
possibleTargets.removeAll(getTargets());
|
||||
for (UUID targetId : possibleTargets) {
|
||||
TargetAnyTarget target = this.copy();
|
||||
target.clearChosen();
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class FrogkinKidnapperEffect extends OneShotEffect {
|
|||
opponent.revealCards(source, opponent.getHand(), game);
|
||||
TargetCard target = new TargetCard(1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
|
||||
Cards toRansom = new CardsImpl();
|
||||
if (controller.chooseTarget(outcome, opponent.getHand(), target, source, game)) {
|
||||
if (controller.choose(outcome, opponent.getHand(), target, source, game)) {
|
||||
toRansom.addAll(target.getTargets());
|
||||
|
||||
String exileName = "Ransomed (owned by " + opponent.getName() + ")";
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ import mage.abilities.keyword.EquipAbility;
|
|||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.AttachmentType;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterCreatureCard;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.ToughnessPredicate;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
|
|||
|
|
@ -69,9 +69,10 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (super.canTarget(controllerId, id, source, game)) {
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), controllerId, game);
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(playerId, game);
|
||||
|
||||
if (super.canTarget(playerId, id, source, game)) {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
for (CardType type : permanent.getCardType(game)) {
|
||||
if (cardTypes.contains(type)) {
|
||||
|
|
@ -84,13 +85,12 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
|
|||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
// get all cardtypes from opponents permanents
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(source.getSourceId(), sourceControllerId, game);
|
||||
Set<CardType> cardTypes = getOpponentPermanentCardTypes(sourceControllerId, game);
|
||||
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject targetSource = game.getObject(source);
|
||||
if (targetSource != null) {
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
for (CardType type : permanent.getCardType(game)) {
|
||||
if (cardTypes.contains(type)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
|
|
@ -99,8 +99,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -108,7 +107,7 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
|
|||
return new GauntletsOfChaosFirstTarget(this);
|
||||
}
|
||||
|
||||
private EnumSet<CardType> getOpponentPermanentCardTypes(UUID sourceId, UUID sourceControllerId, Game game) {
|
||||
private EnumSet<CardType> getOpponentPermanentCardTypes(UUID sourceControllerId, Game game) {
|
||||
Player controller = game.getPlayer(sourceControllerId);
|
||||
EnumSet<CardType> cardTypes = EnumSet.noneOf(CardType.class);
|
||||
if (controller != null) {
|
||||
|
|
@ -125,8 +124,6 @@ class GauntletsOfChaosFirstTarget extends TargetControlledPermanent {
|
|||
|
||||
class GauntletsOfChaosSecondTarget extends TargetPermanent {
|
||||
|
||||
private Permanent firstTarget = null;
|
||||
|
||||
public GauntletsOfChaosSecondTarget() {
|
||||
super();
|
||||
this.filter = this.filter.copy();
|
||||
|
|
@ -136,44 +133,46 @@ class GauntletsOfChaosSecondTarget extends TargetPermanent {
|
|||
|
||||
private GauntletsOfChaosSecondTarget(final GauntletsOfChaosSecondTarget target) {
|
||||
super(target);
|
||||
this.firstTarget = target.firstTarget;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
if (super.canTarget(id, source, game)) {
|
||||
Permanent target1 = game.getPermanent(source.getFirstTarget());
|
||||
Permanent opponentPermanent = game.getPermanent(id);
|
||||
if (target1 != null && opponentPermanent != null) {
|
||||
return target1.shareTypes(opponentPermanent, game);
|
||||
}
|
||||
}
|
||||
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
|
||||
Permanent possiblePermanent = game.getPermanent(id);
|
||||
if (ownPermanent == null || possiblePermanent == null) {
|
||||
return false;
|
||||
}
|
||||
return super.canTarget(id, source, game) && ownPermanent.shareTypes(possiblePermanent, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
if (firstTarget != null) {
|
||||
MageObject targetSource = game.getObject(source);
|
||||
if (targetSource != null) {
|
||||
|
||||
Permanent ownPermanent = game.getPermanent(source.getFirstTarget());
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
if (permanent.shareTypes(firstTarget, game)) {
|
||||
if (ownPermanent == null) {
|
||||
// playable or first target not yet selected
|
||||
// use all
|
||||
possibleTargets.add(permanent.getId());
|
||||
} else {
|
||||
// real
|
||||
// filter by shared type
|
||||
if (permanent.shareTypes(ownPermanent, game)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
possibleTargets.removeIf(id -> ownPermanent != null && ownPermanent.getId().equals(id));
|
||||
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||
firstTarget = game.getPermanent(source.getFirstTarget());
|
||||
return super.chooseTarget(Outcome.Damage, playerId, source, game);
|
||||
// AI hint with better outcome
|
||||
return super.chooseTarget(Outcome.GainControl, playerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
package mage.cards.g;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.keyword.SupportAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility;
|
||||
import mage.filter.StaticFilters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -19,13 +17,6 @@ import java.util.UUID;
|
|||
*/
|
||||
public final class GenerousPatron extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter
|
||||
= new FilterCreaturePermanent("creature you don't control");
|
||||
|
||||
static {
|
||||
filter.add(TargetController.NOT_YOU.getControllerPredicate());
|
||||
}
|
||||
|
||||
public GenerousPatron(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
|
||||
|
||||
|
|
@ -38,7 +29,8 @@ public final class GenerousPatron extends CardImpl {
|
|||
this.addAbility(new SupportAbility(this, 2));
|
||||
|
||||
// Whenever you put one or more counters on a creature you don't control, draw a card.
|
||||
this.addAbility(new PutCounterOnCreatureTriggeredAbility(new DrawCardSourceControllerEffect(1), filter));
|
||||
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new DrawCardSourceControllerEffect(1),
|
||||
null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL));
|
||||
}
|
||||
|
||||
private GenerousPatron(final GenerousPatron card) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class GhastlordOfFugueEffect extends OneShotEffect {
|
|||
TargetCard target = new TargetCard(Zone.HAND, new FilterCard());
|
||||
target.withNotTarget(true);
|
||||
Card chosenCard = null;
|
||||
if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
|
||||
if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
|
||||
chosenCard = game.getCard(target.getFirstTarget());
|
||||
}
|
||||
if (chosenCard != null) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
package mage.cards.g;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
|
|
@ -12,15 +11,11 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.constants.TargetController;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.other.DamagedPlayerThisTurnPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.watchers.common.PlayerDamagedBySourceWatcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -28,6 +23,12 @@ import java.util.UUID;
|
|||
*/
|
||||
public final class GiltspireAvenger extends CardImpl {
|
||||
|
||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature that dealt damage to you this turn");
|
||||
|
||||
static {
|
||||
filter.add(new DamagedPlayerThisTurnPredicate(TargetController.YOU));
|
||||
}
|
||||
|
||||
public GiltspireAvenger(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
|
|
@ -41,7 +42,7 @@ public final class GiltspireAvenger extends CardImpl {
|
|||
|
||||
// {T}: Destroy target creature that dealt damage to you this turn.
|
||||
Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect(), new TapSourceCost());
|
||||
ability.addTarget(new GiltspireAvengerTarget());
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability);
|
||||
|
||||
}
|
||||
|
|
@ -55,64 +56,3 @@ public final class GiltspireAvenger extends CardImpl {
|
|||
return new GiltspireAvenger(this);
|
||||
}
|
||||
}
|
||||
|
||||
class GiltspireAvengerTarget extends TargetPermanent {
|
||||
|
||||
public GiltspireAvengerTarget() {
|
||||
super(1, 1, StaticFilters.FILTER_PERMANENT_CREATURE, false);
|
||||
targetName = "creature that dealt damage to you this turn";
|
||||
}
|
||||
|
||||
private GiltspireAvengerTarget(final GiltspireAvengerTarget target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, source.getControllerId());
|
||||
if (watcher != null && watcher.hasSourceDoneDamage(id, game)) {
|
||||
return super.canTarget(id, source, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId);
|
||||
for (UUID targetId : availablePossibleTargets) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null && watcher != null && watcher.hasSourceDoneDamage(targetId, game)) {
|
||||
possibleTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
int remainingTargets = this.minNumberOfTargets - targets.size();
|
||||
if (remainingTargets == 0) {
|
||||
return true;
|
||||
}
|
||||
int count = 0;
|
||||
MageObject targetSource = game.getObject(source);
|
||||
PlayerDamagedBySourceWatcher watcher = game.getState().getWatcher(PlayerDamagedBySourceWatcher.class, sourceControllerId);
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(permanent.getId()) && permanent.canBeTargetedBy(targetSource, sourceControllerId, source, game)
|
||||
&& watcher != null && watcher.hasSourceDoneDamage(permanent.getId(), game)) {
|
||||
count++;
|
||||
if (count >= remainingTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GiltspireAvengerTarget copy() {
|
||||
return new GiltspireAvengerTarget(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package mage.cards.g;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.common.SourceHasCounterCondition;
|
||||
import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect;
|
||||
|
|
@ -11,11 +9,14 @@ import mage.abilities.effects.OneShotEffect;
|
|||
import mage.abilities.effects.common.DontUntapInControllersUntapStepSourceEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
|
@ -59,8 +60,6 @@ public final class GlyphOfDelusion extends CardImpl {
|
|||
|
||||
class GlyphOfDelusionSecondTarget extends TargetPermanent {
|
||||
|
||||
private Permanent firstTarget = null;
|
||||
|
||||
public GlyphOfDelusionSecondTarget() {
|
||||
super();
|
||||
withTargetName("target creature that target Wall blocked this turn");
|
||||
|
|
@ -68,33 +67,39 @@ class GlyphOfDelusionSecondTarget extends TargetPermanent {
|
|||
|
||||
private GlyphOfDelusionSecondTarget(final GlyphOfDelusionSecondTarget target) {
|
||||
super(target);
|
||||
this.firstTarget = target.firstTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
if (firstTarget != null) {
|
||||
|
||||
BlockedAttackerWatcher watcher = game.getState().getWatcher(BlockedAttackerWatcher.class);
|
||||
if (watcher != null) {
|
||||
MageObject targetSource = game.getObject(source);
|
||||
if (targetSource != null) {
|
||||
for (Permanent creature : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, source, game)) {
|
||||
if (!targets.containsKey(creature.getId()) && creature.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(creature, game), new MageObjectReference(firstTarget, game))) {
|
||||
possibleTargets.add(creature.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (watcher == null) {
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
Permanent targetWall = game.getPermanent(source.getFirstTarget());
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, source, game)) {
|
||||
if (targetWall == null) {
|
||||
// playable or first target not yet selected
|
||||
// use all
|
||||
possibleTargets.add(permanent.getId());
|
||||
} else {
|
||||
// real
|
||||
// filter by blocked
|
||||
if (watcher.creatureHasBlockedAttacker(new MageObjectReference(permanent, game), new MageObjectReference(targetWall, game))) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
possibleTargets.removeIf(id -> targetWall != null && targetWall.getId().equals(id));
|
||||
|
||||
return keepValidPossibleTargets(possibleTargets, sourceControllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||
firstTarget = game.getPermanent(source.getFirstTarget());
|
||||
// AI hint with better outcome
|
||||
return super.chooseTarget(Outcome.Tap, playerId, source, game);
|
||||
}
|
||||
|
||||
|
|
@ -133,8 +138,7 @@ class GlyphOfDelusionEffect extends OneShotEffect {
|
|||
effect.setTargetPointer(new FixedTarget(targetPermanent.getId(), game));
|
||||
game.addEffect(effect, source);
|
||||
|
||||
BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance())
|
||||
);
|
||||
BeginningOfUpkeepTriggeredAbility ability2 = new BeginningOfUpkeepTriggeredAbility(new RemoveCounterSourceEffect(CounterType.GLYPH.createInstance()));
|
||||
GainAbilityTargetEffect effect2 = new GainAbilityTargetEffect(ability2, Duration.Custom);
|
||||
effect2.setTargetPointer(new FixedTarget(targetPermanent.getId(), game));
|
||||
game.addEffect(effect2, source);
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ class GoblinArtisansTarget extends TargetSpell {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(controllerId, id, source, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
MageObjectReference sourceRef = new MageObjectReference(source.getSourceObject(game), game);
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ class GracefulTakedownTarget extends TargetPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(controllerId, id, source, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class GrimeGorgerTarget extends TargetCardInGraveyard {
|
|||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game));
|
||||
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class GurzigostCost extends CostImpl {
|
|||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return this.getTargets().canChoose(controllerId, source, game);
|
||||
return canChooseOrAlreadyChosen(ability, source, controllerId, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ enum HamletGluttonAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (BargainedCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package mage.cards.h;
|
|||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
|
||||
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility;
|
||||
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.cards.CardImpl;
|
||||
|
|
@ -12,6 +12,7 @@ import mage.constants.CardType;
|
|||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.permanent.token.DeathtouchSnakeToken;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
|
|
@ -37,7 +38,8 @@ public final class HapatraVizierOfPoisons extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Whenever you put one or more -1/-1 counters on a creature, create a 1/1 green Snake creature token with deathtouch.
|
||||
this.addAbility(new PutCounterOnCreatureTriggeredAbility(new CreateTokenEffect(new DeathtouchSnakeToken()), CounterType.M1M1.createInstance()));
|
||||
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new CreateTokenEffect(new DeathtouchSnakeToken()),
|
||||
CounterType.M1M1, StaticFilters.FILTER_PERMANENT_CREATURE));
|
||||
}
|
||||
|
||||
private HapatraVizierOfPoisons(final HapatraVizierOfPoisons card) {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ enum IceOutAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (BargainedCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package mage.cards.i;
|
||||
|
||||
import mage.MageItem;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.RevealTargetFromHandCost;
|
||||
|
|
@ -19,9 +17,9 @@ import mage.game.Game;
|
|||
import mage.target.common.TargetCardInHand;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
|
|
@ -67,51 +65,8 @@ class IlluminatedFolioTarget extends TargetCardInHand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
return super.canChoose(sourceControllerId, source, game)
|
||||
&& !possibleTargets(sourceControllerId, source, game).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
if (this.getTargets().size() == 1) {
|
||||
Card card = game.getCard(this.getTargets().get(0));
|
||||
possibleTargets.removeIf(
|
||||
uuid -> !game
|
||||
.getCard(uuid)
|
||||
.getColor(game)
|
||||
.shares(card.getColor(game))
|
||||
);
|
||||
return possibleTargets;
|
||||
}
|
||||
if (possibleTargets.size() < 2) {
|
||||
possibleTargets.clear();
|
||||
return possibleTargets;
|
||||
}
|
||||
Set<Card> allTargets = possibleTargets
|
||||
.stream()
|
||||
.map(game::getCard)
|
||||
.collect(Collectors.toSet());
|
||||
possibleTargets.clear();
|
||||
for (ObjectColor color : ObjectColor.getAllColors()) {
|
||||
Set<Card> inColor = allTargets
|
||||
.stream()
|
||||
.filter(card -> card.getColor(game).shares(color))
|
||||
.collect(Collectors.toSet());
|
||||
if (inColor.size() > 1) {
|
||||
inColor.stream().map(MageItem::getId).forEach(possibleTargets::add);
|
||||
}
|
||||
if (possibleTargets.size() == allTargets.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Game game) {
|
||||
if (!super.canTarget(id, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
List<UUID> targetList = this.getTargets();
|
||||
|
|
@ -123,9 +78,17 @@ class IlluminatedFolioTarget extends TargetCardInHand {
|
|||
&& targetList
|
||||
.stream()
|
||||
.map(game::getCard)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(c -> c.getColor(game).shares(card.getColor(game)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IlluminatedFolioTarget copy() {
|
||||
return new IlluminatedFolioTarget(this);
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class ImpelledGiantCost extends CostImpl {
|
|||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return target.canChoose(controllerId, source, game);
|
||||
return target.canChooseOrAlreadyChosen(controllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -79,9 +79,7 @@ class InvasionOfGobakhanEffect extends OneShotEffect {
|
|||
if (controller == null || opponent == null || opponent.getHand().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
TargetCard target = new TargetCardInHand(
|
||||
0, 1, StaticFilters.FILTER_CARD_A_NON_LAND
|
||||
);
|
||||
TargetCard target = new TargetCard(0, 1, Zone.HAND, StaticFilters.FILTER_CARD_A_NON_LAND);
|
||||
controller.choose(outcome, opponent.getHand(), target, source, game);
|
||||
Card card = opponent.getHand().get(target.getFirstTarget(), game);
|
||||
if (card == null) {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,27 @@
|
|||
|
||||
package mage.cards.i;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.Zone;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
import mage.target.TargetCard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public final class IwamoriOfTheOpenFist extends CardImpl {
|
||||
|
||||
public IwamoriOfTheOpenFist(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}");
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.MONK);
|
||||
|
|
@ -80,18 +75,14 @@ class IwamoriOfTheOpenFistEffect extends OneShotEffect {
|
|||
Cards cards = new CardsImpl();
|
||||
for (UUID playerId : game.getOpponents(controller.getId())) {
|
||||
Player opponent = game.getPlayer(playerId);
|
||||
Target target = new TargetCardInHand(filter);
|
||||
if (opponent != null && target.canChoose(opponent.getId(), source, game)) {
|
||||
if (opponent.chooseUse(Outcome.PutCreatureInPlay, "Put a legendary creature card from your hand onto the battlefield?", source, game)) {
|
||||
if (target.chooseTarget(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) {
|
||||
Target target = new TargetCard(0, 1, Zone.HAND, filter).withChooseHint("put from hand to battlefield");
|
||||
if (target.choose(Outcome.PutCreatureInPlay, opponent.getId(), source, game)) {
|
||||
Card card = game.getCard(target.getFirstTarget());
|
||||
if (card != null) {
|
||||
cards.add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.moveCards(cards.getCards(game), Zone.BATTLEFIELD, source, game, false, false, true, null);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ enum JohannsStopgapAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (BargainedCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class JotunGruntCost extends CostImpl {
|
|||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return this.getTargets().canChoose(controllerId, source, game);
|
||||
return canChooseOrAlreadyChosen(ability, source, controllerId, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import mage.filter.predicate.mageobject.NamePredicate;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
|
||||
import java.util.Objects;
|
||||
|
|
@ -62,28 +61,31 @@ class JourneyForTheElixirEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
TargetCardInLibrary targetCardInLibrary = new JourneyForTheElixirLibraryTarget();
|
||||
player.searchLibrary(targetCardInLibrary, source, game);
|
||||
Cards cards = new CardsImpl(targetCardInLibrary.getTargets());
|
||||
TargetCard target = new JourneyForTheElixirGraveyardTarget(cards);
|
||||
player.choose(outcome, target, source, game);
|
||||
cards.addAll(target.getTargets());
|
||||
player.revealCards(source, cards, game);
|
||||
player.moveCards(cards, Zone.HAND, source, game);
|
||||
player.shuffleLibrary(source, game);
|
||||
|
||||
// search your library and graveyard for 2 cards
|
||||
Cards allCards = new CardsImpl();
|
||||
allCards.addAll(controller.getLibrary().getCardList());
|
||||
allCards.addAll(controller.getGraveyard());
|
||||
TargetCard target = new JourneyForTheElixirTarget();
|
||||
if (controller.choose(Outcome.Benefit, allCards, target, source, game)) {
|
||||
Cards cards = new CardsImpl(target.getTargets());
|
||||
controller.revealCards(source, cards, game);
|
||||
controller.moveCards(cards, Zone.HAND, source, game);
|
||||
controller.shuffleLibrary(source, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary {
|
||||
class JourneyForTheElixirTarget extends TargetCard {
|
||||
|
||||
private static final String name = "Jiang Yanggu";
|
||||
private static final FilterCard filter
|
||||
= new FilterCard("a basic land card and a card named Jiang Yanggu");
|
||||
private static final FilterCard filter = new FilterCard("a basic land card and a card named Jiang Yanggu");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
|
|
@ -95,17 +97,17 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary {
|
|||
));
|
||||
}
|
||||
|
||||
JourneyForTheElixirLibraryTarget() {
|
||||
super(0, 2, filter);
|
||||
JourneyForTheElixirTarget() {
|
||||
super(2, 2, Zone.ALL, filter);
|
||||
}
|
||||
|
||||
private JourneyForTheElixirLibraryTarget(final JourneyForTheElixirLibraryTarget target) {
|
||||
private JourneyForTheElixirTarget(final JourneyForTheElixirTarget target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JourneyForTheElixirLibraryTarget copy() {
|
||||
return new JourneyForTheElixirLibraryTarget(this);
|
||||
public JourneyForTheElixirTarget copy() {
|
||||
return new JourneyForTheElixirTarget(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -117,95 +119,35 @@ class JourneyForTheElixirLibraryTarget extends TargetCardInLibrary {
|
|||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.getTargets().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Cards cards = new CardsImpl(this.getTargets());
|
||||
if (card.isBasic(game)
|
||||
&& card.isLand(game)
|
||||
&& cards
|
||||
boolean hasLand = cards
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(c -> c.isBasic(game))
|
||||
.anyMatch(c -> c.isLand(game))) {
|
||||
return false;
|
||||
}
|
||||
if (name.equals(card.getName())
|
||||
&& cards
|
||||
.anyMatch(c -> c.isLand(game));
|
||||
boolean hasJiang = cards
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.map(MageObject::getName)
|
||||
.anyMatch(name::equals)) {
|
||||
return false;
|
||||
}
|
||||
.anyMatch(name::equals);
|
||||
|
||||
if (!hasLand && card.isBasic(game) && card.isLand(game)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class JourneyForTheElixirGraveyardTarget extends TargetCardInYourGraveyard {
|
||||
|
||||
private static final String name = "Jiang Yanggu";
|
||||
private static final FilterCard filter
|
||||
= new FilterCard("a basic land card and a card named Jiang Yanggu");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
Predicates.and(
|
||||
SuperType.BASIC.getPredicate(),
|
||||
CardType.LAND.getPredicate()
|
||||
),
|
||||
new NamePredicate(name)
|
||||
));
|
||||
if (!hasJiang && name.equals(card.getName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Cards cards = new CardsImpl();
|
||||
|
||||
JourneyForTheElixirGraveyardTarget(Cards cards) {
|
||||
super(0, Integer.MAX_VALUE, filter, true);
|
||||
this.cards.addAll(cards);
|
||||
}
|
||||
|
||||
private JourneyForTheElixirGraveyardTarget(final JourneyForTheElixirGraveyardTarget target) {
|
||||
super(target);
|
||||
this.cards.addAll(target.cards);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JourneyForTheElixirGraveyardTarget copy() {
|
||||
return new JourneyForTheElixirGraveyardTarget(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
Cards alreadyTargeted = new CardsImpl(this.getTargets());
|
||||
alreadyTargeted.addAll(cards);
|
||||
boolean hasBasic = alreadyTargeted
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(c -> c.isLand(game))
|
||||
.anyMatch(c -> c.isBasic(game));
|
||||
possibleTargets.removeIf(uuid -> {
|
||||
Card card = game.getCard(uuid);
|
||||
return card != null
|
||||
&& hasBasic
|
||||
&& card.isLand(game)
|
||||
&& card.isBasic(game);
|
||||
});
|
||||
boolean hasYanggu = alreadyTargeted
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(MageObject::getName)
|
||||
.anyMatch(name::equals);
|
||||
possibleTargets.removeIf(uuid -> {
|
||||
Card card = game.getCard(uuid);
|
||||
return card != null
|
||||
&& hasYanggu
|
||||
&& name.equals(card.getName());
|
||||
});
|
||||
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, source, game));
|
||||
return possibleTargets;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ class KairiTheSwirlingSkyTarget extends TargetPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(controllerId, id, source, game)
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(playerId, id, source, game)
|
||||
&& CardUtil.checkCanTargetTotalValueLimit(
|
||||
this.getTargets(), id, MageObject::getManaValue, 6, game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@ package mage.cards.k;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTappedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.common.ExileSourceCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.common.FilterNonlandPermanent;
|
||||
import mage.filter.predicate.mageobject.ManaValuePredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -33,7 +34,7 @@ public class KarnsSylex extends CardImpl {
|
|||
this.addAbility(new EntersBattlefieldTappedAbility());
|
||||
|
||||
// Players can’t pay life to cast spells or to activate abilities that aren’t mana abilities.
|
||||
this.addAbility(new SimpleStaticAbility(new KarnsSylexEffect()));
|
||||
this.addAbility(new CantPayLifeOrSacrificeAbility(true, null));
|
||||
|
||||
// {X}, {T}, Exile Karn’s Sylex: Destroy each nonland permanent with mana value X or less. Activate only as a sorcery.
|
||||
Ability ability = new ActivateAsSorceryActivatedAbility(new KarnsSylexDestroyEffect(), new ManaCostsImpl<>("{X}"));
|
||||
|
|
@ -52,32 +53,6 @@ public class KarnsSylex extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
class KarnsSylexEffect extends ContinuousEffectImpl {
|
||||
|
||||
KarnsSylexEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment);
|
||||
staticText = "Players can't pay life to cast spells or to activate abilities that aren't mana abilities";
|
||||
}
|
||||
|
||||
private KarnsSylexEffect(final KarnsSylexEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KarnsSylexEffect copy() {
|
||||
return new KarnsSylexEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
player.setPayLifeCostLevel(Player.PayLifeCostLevel.onlyManaAbilities);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class KarnsSylexDestroyEffect extends OneShotEffect {
|
||||
|
||||
KarnsSylexDestroyEffect() {
|
||||
|
|
|
|||
79
Mage.Sets/src/mage/cards/k/KataraTheFearless.java
Normal file
79
Mage.Sets/src/mage/cards/k/KataraTheFearless.java
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class KataraTheFearless extends CardImpl {
|
||||
|
||||
public KataraTheFearless(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.WARRIOR);
|
||||
this.subtype.add(SubType.ALLY);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// If a triggered ability of an Ally you control triggers, that ability triggers an additional time.
|
||||
this.addAbility(new SimpleStaticAbility(new KataraTheFearlessEffect()));
|
||||
}
|
||||
|
||||
private KataraTheFearless(final KataraTheFearless card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KataraTheFearless copy() {
|
||||
return new KataraTheFearless(this);
|
||||
}
|
||||
}
|
||||
|
||||
class KataraTheFearlessEffect extends ReplacementEffectImpl {
|
||||
|
||||
KataraTheFearlessEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "if a triggered ability of an Ally you control triggers, that ability triggers an additional time";
|
||||
}
|
||||
|
||||
private KataraTheFearlessEffect(final KataraTheFearlessEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KataraTheFearlessEffect copy() {
|
||||
return new KataraTheFearlessEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.NUMBER_OF_TRIGGERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
|
||||
return permanent != null
|
||||
&& permanent.isControlledBy(source.getControllerId())
|
||||
&& permanent.hasSubtype(SubType.ALLY, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||
event.setAmount(event.getAmount() + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
60
Mage.Sets/src/mage/cards/k/KataraWaterbendingMaster.java
Normal file
60
Mage.Sets/src/mage/cards/k/KataraWaterbendingMaster.java
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.AttacksTriggeredAbility;
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.condition.common.OpponentsTurnCondition;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.CountersControllerCount;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersPlayersEffect;
|
||||
import mage.abilities.effects.common.discard.DiscardControllerEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.counters.CounterType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class KataraWaterbendingMaster extends CardImpl {
|
||||
|
||||
private static final DynamicValue xValue = new CountersControllerCount(CounterType.EXPERIENCE);
|
||||
|
||||
public KataraWaterbendingMaster(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.WARRIOR);
|
||||
this.subtype.add(SubType.ALLY);
|
||||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// Whenever you cast a spell during an opponent's turn, you get an experience counter.
|
||||
this.addAbility(new SpellCastControllerTriggeredAbility(new AddCountersPlayersEffect(
|
||||
CounterType.EXPERIENCE.createInstance(), TargetController.YOU), false
|
||||
).withTriggerCondition(OpponentsTurnCondition.instance));
|
||||
|
||||
// Whenever Katara attacks, you may draw a card for each experience counter you have. If you do, discard a card.
|
||||
Ability ability = new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(xValue)
|
||||
.setText("draw a card for each experience counter you have"));
|
||||
ability.addEffect(new DiscardControllerEffect(1).concatBy("If you do,"));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private KataraWaterbendingMaster(final KataraWaterbendingMaster card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KataraWaterbendingMaster copy() {
|
||||
return new KataraWaterbendingMaster(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ package mage.cards.k;
|
|||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.common.AttacksTriggeredAbility;
|
||||
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
|
|
@ -18,7 +18,6 @@ import mage.constants.*;
|
|||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.token.SoldierToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -38,7 +37,8 @@ public final class KateStewart extends CardImpl {
|
|||
this.toughness = new MageInt(3);
|
||||
|
||||
// Whenever you put one or more time counters on a permanent you control, create a 1/1 white Soldier creature token.
|
||||
this.addAbility(new KateStewartTriggeredAbility());
|
||||
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new CreateTokenEffect(new SoldierToken()),
|
||||
CounterType.TIME, StaticFilters.FILTER_CONTROLLED_PERMANENT));
|
||||
|
||||
// Whenever Kate Stewart attacks, you may pay {8}. If you do, attacking creatures get +X/+X until end of turn, where X is the number of time counters among permanents you control.
|
||||
this.addAbility(new AttacksTriggeredAbility(new DoIfCostPaid(new BoostAllEffect(
|
||||
|
|
@ -57,35 +57,6 @@ public final class KateStewart extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
class KateStewartTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
KateStewartTriggeredAbility() {
|
||||
super(Zone.BATTLEFIELD, new CreateTokenEffect(new SoldierToken()));
|
||||
setTriggerPhrase("Whenever you put one or more time counters on a permanent you control, ");
|
||||
}
|
||||
|
||||
private KateStewartTriggeredAbility(final KateStewartTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KateStewartTriggeredAbility copy() {
|
||||
return new KateStewartTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.COUNTERS_ADDED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return CounterType.TIME.getName().equals(event.getData())
|
||||
&& this.isControlledBy(event.getPlayerId())
|
||||
&& this.isControlledBy(game.getControllerId(event.getTargetId()));
|
||||
}
|
||||
}
|
||||
|
||||
enum KateStewartValue implements DynamicValue {
|
||||
instance;
|
||||
private static final Hint hint = new ValueHint("Time counters among permanents you control", instance);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
|
|
@ -15,10 +14,8 @@ import mage.filter.FilterOpponent;
|
|||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.token.BeastToken4;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -35,7 +32,7 @@ public final class KeeperOfTheBeasts extends CardImpl {
|
|||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(2);
|
||||
|
||||
// {G}, {tap}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield.
|
||||
// {G}, {T}: Choose target opponent who controlled more creatures than you did as you activated this ability. Put a 2/2 green Beast creature token onto the battlefield.
|
||||
Ability ability = new SimpleActivatedAbility(new CreateTokenEffect(new BeastToken4()).setText("Choose target opponent who controlled more creatures than you did as you activated this ability. Create a 2/2 green Beast creature token."),
|
||||
new ManaCostsImpl<>("{G}"));
|
||||
ability.addCost(new TapSourceCost());
|
||||
|
|
@ -65,42 +62,12 @@ class KeeperOfTheBeastsTarget extends TargetPlayer {
|
|||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
int creaturesController = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game);
|
||||
|
||||
for (UUID targetId : availablePossibleTargets) {
|
||||
if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, targetId, game) > creaturesController) {
|
||||
possibleTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
int myCount = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game);
|
||||
possibleTargets.removeIf(playerId -> game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) < myCount);
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
int count = 0;
|
||||
MageObject targetSource = game.getObject(source);
|
||||
Player controller = game.getPlayer(sourceControllerId);
|
||||
if (controller != null && targetSource != null) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, sourceControllerId, game)
|
||||
< game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game)
|
||||
&& !player.hasLeft()
|
||||
&& filter.match(player, sourceControllerId, source, game)
|
||||
&& player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeeperOfTheBeastsTarget copy() {
|
||||
return new KeeperOfTheBeastsTarget(this);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
|
|
@ -15,21 +12,23 @@ import mage.cards.CardSetInfo;
|
|||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPlayer;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.mageobject.ColorPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.TargetPlayer;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author spjspj
|
||||
*/
|
||||
public final class KeeperOfTheDead extends CardImpl {
|
||||
|
|
@ -95,48 +94,37 @@ class KeeperOfDeadPredicate implements ObjectSourcePlayerPredicate<Player> {
|
|||
|
||||
class KeeperOfTheDeadCreatureTarget extends TargetPermanent {
|
||||
|
||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblack creature that player controls");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.not(new ColorPredicate(ObjectColor.BLACK)));
|
||||
}
|
||||
|
||||
public KeeperOfTheDeadCreatureTarget() {
|
||||
super(1, 1, new FilterCreaturePermanent("nonblack creature that player controls"), false);
|
||||
super(1, 1, filter);
|
||||
}
|
||||
|
||||
private KeeperOfTheDeadCreatureTarget(final KeeperOfTheDeadCreatureTarget target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
UUID firstTarget = source.getFirstTarget();
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
if (firstTarget != null && permanent != null && permanent.isControlledBy(firstTarget)) {
|
||||
return super.canTarget(id, source, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject object = game.getObject(source);
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
|
||||
for (StackObject item : game.getState().getStack()) {
|
||||
if (item.getId().equals(source.getSourceId())) {
|
||||
object = item;
|
||||
}
|
||||
if (item.getSourceId().equals(source.getSourceId())) {
|
||||
object = item;
|
||||
}
|
||||
Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget());
|
||||
if (needPlayer == null) {
|
||||
// playable or not selected - use any
|
||||
} else {
|
||||
// filter by controller
|
||||
possibleTargets.removeIf(id -> {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
return permanent == null
|
||||
|| permanent.getId().equals(source.getFirstTarget())
|
||||
|| !permanent.isControlledBy(needPlayer.getId());
|
||||
});
|
||||
}
|
||||
|
||||
if (object instanceof StackObject) {
|
||||
UUID playerId = ((StackObject) object).getStackAbility().getFirstTarget();
|
||||
for (UUID targetId : availablePossibleTargets) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null && StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK.match(permanent, game) && permanent.isControlledBy(playerId)) {
|
||||
possibleTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
|
||||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
|
|
@ -12,13 +10,11 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterOpponent;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -67,43 +63,19 @@ class KeeperOfTheLightTarget extends TargetPlayer {
|
|||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> availablePossibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
int lifeController = game.getPlayer(sourceControllerId).getLife();
|
||||
Set<UUID> possibleTargets = super.possibleTargets(sourceControllerId, source, game);
|
||||
|
||||
for (UUID targetId : availablePossibleTargets) {
|
||||
Player opponent = game.getPlayer(targetId);
|
||||
if (opponent != null) {
|
||||
int lifeOpponent = opponent.getLife();
|
||||
if (lifeOpponent > lifeController) {
|
||||
possibleTargets.add(targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
Player controller = game.getPlayer(sourceControllerId);
|
||||
if (controller == null) {
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
int count = 0;
|
||||
MageObject targetSource = game.getObject(source);
|
||||
Player controller = game.getPlayer(sourceControllerId);
|
||||
if (controller != null && targetSource != null) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
possibleTargets.removeIf(playerId -> {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& controller.getLife() < player.getLife()
|
||||
&& !player.hasLeft()
|
||||
&& filter.match(player, sourceControllerId, source, game)
|
||||
&& player.canBeTargetedBy(targetSource, sourceControllerId, source, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return player == null || player.getLife() >= controller.getLife();
|
||||
});
|
||||
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ class KeldonBattlewagonCost extends CostImpl {
|
|||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
return target.canChoose(controllerId, source, game);
|
||||
return target.canChooseOrAlreadyChosen(controllerId, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -7,15 +7,14 @@ import mage.abilities.effects.common.continuous.BoostAllEffect;
|
|||
import mage.abilities.keyword.EquipAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreatureCard;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInGraveyard;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
|
|
@ -107,9 +108,9 @@ class KotoseTheSilentSpiderEffect extends OneShotEffect {
|
|||
cards.addAll(targetCardInGraveyard.getTargets());
|
||||
|
||||
filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s hand");
|
||||
TargetCardInHand targetCardInHand = new TargetCardInHand(0, Integer.MAX_VALUE, filter);
|
||||
controller.choose(outcome, opponent.getHand(), targetCardInHand, source, game);
|
||||
cards.addAll(targetCardInHand.getTargets());
|
||||
TargetCard targetCard = new TargetCard(0, Integer.MAX_VALUE, Zone.HAND, filter);
|
||||
controller.choose(outcome, opponent.getHand(), targetCard, source, game);
|
||||
cards.addAll(targetCard.getTargets());
|
||||
|
||||
filter.setMessage("cards named " + card.getName() + " from " + opponent.getName() + "'s library");
|
||||
TargetCardInLibrary target = new TargetCardInLibrary(0, Integer.MAX_VALUE, filter);
|
||||
|
|
|
|||
144
Mage.Sets/src/mage/cards/k/KravensLastHunt.java
Normal file
144
Mage.Sets/src/mage/cards/k/KravensLastHunt.java
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SagaAbility;
|
||||
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.effects.common.MillCardsControllerEffect;
|
||||
import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostTargetEffect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SagaChapter;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class KravensLastHunt extends CardImpl {
|
||||
|
||||
public KravensLastHunt(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}");
|
||||
|
||||
this.subtype.add(SubType.SAGA);
|
||||
|
||||
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)
|
||||
SagaAbility sagaAbility = new SagaAbility(this);
|
||||
|
||||
// I -- Mill five cards. When you do, this Saga deals damage equal to the greatest power among creature cards in your graveyard to target creature.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_I,
|
||||
new MillCardsControllerEffect(4), new KravensLastHuntEffect()
|
||||
);
|
||||
|
||||
// II -- Target creature you control gets +2/+2 until end of turn.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_II,
|
||||
new BoostTargetEffect(2, 2),
|
||||
new TargetControlledCreaturePermanent()
|
||||
);
|
||||
|
||||
// III -- Return target creature card from your graveyard to your hand.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_III, new ReturnFromGraveyardToHandTargetEffect(),
|
||||
new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)
|
||||
);
|
||||
this.addAbility(sagaAbility.addHint(KravensLastHuntValue.getHint()));
|
||||
}
|
||||
|
||||
private KravensLastHunt(final KravensLastHunt card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KravensLastHunt copy() {
|
||||
return new KravensLastHunt(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum KravensLastHuntValue implements DynamicValue {
|
||||
instance;
|
||||
private static final Hint hint = new ValueHint("Greatest power among creature cards in your graveyard", instance);
|
||||
|
||||
public static Hint getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
Player player = game.getPlayer(sourceAbility.getControllerId());
|
||||
if (player == null) {
|
||||
return 0;
|
||||
}
|
||||
return player
|
||||
.getGraveyard()
|
||||
.getCards(StaticFilters.FILTER_CARD_CREATURE, game)
|
||||
.stream()
|
||||
.map(MageObject::getPower)
|
||||
.mapToInt(MageInt::getValue)
|
||||
.max()
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KravensLastHuntValue copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
class KravensLastHuntEffect extends OneShotEffect {
|
||||
|
||||
|
||||
KravensLastHuntEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "When you do, {this} deals damage equal to the greatest power among creature cards in your graveyard to target creature";
|
||||
}
|
||||
|
||||
private KravensLastHuntEffect(final KravensLastHuntEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KravensLastHuntEffect copy() {
|
||||
return new KravensLastHuntEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(
|
||||
new DamageTargetEffect(KravensLastHuntValue.instance)
|
||||
.setText("{this} deals damage equal to the greatest power " +
|
||||
"among creature cards in your graveyard to target creature"), false
|
||||
);
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
game.fireReflexiveTriggeredAbility(ability, source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package mage.cards.k;
|
|||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.abilities.common.PutCounterOnCreatureTriggeredAbility;
|
||||
import mage.abilities.common.PutCounterOnPermanentTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.combat.GoadTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
|
|
@ -42,7 +42,8 @@ public final class KrosDefenseContractor extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Whenever you put one or more counters on a creature you don't control, tap that creature and goad it. It gains trample until your next turn.
|
||||
this.addAbility(new PutCounterOnCreatureTriggeredAbility(new KrosDefenseContractorEffect(), null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, true));
|
||||
this.addAbility(new PutCounterOnPermanentTriggeredAbility(new KrosDefenseContractorEffect(),
|
||||
null, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, true, false));
|
||||
}
|
||||
|
||||
private KrosDefenseContractor(final KrosDefenseContractor card) {
|
||||
|
|
|
|||
|
|
@ -130,8 +130,8 @@ class LagrellaTheMagpieTarget extends TargetPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(controllerId, id, source, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent creature = game.getPermanent(id);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class LethalSchemeEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
HashSet<MageObjectReference> convokingCreatures = CardUtil.getSourceCostsTag(game, source,
|
||||
ConvokeAbility.convokingCreaturesKey, new HashSet<>(0));
|
||||
ConvokeAbility.convokingCreaturesKey, new HashSet<>());
|
||||
Set<AbstractMap.SimpleEntry<UUID, Permanent>> playerPermanentsPairs =
|
||||
convokingCreatures
|
||||
.stream()
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ class LivelyDirgeTarget extends TargetCardInYourGraveyard {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(controllerId, id, source, game)
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(playerId, id, source, game)
|
||||
&& CardUtil.checkCanTargetTotalValueLimit(
|
||||
this.getTargets(), id, MageObject::getManaValue, 4, game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class LobotomyEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExile
|
|||
TargetCard target = new TargetCard(Zone.HAND, filter);
|
||||
target.withNotTarget(true);
|
||||
Card chosenCard = null;
|
||||
if (controller.chooseTarget(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
|
||||
if (controller.choose(Outcome.Benefit, targetPlayer.getHand(), target, source, game)) {
|
||||
chosenCard = game.getCard(target.getFirstTarget());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author weirddan455
|
||||
*/
|
||||
public final class LongRest extends CardImpl {
|
||||
|
|
@ -67,19 +66,22 @@ class LongRestTarget extends TargetCardInYourGraveyard {
|
|||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
Set<Integer> manaValues = new HashSet<>();
|
||||
|
||||
Set<Integer> usedManaValues = new HashSet<>();
|
||||
for (UUID targetId : this.getTargets()) {
|
||||
Card card = game.getCard(targetId);
|
||||
if (card != null) {
|
||||
manaValues.add(card.getManaValue());
|
||||
usedManaValues.add(card.getManaValue());
|
||||
}
|
||||
}
|
||||
|
||||
for (UUID possibleTargetId : super.possibleTargets(sourceControllerId, source, game)) {
|
||||
Card card = game.getCard(possibleTargetId);
|
||||
if (card != null && !manaValues.contains(card.getManaValue())) {
|
||||
if (card != null && !usedManaValues.contains(card.getManaValue())) {
|
||||
possibleTargets.add(possibleTargetId);
|
||||
}
|
||||
}
|
||||
|
||||
return possibleTargets;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ class MarchFromTheTombTarget extends TargetCardInYourGraveyard {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(controllerId, id, source, game)
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(playerId, id, source, game)
|
||||
&& CardUtil.checkCanTargetTotalValueLimit(
|
||||
this.getTargets(), id, MageObject::getManaValue, 8, game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ class ModifyMemoryTarget extends TargetPermanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(controllerId, id, source, game)) {
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
if (!super.canTarget(playerId, id, source, game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent creature = game.getPermanent(id);
|
||||
|
|
|
|||
|
|
@ -112,8 +112,8 @@ class MoorlandRescuerTarget extends TargetCardInYourGraveyard {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(controllerId, id, source, game)
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
return super.canTarget(playerId, id, source, game)
|
||||
&& CardUtil.checkCanTargetTotalValueLimit(
|
||||
this.getTargets(), id, m -> m.getPower().getValue(), xValue, game);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue