Merge branch 'github'
All checks were successful
/ build_release (push) Successful in 10m33s

This commit is contained in:
Failure 2025-06-28 00:11:49 -07:00
commit 9a52e15460
2419 changed files with 19807 additions and 23613 deletions

View file

@ -588,7 +588,10 @@ public class ScryfallImageSupportCards {
add("FIN"); // Final Fantasy
add("FIC"); // Final Fantasy Commander
add("FCA"); // Final Fantasy: Through the Ages
add("EOE"); // Edge of Eternities
add("EOC"); // Edge of Eternities Commander
add("SPE"); // Marvel's Spider-Man Eternal
add("TLA"); // Avatar: The Last Airbender
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
add("CALC"); // Custom Alchemized versions of existing cards

View file

@ -352,6 +352,9 @@ public class ScryfallImageSupportTokens {
put("ELD/Rat", "https://api.scryfall.com/cards/teld/6/en?format=image");
put("ELD/Wolf", "https://api.scryfall.com/cards/teld/14/en?format=image");
// UND
put("UND/Goblin", "https://api.scryfall.com/cards/tund/2?format=image");
// THB
put("THB/Elemental", "https://api.scryfall.com/cards/tthb/8/en?format=image");
put("THB/Goat", "https://api.scryfall.com/cards/tthb/1/en?format=image");
@ -1843,6 +1846,12 @@ public class ScryfallImageSupportTokens {
put("40K/Tyranid Gargoyle", "https://api.scryfall.com/cards/t40k/9/en?format=image");
put("40K/Tyranid Warrior", "https://api.scryfall.com/cards/t40k/19/en?format=image");
// UNF
put("UNF/Clown Robot/1", "https://api.scryfall.com/cards/tunf/2?format=image");
put("UNF/Clown Robot/2", "https://api.scryfall.com/cards/tunf/3?format=image");
put("UNF/Storm Crow", "https://api.scryfall.com/cards/tunf/5?format=image");
put("UNF/Squirrel", "https://api.scryfall.com/cards/tunf/8?format=image");
// BRO
put("BRO/Bear", "https://api.scryfall.com/cards/tbro/2/en?format=image");
put("BRO/Construct/1", "https://api.scryfall.com/cards/tbro/5/en?format=image");
@ -2026,6 +2035,7 @@ public class ScryfallImageSupportTokens {
// DIS
put("DIS/Emblem Momir", "https://api.scryfall.com/cards/pmoa/61/en?format=image");
put("DIS/Elemental", "https://api.scryfall.com/cards/togw/9?format=image");
// MUL
put("MUL/Elemental", "https://api.scryfall.com/cards/tmul/2/en?format=image");
@ -2182,6 +2192,7 @@ public class ScryfallImageSupportTokens {
// WHO
put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image");
put("WHO/Alien Angel", "https://api.scryfall.com/cards/twho/11?format=image");
put("WHO/Alien Insect", "https://api.scryfall.com/cards/twho/19/en?format=image");
put("WHO/Alien Rhino", "https://api.scryfall.com/cards/twho/3/en?format=image");
put("WHO/Alien Salamander", "https://api.scryfall.com/cards/twho/16?format=image");
@ -2536,6 +2547,7 @@ public class ScryfallImageSupportTokens {
put("DSK/Everywhere", "https://api.scryfall.com/cards/tdsk/16?format=image");
put("DSK/Glimmer", "https://api.scryfall.com/cards/tdsk/4?format=image");
put("DSK/Gremlin", "https://api.scryfall.com/cards/tdsk/11?format=image");
put("DSK/Horror", "https://api.scryfall.com/cards/tdsk/10?format=image");
put("DSK/Insect/1", "https://api.scryfall.com/cards/tdsk/13?format=image");
put("DSK/Insect/2", "https://api.scryfall.com/cards/tdsk/5?format=image");
put("DSK/Primo, the Indivisible", "https://api.scryfall.com/cards/tdsk/14?format=image");
@ -2698,7 +2710,8 @@ public class ScryfallImageSupportTokens {
put("TDC/Gold", "https://api.scryfall.com/cards/ttdc/29/en?format=image");
put("TDC/Human", "https://api.scryfall.com/cards/ttdc/5/en?format=image");
put("TDC/Inkling", "https://api.scryfall.com/cards/ttdc/28?format=image");
put("TDC/Insect", "https://api.scryfall.com/cards/ttdc/22/en?format=image");
put("TDC/Insect/1", "https://api.scryfall.com/cards/ttdc/22/en?format=image");
put("TDC/Insect/2", "https://api.scryfall.com/cards/ttdc/23/en?format=image");
put("TDC/Karox Bladewing", "https://api.scryfall.com/cards/ttdc/19?format=image");
put("TDC/Myr", "https://api.scryfall.com/cards/ttdc/30/en?format=image");
put("TDC/Plant", "https://api.scryfall.com/cards/ttdc/24/en?format=image");
@ -2710,6 +2723,7 @@ public class ScryfallImageSupportTokens {
put("TDC/Spider", "https://api.scryfall.com/cards/ttdc/25?format=image");
put("TDC/Spirit", "https://api.scryfall.com/cards/ttdc/6/en?format=image");
put("TDC/Thopter", "https://api.scryfall.com/cards/ttdc/33/en?format=image");
put("TDC/Wall", "https://api.scryfall.com/cards/ttdc/7/en?format=image");
// ACR
put("ACR/Assassin", "https://api.scryfall.com/cards/tacr/4?format=image");
@ -2723,7 +2737,51 @@ public class ScryfallImageSupportTokens {
put("DD2/Elemental Shaman", "https://api.scryfall.com/cards/tdd2/1?format=image");
// FIN
put("FIN/Hero/1", "https://api.scryfall.com/cards/tfin/2/en?format=image");
put("FIN/Hero/2", "https://api.scryfall.com/cards/tfin/3/en?format=image");
put("FIN/Hero/3", "https://api.scryfall.com/cards/tfin/4/en?format=image");
put("FIN/Hero/4", "https://api.scryfall.com/cards/tfin/5/en?format=image");
put("FIN/Hero/5", "https://api.scryfall.com/cards/tfin/6/en?format=image");
put("FIN/Hero/6", "https://api.scryfall.com/cards/tfin/7/en?format=image");
put("FIN/Hero/7", "https://api.scryfall.com/cards/tfin/8/en?format=image");
put("FIN/Hero/8", "https://api.scryfall.com/cards/tfin/9/en?format=image");
put("FIN/Hero/9", "https://api.scryfall.com/cards/tfin/26/en?format=image");
put("FIN/Hero/10", "https://api.scryfall.com/cards/tfin/27/en?format=image");
put("FIN/Hero/11", "https://api.scryfall.com/cards/tfin/28/en?format=image");
put("FIN/Hero/12", "https://api.scryfall.com/cards/tfin/29/en?format=image");
put("FIN/Hero/13", "https://api.scryfall.com/cards/tfin/30/en?format=image");
put("FIN/Hero/14", "https://api.scryfall.com/cards/tfin/31/en?format=image");
put("FIN/Hero/15", "https://api.scryfall.com/cards/tfin/32/en?format=image");
put("FIN/Hero/16", "https://api.scryfall.com/cards/tfin/33/en?format=image");
put("FIN/Knight", "https://api.scryfall.com/cards/tfin/10/en?format=image");
put("FIN/Moogle/1", "https://api.scryfall.com/cards/tfin/11/en?format=image");
put("FIN/Moogle/2", "https://api.scryfall.com/cards/tfin/34/en?format=image");
put("FIN/Robot", "https://api.scryfall.com/cards/tfin/12/en?format=image");
put("FIN/Horror", "https://api.scryfall.com/cards/tfin/13/en?format=image");
put("FIN/Wizard/1", "https://api.scryfall.com/cards/tfin/14/en?format=image");
put("FIN/Wizard/2", "https://api.scryfall.com/cards/tfin/15/en?format=image");
put("FIN/Wizard/3", "https://api.scryfall.com/cards/tfin/35/en?format=image");
put("FIN/Bird/1", "https://api.scryfall.com/cards/tfin/16/en?format=image");
put("FIN/Bird/2", "https://api.scryfall.com/cards/tfin/17/en?format=image");
put("FIN/Frog", "https://api.scryfall.com/cards/tfin/18/en?format=image");
put("FIN/Angelo", "https://api.scryfall.com/cards/tfin/19/en?format=image");
put("FIN/Darkstar", "https://api.scryfall.com/cards/tfin/20/en?format=image");
put("FIN/Elemental", "https://api.scryfall.com/cards/tfin/21/en?format=image");
put("FIN/Food", "https://api.scryfall.com/cards/tfin/22?format=image");
put("FIN/Treasure/1", "https://api.scryfall.com/cards/tfin/23/en?format=image");
put("FIN/Treasure/2", "https://api.scryfall.com/cards/tfin/36/en?format=image");
put("FIN/Emblem Sephiroth", "https://api.scryfall.com/cards/tfin/24/en?format=image");
// FIC
put("FIC/Human Soldier", "https://api.scryfall.com/cards/tfic/1/en?format=image");
put("FIC/Soldier", "https://api.scryfall.com/cards/tfic/2/en?format=image");
put("FIC/Spirit", "https://api.scryfall.com/cards/tfic/3/en?format=image");
put("FIC/Bird", "https://api.scryfall.com/cards/tfic/4/en?format=image");
put("FIC/Squid", "https://api.scryfall.com/cards/tfic/5/en?format=image");
put("FIC/Zombie", "https://api.scryfall.com/cards/tfic/6/en?format=image");
put("FIC/Rebel", "https://api.scryfall.com/cards/tfic/7/en?format=image");
put("FIC/The Blackjack", "https://api.scryfall.com/cards/tfic/8/en?format=image");
put("FIC/Clue", "https://api.scryfall.com/cards/tfic/9/en?format=image");
// JVC
put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image");
@ -2756,6 +2814,7 @@ public class ScryfallImageSupportTokens {
// UGL
put("UGL/Goblin", "https://api.scryfall.com/cards/tugl/4?format=image");
put("UGL/Pegasus", "https://api.scryfall.com/cards/tugl/1?format=image");
put("UGL/Rabid Sheep", "https://api.scryfall.com/cards/tugl/5?format=image");
put("UGL/Soldier", "https://api.scryfall.com/cards/tugl/2?format=image");
put("UGL/Squirrel", "https://api.scryfall.com/cards/tugl/6?format=image");
put("UGL/Zombie", "https://api.scryfall.com/cards/tugl/3?format=image");

View file

@ -0,0 +1,29 @@
package mage.utils.testers;
import java.util.List;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
public class AmountTestableResult extends BaseTestableResult {
int amount = 0;
public void onFinish(String resDebugSource, boolean status, List<String> info, int amount) {
this.onFinish(resDebugSource, status, info);
this.amount = amount;
}
@Override
public String getResAssert() {
return null; // TODO: implement
}
@Override
public void onClear() {
super.onClear();
this.amount = 0;
}
}

View file

@ -3,6 +3,7 @@ package mage.utils.testers;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -25,7 +26,8 @@ class AnnounceXTestableDialog extends BaseTestableDialog {
public AnnounceXTestableDialog(boolean isYou, boolean isMana, int min, int max) {
super(String.format("player.announceX(%s)", isYou ? "you" : "AI"),
String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), "");
String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), "",
new AmountTestableResult());
this.isYou = isYou;
this.isMana = isMana;
this.min = min;
@ -33,14 +35,15 @@ class AnnounceXTestableDialog extends BaseTestableDialog {
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
String message = "<font color=green>message</font> with html";
int chooseRes;
chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
return result;
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
int chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana);
List<String> res = new ArrayList<>();
res.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
((AmountTestableResult) this.getResult()).onFinish(chooseDebugSource, true, res, chooseRes);
}
static public void register(TestableDialogsRunner runner) {

View file

@ -1,11 +1,12 @@
package mage.utils.testers;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.constants.ComparisonType;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCreaturePermanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetPermanentOrPlayer;
/**
@ -15,14 +16,27 @@ import mage.target.common.TargetPermanentOrPlayer;
*/
abstract class BaseTestableDialog implements TestableDialog {
private Integer regNumber; // dialog number in runner (use it to find results and debugging)
private final String group;
private final String name;
private final String description;
private final TestableResult result;
public BaseTestableDialog(String group, String name, String description) {
public BaseTestableDialog(String group, String name, String description, TestableResult result) {
this.group = group;
this.name = name;
this.description = description;
this.result = result;
}
@Override
public void setRegNumber(Integer regNumber) {
this.regNumber = regNumber;
}
@Override
public Integer getRegNumber() {
return this.regNumber;
}
@Override
@ -41,13 +55,23 @@ abstract class BaseTestableDialog implements TestableDialog {
}
@Override
final public void showResult(Player player, Game game, String result) {
public void prepare() {
this.result.onClear();
}
@Override
final public void showResult(Player player, Game game) {
// show message with result
game.informPlayer(player, result);
game.informPlayer(player, String.join("<br>", getResult().getResDetails()));
// reset game and gui (in most use cases it must return to player's priority)
game.firePriorityEvent(player.getId());
}
@Override
public TestableResult getResult() {
return this.result;
}
static Target createAnyTarget(int min, int max) {
return createAnyTarget(min, max, false);
}
@ -56,19 +80,18 @@ abstract class BaseTestableDialog implements TestableDialog {
return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget);
}
static Target createCreatureTarget(int min, int max) {
return createCreatureTarget(min, max, false);
}
private static final FilterPermanent impossibleFilter = new FilterPermanent();
private static Target createCreatureTarget(int min, int max, boolean notTarget) {
return new TargetCreaturePermanent(min, max).withNotTarget(notTarget);
static {
impossibleFilter.add(new ManaValuePredicate(ComparisonType.OR_LESS, -1));
}
static Target createImpossibleTarget(int min, int max) {
return createImpossibleTarget(min, max, false);
return new TargetPermanent(min, max, impossibleFilter);
}
private static Target createImpossibleTarget(int min, int max, boolean notTarget) {
return new TargetCreaturePermanent(min, max, new FilterCreaturePermanent(SubType.TROOPER, "rare type"), notTarget);
@Override
public String toString() {
return this.getGroup() + " - " + this.getName() + " - " + this.getDescription();
}
}

View file

@ -0,0 +1,57 @@
package mage.utils.testers;
import java.util.ArrayList;
import java.util.List;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
public class BaseTestableResult implements TestableResult {
boolean isFinished = false;
String resDebugSource = ""; // source code line to find starting place to debug
boolean resStatus = false;
List<String> resInfo = new ArrayList<>();
@Override
public String getResDebugSource() {
return this.resDebugSource;
}
@Override
public boolean getResStatus() {
return this.resStatus;
}
@Override
public List<String> getResDetails() {
return this.resInfo;
}
@Override
public String getResAssert() {
return null; // TODO: implement
}
@Override
public void onFinish(String resDebugSource, boolean resStatus, List<String> resDetails) {
this.isFinished = true;
this.resDebugSource = resDebugSource;
this.resStatus = resStatus;
this.resInfo = resDetails;
}
@Override
public boolean isFinished() {
return this.isFinished;
}
@Override
public void onClear() {
this.isFinished = false;
this.resStatus = false;
this.resInfo.clear();
}
}

View file

@ -0,0 +1,29 @@
package mage.utils.testers;
import java.util.List;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
public class ChoiceTestableResult extends BaseTestableResult {
String choice = null;
public void onFinish(String resDebugSource, boolean status, List<String> info, String choice) {
this.onFinish(resDebugSource, status, info);
this.choice = choice;
}
@Override
public String getResAssert() {
return null; // TODO: implement
}
@Override
public void onClear() {
super.onClear();
this.choice = null;
}
}

View file

@ -7,6 +7,7 @@ import mage.players.Player;
import mage.target.TargetAmount;
import mage.target.Targets;
import mage.target.common.TargetAnyTargetAmount;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -32,27 +33,39 @@ class ChooseAmountTestableDialog extends BaseTestableDialog {
public ChooseAmountTestableDialog(boolean isYou, String name, int distributeAmount, int targetsMin, int targetsMax) {
super(String.format("player.chooseTarget(%s, amount)", isYou ? "you" : "AI"),
name,
String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax));
String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax),
new TargetTestableResult());
this.isYou = isYou;
this.distributeAmount = distributeAmount;
this.targetsMin = targetsMin;
this.targetsMax = targetsMax;
}
private ChooseAmountTestableDialog aiMustChoose(boolean resStatus, int targetsCount) {
// TODO: AI use default distribution, improve someday
TargetTestableResult res = ((TargetTestableResult) this.getResult());
res.aiAssertEnabled = true;
res.aiAssertResStatus = resStatus;
res.aiAssertTargetsCount = targetsCount;
return this;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax);
Player choosingPlayer = this.isYou ? player : opponent;
// TODO: add "damage" word in ability text, so chooseTargetAmount an show diff dialog (due inner logic - distribute damage or 1/1)
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
boolean chooseRes = choosingPlayer.chooseTargetAmount(Outcome.Benefit, choosingTarget, source, game);
List<String> result = new ArrayList<>();
List<String> res = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, res);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, res);
}
return result;
((TargetTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choosingTarget);
}
static public void register(TestableDialogsRunner runner) {
@ -61,53 +74,55 @@ class ChooseAmountTestableDialog extends BaseTestableDialog {
List<Boolean> isYous = Arrays.asList(false, true);
// current AI will choose 1 target and assign all values to it (except with outcome.Damage)
// TODO: add use cases for damage effects?
for (boolean isYou : isYous) {
// up to
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5).aiMustChoose(false, 0));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5).aiMustChoose(true, 1));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5).aiMustChoose(true, 1));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5).aiMustChoose(true, 1));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5).aiMustChoose(true, 1));
// need target
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3).aiMustChoose(false, 0));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5).aiMustChoose(false, 0));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5).aiMustChoose(true, 1));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5).aiMustChoose(true, 1));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5).aiMustChoose(true, 1));
//
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3).aiMustChoose(true, 1));
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5).aiMustChoose(true, 1));
}
}
}

View file

@ -13,6 +13,7 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.target.Targets;
import mage.target.common.TargetCardInHand;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -36,16 +37,17 @@ class ChooseCardsTestableDialog extends BaseTestableDialog {
public ChooseCardsTestableDialog(boolean isTargetChoice, boolean notTarget, boolean isYou, String name, TargetCard target) {
super(String.format("%s(%s, %s, cards)",
isTargetChoice ? "player.chooseTarget" : "player.choose",
isYou ? "you" : "AI",
notTarget ? "not target" : "target"), name, target.toString());
isTargetChoice ? "player.chooseTarget" : "player.choose",
isYou ? "you" : "AI",
notTarget ? "not target" : "target"), name, target.toString(),
new TargetTestableResult());
this.isTargetChoice = isTargetChoice;
this.target = target.withNotTarget(notTarget);
this.isYou = isYou;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
TargetCard choosingTarget = this.target.copy();
Player choosingPlayer = this.isYou ? player : opponent;
@ -56,19 +58,23 @@ class ChooseCardsTestableDialog extends BaseTestableDialog {
Cards choosingCards = new CardsImpl(all.stream().limit(100).collect(Collectors.toList()));
boolean chooseRes;
String chooseDebugSource;
if (this.isTargetChoice) {
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingCards, choosingTarget, source, game);
} else {
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingCards, choosingTarget, source, game);
}
List<String> result = new ArrayList<>();
List<String> res = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, res);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, res);
}
return result;
((TargetTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choosingTarget);
}
static public void register(TestableDialogsRunner runner) {

View file

@ -5,6 +5,7 @@ import mage.choices.*;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -24,28 +25,36 @@ class ChooseChoiceTestableDialog extends BaseTestableDialog {
Choice choice;
public ChooseChoiceTestableDialog(boolean isYou, String name, Choice choice) {
super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"), name, choice.getClass().getSimpleName());
super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"),
name,
choice.getClass().getSimpleName(),
new ChoiceTestableResult()
);
this.isYou = isYou;
this.choice = choice;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
Choice dialog = this.choice.copy();
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
boolean chooseRes = choosingPlayer.choose(Outcome.Benefit, dialog, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
result.add("");
List<String> res = new ArrayList<>();
res.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
res.add("");
String choice;
if (dialog.isKeyChoice()) {
String key = dialog.getChoiceKey();
result.add(String.format("* selected key: %s (%s)", key, dialog.getKeyChoices().getOrDefault(key, null)));
choice = dialog.getKeyChoices().getOrDefault(key, null);
res.add(String.format("* selected key: %s (%s)", key, choice));
} else {
result.add(String.format("* selected value: %s", dialog.getChoice()));
choice = dialog.getChoice();
res.add(String.format("* selected value: %s", choice));
}
return result;
((ChoiceTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choice);
}
static public void register(TestableDialogsRunner runner) {

View file

@ -6,6 +6,7 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -28,14 +29,18 @@ class ChoosePileTestableDialog extends BaseTestableDialog {
int pileSize2;
public ChoosePileTestableDialog(boolean isYou, int pileSize1, int pileSize2) {
super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"), "pile sizes: " + pileSize1 + " and " + pileSize2, "");
super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"),
"pile sizes: " + pileSize1 + " and " + pileSize2,
"",
new BaseTestableResult()
);
this.isYou = isYou;
this.pileSize1 = pileSize1;
this.pileSize2 = pileSize2;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
// TODO: it's ok to show broken title - must add html support in windows's title someday
String mainMessage = "main <font color=green>message</font> with html" + CardUtil.getSourceLogName(game, source);
@ -47,11 +52,13 @@ class ChoosePileTestableDialog extends BaseTestableDialog {
List<Card> pile2 = all.stream().limit(this.pileSize2).collect(Collectors.toList());
Player choosingPlayer = this.isYou ? player : opponent;
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
boolean chooseRes = choosingPlayer.choosePile(Outcome.Benefit, mainMessage, pile1, pile2, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
result.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2"));
return result;
List<String> res = new ArrayList<>();
res.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
res.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2"));
this.getResult().onFinish(chooseDebugSource, chooseRes, res);
}
static public void register(TestableDialogsRunner runner) {

View file

@ -6,6 +6,7 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.Targets;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -31,10 +32,14 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
public ChooseTargetTestableDialog(boolean isPlayerChoice, boolean isTargetChoice, boolean notTarget, boolean isYou, String name, Target target) {
super(String.format("%s%s(%s, %s)",
isPlayerChoice ? "player.choose" : "target.choose",
isTargetChoice ? "Target" : "", // chooseTarget or choose
isYou ? "you" : "AI",
notTarget ? "not target" : "target"), name, target.toString());
isPlayerChoice ? "player.choose" : "target.choose",
isTargetChoice ? "Target" : "", // chooseTarget or choose
isYou ? "you" : "AI",
notTarget ? "not target" : "target"),
name,
target.toString(),
new TargetTestableResult()
);
this.isPlayerChoice = isPlayerChoice;
this.isTargetChoice = isTargetChoice;
this.target = target.withNotTarget(notTarget);
@ -42,34 +47,48 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
Target choosingTarget = this.target.copy();
Player choosingPlayer = this.isYou ? player : opponent;
boolean chooseRes;
String chooseDebugSource;
if (this.isPlayerChoice) {
// player.chooseXXX
if (this.isTargetChoice) {
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingTarget, source, game);
} else {
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingTarget, source, game);
}
} else {
// target.chooseXXX
if (this.isTargetChoice) {
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
chooseRes = choosingTarget.chooseTarget(Outcome.Benefit, choosingPlayer.getId(), source, game);
} else {
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
chooseRes = choosingTarget.choose(Outcome.Benefit, choosingPlayer.getId(), source, game);
}
}
List<String> result = new ArrayList<>();
List<String> res = new ArrayList<>();
if (chooseRes) {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, res);
} else {
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, res);
}
return result;
((TargetTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choosingTarget);
}
private ChooseTargetTestableDialog aiMustChoose(boolean resStatus, int targetsCount) {
TargetTestableResult res = ((TargetTestableResult) this.getResult());
res.aiAssertEnabled = true;
res.aiAssertResStatus = resStatus;
res.aiAssertTargetsCount = targetsCount;
return this;
}
static public void register(TestableDialogsRunner runner) {
@ -84,37 +103,29 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
for (boolean isYou : isYous) {
for (boolean isTargetChoice : isTargetChoices) {
for (boolean isPlayerChoice : isPlayerChoices) {
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0 e.g. X=0", createAnyTarget(0, 0))); // simulate X=0
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1", createAnyTarget(1, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3", createAnyTarget(3, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 5", createAnyTarget(5, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any max", createAnyTarget(0, Integer.MAX_VALUE)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-1", createAnyTarget(0, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-3", createAnyTarget(0, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-5", createAnyTarget(0, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-3", createAnyTarget(1, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-3", createAnyTarget(2, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-5", createAnyTarget(1, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-5", createAnyTarget(2, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3-5", createAnyTarget(3, 5)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 4-5", createAnyTarget(4, 5))); // impossible on 3 targets
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0 e.g. X=0", createAnyTarget(0, 0)).aiMustChoose(false, 0)); // simulate X=0
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1", createAnyTarget(1, 1)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3", createAnyTarget(3, 3)).aiMustChoose(true, 3));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 5", createAnyTarget(5, 5)).aiMustChoose(true, 5));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any max", createAnyTarget(0, Integer.MAX_VALUE)).aiMustChoose(true, 6 + 1)); // 6 own cards + 1 own player
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-1", createAnyTarget(0, 1)).aiMustChoose(true, 1));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-3", createAnyTarget(0, 3)).aiMustChoose(true, 3));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-5", createAnyTarget(0, 5)).aiMustChoose(true, 5));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-3", createAnyTarget(1, 3)).aiMustChoose(true, 3));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-3", createAnyTarget(2, 3)).aiMustChoose(true, 3));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-5", createAnyTarget(1, 5)).aiMustChoose(true, 5));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-5", createAnyTarget(2, 5)).aiMustChoose(true, 5));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3-5", createAnyTarget(3, 5)).aiMustChoose(true, 5));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 4-5", createAnyTarget(4, 5)).aiMustChoose(true, 5));
//
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0, e.g. X=0", createImpossibleTarget(0, 0)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1", createImpossibleTarget(1, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 3", createImpossibleTarget(3, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-1", createImpossibleTarget(0, 1)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-3", createImpossibleTarget(0, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)));
//
/*
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 0, e.g. X=0", createCreatureTarget(0, 0))); // simulate X=0
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 1", createCreatureTarget(1, 1)));
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 3", createCreatureTarget(3, 3)));
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 5", createCreatureTarget(5, 5)));
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures max", createCreatureTarget(0, Integer.MAX_VALUE)));
*/
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0, e.g. X=0", createImpossibleTarget(0, 0)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1", createImpossibleTarget(1, 1)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 3", createImpossibleTarget(3, 3)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-1", createImpossibleTarget(0, 1)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-3", createImpossibleTarget(0, 3)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)).aiMustChoose(false, 0));
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)).aiMustChoose(false, 0));
}
}
}

View file

@ -5,6 +5,7 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -27,7 +28,11 @@ class ChooseUseTestableDialog extends BaseTestableDialog {
String messageAdditional;
public ChooseUseTestableDialog(boolean isYou, String name, String trueText, String falseText, String messageMain, String messageAdditional) {
super(String.format("player.chooseUse(%s)", isYou ? "you" : "AI"), name + buildName(trueText, falseText, messageMain, messageAdditional), "");
super(String.format("player.chooseUse(%s)", isYou ? "you" : "AI"),
name + buildName(trueText, falseText, messageMain, messageAdditional),
"",
new BaseTestableResult()
);
this.isYou = isYou;
this.trueText = trueText;
this.falseText = falseText;
@ -42,8 +47,9 @@ class ChooseUseTestableDialog extends BaseTestableDialog {
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
boolean chooseRes = choosingPlayer.chooseUse(
Outcome.Benefit,
messageMain,
@ -53,9 +59,10 @@ class ChooseUseTestableDialog extends BaseTestableDialog {
source,
game
);
List<String> result = new ArrayList<>();
result.add(chooseRes ? "TRUE" : "FALSE");
return result;
List<String> res = new ArrayList<>();
res.add(chooseRes ? "TRUE" : "FALSE");
this.getResult().onFinish(chooseDebugSource, chooseRes, res);
}
static public void register(TestableDialogsRunner runner) {

View file

@ -3,6 +3,7 @@ package mage.utils.testers;
import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import mage.util.DebugUtil;
import java.util.ArrayList;
import java.util.Arrays;
@ -26,21 +27,25 @@ class GetAmountTestableDialog extends BaseTestableDialog {
public GetAmountTestableDialog(boolean isYou, int min, int max) {
super(String.format("player.getAmount(%s)", isYou ? "you" : "AI"),
String.format("from %d to %d", min, max), "");
String.format("from %d to %d", min, max),
"",
new AmountTestableResult()
);
this.isYou = isYou;
this.min = min;
this.max = max;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
String message = "<font color=green>message</font> with html";
int chooseRes;
chooseRes = choosingPlayer.getAmount(this.min, this.max, message, source, game);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
return result;
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
int chooseRes = choosingPlayer.getAmount(this.min, this.max, message, source, game);
List<String> res = new ArrayList<>();
res.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
((AmountTestableResult) this.getResult()).onFinish(chooseDebugSource, true, res, chooseRes);
}
static public void register(TestableDialogsRunner runner) {

View file

@ -5,12 +5,14 @@ import mage.constants.MultiAmountType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.DebugUtil;
import mage.util.MultiAmountMessage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Part of testable game dialogs
@ -36,7 +38,9 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
public GetMultiAmountTestableDialog(boolean isYou, String info, int totalMin, int totalMax, List<List<Integer>> options) {
super(String.format("player.getMultiAmount(%s)", isYou ? "you" : "AI"),
String.format("%s, %d options from [%d-%d]", info, options.size(), totalMin, totalMax),
"");
"",
new MultiAmountTestableResult()
);
this.isYou = isYou;
this.totalMin = totalMin;
this.totalMax = totalMax;
@ -48,13 +52,33 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
}
}
private GetMultiAmountTestableDialog aiMustChoose(Integer... needValues) {
// TODO: AI use default distribution (min possible values), improve someday
MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult());
res.aiAssertEnabled = true;
res.aiAssertValues = Arrays.stream(needValues).collect(Collectors.toList());
return this;
}
private GetMultiAmountTestableDialog aiMustChooseMany(Integer options, Integer perOption) {
List<Integer> need = new ArrayList<>();
IntStream.rangeClosed(1, options).forEach(x -> {
need.add(perOption);
});
MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult());
res.aiAssertEnabled = true;
res.aiAssertValues = need;
return this;
}
@Override
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
public void showDialog(Player player, Ability source, Game game, Player opponent) {
Player choosingPlayer = this.isYou ? player : opponent;
//String message = "<font color=green>message</font> with html";
List<Integer> chooseRes;
List<MultiAmountMessage> options = this.amountOptions.stream().map(MultiAmountMessage::copy).collect(Collectors.toList());
chooseRes = choosingPlayer.getMultiAmountWithIndividualConstraints(
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
List<Integer> chooseRes = choosingPlayer.getMultiAmountWithIndividualConstraints(
Outcome.Benefit,
options,
this.totalMin,
@ -63,24 +87,24 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
game
);
List<String> result = new ArrayList<>();
result.add(getGroup() + " - " + this.getName());
List<String> res = new ArrayList<>();
res.add(getGroup() + " - " + this.getName());
int selectedIndex = -1;
int selectedTotal = 0;
for (Integer selectedValue : chooseRes) {
selectedIndex++;
selectedTotal += selectedValue;
MultiAmountMessage option = this.amountOptions.get(selectedIndex);
result.add(String.format("%d from [%d-%d, def %d]",
res.add(String.format("%d from [%d-%d, def %d]",
selectedValue,
option.min,
option.max,
option.defaultValue
));
}
result.add("total selected: " + selectedTotal);
res.add("total selected: " + selectedTotal);
return result;
((MultiAmountTestableResult) this.getResult()).onFinish(chooseDebugSource, true, res, chooseRes);
}
static public void register(TestableDialogsRunner runner) {
@ -88,28 +112,29 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
for (boolean isYou : isYous) {
// make sure default values are valid due min/max settings
// TODO: add bad effect for AI (must test default distribution)
// single target
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 1, genSameOptions(1, 0, 1, 0)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 3, genSameOptions(1, 0, 3, 0)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 1, genSameOptions(1, 1, 1, 1)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 3, genSameOptions(1, 1, 3, 1)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 5 def", 0, 10, genSameOptions(1, 0, 10, 5)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 10 def", 10, 10, genSameOptions(1, 0, 10, 10)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 1, genSameOptions(1, 0, 1, 0)).aiMustChoose(1));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 3, genSameOptions(1, 0, 3, 0)).aiMustChoose(3));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 1, genSameOptions(1, 1, 1, 1)).aiMustChoose(1));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 3, genSameOptions(1, 1, 3, 1)).aiMustChoose(3));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 5 def", 0, 10, genSameOptions(1, 0, 10, 5)).aiMustChoose(10));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 10 def", 10, 10, genSameOptions(1, 0, 10, 10)).aiMustChoose(10));
// multiple targets
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 0 def", 0, 5, genSameOptions(3, 0, 3, 0)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 0 def", 0, 5, genSameOptions(3, 0, 3, 0)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 1 def", 1, 5, genSameOptions(3, 1, 3, 1)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 1 def", 1, 5, genSameOptions(3, 1, 3, 1)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 0, 60, genSameOptions(3, 0, 60, 20)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 60, 60, genSameOptions(3, 0, 60, 20)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 0 def", 0, 5, genSameOptions(3, 0, 3, 0)).aiMustChoose(2, 2, 1));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 1 def", 1, 5, genSameOptions(3, 1, 3, 1)).aiMustChoose(2, 2, 1));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 0, 60, genSameOptions(3, 0, 60, 20)).aiMustChoose(20, 20, 20));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 60, 60, genSameOptions(3, 0, 60, 20)).aiMustChoose(20, 20, 20));
// big lists
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "big list", 0, 100, genSameOptions(20, 0, 100, 0)));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "big list", 0, 100, genSameOptions(20, 0, 100, 0)).aiMustChooseMany(20, 5));
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "big list", 0, 100, genSameOptions(100, 0, 100, 0)).aiMustChooseMany(100, 1));
}
}
private static List<List<Integer>> genSameOptions(int amount, int min, int max, int def) {
private static List<List<Integer>> genSameOptions(int options, int min, int max, int def) {
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < amount; i++) {
for (int i = 0; i < options; i++) {
// min, max, default
res.add(Arrays.asList(min, max, def));
}

View file

@ -0,0 +1,54 @@
package mage.utils.testers;
import java.util.ArrayList;
import java.util.List;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
public class MultiAmountTestableResult extends BaseTestableResult {
List<Integer> selectedValues;
boolean aiAssertEnabled = false;
List<Integer> aiAssertValues = new ArrayList<>();
public void onFinish(String resDebugSource, boolean status, List<String> info, List<Integer> selectedValues) {
this.onFinish(resDebugSource, status, info);
this.selectedValues = selectedValues;
}
@Override
public String getResAssert() {
if (!this.aiAssertEnabled) {
return null;
}
// not finished
if (this.selectedValues == null) {
return null;
}
// wrong selection
String selected = this.selectedValues.toString();
String need = this.aiAssertValues.toString();
if (!selected.equals(need)) {
return String.format("Wrong selection: need %s, but get %s",
need,
selected
);
}
// all fine
return "";
}
@Override
public void onClear() {
super.onClear();
this.selectedValues = null;
}
}

View file

@ -0,0 +1,61 @@
package mage.utils.testers;
import mage.target.Target;
import java.util.List;
/**
* Part of testable game dialogs
*
* @author JayDi85
*/
public class TargetTestableResult extends BaseTestableResult {
Target target = null;
boolean aiAssertEnabled = false;
boolean aiAssertResStatus = false;
int aiAssertTargetsCount = 0;
public void onFinish(String resDebugSource, boolean status, List<String> info, Target target) {
this.onFinish(resDebugSource, status, info);
this.target = target;
}
@Override
public String getResAssert() {
if (!this.aiAssertEnabled) {
return null;
}
// not finished
if (this.target == null) {
return null;
}
// wrong choose
if (this.getResStatus() != this.aiAssertResStatus) {
return String.format("Wrong status: need %s, but get %s",
this.aiAssertResStatus,
this.getResStatus()
);
}
// wrong targets
if (this.target.getTargets().size() != this.aiAssertTargetsCount) {
return String.format("Wrong targets count: need %d, but get %d",
this.aiAssertTargetsCount,
this.target.getTargets().size()
);
}
// all fine
return "";
}
@Override
public void onClear() {
super.onClear();
this.target = null;
}
}

View file

@ -4,8 +4,6 @@ import mage.abilities.Ability;
import mage.game.Game;
import mage.players.Player;
import java.util.List;
/**
* Part of testable game dialogs
* <p>
@ -17,7 +15,11 @@ import java.util.List;
*
* @author JayDi85
*/
interface TestableDialog {
public interface TestableDialog {
void setRegNumber(Integer regNumber);
Integer getRegNumber();
String getGroup();
@ -25,7 +27,20 @@ interface TestableDialog {
String getDescription();
List<String> showDialog(Player player, Ability source, Game game, Player opponent);
TestableResult getResult();
void showResult(Player player, Game game, String result);
/**
* Prepare dialog before show, e.g. clear prev results
*/
void prepare();
/**
* Show game dialog to the user and save result
*/
void showDialog(Player player, Ability source, Game game, Player opponent);
/**
* Show result dialog to the user
*/
void showResult(Player player, Game game);
}

View file

@ -8,8 +8,10 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -55,7 +57,7 @@ import java.util.stream.Collectors;
*/
public class TestableDialogsRunner {
private final List<TestableDialog> dialogs = new ArrayList<>();
private final Map<Integer, TestableDialog> dialogs = new LinkedHashMap<>();
static final int LAST_SELECTED_GROUP_ID = 997;
static final int LAST_SELECTED_DIALOG_ID = 998;
@ -79,12 +81,14 @@ public class TestableDialogsRunner {
}
void registerDialog(TestableDialog dialog) {
this.dialogs.add(dialog);
Integer regNumber = this.dialogs.size() + 1;
dialog.setRegNumber(regNumber);
this.dialogs.put(regNumber, dialog);
}
public void selectAndShowTestableDialog(Player player, Ability source, Game game, Player opponent) {
// select group or fast links
List<String> groups = this.dialogs.stream()
List<String> groups = this.dialogs.values().stream()
.map(TestableDialog::getGroup)
.distinct()
.sorted()
@ -129,8 +133,9 @@ public class TestableDialogsRunner {
// all fine, can show it and finish
lastSelectedGroup = needGroup;
lastSelectedDialog = needDialog;
List<String> resInfo = needDialog.showDialog(player, source, game, opponent);
needDialog.showResult(player, game, String.join("<br>", resInfo));
needDialog.prepare();
needDialog.showDialog(player, source, game, opponent);
needDialog.showResult(player, game);
}
private Choice prepareSelectGroupChoice(List<String> groups) {
@ -199,5 +204,9 @@ public class TestableDialogsRunner {
}
return choice;
}
public Collection<TestableDialog> getDialogs() {
return this.dialogs.values();
}
}

View file

@ -0,0 +1,43 @@
package mage.utils.testers;
import java.util.List;
/**
* Part of testable game dialogs, must contain dialogs result
*
* @author JayDi85
*/
public interface TestableResult {
/**
* Get source code line with called dialog, use it as starting debug point
*/
String getResDebugSource();
/**
* Dialog's result
*/
boolean getResStatus();
/**
* Dialog's detail result
*/
List<String> getResDetails();
/**
* Save new result after show dialog
*/
void onFinish(String chooseDebugSource, boolean resStatus, List<String> resDetails);
boolean isFinished();
void onClear();
/**
* Assert dialog result
* - null - not ready (dev must setup wanted result)
* - empty - good
* - not empty - fail (return error message)
*/
String getResAssert();
}

View file

@ -67,6 +67,7 @@ public class GameView implements Serializable {
// TODO: implement and support in admin tools
private int totalErrorsCount;
private int totalEffectsCount;
private int gameCycle;
public GameView(GameState state, Game game, UUID createdForPlayerId, UUID watcherUserId) {
Player createdForPlayer = null;
@ -214,6 +215,7 @@ public class GameView implements Serializable {
this.rollbackTurnsAllowed = game.getOptions().rollbackTurnsAllowed;
this.totalErrorsCount = game.getTotalErrorsCount();
this.totalEffectsCount = game.getTotalEffectsCount();
this.gameCycle = game.getState().getApplyEffectsCounter();
}
private void checkPaid(UUID uuid, StackAbility stackAbility) {
@ -358,4 +360,8 @@ public class GameView implements Serializable {
public int getTotalEffectsCount() {
return this.totalEffectsCount;
}
public int getGameCycle() {
return this.gameCycle;
}
}

View file

@ -18,11 +18,6 @@ public class Standard extends Constructed {
super("Constructed - Standard");
setCodes.addAll(makeLegalSets());
banned.add("The Meathook Massacre");
banned.add("Fable of the Mirror-Breaker");
banned.add("Reckoner Bankbuster");
banned.add("Invoke Despair");
}
static List<String> makeLegalSets() {

View file

@ -23,6 +23,7 @@ import mage.game.stack.StackAbility;
import mage.game.stack.StackObject;
import mage.player.ai.ma.optimizers.TreeOptimizer;
import mage.player.ai.ma.optimizers.impl.*;
import mage.player.ai.score.GameStateEvaluator2;
import mage.player.ai.util.CombatInfo;
import mage.player.ai.util.CombatUtil;
import mage.players.Player;
@ -399,7 +400,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (effect != null
&& stackObject.getControllerId().equals(playerId)) {
Target target = effect.getTarget();
if (!target.isChoiceCompleted(game)) {
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
Game sim = game.createSimulationForAI();
StackAbility newAbility = (StackAbility) stackObject.copy();
@ -848,10 +849,12 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (targets.isEmpty()) {
return super.chooseTarget(outcome, cards, target, source, game);
}
if (!target.isChoiceCompleted(game)) {
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
for (UUID targetId : targets) {
target.addTarget(targetId, source, game);
if (target.isChoiceCompleted(game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
targets.clear();
return true;
}
@ -866,10 +869,12 @@ public class ComputerPlayer6 extends ComputerPlayer {
if (targets.isEmpty()) {
return super.choose(outcome, cards, target, source, game);
}
if (!target.isChoiceCompleted(game)) {
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
for (UUID targetId : targets) {
target.add(targetId, game);
if (target.isChoiceCompleted(game)) {
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
targets.clear();
return true;
}

View file

@ -3,6 +3,7 @@ package mage.player.ai;
import mage.abilities.Ability;
import mage.constants.RangeOfInfluence;
import mage.game.Game;
import mage.player.ai.score.GameStateEvaluator2;
import org.apache.log4j.Logger;
import java.util.Date;
@ -111,8 +112,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
protected void calculateActions(Game game) {
if (!getNextAction(game)) {
//logger.info("--- calculating possible actions for " + this.getName() + " on " + game.toString());
Date startTime = new Date();
currentScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
Game sim = createSimulation(game);
SimulationNode2.resetCount();
@ -145,15 +144,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
} else {
logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip");
}
Date endTime = new Date();
this.setLastThinkTime((endTime.getTime() - startTime.getTime()));
/*
logger.warn("Last think time: " + this.getLastThinkTime()
+ "; actions: " + actions.size()
+ "; hand: " + this.getHand().size()
+ "; permanents: " + game.getBattlefield().getAllPermanents().size());
*/
} else {
logger.debug("Next Action exists!");
}

View file

@ -13,7 +13,7 @@ import mage.game.permanent.Permanent;
import mage.game.turn.CombatDamageStep;
import mage.game.turn.EndOfCombatStep;
import mage.game.turn.Step;
import mage.player.ai.GameStateEvaluator2;
import mage.player.ai.score.GameStateEvaluator2;
import mage.players.Player;
import org.apache.log4j.Logger;

View file

@ -0,0 +1,152 @@
package mage.player.ai;
import mage.MageItem;
import mage.MageObject;
import mage.cards.Card;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.player.ai.score.GameStateEvaluator2;
import mage.players.PlayableObjectsList;
import mage.players.Player;
import java.util.Comparator;
import java.util.UUID;
/**
* AI related code - compare and sort possible targets due target/effect type
*
* @author JayDi85
*/
public class PossibleTargetsComparator {
UUID abilityControllerId;
Game game;
PlayableObjectsList playableItems = new PlayableObjectsList();
public PossibleTargetsComparator(UUID abilityControllerId, Game game) {
this.abilityControllerId = abilityControllerId;
this.game = game;
}
public void findPlayableItems() {
this.playableItems = this.game.getPlayer(this.abilityControllerId).getPlayableObjects(this.game, Zone.ALL);
}
private int getScoreFromBattlefield(MageItem item) {
if (item instanceof Permanent) {
// use battlefield score instead simple life
return GameStateEvaluator2.evaluatePermanent((Permanent) item, game, false);
} else {
return getScoreFromLife(item);
}
}
private String getName(MageItem item) {
if (item instanceof Player) {
return ((Player) item).getName();
} else if (item instanceof MageObject) {
return ((MageObject) item).getName();
} else {
return "unknown";
}
}
public static int getLifeForDamage(MageItem item, Game game) {
int res = 0;
if (item instanceof Player) {
res = ((Player) item).getLife();
} else if (item instanceof Card) {
Card card = (Card) item;
if (card.isPlaneswalker(game)) {
res = card.getCounters(game).getCount(CounterType.LOYALTY);
} else if (card.isBattle(game)) {
res = card.getCounters(game).getCount(CounterType.DEFENSE);
} else {
int damage = 0;
if (card instanceof Permanent) {
damage = ((Permanent) card).getDamage();
}
res = Math.max(0, card.getToughness().getValue() - damage);
}
}
return res;
}
private int getScoreFromLife(MageItem item) {
// TODO: replace permanent/card life by battlefield score?
int res = getLifeForDamage(item, game);
if (res == 0 && item instanceof Card) {
res = ((Card) item).getManaValue();
}
return res;
}
private boolean isMyItem(MageItem item) {
return PossibleTargetsSelector.isMyItem(this.abilityControllerId, item);
}
// sort by name-id at the end, so AI will use same choices in all simulations
private final Comparator<MageItem> BY_NAME = (o1, o2) -> getName(o2).compareTo(getName(o1));
private final Comparator<MageItem> BY_ID = Comparator.comparing(MageItem::getId);
private final Comparator<MageItem> BY_ME = (o1, o2) -> Boolean.compare(
isMyItem(o2),
isMyItem(o1)
);
private final Comparator<MageItem> BY_BIGGER_SCORE = (o1, o2) -> Integer.compare(
getScoreFromBattlefield(o2),
getScoreFromBattlefield(o1)
);
private final Comparator<MageItem> BY_PLAYABLE = (o1, o2) -> Boolean.compare(
this.playableItems.containsObject(o2.getId()),
this.playableItems.containsObject(o1.getId())
);
private final Comparator<MageItem> BY_LAND = (o1, o2) -> {
boolean isLand1 = o1 instanceof MageObject && ((MageObject) o1).isLand(game);
boolean isLand2 = o2 instanceof MageObject && ((MageObject) o2).isLand(game);
return Boolean.compare(isLand2, isLand1);
};
private final Comparator<MageItem> BY_TYPE_PLAYER = (o1, o2) -> Boolean.compare(
o2 instanceof Player,
o1 instanceof Player
);
private final Comparator<MageItem> BY_TYPE_PLANESWALKER = (o1, o2) -> {
boolean isPlaneswalker1 = o1 instanceof MageObject && ((MageObject) o1).isPlaneswalker(game);
boolean isPlaneswalker2 = o2 instanceof MageObject && ((MageObject) o2).isPlaneswalker(game);
return Boolean.compare(isPlaneswalker2, isPlaneswalker1);
};
private final Comparator<MageItem> BY_TYPE_BATTLE = (o1, o2) -> {
boolean isBattle1 = o1 instanceof MageObject && ((MageObject) o1).isBattle(game);
boolean isBattle2 = o2 instanceof MageObject && ((MageObject) o2).isBattle(game);
return Boolean.compare(isBattle2, isBattle1);
};
private final Comparator<MageItem> BY_TYPES = BY_TYPE_PLANESWALKER
.thenComparing(BY_TYPE_BATTLE)
.thenComparing(BY_TYPE_PLAYER);
/**
* Default sorting for good effects - put the biggest items to the top
*/
public final Comparator<MageItem> ANY_MOST_VALUABLE_FIRST = BY_TYPES
.thenComparing(BY_BIGGER_SCORE)
.thenComparing(BY_NAME)
.thenComparing(BY_ID);
public final Comparator<MageItem> ANY_MOST_VALUABLE_LAST = ANY_MOST_VALUABLE_FIRST.reversed();
/**
* Sorting for discard effects - put the biggest unplayable at the top, lands at the end anyway
*/
public final Comparator<MageItem> ANY_UNPLAYABLE_AND_USELESS = BY_LAND.reversed()
.thenComparing(BY_PLAYABLE.reversed())
.thenComparing(ANY_MOST_VALUABLE_FIRST);
}

View file

@ -0,0 +1,188 @@
package mage.player.ai;
import mage.MageItem;
import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.ControllableOrOwnerable;
import mage.game.Game;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyardBattlefieldOrStack;
import mage.target.common.TargetDiscard;
import java.util.*;
import java.util.stream.Collectors;
/**
* AI related code - find possible targets and sort it due priority
*
* @author JayDi85
*/
public class PossibleTargetsSelector {
Outcome outcome;
Target target;
UUID abilityControllerId;
Ability source;
Game game;
PossibleTargetsComparator comparators;
// possible targets lists
List<MageItem> me = new ArrayList<>();
List<MageItem> opponents = new ArrayList<>();
List<MageItem> any = new ArrayList<>(); // for outcomes with any target like copy
public PossibleTargetsSelector(Outcome outcome, Target target, UUID abilityControllerId, Ability source, Game game) {
this.outcome = outcome;
this.target = target;
this.abilityControllerId = abilityControllerId;
this.source = source;
this.game = game;
this.comparators = new PossibleTargetsComparator(abilityControllerId, game);
}
public void findNewTargets(Set<UUID> fromTargetsList) {
// collect new valid targets
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game).stream()
.filter(id -> !target.contains(id))
.filter(id -> fromTargetsList == null || fromTargetsList.contains(id))
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
.map(id -> {
Player player = game.getPlayer(id);
if (player != null) {
return player;
} else {
return game.getObject(id);
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// split targets between me and opponents
found.forEach(item -> {
if (isMyItem(abilityControllerId, item)) {
this.me.add(item);
} else {
this.opponents.add(item);
}
this.any.add(item);
});
if (target instanceof TargetDiscard) {
// sort due unplayable
sortByUnplayableAndUseless();
} else {
// sort due good/bad outcome
sortByMostValuableTargets();
}
}
/**
* Sorting for any good/bad effects
*/
private void sortByMostValuableTargets() {
if (isGoodEffect()) {
// for good effect must choose the biggest objects
this.me.sort(comparators.ANY_MOST_VALUABLE_FIRST);
this.opponents.sort(comparators.ANY_MOST_VALUABLE_LAST);
this.any.sort(comparators.ANY_MOST_VALUABLE_FIRST);
} else {
// for bad effect must choose the smallest objects
this.me.sort(comparators.ANY_MOST_VALUABLE_LAST);
this.opponents.sort(comparators.ANY_MOST_VALUABLE_FIRST);
this.any.sort(comparators.ANY_MOST_VALUABLE_LAST);
}
}
/**
* Sorting for discard
*/
private void sortByUnplayableAndUseless() {
// used
// no good or bad effect - you must choose
comparators.findPlayableItems();
this.me.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
this.opponents.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
this.any.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
}
/**
* Priority targets. Try to use as much as possible.
*/
public List<MageItem> getGoodTargets() {
if (isAnyEffect()) {
return this.any;
}
if (isGoodEffect()) {
return this.me;
} else {
return this.opponents;
}
}
/**
* Optional targets. Try to ignore bad targets (e.g. opponent's creatures for your good effect).
*/
public List<MageItem> getBadTargets() {
if (isAnyEffect()) {
return Collections.emptyList();
}
if (isGoodEffect()) {
return this.opponents;
} else {
return this.me;
}
}
public static boolean isMyItem(UUID abilityControllerId, MageItem item) {
if (item instanceof Player) {
return item.getId().equals(abilityControllerId);
} else if (item instanceof ControllableOrOwnerable) {
return ((ControllableOrOwnerable) item).getControllerOrOwnerId().equals(abilityControllerId);
}
return false;
}
private boolean isAnyEffect() {
boolean isAnyEffect = outcome.anyTargetHasSameValue();
if (hasGoodExile()) {
isAnyEffect = true;
}
return isAnyEffect;
}
private boolean isGoodEffect() {
boolean isGoodEffect = outcome.isGood();
if (hasGoodExile()) {
isGoodEffect = true;
}
return isGoodEffect;
}
private boolean hasGoodExile() {
// exile workaround: exile is bad, but exile from library or graveyard in most cases is good
// (more exiled -- more good things you get, e.g. delve's pay or search cards with same name)
if (outcome == Outcome.Exile) {
if (Zone.GRAVEYARD.match(target.getZone())
|| Zone.LIBRARY.match(target.getZone())) {
// TargetCardInGraveyardBattlefieldOrStack - used for additional payment like Craft, so do not allow big cards for it
if (!(target instanceof TargetCardInGraveyardBattlefieldOrStack)) {
return true;
}
}
}
return false;
}
boolean hasAnyTargets() {
return !this.any.isEmpty();
}
}

View file

@ -1,4 +1,4 @@
package mage.player.ai.ma;
package mage.player.ai.score;
import mage.MageObject;
import mage.abilities.Ability;

View file

@ -1,8 +1,7 @@
package mage.player.ai;
package mage.player.ai.score;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.player.ai.ma.ArtificialScoringSystem;
import mage.players.Player;
import org.apache.log4j.Logger;

View file

@ -1,4 +1,4 @@
package mage.player.ai.ma;
package mage.player.ai.score;
import mage.abilities.Ability;
import mage.abilities.keyword.*;
@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* TODO: outdated, replace by edh or commander brackets ability score
* @author nantuko
*/
public final class MagicAbility {
@ -18,10 +19,10 @@ public final class MagicAbility {
put(DoubleStrikeAbility.getInstance().getRule(), 100);
put(new ExaltedAbility().getRule(), 10);
put(FirstStrikeAbility.getInstance().getRule(), 50);
put(FlashAbility.getInstance().getRule(), 0);
put(FlashAbility.getInstance().getRule(), 20);
put(FlyingAbility.getInstance().getRule(), 50);
put(new ForestwalkAbility().getRule(), 10);
put(HasteAbility.getInstance().getRule(), 0);
put(HasteAbility.getInstance().getRule(), 20);
put(IndestructibleAbility.getInstance().getRule(), 150);
put(InfectAbility.getInstance().getRule(), 60);
put(IntimidateAbility.getInstance().getRule(), 50);
@ -46,7 +47,7 @@ public final class MagicAbility {
if (!scores.containsKey(ability.getRule())) {
//System.err.println("Couldn't find ability score: " + ability.getClass().getSimpleName() + " - " + ability.toString());
//TODO: add handling protection from ..., levelup, kicker, etc. abilities
return 0;
return 2; // more abilities - more score in any use cases
}
return scores.get(ability.getRule());
}

View file

@ -1,65 +0,0 @@
package mage.player.ai.simulators;
import mage.abilities.ActivatedAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.player.ai.ComputerPlayer;
import mage.player.ai.PermanentEvaluator;
import mage.players.Player;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class ActionSimulator {
private ComputerPlayer player;
private List<Card> playableInstants = new ArrayList<>();
private List<ActivatedAbility> playableAbilities = new ArrayList<>();
private Game game;
public ActionSimulator(ComputerPlayer player) {
this.player = player;
}
public void simulate(Game game) {
}
public int evaluateState() {
// must find all leaved opponents
Player opponent = game.getPlayer(game.getOpponents(player.getId(), false).stream().findFirst().orElse(null));
if (opponent == null) {
return Integer.MAX_VALUE;
}
if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) {
return Integer.MIN_VALUE;
}
if (opponent.hasLost() || player.hasWon()) {
return Integer.MAX_VALUE;
}
}
int value = player.getLife();
value -= opponent.getLife();
PermanentEvaluator evaluator = new PermanentEvaluator();
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(player.getId())) {
value += evaluator.evaluate(permanent, game);
}
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(player.getId())) {
value -= evaluator.evaluate(permanent, game);
}
value += player.getHand().size();
value -= opponent.getHand().size();
return value;
}
}

View file

@ -1,148 +0,0 @@
package mage.player.ai.simulators;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CombatGroupSimulator implements Serializable {
public List<CreatureSimulator> attackers = new ArrayList<>();
public List<CreatureSimulator> blockers = new ArrayList<>();
public UUID defenderId;
public boolean defenderIsPlaneswalker;
public int unblockedDamage;
private CreatureSimulator attacker;
public CombatGroupSimulator(UUID defenderId, List<UUID> attackers, List<UUID> blockers, Game game) {
this.defenderId = defenderId;
for (UUID attackerId: attackers) {
Permanent permanent = game.getPermanent(attackerId);
this.attackers.add(new CreatureSimulator(permanent));
}
for (UUID blockerId: blockers) {
Permanent permanent = game.getPermanent(blockerId);
this.blockers.add(new CreatureSimulator(permanent));
}
//NOTE: assumes no banding
attacker = this.attackers.get(0);
}
private boolean hasFirstOrDoubleStrike() {
for (CreatureSimulator creature: attackers) {
if (creature.hasDoubleStrike || creature.hasFirstStrike)
return true;
}
for (CreatureSimulator creature: blockers) {
if (creature.hasDoubleStrike || creature.hasFirstStrike)
return true;
}
return false;
}
public boolean canBlock(Permanent blocker, Game game) {
return blocker.canBlock(attacker.id, game);
}
public void simulateCombat(Game game) {
unblockedDamage = 0;
if (hasFirstOrDoubleStrike())
assignDamage(true, game);
assignDamage(false, game);
}
private void assignDamage(boolean first, Game game) {
if (blockers.isEmpty()) {
if (canDamage(attacker, first))
unblockedDamage += attacker.power;
}
else if (blockers.size() == 1) {
CreatureSimulator blocker = blockers.get(0);
if (canDamage(attacker, first)) {
if (attacker.hasTrample) {
int lethalDamage = blocker.getLethalDamage(game);
if (attacker.power > lethalDamage) {
blocker.damage += lethalDamage;
unblockedDamage += attacker.power - lethalDamage;
}
else {
blocker.damage += attacker.power;
}
}
}
if (canDamage(blocker, first)) {
attacker.damage += blocker.power;
}
}
else {
int damage = attacker.power;
for (CreatureSimulator blocker: blockers) {
if (damage > 0 && canDamage(attacker, first)) {
int lethalDamage = blocker.getLethalDamage(game);
if (damage > lethalDamage) {
blocker.damage += lethalDamage;
damage -= lethalDamage;
}
else {
blocker.damage += damage;
damage = 0;
}
}
if (canDamage(blocker, first)) {
attacker.damage += blocker.power;
}
}
if (damage > 0) {
if (attacker.hasTrample) {
unblockedDamage += damage;
}
else {
blockers.get(0).damage += damage;
}
}
}
}
private boolean canDamage(CreatureSimulator creature, boolean first) {
if (first && (creature.hasFirstStrike || creature.hasDoubleStrike))
return true;
if (!first && (!creature.hasFirstStrike || creature.hasDoubleStrike))
return true;
return false;
}
/**
* returns 3 attacker survives blockers destroyed
* returns 2 both destroyed
* returns 1 both survive
* returns 0 attacker destroyed blockers survive
*
* @return int
*/
public int evaluateCombat() {
int survivingBlockers = 0;
for (CreatureSimulator blocker: blockers) {
if (blocker.damage < blocker.toughness)
survivingBlockers++;
}
if (attacker.isDead()) {
if (survivingBlockers > 0) {
return 0;
}
return 2;
}
else {
if (survivingBlockers > 0) {
return 1;
}
return 3;
}
}
}

View file

@ -1,86 +0,0 @@
package mage.player.ai.simulators;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CombatSimulator implements Serializable {
public List<CombatGroupSimulator> groups = new ArrayList<>();
public List<UUID> defenders = new ArrayList<>();
public Map<UUID, Integer> playersLife = new HashMap<>();
public Map<UUID, Integer> planeswalkerLoyalty = new HashMap<>();
public UUID attackerId;
public int rating = 0;
public static CombatSimulator load(Game game) {
CombatSimulator simCombat = new CombatSimulator();
for (CombatGroup group: game.getCombat().getGroups()) {
simCombat.groups.add(new CombatGroupSimulator(group.getDefenderId(), group.getAttackers(), group.getBlockers(), game));
}
for (UUID defenderId: game.getCombat().getDefenders()) {
simCombat.defenders.add(defenderId);
Player player = game.getPlayer(defenderId);
if (player != null) {
simCombat.playersLife.put(defenderId, player.getLife());
}
else {
Permanent permanent = game.getPermanent(defenderId);
simCombat.planeswalkerLoyalty.put(defenderId, permanent.getCounters(game).getCount(CounterType.LOYALTY));
}
}
return simCombat;
}
public CombatSimulator() {}
public void clear() {
groups.clear();
defenders.clear();
attackerId = null;
}
public void simulate(Game game) {
for (CombatGroupSimulator group: groups) {
group.simulateCombat(game);
}
}
public int evaluate() {
Map<UUID, Integer> damage = new HashMap<>();
int result = 0;
for (CombatGroupSimulator group: groups) {
if (!damage.containsKey(group.defenderId)) {
damage.put(group.defenderId, group.unblockedDamage);
}
else {
damage.put(group.defenderId, damage.get(group.defenderId) + group.unblockedDamage);
}
}
//check for lethal damage to player
for (Entry<UUID, Integer> entry: playersLife.entrySet()) {
if (damage.containsKey(entry.getKey()) && entry.getValue() <= damage.get(entry.getKey())) {
//TODO: check for protection
//NOTE: not applicable for mulitplayer games
return Integer.MAX_VALUE;
}
}
for (CombatGroupSimulator group: groups) {
result += group.evaluateCombat();
}
rating = result;
return result;
}
}

View file

@ -1,57 +0,0 @@
package mage.player.ai.simulators;
import mage.abilities.keyword.DoubleStrikeAbility;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class CreatureSimulator implements Serializable {
public UUID id;
public int damage;
public int power;
public int toughness;
public boolean hasFirstStrike;
public boolean hasDoubleStrike;
public boolean hasTrample;
public Permanent permanent;
public CreatureSimulator(Permanent permanent) {
this.id = permanent.getId();
this.damage = permanent.getDamage();
this.power = permanent.getPower().getValue();
this.toughness = permanent.getToughness().getValue();
this.hasDoubleStrike = permanent.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
this.hasFirstStrike = permanent.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId());
this.hasTrample = permanent.getAbilities().containsKey(TrampleAbility.getInstance().getId());
this.permanent = permanent;
}
public boolean isDead() {
return damage >= toughness;
}
public int getLethalDamage(Game game) {
List<FilterCreaturePermanent> usePowerInsteadOfToughnessForDamageLethalityFilters = game.getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters();
/*
* for handling Zilortha, Strength Incarnate:
* 2020-04-17
* Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on.
*/
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
.anyMatch(filter -> filter.match(permanent, game));
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isnt destroyed unless it has at least 1 damage marked on it.
Math.max(power, 1) : toughness;
return Math.max(lethalDamageThreshold - damage, 0);
}
}

View file

@ -288,6 +288,16 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
@Override
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
// nothing to choose
target.prepareAmount(source, game);
if (target.getAmountRemaining() <= 0) {
return false;
}
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
return false;
}
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
if (possibleTargets.isEmpty()) {
return !target.isRequired(source);

View file

@ -297,7 +297,7 @@ public class HumanPlayer extends PlayerImpl {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Setting game priority for " + getId() + " [" + DebugUtil.getMethodNameWithSource(1) + ']');
logger.debug("Setting game priority for " + getId() + " [" + DebugUtil.getMethodNameWithSource(1, "method") + ']');
}
game.getState().setPriorityPlayerId(getId());
}
@ -328,7 +328,7 @@ public class HumanPlayer extends PlayerImpl {
while (loop) {
// start waiting for next answer
response.clear();
response.setActiveAction(game, DebugUtil.getMethodNameWithSource(1));
response.setActiveAction(game, DebugUtil.getMethodNameWithSource(1, "method"));
game.resumeTimer(getTurnControlledBy());
responseOpenedForAnswer = true;
@ -690,12 +690,8 @@ public class HumanPlayer extends PlayerImpl {
return false;
}
// choose one or multiple permanents
UUID abilityControllerId = playerId;
if (target.getTargetController() != null
&& target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
}
// choose one or multiple targets
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
if (options == null) {
options = new HashMap<>();
}
@ -782,11 +778,7 @@ public class HumanPlayer extends PlayerImpl {
}
// choose one or multiple targets
UUID abilityControllerId = playerId;
if (target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
Map<String, Serializable> options = new HashMap<>();
while (canRespond()) {
@ -869,13 +861,7 @@ public class HumanPlayer extends PlayerImpl {
return false;
}
UUID abilityControllerId;
if (target.getTargetController() != null
&& target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
} else {
abilityControllerId = playerId;
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
while (canRespond()) {
@ -966,13 +952,7 @@ public class HumanPlayer extends PlayerImpl {
return false;
}
UUID abilityControllerId;
if (target.getTargetController() != null
&& target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
} else {
abilityControllerId = playerId;
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
while (canRespond()) {
boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source);
@ -1042,14 +1022,20 @@ public class HumanPlayer extends PlayerImpl {
return false;
}
// nothing to choose
target.prepareAmount(source, game);
if (target.getAmountRemaining() <= 0) {
return false;
}
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
return false;
}
if (source == null) {
return false;
}
UUID abilityControllerId = playerId;
if (target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
}
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
int amountTotal = target.getAmountTotal(game, source);
if (amountTotal == 0) {

View file

@ -14,6 +14,7 @@ import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetAnyTarget;
import mage.target.common.TargetCreaturePermanent;
@ -39,7 +40,7 @@ public final class AbunaAcolyte extends CardImpl {
Ability ability1 = new SimpleActivatedAbility(new PreventDamageToTargetEffect(Duration.EndOfTurn, 1), new TapSourceCost());
ability1.addTarget(new TargetAnyTarget());
Ability ability2 = new SimpleActivatedAbility(new PreventDamageToTargetEffect(Duration.EndOfTurn, 2), new TapSourceCost());
ability2.addTarget(new TargetCreaturePermanent(filter));
ability2.addTarget(new TargetPermanent(filter));
this.addAbility(ability1);
this.addAbility(ability2);
}

View file

@ -13,6 +13,7 @@ import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetCreaturePermanentAmount;
@ -33,7 +34,7 @@ public final class AbzanCharm extends CardImpl {
// Choose one -
// *Exile target creature with power 3 or greater
this.getSpellAbility().addTarget(new TargetCreaturePermanent(FILTER));
this.getSpellAbility().addTarget(new TargetPermanent(FILTER));
this.getSpellAbility().addEffect(new ExileTargetEffect());
// *You draw two cards and you lose 2 life

View file

@ -16,10 +16,13 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledPermanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
/**
* @author JRHerlehy
*/
@ -47,7 +50,7 @@ public final class AcademyJourneymage extends CardImpl {
// When Academy Journeymage enters the battlefield, return target creature an opponent controls to its owner's hand.
ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect());
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
ability.addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE));
this.addAbility(ability);
}

View file

@ -1,7 +1,6 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
@ -11,8 +10,9 @@ import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.NamePredicate;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class AccumulatedKnowledge extends CardImpl {
@ -24,13 +24,13 @@ public final class AccumulatedKnowledge extends CardImpl {
}
public AccumulatedKnowledge(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}");
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Draw a card, then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1));
Effect effect = new DrawCardSourceControllerEffect(new CardsInAllGraveyardsCount(filter));
effect.setText(", then draw cards equal to the number of cards named {this} in all graveyards");
effect.setText(", then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards");
this.getSpellAbility().addEffect(effect);
}

View file

@ -44,9 +44,7 @@ public final class AcererakTheArchlich extends CardImpl {
Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandSourceEffect(true))
.withInterveningIf(AcererakTheArchlichCondition.instance);
ability.addEffect(new VentureIntoTheDungeonEffect().concatBy("and"));
ability.addHint(CurrentDungeonHint.instance);
ability.addHint(CompletedDungeonCondition.getHint());
this.addAbility(ability, new CompletedDungeonWatcher());
this.addAbility(ability.addHint(CurrentDungeonHint.instance).addHint(CompletedDungeonCondition.getHint()), new CompletedDungeonWatcher());
// Whenever Acererak the Archlich attacks, for each opponent, you create a 2/2 black Zombie creature token unless that player sacrifices a creature.
this.addAbility(new AttacksTriggeredAbility(new AcererakTheArchlichEffect()));
@ -83,7 +81,7 @@ class AcererakTheArchlichEffect extends OneShotEffect {
AcererakTheArchlichEffect() {
super(Outcome.Benefit);
staticText = "for each opponent, you create a 2/2 black Zombie creature " +
"token unless that player sacrifices a creature";
"token unless that player sacrifices a creature of their choice";
}
private AcererakTheArchlichEffect(final AcererakTheArchlichEffect effect) {

View file

@ -15,7 +15,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.TargetPermanent;
import java.util.UUID;
@ -60,7 +60,7 @@ public final class AcesBaseballBat extends CardImpl {
// Equip legendary creature (1)
this.addAbility(new EquipAbility(
Outcome.AddAbility, new GenericManaCost(1),
new TargetControlledCreaturePermanent(filterLegendary), false
new TargetPermanent(filterLegendary), false
));
// Equip {3}

View file

@ -5,7 +5,7 @@ import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.condition.common.BeforeBlockersAreDeclaredCondition;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.decorator.ConditionalActivatedAbility;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.DestroyTargetEffect;
@ -39,7 +39,7 @@ public final class AcidicDagger extends CardImpl {
// {4}, {tap}: Whenever target creature deals combat damage to a non-Wall creature this turn,
// destroy that non-Wall creature. When the targeted creature leaves the battlefield this turn,
// sacrifice Acidic Dagger. Activate this ability only before blockers are declared.
Ability ability = new ConditionalActivatedAbility(
Ability ability = new ActivateIfConditionActivatedAbility(
new CreateDelayedTriggeredAbilityEffect(new AcidicDaggerDestroyNonWallAbility()),
new GenericManaCost(4),
BeforeBlockersAreDeclaredCondition.instance);

View file

@ -11,8 +11,11 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
/**
*
* @author North
@ -21,7 +24,7 @@ public final class ActOfAggression extends CardImpl {
public ActOfAggression(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R/P}{R/P}");
this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
this.getSpellAbility().addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE));
this.getSpellAbility().addEffect(new GainControlTargetEffect(Duration.EndOfTurn));
this.getSpellAbility().addEffect(new UntapTargetEffect().setText("Untap that creature"));
this.getSpellAbility().addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn).setText("It gains haste until end of turn."));

View file

@ -1,7 +1,5 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
@ -9,26 +7,26 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.hint.common.ArtifactYouControlHint;
import mage.abilities.keyword.EquipAbility;
import mage.constants.Outcome;
import mage.constants.PutCards;
import mage.constants.SubType;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.PutCards;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
*
* @author sobiech
*/
public final class AdaptiveOmnitool extends CardImpl {
private final static DynamicValue artifactYouControlCount = new PermanentsOnBattlefieldCount(new FilterControlledArtifactPermanent());
private final static Hint hint = new ValueHint("Artifacts you control", artifactYouControlCount);
public AdaptiveOmnitool(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}");
@ -37,7 +35,7 @@ public final class AdaptiveOmnitool extends CardImpl {
// Equipped creature gets +1/+1 for each artifact you control.
this.addAbility(
new SimpleStaticAbility(new BoostEquippedEffect(artifactYouControlCount, artifactYouControlCount)).addHint(hint)
new SimpleStaticAbility(new BoostEquippedEffect(artifactYouControlCount, artifactYouControlCount)).addHint(ArtifactYouControlHint.instance)
);
// Whenever equipped creature attacks, look at the top six cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.

View file

@ -31,7 +31,7 @@ public final class AdaptiveTrainingPost extends CardImpl {
this.addAbility(new SpellCastControllerTriggeredAbility(
new AddCountersSourceEffect(CounterType.CHARGE.createInstance()),
StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false
).withInterveningIf(condition));
).withInterveningIf(condition).withRuleTextReplacement(true));
// Remove three charge counters from this artifact: When you next cast an instant or sorcery spell this turn, copy it and you may choose new targets for the copy.
this.addAbility(new SimpleActivatedAbility(

View file

@ -1,7 +1,6 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
@ -20,13 +19,15 @@ import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
*
* @author jeffwadsworth
*/
public final class AdmonitionAngel extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("nonland permanent other than Admonition Angel");
private static final FilterPermanent filter = new FilterPermanent("nonland permanent other than {this}");
static {
filter.add(AnotherPredicate.instance);

View file

@ -14,6 +14,7 @@ import mage.constants.TargetController;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -38,7 +39,7 @@ public final class AdvocateOfTheBeast extends CardImpl {
// At the beginning of your end step, put a +1/+1 counter on target Beast creature you control.
Ability ability = new BeginningOfEndStepTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
Target target = new TargetCreaturePermanent(filter);
Target target = new TargetPermanent(filter);
ability.addTarget(target);
this.addAbility(ability);
}

View file

@ -1,6 +1,5 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
@ -10,12 +9,12 @@ 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.target.common.TargetControlledCreaturePermanent;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class AegisAutomaton extends CardImpl {
@ -29,7 +28,7 @@ public final class AegisAutomaton extends CardImpl {
// {4}{W}: Return another target creature you control to its owner's hand.
Ability ability = new SimpleActivatedAbility(new ReturnToHandTargetEffect(), new ManaCostsImpl<>("{4}{W}"));
ability.addTarget(new TargetControlledCreaturePermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL));
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL));
this.addAbility(ability);
}

View file

@ -16,6 +16,7 @@ import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.filter.predicate.mageobject.ToughnessPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -37,7 +38,7 @@ public final class AegisOfTheMeek extends CardImpl {
// {1}, {T}: Target 1/1 creature gets +1/+2 until end of turn.
Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(1, 2, Duration.EndOfTurn), new ManaCostsImpl<>("{1}"));
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -12,6 +12,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -31,7 +32,7 @@ public final class AerialPredation extends CardImpl {
// Destroy target creature with flying. You gain 2 life.
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
this.getSpellAbility().addTarget(new TargetPermanent(filter));
this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addEffect(new GainLifeEffect(2));
}

View file

@ -84,7 +84,7 @@ enum AerialSurveyorCondition implements Condition {
@Override
public String toString() {
return "";
return "defending player controls more lands than you";
}
}

View file

@ -16,6 +16,7 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
@ -42,7 +43,7 @@ public final class AerieOuphes extends CardImpl {
// Sacrifice Aerie Ouphes: Aerie Ouphes deals damage equal to its power to target creature with flying.
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE)
.setText("it deals damage equal to its power to target creature with flying"), new SacrificeSourceCost());
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
// Persist

View file

@ -44,7 +44,7 @@ class AetherBarrierEffect extends SacrificeEffect {
AetherBarrierEffect() {
super(new FilterPermanent("permanent to sacrifice"), 1, "that player");
this.staticText = "that player sacrifices a permanent unless they pay {1}";
this.staticText = "that player sacrifices a permanent of their choice unless they pay {1}";
}
private AetherBarrierEffect(final AetherBarrierEffect effect) {

View file

@ -1,22 +1,22 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.IndestructibleAbility;
import mage.constants.SubType;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
*
* @author TheElk801
*/
public final class AethershieldArtificer extends CardImpl {
@ -45,7 +45,7 @@ public final class AethershieldArtificer extends CardImpl {
IndestructibleAbility.getInstance(),
Duration.EndOfTurn
).setText("and gains indestructible until end of turn"));
ability.addTarget(new TargetControlledCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -13,6 +13,7 @@ import mage.filter.common.FilterAttackingOrBlockingCreature;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -28,7 +29,7 @@ public final class Aethertow extends CardImpl {
// Put target attacking or blocking creature on top of its owner's library.
this.getSpellAbility().addEffect(new AethertowEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
this.getSpellAbility().addTarget(new TargetPermanent(filter));
// Conspire
this.addAbility(new ConspireAbility(ConspireAbility.ConspireTargets.ONE));

View file

@ -9,10 +9,13 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import static mage.filter.StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL;
/**
* @author TheElk801
*/
@ -32,7 +35,7 @@ public final class AffectionateIndrik extends CardImpl {
"<i>(Each deals damage equal to its power to the other.)</i>"),
true
);
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL));
ability.addTarget(new TargetPermanent(FILTER_CREATURE_YOU_DONT_CONTROL));
this.addAbility(ability);
}

View file

@ -5,7 +5,9 @@ import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.hint.ValueHint;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -13,9 +15,9 @@ import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCardInOpponentsGraveyard;
@ -37,7 +39,10 @@ public final class AgadeemOccultist extends CardImpl {
this.toughness = new MageInt(2);
// {tap}: Put target creature card from an opponent's graveyard onto the battlefield under your control if its converted mana cost is less than or equal to the number of Allies you control.
this.addAbility(new SimpleActivatedAbility(new AgadeemOccultistEffect(), new TapSourceCost()));
Ability ability = new SimpleActivatedAbility(new AgadeemOccultistEffect(), new TapSourceCost());
ability.addTarget(new TargetCardInOpponentsGraveyard(new FilterCreatureCard("target creature card from an opponent's graveyard")));
ability.addHint(new ValueHint("Allies you control", new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.ALLY))));
this.addAbility(ability);
}
@ -50,7 +55,6 @@ public final class AgadeemOccultist extends CardImpl {
return new AgadeemOccultist(this);
}
}
class AgadeemOccultistEffect extends OneShotEffect {
AgadeemOccultistEffect() {
@ -70,26 +74,12 @@ class AgadeemOccultistEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
int allycount = 0;
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) {
if (permanent.hasSubtype(SubType.ALLY, game)) {
allycount++;
}
}
FilterCard filter = new FilterCard("creature card in an opponent's graveyard");
filter.add(CardType.CREATURE.getPredicate());
TargetCardInOpponentsGraveyard target = new TargetCardInOpponentsGraveyard(1, 1, filter);
if (controller != null) {
if (target.canChoose(source.getControllerId(), source, game)
&& controller.choose(Outcome.GainControl, target, source, game)) {
if (!target.getTargets().isEmpty()) {
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
if (card.getManaValue() <= allycount) {
return controller.moveCards(card, Zone.BATTLEFIELD, source, game);
}
}
Card card = game.getCard(source.getFirstTarget());
if (card != null) {
int allycount = new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.ALLY)).calculate(game, source, this);
if (card.getManaValue() <= allycount) {
return controller.moveCards(card, Zone.BATTLEFIELD, source, game);
}
}
return true;

View file

@ -1,15 +1,16 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.InfoEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.InfoEffect;
import java.util.UUID;
/**
*
@ -27,7 +28,7 @@ public final class AgentOfAcquisitions extends CardImpl {
// TODO: Draft specific abilities not implemented
// Draft Agent of Acquisitions face up.
this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft Agent of Acquisitions face up - not implemented.")));
this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Draft {this} face up - not implemented.")));
// Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and you cant draft cards for the rest of this draft round.
this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Instead of drafting a card from a booster pack, "

View file

@ -5,11 +5,14 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import static mage.filter.StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL;
/**
* @author TheElk801
*/
@ -21,7 +24,7 @@ public final class AggressiveInstinct extends CardImpl {
// Target creature you control deals damage equal to its power to target creature you don't control.
this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect());
this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL));
this.getSpellAbility().addTarget(new TargetPermanent(FILTER_CREATURE_YOU_DONT_CONTROL));
}
private AggressiveInstinct(final AggressiveInstinct card) {

View file

@ -10,10 +10,13 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import static mage.filter.StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK;
/**
* @author FenrisulfrX
*/
@ -27,7 +30,7 @@ public final class AgonizingDemise extends CardImpl {
// Destroy target nonblack creature. It can't be regenerated.
this.getSpellAbility().addEffect(new DestroyTargetEffect(true));
this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK));
this.getSpellAbility().addTarget(new TargetPermanent(FILTER_PERMANENT_CREATURE_NON_BLACK));
// If Agonizing Demise was kicked, it deals damage equal to that creature's power to the creature's controller.
this.getSpellAbility().addEffect(new ConditionalOneShotEffect(

View file

@ -23,7 +23,6 @@ import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.Target;
import mage.util.CardUtil;
import java.util.Collection;
import java.util.List;
@ -90,7 +89,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl {
if (!event.getTargetId().equals(getSourceId())) {
return false;
}
StackObject targetingObject = CardUtil.findTargetingStackObject(this.getId().toString(), event, game);
StackObject targetingObject = game.findTargetingStackObject(this.getId().toString(), event);
if (targetingObject == null || targetingObject instanceof Spell) {
return false;
}

View file

@ -50,7 +50,7 @@ class AidFromTheCowlEffect extends OneShotEffect {
AidFromTheCowlEffect() {
super(Outcome.PutCreatureInPlay);
this.staticText = "reveal the top card of your library. If it's a permanent card, " +
"you may put it onto the battlefield. Otherwise, you may put that card on the bottom of your library";
"you may put it onto the battlefield. Otherwise, you may put it on the bottom of your library";
}
private AidFromTheCowlEffect(final AidFromTheCowlEffect effect) {

View file

@ -1,21 +1,21 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.constants.SubType;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.common.TargetCreaturePermanent;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
*
* @author weirddan455
*/
public final class AirCultElemental extends CardImpl {
@ -38,7 +38,7 @@ public final class AirCultElemental extends CardImpl {
// Whirlwind When Air-Cult Elemental enters the battlefield, return up to one other target creature to its owner's hand.
Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect());
ability.addTarget(new TargetCreaturePermanent(0, 1, filter, false));
ability.addTarget(new TargetPermanent(0, 1, filter));
this.addAbility(ability.withFlavorWord("Whirlwind"));
}

View file

@ -16,6 +16,7 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -38,7 +39,7 @@ public final class AirServant extends CardImpl {
this.toughness = new MageInt(3);
this.addAbility(FlyingAbility.getInstance());
Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{2}{U}"));
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -23,6 +23,8 @@ import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import static mage.constants.SagaChapter.CHAPTER_I;
/**
* @author Susucr
*/
@ -45,8 +47,8 @@ public final class AjaniFellsTheGodsire extends CardImpl {
// I -- Exile target creature an opponent controls with power 3 or greater.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I,
new ExileTargetEffect(), new TargetCreaturePermanent(filter)
this, CHAPTER_I, CHAPTER_I,
new ExileTargetEffect(), new TargetPermanent(filter)
);
// II -- Create a 2/1 white Cat Warrior creature token, then put a vigilance counter on a creature you control.

View file

@ -1,24 +1,22 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.common.OnEventTriggeredAbility;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.game.events.GameEvent.EventType;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public final class AjanisMantra extends CardImpl {
public AjanisMantra(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{W}");
this.addAbility(new OnEventTriggeredAbility(EventType.UPKEEP_STEP_PRE, "beginning of your upkeep", new GainLifeEffect(1), true));
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}");
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new GainLifeEffect(1), true));
}
private AjanisMantra(final AjanisMantra card) {
@ -29,5 +27,4 @@ public final class AjanisMantra extends CardImpl {
public AjanisMantra copy() {
return new AjanisMantra(this);
}
}

View file

@ -16,8 +16,11 @@ import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import static mage.filter.StaticFilters.FILTER_ANOTHER_TARGET_CREATURE;
/**
*
* @author LevelX2
@ -40,7 +43,7 @@ public final class AkroanConscriptor extends CardImpl {
effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn);
effect.setText("It gains haste until end of turn");
ability.addEffect(effect);
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE));
ability.addTarget(new TargetPermanent(FILTER_ANOTHER_TARGET_CREATURE));
this.addAbility(ability);
}

View file

@ -1,12 +1,12 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.condition.common.MoreCardsInHandThanOpponentsCondition;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -15,18 +15,14 @@ import mage.filter.common.FilterControlledPermanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class AkutaBornOfAsh extends CardImpl {
private static final FilterControlledPermanent filterSwamp = new FilterControlledPermanent("a Swamp");
static {
filterSwamp.add(SubType.SWAMP.getPredicate());
}
private static final FilterControlledPermanent filterSwamp = new FilterControlledPermanent(SubType.SWAMP, "a Swamp");
public AkutaBornOfAsh(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}{B}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIRIT);
@ -37,9 +33,14 @@ public final class AkutaBornOfAsh extends CardImpl {
this.addAbility(HasteAbility.getInstance());
// At the beginning of your upkeep, if you have more cards in hand than each opponent, you may sacrifice a Swamp. If you do, return Akuta, Born of Ash from your graveyard to the battlefield.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.GRAVEYARD,
TargetController.YOU, new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new SacrificeTargetCost(filterSwamp)),
false).withInterveningIf(MoreCardsInHandThanOpponentsCondition.instance));
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
Zone.GRAVEYARD, TargetController.YOU,
new DoIfCostPaid(
new ReturnSourceFromGraveyardToBattlefieldEffect()
.setText("return {this} from your graveyard to the battlefield"),
new SacrificeTargetCost(filterSwamp)
), false
).withInterveningIf(MoreCardsInHandThanOpponentsCondition.instance));
}
private AkutaBornOfAsh(final AkutaBornOfAsh card) {

View file

@ -1,7 +1,5 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
@ -13,25 +11,27 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class AlabornVeteran extends CardImpl {
public AlabornVeteran(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.KNIGHT);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// {tap}: Target creature gets +2/+2 until end of turn. Activate this ability only during your turn, before attackers are declared.
Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD,
new BoostTargetEffect(2, 2, Duration.EndOfTurn), new TapSourceCost(), MyTurnBeforeAttackersDeclaredCondition.instance);
Ability ability = new ActivateIfConditionActivatedAbility(
new BoostTargetEffect(2, 2, Duration.EndOfTurn),
new TapSourceCost(), MyTurnBeforeAttackersDeclaredCondition.instance
);
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
}

View file

@ -42,10 +42,13 @@ public final class AlaniaDivergentStorm extends CardImpl {
// Whenever you cast a spell, if it's the first instant spell, the first sorcery spell, or the first Otter
// spell other than Alania you've cast this turn, you may have target opponent draw a card. If you do, copy
// that spell. You may choose new targets for the copy.
this.addAbility(new SpellCastControllerTriggeredAbility(
new DoIfCostPaid(new CopyTargetStackObjectEffect(true), new AlaniaDivergentStormCost()),
Ability ability = new SpellCastControllerTriggeredAbility(
new DoIfCostPaid(new CopyTargetStackObjectEffect(true).setText("copy that spell. You may choose new targets for the copy")
, new AlaniaDivergentStormCost()),
null, false, SetTargetPointer.SPELL
).withInterveningIf(AlaniaDivergentStormCondition.instance), new AlaniaDivergentStormWatcher());
).withInterveningIf(AlaniaDivergentStormCondition.instance);
ability.addTarget(new TargetOpponent());
this.addAbility(ability, new AlaniaDivergentStormWatcher());
}
private AlaniaDivergentStorm(final AlaniaDivergentStorm card) {
@ -58,12 +61,10 @@ public final class AlaniaDivergentStorm extends CardImpl {
}
}
// Based on MarathWillOfTheWildRemoveCountersCost
class AlaniaDivergentStormCost extends CostImpl {
AlaniaDivergentStormCost() {
this.text = "have target opponent draw a card";
this.addTarget(new TargetOpponent());
}
private AlaniaDivergentStormCost(AlaniaDivergentStormCost cost) {
@ -73,32 +74,18 @@ class AlaniaDivergentStormCost extends CostImpl {
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
Player player = game.getPlayer(controllerId);
if (player == null) {
return false;
}
for (UUID opponentID : game.getOpponents(controllerId)) {
Player opponent = game.getPlayer(opponentID);
if (opponent == null) {
continue;
}
if (opponent.canBeTargetedBy(source.getSourceObject(game), controllerId, source, game)) {
return true;
}
}
return false;
Player opponent = game.getPlayer(source.getFirstTarget());
return player != null && opponent != null;
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
this.getTargets().clearChosen();
paid = false;
if (this.getTargets().choose(Outcome.DrawCard, controllerId, source.getSourceId(), source, game)) {
Player opponent = game.getPlayer(this.getTargets().getFirstTarget());
if (opponent == null || !opponent.canRespond()) {
return false;
}
paid = opponent.drawCards(1, source, game) > 0;
Player opponent = game.getPlayer(source.getFirstTarget());
if (opponent == null || !opponent.canRespond()) {
return false;
}
paid = opponent.drawCards(1, source, game) > 0;
return paid;
}

View file

@ -12,6 +12,7 @@ import mage.constants.Duration;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -34,7 +35,7 @@ public final class Alarum extends CardImpl {
Effect effect = new BoostTargetEffect(1, 3, Duration.EndOfTurn);
effect.setText("It gets +1/+3 until end of turn");
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
this.getSpellAbility().addTarget(new TargetPermanent(filter));
}
private Alarum(final Alarum card) {

View file

@ -66,7 +66,7 @@ public final class AlchemistsTalent extends CardImpl {
new SpellCastControllerTriggeredAbility(
new DamagePlayersEffect(AlchemistsTalentValue.instance, TargetController.OPPONENT)
.setText("{this} deals damage equal to that spell's mana value to each opponent"),
StaticFilters.FILTER_SPELL, false, SetTargetPointer.SPELL
StaticFilters.FILTER_SPELL_A, false, SetTargetPointer.SPELL
).withInterveningIf(AlchemistsTalentCondition.instance), 3
)));
}

View file

@ -8,6 +8,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.filter.common.FilterBlockingCreature;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -21,7 +22,7 @@ public final class AlibansTower extends CardImpl {
// Target blocking creature gets +3/+1 until end of turn.
this.getSpellAbility().addEffect(new BoostTargetEffect(3, 1, Duration.EndOfTurn));
this.getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterBlockingCreature()));
this.getSpellAbility().addTarget(new TargetPermanent(new FilterBlockingCreature()));
}
private AlibansTower(final AlibansTower card) {

View file

@ -12,8 +12,11 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
/**
*
* @author BetaSteward_at_googlemail.com
@ -29,7 +32,7 @@ public final class AlluringSiren extends CardImpl {
// {T}: Target creature an opponent controls attacks you this turn if able.
Ability ability = new SimpleActivatedAbility(new AttacksIfAbleTargetEffect(Duration.EndOfTurn, TargetController.YOU), new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
ability.addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE));
this.addAbility(ability);
}

View file

@ -14,8 +14,11 @@ import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
/**
*
* @author BetaSteward
@ -27,7 +30,7 @@ public final class AlphaBrawl extends CardImpl {
// Target creature an opponent controls deals damage equal to its power to each other creature that player controls, then each of those creatures deals damage equal to its power to that creature.
this.getSpellAbility().addEffect(new AlphaBrawlEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
this.getSpellAbility().addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE));
}

View file

@ -14,6 +14,7 @@ import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -37,7 +38,7 @@ public final class AlphaKavu extends CardImpl {
// {1}{G}: Target Kavu creature gets -1/+1 until end of turn.
Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(-1, 1, Duration.EndOfTurn),
new ManaCostsImpl<>("{1}{G}"));
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -0,0 +1,63 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.decorator.ConditionalContinuousEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.effects.common.discard.DiscardControllerEffect;
import mage.abilities.keyword.DeathtouchAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AlpharaelDreamingAcolyte extends CardImpl {
public AlpharaelDreamingAcolyte(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CLERIC);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// When Alpharael enters, draw two cards. Then discard two cards unless you discard an artifact card.
Ability ability = new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(2));
ability.addEffect(new DoIfCostPaid(
null, new DiscardControllerEffect(2),
new DiscardCardCost(StaticFilters.FILTER_CARD_ARTIFACT)
.setText("discard an artifact card instead of discarding two cards")
).setText("Then discard two cards unless you discard an artifact card"));
this.addAbility(ability);
// During your turn, Alpharael has deathtouch.
this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect(
new GainAbilitySourceEffect(DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield),
MyTurnCondition.instance, "during your turn, {this} has deathtouch"
)));
}
private AlpharaelDreamingAcolyte(final AlpharaelDreamingAcolyte card) {
super(card);
}
@Override
public AlpharaelDreamingAcolyte copy() {
return new AlpharaelDreamingAcolyte(this);
}
}

View file

@ -1,7 +1,6 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
@ -17,24 +16,17 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.TappedPredicate;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetControlledPermanent;
import java.util.UUID;
/**
*
* @author jeffwadsworth
*
*/
public final class AltarGolem extends CardImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped creatures you control");
static {
filter.add(TappedPredicate.UNTAPPED);
}
public AltarGolem(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{7}");
this.subtype.add(SubType.GOLEM);
@ -53,7 +45,7 @@ public final class AltarGolem extends CardImpl {
this.addAbility(new SimpleStaticAbility(new DontUntapInControllersUntapStepSourceEffect()));
// Tap five untapped creatures you control: Untap Altar Golem.
this.addAbility(new SimpleActivatedAbility(new UntapSourceEffect(), new TapTargetCost(new TargetControlledCreaturePermanent(5, 5, filter, true))));
this.addAbility(new SimpleActivatedAbility(new UntapSourceEffect(), new TapTargetCost(new TargetControlledPermanent(5, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURES))));
}

View file

@ -11,7 +11,10 @@ import mage.abilities.hint.common.CovenHint;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.AbilityWord;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterCard;
import mage.target.common.TargetCardInLibrary;
@ -46,8 +49,7 @@ public final class AmbitiousFarmhand extends CardImpl {
// Coven{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers.
this.addAbility(new TransformAbility());
this.addAbility(new ActivateIfConditionActivatedAbility(
Zone.BATTLEFIELD, new TransformSourceEffect(),
new ManaCostsImpl<>("{1}{W}{W}"), CovenCondition.instance
new TransformSourceEffect(), new ManaCostsImpl<>("{1}{W}{W}"), CovenCondition.instance
).setAbilityWord(AbilityWord.COVEN).addHint(CovenHint.instance));
}

View file

@ -118,8 +118,8 @@ class AminatouUltimateEffect extends OneShotEffect {
AminatouUltimateEffect() {
super(Outcome.Benefit);
staticText = "Choose left or right. Each player gains control of all nonland permanents other than Aminatou,"
+ " the Fateshifter controlled by the next player in the chosen direction.";
staticText = "Choose left or right. Each player gains control of all nonland permanents other than {this}"
+ " controlled by the next player in the chosen direction.";
}
private AminatouUltimateEffect(final AminatouUltimateEffect effect) {

View file

@ -0,0 +1,94 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterAttackingCreature;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.target.targetpointer.SecondTargetPointer;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AmyRose extends CardImpl {
private static final FilterPermanent filter = new FilterAttackingCreature("other attacking creature");
static {
filter.add(AnotherPredicate.instance);
}
public AmyRose(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HEDGEHOG);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Haste
this.addAbility(HasteAbility.getInstance());
// Whenever Amy Rose attacks, attach up to one target Equipment to her. Then up to one other target attacking creature gets +X/+0 until end of turn, where X is Amy Rose's power.
Ability ability = new AttacksTriggeredAbility(new AmyRoseEffect());
ability.addEffect(new BoostTargetEffect(
SourcePermanentPowerValue.NOT_NEGATIVE, StaticValue.get(0)
).setTargetPointer(new SecondTargetPointer())
.setText("Then up to one other target attacking creature gets +X/+0 until end of turn, where X is {this}'s power"));
ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_PERMANENT_EQUIPMENT));
ability.addTarget(new TargetPermanent(0, 1, filter));
this.addAbility(ability);
}
private AmyRose(final AmyRose card) {
super(card);
}
@Override
public AmyRose copy() {
return new AmyRose(this);
}
}
class AmyRoseEffect extends OneShotEffect {
AmyRoseEffect() {
super(Outcome.Benefit);
staticText = "attach up to one target Equipment to her";
}
private AmyRoseEffect(final AmyRoseEffect effect) {
super(effect);
}
@Override
public AmyRoseEffect copy() {
return new AmyRoseEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
Permanent equipment = game.getPermanent(getTargetPointer().getFirst(game, source));
return permanent != null && equipment != null && permanent.addAttachment(equipment.getId(), source, game);
}
}

View file

@ -19,6 +19,7 @@ import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.TargetPlayer;
import mage.target.common.TargetCreaturePermanent;
@ -58,7 +59,7 @@ public final class AnaBattlemage extends CardImpl {
// When Ana Battlemage enters the battlefield, if it was kicked with its {1}{B} kicker, tap target untapped creature and that creature deals damage equal to its power to its controller.
ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()).withInterveningIf(condition2);
ability.addEffect(new AnaBattlemageEffect());
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}
@ -76,7 +77,7 @@ class AnaBattlemageEffect extends OneShotEffect {
AnaBattlemageEffect() {
super(Outcome.Detriment);
this.staticText = "and it deals damage equal to its power to its controller";
this.staticText = "and that creature deals damage equal to its power to its controller";
}
private AnaBattlemageEffect(final AnaBattlemageEffect effect) {

View file

@ -1,24 +1,18 @@
package mage.cards.a;
import java.util.UUID;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.SanctuaryInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.common.SanctuaryTriggeredAbility;
import mage.abilities.effects.common.AddContinuousEffectToGame;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
*
* @author TheElk801
*/
public final class AnaSanctuary extends CardImpl {
@ -27,10 +21,11 @@ public final class AnaSanctuary extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
// At the beginning of your upkeep, if you control a blue or black permanent, target creature gets +1/+1 until end of turn. If you control a blue permanent and a black permanent, that creature gets +5/+5 until end of turn instead.
Ability ability = new SanctuaryInterveningIfTriggeredAbility(
new BoostEffect(1), new BoostEffect(5), ObjectColor.BLACK, ObjectColor.BLUE,
"At the beginning of your upkeep, if you control a blue or black permanent, "
+ "target creature gets +1/+1 until end of turn. If you control a blue permanent and a black permanent, that creature gets +5/+5 until end of turn instead."
Ability ability = new SanctuaryTriggeredAbility(
new AddContinuousEffectToGame(new BoostTargetEffect(1, 1)),
new AddContinuousEffectToGame(new BoostTargetEffect(5, 5)),
ObjectColor.BLACK, ObjectColor.BLUE, "target creature gets +1/+1 until end of turn. " +
"If you control a blue permanent and a black permanent, that creature gets +5/+5 until end of turn instead."
);
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
@ -45,31 +40,3 @@ public final class AnaSanctuary extends CardImpl {
return new AnaSanctuary(this);
}
}
class BoostEffect extends OneShotEffect {
private final int amount;
BoostEffect(int amount) {
super(Outcome.Benefit);
this.amount = amount;
}
private BoostEffect(final BoostEffect effect) {
super(effect);
this.amount = effect.amount;
}
@Override
public BoostEffect copy() {
return new BoostEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
ContinuousEffect effect = new BoostTargetEffect(amount, amount, Duration.EndOfTurn);
effect.setTargetPointer(new FixedTarget(source.getFirstTarget(), game));
game.addEffect(effect, source);
return true;
}
}

View file

@ -15,6 +15,7 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -39,7 +40,7 @@ public final class AnabaAncestor extends CardImpl {
// {T}: Another target Minotaur creature gets +1/+1 until end of turn.
Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(1, 1, Duration.EndOfTurn), new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -21,7 +21,7 @@ import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import mage.players.Player;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.TargetPermanent;
import java.util.UUID;
@ -48,7 +48,7 @@ public final class AnafenzaTheForemost extends CardImpl {
// Whenever Anafenza, the Foremost attacks, put a +1/+1 counter on another target tapped creature you control.
Ability ability = new AttacksTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false);
ability.addTarget(new TargetControlledCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
// If a nontoken creature an opponent owns would die or a creature card not on the battlefield would be put into an opponent's graveyard, exile that card instead.

View file

@ -2,7 +2,6 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility;
@ -19,9 +18,10 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import java.util.UUID;
/**
* @author Loki
*/
@ -49,9 +49,9 @@ public final class Anavolver extends CardImpl {
// If Anavolver was kicked with its {B} kicker, it enters with a +1/+1 counter on it and with "Pay 3 life: Regenerate Anavolver."
EntersBattlefieldAbility ability2 = new EntersBattlefieldAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance(1),false), new KickedCostCondition("{B}"),
"If {this} was kicked with its {B} kicker, it enters with a +1/+1 counter on it and with \"Pay 3 life: Regenerate Anavolver.\"",
"{this} enters with a +1/+1 counter on it and with \"Pay 3 life: Regenerate Anavolver.\"");
new AddCountersSourceEffect(CounterType.P1P1.createInstance(1), false), new KickedCostCondition("{B}"),
"If {this} was kicked with its {B} kicker, it enters with a +1/+1 counter on it and with \"Pay 3 life: Regenerate {this}.\"",
"{this} enters with a +1/+1 counter on it and with \"Pay 3 life: Regenerate {this}.\"");
((EntersBattlefieldEffect)ability2.getEffects().get(0)).addEffect(new GainAbilitySourceEffect(new SimpleActivatedAbility(new RegenerateSourceEffect(), new PayLifeCost(3)), Duration.WhileOnBattlefield));
this.addAbility(ability2);
}

View file

@ -1,18 +1,16 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.condition.common.SourceAttackingCondition;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalActivatedAbility;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate;
import mage.target.TargetPermanent;
@ -42,7 +40,9 @@ public final class AncientHellkite extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// {R}: Ancient Hellkite deals 1 damage to target creature defending player controls. Activate this ability only if Ancient Hellkite is attacking.
Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(1), new ManaCostsImpl<>("{R}"), SourceAttackingCondition.instance);
Ability ability = new ActivateIfConditionActivatedAbility(
new DamageTargetEffect(1), new ManaCostsImpl<>("{R}"), SourceAttackingCondition.instance
);
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -15,10 +15,13 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import static mage.filter.StaticFilters.FILTER_ANOTHER_TARGET_CREATURE;
/**
* @author emerald000
*/
@ -42,14 +45,14 @@ public final class AngelOfCondemnation extends CardImpl {
new ExileReturnBattlefieldNextEndStepTargetEffect(), new ManaCostsImpl<>("{2}{W}")
);
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE));
ability.addTarget(new TargetPermanent(FILTER_ANOTHER_TARGET_CREATURE));
this.addAbility(ability);
// {2}{W}, {T}, Exert Angel of Condemnation: Exile another target creature until Angel of Condemnation leaves the battlefield.
ability = new SimpleActivatedAbility(new ExileUntilSourceLeavesEffect(), new ManaCostsImpl<>("{2}{W}"));
ability.addCost(new TapSourceCost());
ability.addCost(new ExertSourceCost());
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE));
ability.addTarget(new TargetPermanent(FILTER_ANOTHER_TARGET_CREATURE));
this.addAbility(ability);
}

View file

@ -15,6 +15,7 @@ import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterAttackingOrBlockingCreature;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -35,7 +36,7 @@ public final class AngelicPage extends CardImpl {
//{T}: Target attacking or blocking creature gets +1/+1 until end of turn.
Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(1, 1, Duration.EndOfTurn), new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(new FilterAttackingOrBlockingCreature()));
ability.addTarget(new TargetPermanent(new FilterAttackingOrBlockingCreature()));
this.addAbility(ability);
}

View file

@ -2,14 +2,17 @@ package mage.cards.a;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilityAllEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
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.TargetController;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -47,7 +50,7 @@ class AngelsTrumpetTapEffect extends OneShotEffect {
AngelsTrumpetTapEffect() {
super(Outcome.Tap);
this.staticText = "tap all untapped creatures that player controls that didn't attack this turn. Angel's Trumpet deals damage to the player equal to the number of creatures tapped this way";
this.staticText = "tap all untapped creatures that player controls that didn't attack this turn. {this} deals damage to the player equal to the number of creatures tapped this way";
}
private AngelsTrumpetTapEffect(final AngelsTrumpetTapEffect effect) {

View file

@ -1,28 +1,26 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import java.util.UUID;
/**
*
* @author shieldal
*/
public final class AngusMackenzie extends CardImpl {
public AngusMackenzie(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}{W}{U}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.CLERIC);
@ -30,14 +28,11 @@ public final class AngusMackenzie extends CardImpl {
this.power = new MageInt(2);
this.toughness = new MageInt(2);
Effect effect = new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true);
effect.setText("Prevent all combat damage that would be dealt this turn");
// {G}{W}{U}, {tap}: Prevent all combat damage that would be dealt this turn. Activate this ability only before the combat damage step.
Ability ability = new ActivateIfConditionActivatedAbility(
Zone.BATTLEFIELD,
effect,
new ManaCostsImpl<>("{G}{W}{U}"),
BeforeCombatDamageCondition.getInstance()
new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true)
.setText("Prevent all combat damage that would be dealt this turn"),
new ManaCostsImpl<>("{G}{W}{U}"), BeforeCombatDamageCondition.getInstance()
);
ability.addCost(new TapSourceCost());
this.addAbility(ability);
@ -62,15 +57,15 @@ class BeforeCombatDamageCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
PhaseStep phaseStep = game.getTurnStepType();
if(phaseStep.getIndex() < PhaseStep.FIRST_COMBAT_DAMAGE.getIndex()) {
return true;
}
PhaseStep phaseStep = game.getTurnStepType();
if (phaseStep.getIndex() < PhaseStep.FIRST_COMBAT_DAMAGE.getIndex()) {
return true;
}
return false;
}
@Override
public String toString() {
return "before the combat damage step";
return "before the combat damage step";
}
}

View file

@ -34,7 +34,7 @@ public final class AnimateWall extends CardImpl {
// Enchant Wall
TargetPermanent auraTarget = new TargetCreaturePermanent(filter);
TargetPermanent auraTarget = new TargetPermanent(filter);
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility));
Ability ability = new EnchantAbility(auraTarget);

View file

@ -7,8 +7,11 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import static mage.filter.StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK;
/**
*
* @author LevelX2
@ -20,7 +23,7 @@ public final class Annihilate extends CardImpl {
// Destroy target nonblack creature. It can't be regenerated.
this.getSpellAbility().addEffect(new DestroyTargetEffect(true));
this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_PERMANENT_CREATURE_NON_BLACK));
this.getSpellAbility().addTarget(new TargetPermanent(FILTER_PERMANENT_CREATURE_NON_BLACK));
// Draw a card.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("<br>"));
}

View file

@ -10,6 +10,7 @@ import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
@ -36,7 +37,7 @@ public final class AnointedDeacon extends CardImpl {
// At the beginning of combat on your turn, you may have target Vampire get +2/+0 until end of turn.
Ability ability = new BeginningOfCombatTriggeredAbility(
new BoostTargetEffect(2, 0, Duration.EndOfTurn), true);
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -9,9 +9,9 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.targetpointer.FixedTargets;
@ -61,12 +61,8 @@ class AnotherRoundEffect extends OneShotEffect {
}
int xValue = GetXValue.instance.calculate(game, source, this);
TargetControlledCreaturePermanent target =
new TargetControlledCreaturePermanent(
0, Integer.MAX_VALUE,
StaticFilters.FILTER_CONTROLLED_CREATURE, true
);
TargetPermanent target = new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE);
target.withNotTarget(true);
for (int i = 0; i <= xValue; ++i) {
target.clearChosen();
controller.chooseTarget(Outcome.Benefit, target, source, game);

View file

@ -17,6 +17,7 @@ import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
@ -41,7 +42,7 @@ public final class AntlerSkulkin extends CardImpl {
// {2}: Target white creature gains persist until end of turn.
Ability ability = new SimpleActivatedAbility(new GainAbilityTargetEffect(new PersistAbility(), Duration.EndOfTurn), new ManaCostsImpl<>("{2}"));
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability);
}

View file

@ -1,7 +1,5 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
@ -12,25 +10,27 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class ApprenticeSorcerer extends CardImpl {
public ApprenticeSorcerer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// {tap}: Apprentice Sorcerer deals 1 damage to any target. Activate this ability only during your turn, before attackers are declared.
Ability ability = new ActivateIfConditionActivatedAbility(Zone.BATTLEFIELD,
new DamageTargetEffect(1), new TapSourceCost(), MyTurnBeforeAttackersDeclaredCondition.instance);
Ability ability = new ActivateIfConditionActivatedAbility(
new DamageTargetEffect(1), new TapSourceCost(),
MyTurnBeforeAttackersDeclaredCondition.instance
);
ability.addTarget(new TargetAnyTarget());
this.addAbility(ability);
}

View file

@ -48,7 +48,7 @@ class ApproachOfTheSecondSunEffect extends OneShotEffect {
ApproachOfTheSecondSunEffect() {
super(Outcome.Win);
this.staticText
= "If this spell was cast from your hand and you've cast another spell named {this} this game, you win the game. "
= "If this spell was cast from your hand and you've cast another spell named Approach of the Second Sun this game, you win the game. "
+ "Otherwise, put {this} into its owner's library seventh from the top and you gain 7 life.";
}

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