forked from External/mage
master #17
433 changed files with 8704 additions and 1435 deletions
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-client</artifactId>
|
||||
|
|
|
|||
|
|
@ -949,11 +949,15 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
|
|||
} catch (SocketException ex) {
|
||||
}
|
||||
currentConnection.setUserIdStr(System.getProperty("user.name") + ":" + System.getProperty("os.name") + ":" + MagePreferences.getUserNames() + ":" + allMAC);
|
||||
currentConnection.setProxyType(proxyType);
|
||||
currentConnection.setProxyHost(proxyServer);
|
||||
currentConnection.setProxyPort(proxyPort);
|
||||
currentConnection.setProxyUsername(proxyUsername);
|
||||
currentConnection.setProxyPassword(proxyPassword);
|
||||
if (PreferencesDialog.NETWORK_ENABLE_PROXY_SUPPORT) {
|
||||
currentConnection.setProxyType(proxyType);
|
||||
currentConnection.setProxyHost(proxyServer);
|
||||
currentConnection.setProxyPort(proxyPort);
|
||||
currentConnection.setProxyUsername(proxyUsername);
|
||||
currentConnection.setProxyPassword(proxyPassword);
|
||||
} else {
|
||||
currentConnection.setProxyType(ProxyType.NONE);
|
||||
}
|
||||
setUserPrefsToConnection(currentConnection);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3170,7 +3170,7 @@
|
|||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="Proxy for server connection and images download">
|
||||
<TitledBorder title="Proxy for server connection and images download (DO NOT SUPPORTED)">
|
||||
<Border PropertyName="innerBorder" info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
|
||||
<EtchetBorder/>
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
|
|||
|
||||
private static PreferencesDialog instance; // shared dialog instance
|
||||
|
||||
public static final boolean NETWORK_ENABLE_PROXY_SUPPORT = false; // TODO: delete proxy at all after few releases, 2025-02-09
|
||||
|
||||
// WARNING, do not change const values - it must be same for compatibility with user's saved settings
|
||||
public static final String KEY_SHOW_TOOLTIPS_DELAY = "showTooltipsDelay";
|
||||
public static final String KEY_SHOW_CARD_NAMES = "showCardNames";
|
||||
|
|
@ -2795,7 +2797,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
|
|||
|
||||
tabsPanel.addTab("Sounds", tabSounds);
|
||||
|
||||
connection_Proxy.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Proxy for server connection and images download"));
|
||||
connection_Proxy.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Proxy for server connection and images download (DO NOT SUPPORTED)"));
|
||||
|
||||
cbProxyType.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
|
|
@ -3317,6 +3319,11 @@ public class PreferencesDialog extends javax.swing.JDialog {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!NETWORK_ENABLE_PROXY_SUPPORT) {
|
||||
connection.setProxyType(ProxyType.NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
connection.setProxyType(configProxyType);
|
||||
if (configProxyType != ProxyType.NONE) {
|
||||
String host = getCachedValue(KEY_PROXY_ADDRESS, "");
|
||||
|
|
|
|||
|
|
@ -479,6 +479,7 @@ public class ScryfallImageSupportCards {
|
|||
add("KLR"); // Kaladesh Remastered
|
||||
add("CMR"); // Commander Legends
|
||||
add("CC1"); // Commander Collection: Green
|
||||
add("PJ21"); // Judge Gift Cards 2021
|
||||
add("PL21"); // Year of the Ox 2021
|
||||
add("KHM"); // Kaldheim
|
||||
add("KHC"); // Kaldheim Commander
|
||||
|
|
@ -490,6 +491,7 @@ public class ScryfallImageSupportCards {
|
|||
add("C21"); // Commander 2021
|
||||
add("MH2"); // Modern Horizons 2
|
||||
add("H1R"); // Modern Horizons 1 Timeshifts
|
||||
add("PW21"); // Wizards Play Network 2021
|
||||
add("PLG21"); // Love Your LGS 2021
|
||||
add("AFR"); // Adventures in the Forgotten Realms
|
||||
add("AFC"); // Forgotten Realms Commander
|
||||
|
|
@ -499,12 +501,15 @@ public class ScryfallImageSupportCards {
|
|||
add("VOW"); // Innistrad: Crimson Vow
|
||||
add("VOC"); // Crimson Vow Commander
|
||||
add("YMID"); // Alchemy: Innistrad
|
||||
add("P22"); // Judge Gift Cards 2022
|
||||
add("DBL"); // Innistrad: Double Feature
|
||||
add("CC2"); // Commander Collection: Black
|
||||
add("NEO"); // Kamigawa: Neon Dynasty
|
||||
add("YNEO"); // Alchemy: Kamigawa
|
||||
add("NEC"); // Neon Dynasty Commander
|
||||
add("PL22"); // Year of the Tiger 2022
|
||||
add("PW22"); // Wizards Play Network 2022
|
||||
add("GDY"); // Game Day Promos
|
||||
add("SNC"); // Streets of New Capenna
|
||||
add("NCC"); // New Capenna Commander
|
||||
add("SLX"); // Universes Within
|
||||
|
|
@ -522,6 +527,8 @@ public class ScryfallImageSupportCards {
|
|||
add("BOT"); // Transformers
|
||||
add("J22"); // Jumpstart 2022
|
||||
add("SCD"); // Starter Commander Decks
|
||||
add("PW23"); // Wizards Play Network 2023
|
||||
add("P23"); // Judge Gift Cards 2023
|
||||
add("SLC"); // Secret Lair 30th Anniversary Countdown Kit
|
||||
add("DMR"); // Dominaria Remastered
|
||||
add("ONE"); // Phyrexia: All Will Be One
|
||||
|
|
@ -547,6 +554,7 @@ public class ScryfallImageSupportCards {
|
|||
add("LCC"); // The The Lost Caverns of Ixalan Commander
|
||||
add("REX"); // Jurassic World Collection
|
||||
add("SPG"); // Special Guests
|
||||
add("PW24"); // Wizards Play Network 2024
|
||||
add("RVR"); // Ravnica Remastered
|
||||
add("PIP"); // Fallout
|
||||
add("MKM"); // Murders at Karlov Manor
|
||||
|
|
@ -567,6 +575,7 @@ public class ScryfallImageSupportCards {
|
|||
add("FDN"); // Foundations
|
||||
add("J25"); // Foundations Jumpstart
|
||||
add("PIO"); // Pioneer Masters
|
||||
add("PW25"); // Wizards Play Network 2025
|
||||
add("INR"); // Innistrad Remastered
|
||||
add("DFT"); // Aetherdrift
|
||||
add("DRC"); // Aetherdrift Commander
|
||||
|
|
|
|||
|
|
@ -2555,6 +2555,38 @@ public class ScryfallImageSupportTokens {
|
|||
// H17
|
||||
put("H17/Dragon", "https://api.scryfall.com/cards/h17/4/en?format=image");
|
||||
|
||||
// INR
|
||||
put("INR/Emblem Arlinn", "https://api.scryfall.com/cards/tinr/23/en?format=image");
|
||||
put("INR/Blood", "https://api.scryfall.com/cards/tinr/21/en?format=image");
|
||||
put("INR/Emblem Chandra", "https://api.scryfall.com/cards/tinr/24/en?format=image");
|
||||
put("INR/Clue", "https://api.scryfall.com/cards/tinr/22/en?format=image");
|
||||
put("INR/Demon", "https://api.scryfall.com/cards/tinr/6/en?format=image");
|
||||
put("INR/Eldrazi Horror", "https://api.scryfall.com/cards/tinr/1/en?format=image");
|
||||
put("INR/Elemental", "https://api.scryfall.com/cards/tinr/13/en?format=image");
|
||||
put("INR/Human/1", "https://api.scryfall.com/cards/tinr/14/en?format=image");
|
||||
put("INR/Human/2", "https://api.scryfall.com/cards/tinr/2/en?format=image");
|
||||
put("INR/Human Cleric", "https://api.scryfall.com/cards/tinr/19/en?format=image");
|
||||
put("INR/Human Soldier/1", "https://api.scryfall.com/cards/tinr/3/en?format=image");
|
||||
put("INR/Human Soldier/2", "https://api.scryfall.com/cards/tinr/20/en?format=image");
|
||||
put("INR/Human Wizard", "https://api.scryfall.com/cards/tinr/5/en?format=image");
|
||||
put("INR/Insect", "https://api.scryfall.com/cards/tinr/15/en?format=image");
|
||||
put("INR/Emblem Jace", "https://api.scryfall.com/cards/tinr/25/en?format=image");
|
||||
put("INR/Spider", "https://api.scryfall.com/cards/tinr/16/en?format=image");
|
||||
put("INR/Spirit", "https://api.scryfall.com/cards/tinr/4/en?format=image");
|
||||
put("INR/Emblem Tamiyo", "https://api.scryfall.com/cards/tinr/26/en?format=image");
|
||||
put("INR/Treefolk", "https://api.scryfall.com/cards/tinr/17/en?format=image");
|
||||
put("INR/Vampire/1", "https://api.scryfall.com/cards/tinr/7/en?format=image");
|
||||
put("INR/Vampire/2", "https://api.scryfall.com/cards/tinr/8/en?format=image");
|
||||
put("INR/Wolf/1", "https://api.scryfall.com/cards/tinr/9/en?format=image");
|
||||
put("INR/Wolf/2", "https://api.scryfall.com/cards/tinr/18/en?format=image");
|
||||
put("INR/Emblem Wrenn", "https://api.scryfall.com/cards/tinr/27/en?format=image");
|
||||
put("INR/Zombie/1", "https://api.scryfall.com/cards/tinr/12/en?format=image");
|
||||
put("INR/Zombie/2", "https://api.scryfall.com/cards/tinr/10/en?format=image");
|
||||
put("INR/Zombie/3", "https://api.scryfall.com/cards/tinr/11/en?format=image");
|
||||
|
||||
// DFT
|
||||
put("DFT/Emblem Chandra", "https://api.scryfall.com/cards/tdft/13/en?format=image");
|
||||
|
||||
// generate supported sets
|
||||
supportedSets.clear();
|
||||
for (String cardName : this.keySet()) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import java.io.InputStream;
|
|||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
@Ignore // TODO: too many fails due third party servers downtime, migrate to more stable resources or just run it manually
|
||||
public class DownloaderTest {
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-common</artifactId>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
|
|||
// * launcher gives priority to 1.4.48 instead 1.4.48-any-text, so don't use empty release info
|
||||
public static final int MAGE_VERSION_MAJOR = 1;
|
||||
public static final int MAGE_VERSION_MINOR = 4;
|
||||
public static final int MAGE_VERSION_RELEASE = 55;
|
||||
public static final int MAGE_VERSION_RELEASE = 56;
|
||||
public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas
|
||||
|
||||
// strict mode
|
||||
|
|
|
|||
|
|
@ -944,7 +944,7 @@ public class CardView extends SimpleCardView {
|
|||
this(true);
|
||||
this.gameObject = true;
|
||||
this.id = designation.getId();
|
||||
this.mageObjectType = MageObjectType.NULL;
|
||||
this.mageObjectType = MageObjectType.DESIGNATION;
|
||||
this.name = designation.getName();
|
||||
this.displayName = name;
|
||||
this.displayFullName = name;
|
||||
|
|
@ -955,9 +955,8 @@ public class CardView extends SimpleCardView {
|
|||
this.frameStyle = FrameStyle.M15_NORMAL;
|
||||
this.cardNumber = designation.getCardNumber();
|
||||
this.expansionSetCode = designation.getExpansionSetCode();
|
||||
this.cardNumber = "";
|
||||
this.imageFileName = "";
|
||||
this.imageNumber = 0;
|
||||
this.imageFileName = designation.getImageFileName();
|
||||
this.imageNumber = designation.getImageNumber();
|
||||
this.rarity = Rarity.SPECIAL;
|
||||
|
||||
// no playable/chooseable marks for designations
|
||||
|
|
|
|||
|
|
@ -62,7 +62,11 @@ public class GameView implements Serializable {
|
|||
private final int turn;
|
||||
private boolean special = false;
|
||||
private final boolean rollbackTurnsAllowed;
|
||||
|
||||
// for debug only
|
||||
// TODO: implement and support in admin tools
|
||||
private int totalErrorsCount;
|
||||
private int totalEffectsCount;
|
||||
|
||||
public GameView(GameState state, Game game, UUID createdForPlayerId, UUID watcherUserId) {
|
||||
Player createdForPlayer = null;
|
||||
|
|
@ -209,6 +213,7 @@ public class GameView implements Serializable {
|
|||
}
|
||||
this.rollbackTurnsAllowed = game.getOptions().rollbackTurnsAllowed;
|
||||
this.totalErrorsCount = game.getTotalErrorsCount();
|
||||
this.totalEffectsCount = game.getTotalEffectsCount();
|
||||
}
|
||||
|
||||
private void checkPaid(UUID uuid, StackAbility stackAbility) {
|
||||
|
|
@ -349,4 +354,8 @@ public class GameView implements Serializable {
|
|||
public int getTotalErrorsCount() {
|
||||
return this.totalErrorsCount;
|
||||
}
|
||||
|
||||
public int getTotalEffectsCount() {
|
||||
return this.totalEffectsCount;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import java.io.Serializable;
|
|||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -19,7 +20,11 @@ public class RevealedView implements Serializable {
|
|||
public RevealedView(String name, Cards cards, Game game) {
|
||||
this.name = name;
|
||||
for (Card card : cards.getCards(game)) {
|
||||
this.cards.put(card.getId(), new CardView(card, game));
|
||||
if (card instanceof PermanentCard && card.isFaceDown(game)) {
|
||||
this.cards.put(card.getId(), new CardView(card.getMainCard())); // do not use game param, so it will take default card
|
||||
} else {
|
||||
this.cards.put(card.getId(), new CardView(card, game));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-counter-plugin</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-plugins</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-reports</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-server-console</artifactId>
|
||||
|
|
|
|||
|
|
@ -365,12 +365,17 @@ public class ConnectDialog extends JDialog {
|
|||
connection.setPort(Integer.parseInt(this.txtPort.getText()));
|
||||
connection.setAdminPassword(new String(txtPassword.getPassword()));
|
||||
connection.setUsername(SessionImpl.ADMIN_NAME);
|
||||
connection.setProxyType((ProxyType) this.cbProxyType.getSelectedItem());
|
||||
if (!this.cbProxyType.getSelectedItem().equals(ProxyType.NONE)) {
|
||||
connection.setProxyHost(this.txtProxyServer.getText());
|
||||
connection.setProxyPort(Integer.parseInt(this.txtProxyPort.getText()));
|
||||
connection.setProxyUsername(this.txtProxyUserName.getText());
|
||||
connection.setProxyPassword(new String(this.txtPasswordField.getPassword()));
|
||||
|
||||
if (false) { // TODO: delete proxy at all after few releases, 2025-02-09
|
||||
connection.setProxyType((ProxyType) this.cbProxyType.getSelectedItem());
|
||||
if (!this.cbProxyType.getSelectedItem().equals(ProxyType.NONE)) {
|
||||
connection.setProxyHost(this.txtProxyServer.getText());
|
||||
connection.setProxyPort(Integer.parseInt(this.txtProxyPort.getText()));
|
||||
connection.setProxyUsername(this.txtProxyUserName.getText());
|
||||
connection.setProxyPassword(new String(this.txtPasswordField.getPassword()));
|
||||
}
|
||||
} else {
|
||||
connection.setProxyType(ProxyType.NONE);
|
||||
}
|
||||
|
||||
logger.debug("connecting: " + connection.getProxyType() + ' ' + connection.getProxyHost() + ' ' + connection.getProxyPort());
|
||||
|
|
|
|||
|
|
@ -101,13 +101,18 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient {
|
|||
newConnection.setPort(ConsoleFrame.getPreferences().getInt("serverPort", 17171));
|
||||
newConnection.setUsername(SessionImpl.ADMIN_NAME);
|
||||
newConnection.setAdminPassword(ConsoleFrame.getPreferences().get("password", ""));
|
||||
newConnection.setProxyType(Connection.ProxyType.valueOf(ConsoleFrame.getPreferences().get("proxyType", "NONE").toUpperCase(Locale.ENGLISH)));
|
||||
if (!newConnection.getProxyType().equals(Connection.ProxyType.NONE)) {
|
||||
newConnection.setProxyHost(ConsoleFrame.getPreferences().get("proxyAddress", ""));
|
||||
newConnection.setProxyPort(ConsoleFrame.getPreferences().getInt("proxyPort", 0));
|
||||
newConnection.setProxyUsername(ConsoleFrame.getPreferences().get("proxyUsername", ""));
|
||||
newConnection.setProxyPassword(ConsoleFrame.getPreferences().get("proxyPassword", ""));
|
||||
if (false) { // TODO: delete proxy at all after few releases, 2025-02-09
|
||||
newConnection.setProxyType(Connection.ProxyType.valueOf(ConsoleFrame.getPreferences().get("proxyType", "NONE").toUpperCase(Locale.ENGLISH)));
|
||||
if (!newConnection.getProxyType().equals(Connection.ProxyType.NONE)) {
|
||||
newConnection.setProxyHost(ConsoleFrame.getPreferences().get("proxyAddress", ""));
|
||||
newConnection.setProxyPort(ConsoleFrame.getPreferences().getInt("proxyPort", 0));
|
||||
newConnection.setProxyUsername(ConsoleFrame.getPreferences().get("proxyUsername", ""));
|
||||
newConnection.setProxyPassword(ConsoleFrame.getPreferences().get("proxyPassword", ""));
|
||||
}
|
||||
} else {
|
||||
newConnection.setProxyType(Connection.ProxyType.NONE);
|
||||
}
|
||||
|
||||
status = connect(newConnection);
|
||||
}
|
||||
return status;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-deck-constructed</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-deck-limited</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-brawlduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-brawlfreeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-canadianhighlanderduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-commanderduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-commanderfreeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-custompillaroftheparunsduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-freeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-freeformcommanderduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-freeformcommanderfreeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-freeformunlimitedcommander</artifactId>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<dependency>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-game-freeformcommanderfreeforall</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-momirduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-momirfreeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-oathbreakerduel</artifactId>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<dependency>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-game-oathbreakerfreeforall</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-oathbreakerfreeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-pennydreadfulcommanderfreeforall</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-tinyleadersduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-game-twoplayerduel</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-player-ai-draftbot</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-player-ai-ma</artifactId>
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
+ (p.isTapped() ? ",tapped" : "")
|
||||
+ (p.isAttacking() ? ",attacking" : "")
|
||||
+ (p.getBlocking() > 0 ? ",blocking" : "")
|
||||
+ ":" + GameStateEvaluator2.evaluatePermanent(p, game))
|
||||
+ ":" + GameStateEvaluator2.evaluatePermanent(p, game, true))
|
||||
.collect(Collectors.joining("; "));
|
||||
sb.append("-> Permanents: [").append(ownPermanentsInfo).append("]");
|
||||
logger.info(sb.toString());
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ public final class GameStateEvaluator2 {
|
|||
public static final int HAND_CARD_SCORE = 5;
|
||||
|
||||
public static PlayerEvaluateScore evaluate(UUID playerId, Game game) {
|
||||
return evaluate(playerId, game, true);
|
||||
}
|
||||
|
||||
public static PlayerEvaluateScore evaluate(UUID playerId, Game game, boolean useCombatPermanentScore) {
|
||||
// TODO: add multi opponents support, so AI can take better actions
|
||||
Player player = game.getPlayer(playerId);
|
||||
Player opponent = game.getPlayer(game.getOpponents(playerId).stream().findFirst().orElse(null));
|
||||
|
|
@ -63,7 +67,7 @@ public final class GameStateEvaluator2 {
|
|||
|
||||
// add values of player
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
|
||||
int onePermScore = evaluatePermanent(permanent, game);
|
||||
int onePermScore = evaluatePermanent(permanent, game, useCombatPermanentScore);
|
||||
playerPermanentsScore += onePermScore;
|
||||
if (logger.isDebugEnabled()) {
|
||||
sbPlayer.append(permanent.getName()).append('[').append(onePermScore).append("] ");
|
||||
|
|
@ -77,7 +81,7 @@ public final class GameStateEvaluator2 {
|
|||
|
||||
// add values of opponent
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponent.getId())) {
|
||||
int onePermScore = evaluatePermanent(permanent, game);
|
||||
int onePermScore = evaluatePermanent(permanent, game, useCombatPermanentScore);
|
||||
opponentPermanentsScore += onePermScore;
|
||||
if (logger.isDebugEnabled()) {
|
||||
sbOpponent.append(permanent.getName()).append('[').append(onePermScore).append("] ");
|
||||
|
|
@ -121,7 +125,7 @@ public final class GameStateEvaluator2 {
|
|||
opponentLifeScore, opponentHandScore, opponentPermanentsScore);
|
||||
}
|
||||
|
||||
public static int evaluatePermanent(Permanent permanent, Game game) {
|
||||
public static int evaluatePermanent(Permanent permanent, Game game, boolean useCombatPermanentScore) {
|
||||
// prevent AI from attaching bad auras to its own permanents ex: Brainwash and Demonic Torment (no immediate penalty on the battlefield)
|
||||
int value = 0;
|
||||
if (!permanent.getAttachments().isEmpty()) {
|
||||
|
|
@ -137,14 +141,11 @@ public final class GameStateEvaluator2 {
|
|||
}
|
||||
}
|
||||
}
|
||||
value += ArtificialScoringSystem.getFixedPermanentScore(game, permanent)
|
||||
+ ArtificialScoringSystem.getVariablePermanentScore(game, permanent);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int evaluateCreature(Permanent creature, Game game) {
|
||||
int value = ArtificialScoringSystem.getFixedPermanentScore(game, creature)
|
||||
+ ArtificialScoringSystem.getVariablePermanentScore(game, creature);
|
||||
value += ArtificialScoringSystem.getFixedPermanentScore(game, permanent);
|
||||
value += ArtificialScoringSystem.getDynamicPermanentScore(game, permanent);
|
||||
if (useCombatPermanentScore) {
|
||||
value += ArtificialScoringSystem.getCombatPermanentScore(game, permanent);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ public final class SimulatedPlayer2 extends ComputerPlayer {
|
|||
logger.debug("simulating -- node #:" + SimulationNode2.getCount() + " triggered ability option");
|
||||
for (Target target : ability.getTargets()) {
|
||||
for (UUID targetId : target.getTargets()) {
|
||||
newNode.getTargets().add(targetId);
|
||||
newNode.getTargets().add(targetId); // save for info only (real targets in newNode.ability already)
|
||||
}
|
||||
}
|
||||
parent.children.add(newNode);
|
||||
|
|
|
|||
|
|
@ -63,14 +63,11 @@ public final class ArtificialScoringSystem {
|
|||
return score;
|
||||
}
|
||||
|
||||
public static int getVariablePermanentScore(final Game game, final Permanent permanent) {
|
||||
public static int getDynamicPermanentScore(final Game game, final Permanent permanent) {
|
||||
|
||||
int score = permanent.getCounters(game).getCount(CounterType.CHARGE) * 30;
|
||||
score += permanent.getCounters(game).getCount(CounterType.LEVEL) * 30;
|
||||
score -= permanent.getDamage() * 2;
|
||||
if (!canTap(game, permanent)) {
|
||||
score += getTappedScore(game, permanent);
|
||||
}
|
||||
if (permanent.getCardType(game).contains(CardType.CREATURE)) {
|
||||
final int power = permanent.getPower().getValue();
|
||||
final int toughness = permanent.getToughness().getValue();
|
||||
|
|
@ -95,11 +92,19 @@ public final class ArtificialScoringSystem {
|
|||
}
|
||||
}
|
||||
score += equipments + enchantments;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
public static int getCombatPermanentScore(final Game game, final Permanent permanent) {
|
||||
int score = 0;
|
||||
if (!canTap(game, permanent)) {
|
||||
score += getTappedScore(game, permanent);
|
||||
}
|
||||
if (permanent.getCardType(game).contains(CardType.CREATURE)) {
|
||||
if (!permanent.canAttack(null, game)) {
|
||||
score -= 100;
|
||||
}
|
||||
|
||||
if (!permanent.canBlockAny(game)) {
|
||||
score -= 30;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@ public class CombatInfo {
|
|||
private Map<Permanent, List<Permanent>> combat = new HashMap<>();
|
||||
|
||||
public void addPair(Permanent attacker, Permanent blocker) {
|
||||
List<Permanent> blockers = combat.get(attacker);
|
||||
if (blockers == null) {
|
||||
blockers = new ArrayList<>();
|
||||
combat.put(attacker, blockers);
|
||||
}
|
||||
List<Permanent> blockers = combat.computeIfAbsent(attacker, k -> new ArrayList<>());
|
||||
blockers.add(blocker);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
package mage.player.ai.util;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.abilities.keyword.InfectAbility;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.Combat;
|
||||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.events.GameEvent;
|
||||
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.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -89,12 +93,22 @@ public final class CombatUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static Permanent getWorstCreature(List<Permanent> creatures) {
|
||||
if (creatures.isEmpty()) {
|
||||
return null;
|
||||
public static Permanent getWorstCreature(List<Permanent>... lists) {
|
||||
for (List<Permanent> list : lists) {
|
||||
if (!list.isEmpty()) {
|
||||
list.sort(Comparator.comparingInt(p -> p.getPower().getValue()));
|
||||
return list.get(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void removeWorstCreature(Permanent permanent, List<Permanent>... lists) {
|
||||
for (List<Permanent> list : lists) {
|
||||
if (!list.isEmpty()) {
|
||||
list.remove(permanent);
|
||||
}
|
||||
}
|
||||
creatures.sort(Comparator.comparingInt(p -> p.getPower().getValue()));
|
||||
return creatures.get(0);
|
||||
}
|
||||
|
||||
private static int sumDamage(List<Permanent> attackersThatWontBeBlocked, Player defender) {
|
||||
|
|
@ -144,25 +158,100 @@ public final class CombatUtil {
|
|||
return canBlock;
|
||||
}
|
||||
|
||||
public static CombatInfo blockWithGoodTrade(Game game, List<Permanent> attackers, List<Permanent> blockers) {
|
||||
|
||||
/**
|
||||
* AI related code, find better block combination for attackers
|
||||
*/
|
||||
public static CombatInfo blockWithGoodTrade2(Game game, List<Permanent> attackers, List<Permanent> blockers) {
|
||||
UUID attackerId = game.getCombat().getAttackingPlayerId();
|
||||
UUID defenderId = game.getCombat().getDefenders().iterator().next();
|
||||
if (attackerId == null || defenderId == null) {
|
||||
log.warn("Couldn't find attacker or defender: " + attackerId + ' ' + defenderId);
|
||||
return new CombatInfo();
|
||||
}
|
||||
|
||||
// TODO: implement full game simulations of all possible combinations (e.g. multiblockers support)
|
||||
|
||||
CombatInfo combatInfo = new CombatInfo();
|
||||
for (Permanent attacker : attackers) {
|
||||
//TODO: handle attackers with "can't be blocked except"
|
||||
List<Permanent> possibleBlockers = getPossibleBlockers(game, attacker, blockers);
|
||||
List<Permanent> survivedBlockers = getBlockersThatWillSurvive(game, attackerId, defenderId, attacker, possibleBlockers);
|
||||
if (!survivedBlockers.isEmpty()) {
|
||||
Permanent blocker = getWorstCreature(survivedBlockers);
|
||||
// simple combat simulation (1 vs 1)
|
||||
List<Permanent> allBlockers = getPossibleBlockers(game, attacker, blockers);
|
||||
List<SurviveInfo> blockerStats = getBlockersThatWillSurvive2(game, attackerId, defenderId, attacker, allBlockers);
|
||||
Map<Permanent, Integer> blockingDiffScore = new HashMap<>();
|
||||
Map<Permanent, Integer> nonBlockingDiffScore = new HashMap<>();
|
||||
blockerStats.forEach(s -> {
|
||||
blockingDiffScore.put(s.getBlocker(), s.getDiffBlockingScore());
|
||||
nonBlockingDiffScore.put(s.getBlocker(), s.getDiffNonblockingScore());
|
||||
});
|
||||
|
||||
// split blockers by usage priority
|
||||
List<Permanent> survivedAndKillBlocker = new ArrayList<>();
|
||||
List<Permanent> survivedBlockers = new ArrayList<>();
|
||||
List<Permanent> diedBlockers = new ArrayList<>();
|
||||
blockerStats.forEach(stats -> {
|
||||
if (stats.isAttackerDied() && !stats.isBlockerDied()) {
|
||||
survivedAndKillBlocker.add(stats.getBlocker());
|
||||
} else if (!stats.isBlockerDied()) {
|
||||
survivedBlockers.add(stats.getBlocker());
|
||||
} else {
|
||||
diedBlockers.add(stats.getBlocker());
|
||||
}
|
||||
});
|
||||
|
||||
int blockedCount = 0;
|
||||
|
||||
// find good blocker
|
||||
Permanent blocker = getWorstCreature(survivedAndKillBlocker, survivedBlockers);
|
||||
if (blocker != null) {
|
||||
combatInfo.addPair(attacker, blocker);
|
||||
blockers.remove(blocker);
|
||||
removeWorstCreature(blocker, blockers, survivedAndKillBlocker, survivedBlockers);
|
||||
blockedCount++;
|
||||
}
|
||||
|
||||
// find good sacrifices (chump blocks also supported due bad game score on loose)
|
||||
// TODO: add chump blocking support here?
|
||||
// TODO: there are many triggers on damage, attack, etc - it can't be processed without real game simulations
|
||||
if (blocker == null) {
|
||||
blocker = getWorstCreature(diedBlockers);
|
||||
if (blocker != null) {
|
||||
int diffBlockingScore = blockingDiffScore.getOrDefault(blocker, 0);
|
||||
int diffNonBlockingScore = nonBlockingDiffScore.getOrDefault(blocker, 0);
|
||||
if (diffBlockingScore >= 0 || diffBlockingScore > diffNonBlockingScore) {
|
||||
// it's good - can sacrifice and get better game state, also protect from game loose
|
||||
combatInfo.addPair(attacker, blocker);
|
||||
removeWorstCreature(blocker, blockers, diedBlockers);
|
||||
blockedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find blockers for restrictions
|
||||
while (true) {
|
||||
if (blockers.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: add multiple use case support with min/max blockedBy conditional and other
|
||||
// see all possible use cases in checkBlockRestrictions, checkBlockRequirementsAfter and checkBlockRestrictionsAfter
|
||||
|
||||
// effects support: can't be blocked except by xxx or more creatures
|
||||
if (blockedCount > 0 && attacker.getMinBlockedBy() > blockedCount) {
|
||||
// it already has 1 blocker (killer in best use case), so no needs in second killer
|
||||
blocker = getWorstCreature(survivedBlockers, survivedAndKillBlocker, diedBlockers);
|
||||
if (blocker != null) {
|
||||
combatInfo.addPair(attacker, blocker);
|
||||
removeWorstCreature(blocker, blockers, survivedBlockers, survivedAndKillBlocker, diedBlockers);
|
||||
blockedCount++;
|
||||
continue; // try to find next required blocker
|
||||
} else {
|
||||
// invalid configuration, must stop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no more active restrictions
|
||||
break;
|
||||
}
|
||||
|
||||
// no more blockers to use
|
||||
if (blockers.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -171,40 +260,43 @@ public final class CombatUtil {
|
|||
return combatInfo;
|
||||
}
|
||||
|
||||
private static List<Permanent> getBlockersThatWillSurvive(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) {
|
||||
List<Permanent> blockers = new ArrayList<>();
|
||||
/**
|
||||
* Game simulations to find all survived/killer blocker
|
||||
*/
|
||||
private static List<SurviveInfo> getBlockersThatWillSurvive2(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) {
|
||||
List<SurviveInfo> res = new ArrayList<>();
|
||||
for (Permanent blocker : possibleBlockers) {
|
||||
SurviveInfo info = willItSurvive(game, attackerId, defenderId, attacker, blocker);
|
||||
//if (info.isAttackerDied() && !info.isBlockerDied()) {
|
||||
if (info != null) {
|
||||
if (info.isAttackerDied()) {
|
||||
blockers.add(blocker);
|
||||
} else if (!info.isBlockerDied()) {
|
||||
blockers.add(blocker);
|
||||
}
|
||||
// TODO: enable willItSurviveSimulation and check stability
|
||||
SurviveInfo info = willItSurviveSimple(game, attackerId, defenderId, attacker, blocker);
|
||||
if (info == null) {
|
||||
continue;
|
||||
}
|
||||
info.setBlocker(blocker);
|
||||
res.add(info);
|
||||
}
|
||||
return blockers;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated TODO: unused, can be deleted?
|
||||
*/
|
||||
public static SurviveInfo willItSurvive(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) {
|
||||
Game sim = game.createSimulationForAI();
|
||||
|
||||
// TODO: bugged, miss combat.clear code (possible bugs - wrong blocker declare by AI on multiple options?)
|
||||
Combat combat = sim.getCombat();
|
||||
combat.setAttacker(attackingPlayerId);
|
||||
combat.setDefenders(sim);
|
||||
|
||||
public static SurviveInfo willItSurviveSimulation(Game originalGame, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) {
|
||||
Game sim = originalGame.createSimulationForAI();
|
||||
if (blocker == null || attacker == null || sim.getPlayer(defendingPlayerId) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: need code research, possible bugs in miss prepare code due real combat logic
|
||||
// TODO: bugged, miss combat.clear code (possible bugs - wrong blocker declare by AI on multiple options?)
|
||||
Combat combat = sim.getCombat();
|
||||
combat.setAttacker(attackingPlayerId);
|
||||
combat.setDefenders(sim);
|
||||
int startScore = GameStateEvaluator2.evaluate(defendingPlayerId, sim).getTotalScore();
|
||||
|
||||
// real game simulation
|
||||
// TODO: need debug and testing, old code from 2012
|
||||
// must have infinite/freeze protection (e.g. limit stack resolves)
|
||||
|
||||
// declare
|
||||
sim.getPlayer(defendingPlayerId).declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), sim);
|
||||
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defendingPlayerId, defendingPlayerId));
|
||||
|
||||
sim.checkStateAndTriggered();
|
||||
while (!sim.getStack().isEmpty()) {
|
||||
sim.getStack().resolve(sim);
|
||||
|
|
@ -212,109 +304,119 @@ public final class CombatUtil {
|
|||
}
|
||||
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId()));
|
||||
|
||||
// combat
|
||||
simulateStep(sim, new CombatDamageStep(true));
|
||||
simulateStep(sim, new CombatDamageStep(false));
|
||||
simulateStep(sim, new EndOfCombatStep());
|
||||
// The following commented out call produces random freezes.
|
||||
//sim.checkStateAndTriggered();
|
||||
|
||||
// after
|
||||
sim.checkStateAndTriggered();
|
||||
while (!sim.getStack().isEmpty()) {
|
||||
sim.getStack().resolve(sim);
|
||||
sim.applyEffects();
|
||||
}
|
||||
|
||||
return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), !sim.getBattlefield().containsPermanent(blocker.getId()));
|
||||
int endBlockingScore = GameStateEvaluator2.evaluate(defendingPlayerId, sim).getTotalScore();
|
||||
int endNonBlockingScore = startScore; // TODO: implement
|
||||
return new SurviveInfo(
|
||||
!sim.getBattlefield().containsPermanent(attacker.getId()),
|
||||
!sim.getBattlefield().containsPermanent(blocker.getId()),
|
||||
endBlockingScore - startScore,
|
||||
endNonBlockingScore - startScore
|
||||
);
|
||||
}
|
||||
|
||||
protected static void simulateStep(Game game, Step step) {
|
||||
game.getPhase().setStep(step);
|
||||
if (!step.skipStep(game, game.getActivePlayerId())) {
|
||||
step.beginStep(game, game.getActivePlayerId());
|
||||
// The following commented out call produces random freezes.
|
||||
//game.checkStateAndTriggered();
|
||||
while (!game.getStack().isEmpty()) {
|
||||
game.getStack().resolve(game);
|
||||
game.applyEffects();
|
||||
}
|
||||
step.endStep(game, game.getActivePlayerId());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean canBlock(Game game, Permanent blocker) {
|
||||
boolean canBlock = true;
|
||||
if (!blocker.isTapped()) {
|
||||
try {
|
||||
canBlock = blocker.canBlock(null, game);
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return canBlock;
|
||||
}
|
||||
|
||||
public static CombatInfo blockWithGoodTrade2(Game game, List<Permanent> attackers, List<Permanent> blockers) {
|
||||
|
||||
UUID attackerId = game.getCombat().getAttackingPlayerId();
|
||||
UUID defenderId = game.getCombat().getDefenders().iterator().next();
|
||||
if (attackerId == null || defenderId == null) {
|
||||
log.warn("Couldn't find attacker or defender: " + attackerId + ' ' + defenderId);
|
||||
return new CombatInfo();
|
||||
}
|
||||
|
||||
CombatInfo combatInfo = new CombatInfo();
|
||||
for (Permanent attacker : attackers) {
|
||||
//TODO: handle attackers with "can't be blocked except"
|
||||
List<Permanent> possibleBlockers = getPossibleBlockers(game, attacker, blockers);
|
||||
List<Permanent> survivedBlockers = getBlockersThatWillSurvive2(game, attackerId, defenderId, attacker, possibleBlockers);
|
||||
if (!survivedBlockers.isEmpty()) {
|
||||
Permanent blocker = getWorstCreature(survivedBlockers);
|
||||
combatInfo.addPair(attacker, blocker);
|
||||
blockers.remove(blocker);
|
||||
}
|
||||
if (blockers.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return combatInfo;
|
||||
}
|
||||
|
||||
private static List<Permanent> getBlockersThatWillSurvive2(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) {
|
||||
List<Permanent> blockers = new ArrayList<>();
|
||||
for (Permanent blocker : possibleBlockers) {
|
||||
SurviveInfo info = willItSurvive2(game, attackerId, defenderId, attacker, blocker);
|
||||
//if (info.isAttackerDied() && !info.isBlockerDied()) {
|
||||
if (info != null) {
|
||||
if (info.isAttackerDied()) {
|
||||
blockers.add(blocker);
|
||||
} else if (!info.isBlockerDied()) {
|
||||
blockers.add(blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
return blockers;
|
||||
}
|
||||
|
||||
public static SurviveInfo willItSurvive2(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) {
|
||||
|
||||
Game sim = game.createSimulationForAI();
|
||||
|
||||
// TODO: bugged, miss combat.clear code (possible bugs - wrong blocker declare by AI on multiple options?)
|
||||
Combat combat = sim.getCombat();
|
||||
combat.setAttacker(attackingPlayerId);
|
||||
combat.setDefenders(sim);
|
||||
|
||||
public static SurviveInfo willItSurviveSimple(Game originalGame, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) {
|
||||
Game sim = originalGame.createSimulationForAI();
|
||||
if (blocker == null || attacker == null || sim.getPlayer(defendingPlayerId) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Combat combat = sim.getCombat();
|
||||
combat.setAttacker(attackingPlayerId);
|
||||
combat.setDefenders(sim);
|
||||
|
||||
Game simNonBlocking = sim.copy();
|
||||
|
||||
// attacker tapped before attack, it will add additional score to blocker, but it must be ignored
|
||||
// so blocker will block same creature with same score without penalty
|
||||
int startScore = GameStateEvaluator2.evaluate(defendingPlayerId, sim, false).getTotalScore();
|
||||
|
||||
// fake combat simulation (simple damage simulation)
|
||||
Permanent simAttacker = sim.getPermanent(attacker.getId());
|
||||
Permanent simBlocker = sim.getPermanent(blocker.getId());
|
||||
if (simAttacker == null || simBlocker == null) {
|
||||
throw new IllegalArgumentException("Broken sim game, can't find attacker or blocker");
|
||||
}
|
||||
// don't ask about that hacks - just replace to real combat simulation someday (another hack but with full stack resolve)
|
||||
// first damage step
|
||||
simulateCombatDamage(sim, simBlocker, simAttacker, true);
|
||||
simulateCombatDamage(sim, simAttacker, simBlocker, true);
|
||||
simAttacker.applyDamage(sim);
|
||||
simBlocker.applyDamage(sim);
|
||||
sim.checkStateAndTriggered();
|
||||
sim.processAction();
|
||||
// second damage step
|
||||
if (sim.getPermanent(simBlocker.getId()) != null && sim.getPermanent(simAttacker.getId()) != null) {
|
||||
simulateCombatDamage(sim, simBlocker, simAttacker, false);
|
||||
simulateCombatDamage(sim, simAttacker, simBlocker, false);
|
||||
simAttacker.applyDamage(sim);
|
||||
simBlocker.applyDamage(sim);
|
||||
sim.checkStateAndTriggered();
|
||||
sim.processAction();
|
||||
}
|
||||
|
||||
/* old manual PT compare
|
||||
if (attacker.getPower().getValue() >= blocker.getToughness().getValue()) {
|
||||
sim.getBattlefield().removePermanent(blocker.getId());
|
||||
}
|
||||
if (attacker.getToughness().getValue() <= blocker.getPower().getValue()) {
|
||||
sim.getBattlefield().removePermanent(attacker.getId());
|
||||
}
|
||||
*/
|
||||
|
||||
return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), !sim.getBattlefield().containsPermanent(blocker.getId()));
|
||||
// fake non-block simulation
|
||||
simNonBlocking.getPlayer(defendingPlayerId).damage(
|
||||
attacker.getPower().getValue(),
|
||||
attacker.getId(),
|
||||
null,
|
||||
simNonBlocking,
|
||||
true,
|
||||
true
|
||||
);
|
||||
simNonBlocking.checkStateAndTriggered();
|
||||
simNonBlocking.processAction();
|
||||
|
||||
int endBlockingScore = GameStateEvaluator2.evaluate(defendingPlayerId, sim, false).getTotalScore();
|
||||
int endNonBlockingScore = GameStateEvaluator2.evaluate(defendingPlayerId, simNonBlocking, false).getTotalScore();
|
||||
return new SurviveInfo(
|
||||
!sim.getBattlefield().containsPermanent(attacker.getId()),
|
||||
!sim.getBattlefield().containsPermanent(blocker.getId()),
|
||||
endBlockingScore - startScore,
|
||||
endNonBlockingScore - startScore
|
||||
);
|
||||
}
|
||||
|
||||
private static void simulateCombatDamage(Game sim, Permanent fromCreature, Permanent toCreature, boolean isFirstDamageStep) {
|
||||
Ability fakeAbility = new SimpleStaticAbility(null);
|
||||
if (CombatGroup.dealsDamageThisStep(fromCreature, isFirstDamageStep, sim)) {
|
||||
fakeAbility.setSourceId(fromCreature.getId());
|
||||
fakeAbility.setControllerId(fromCreature.getControllerId());
|
||||
toCreature.damage(fromCreature.getPower().getValue(), fromCreature.getId(), fakeAbility, sim, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void simulateStep(Game sim, Step step) {
|
||||
sim.getPhase().setStep(step);
|
||||
if (!step.skipStep(sim, sim.getActivePlayerId())) {
|
||||
step.beginStep(sim, sim.getActivePlayerId());
|
||||
// The following commented out call produces random freezes.
|
||||
//game.checkStateAndTriggered();
|
||||
while (!sim.getStack().isEmpty()) {
|
||||
sim.getStack().resolve(sim);
|
||||
sim.applyEffects();
|
||||
}
|
||||
step.endStep(sim, sim.getActivePlayerId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,48 @@
|
|||
package mage.player.ai.util;
|
||||
|
||||
import mage.players.Player;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author noxx
|
||||
* AI: combat simulation result
|
||||
*
|
||||
* @author noxx, JayDi85
|
||||
*/
|
||||
public class SurviveInfo {
|
||||
private boolean attackerDied;
|
||||
private boolean blockerDied;
|
||||
private final boolean attackerDied;
|
||||
private final boolean blockerDied;
|
||||
private final int diffBlockingScore;
|
||||
private final int diffNonblockingScore;
|
||||
|
||||
private Player defender;
|
||||
private boolean triggered;
|
||||
private Permanent blocker; // for final result
|
||||
|
||||
public SurviveInfo(boolean attackerDied, boolean blockerDied, Player defender, boolean triggered) {
|
||||
public SurviveInfo(boolean attackerDied, boolean blockerDied, int diffBlockingScore, int diffNonblockingScore) {
|
||||
this.attackerDied = attackerDied;
|
||||
this.blockerDied = blockerDied;
|
||||
this.defender = defender;
|
||||
this.triggered = triggered;
|
||||
this.diffBlockingScore = diffBlockingScore;
|
||||
this.diffNonblockingScore = diffNonblockingScore;
|
||||
}
|
||||
|
||||
public SurviveInfo(boolean attackerDied, boolean blockerDied) {
|
||||
this.attackerDied = attackerDied;
|
||||
this.blockerDied = blockerDied;
|
||||
public void setBlocker(Permanent blocker) {
|
||||
this.blocker = blocker;
|
||||
}
|
||||
|
||||
public Permanent getBlocker() {
|
||||
return this.blocker;
|
||||
}
|
||||
|
||||
public int getDiffBlockingScore() {
|
||||
return this.diffBlockingScore;
|
||||
}
|
||||
|
||||
public int getDiffNonblockingScore() {
|
||||
return this.diffNonblockingScore;
|
||||
}
|
||||
|
||||
public boolean isAttackerDied() {
|
||||
return attackerDied;
|
||||
}
|
||||
|
||||
public void setAttackerDied(boolean attackerDied) {
|
||||
this.attackerDied = attackerDied;
|
||||
}
|
||||
|
||||
public boolean isBlockerDied() {
|
||||
return blockerDied;
|
||||
}
|
||||
|
||||
public void setBlockerDied(boolean blockerDied) {
|
||||
this.blockerDied = blockerDied;
|
||||
}
|
||||
|
||||
public Player getDefender() {
|
||||
return defender;
|
||||
}
|
||||
|
||||
public boolean isTriggered() {
|
||||
return triggered;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-player-ai</artifactId>
|
||||
|
|
|
|||
|
|
@ -70,9 +70,21 @@ public class ComputerPlayer extends PlayerImpl {
|
|||
protected int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available
|
||||
|
||||
// debug only: set TRUE to debug simulation's code/games (on false sim thread will be stopped after few secs by timeout)
|
||||
protected boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false;
|
||||
protected boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false; // DebugUtil.AI_ENABLE_DEBUG_MODE;
|
||||
|
||||
// AI agents uses game simulation thread for all calcs and it's high CPU consumption
|
||||
// More AI threads - more parallel AI games can be calculate
|
||||
// If you catch errors like ConcurrentModificationException, then AI implementation works with wrong data
|
||||
// (e.g. with original game instead copy) or AI use wrong logic (one sim result depends on another sim result)
|
||||
// How-to use:
|
||||
// * 1 for debug or stable
|
||||
// * 5 for good performance on average computer
|
||||
// * use your's CPU cores for best performance
|
||||
// TODO: add server config to control max AI threads (with CPU cores by default)
|
||||
// TODO: rework AI implementation to use multiple sims calculation instead one by one
|
||||
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 5;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5;
|
||||
|
||||
|
||||
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1; // TODO: rework simulations logic to use multiple calcs instead one by one
|
||||
|
||||
private final transient Map<Mana, Card> unplayable = new TreeMap<>();
|
||||
private final transient List<Card> playableNonInstant = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-player-ai-mcts</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-player-human</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-tournament-boosterdraft</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-tournament-constructed</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-tournament-sealed</artifactId>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-server</artifactId>
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ set JAVA_HOME="C:\Program Files\Java\jre7\"
|
|||
set CLASSPATH=%JAVA_HOME%/bin;%CLASSPATH%
|
||||
set PATH=%JAVA_HOME%/bin;%PATH%
|
||||
:NOJAVADIR
|
||||
java -Xmx1024m -XX:MaxPermSize=384m -jar ./lib/mage-server-${project.version}.jar
|
||||
java -Xmx1024m -jar ./lib/mage-server-${project.version}.jar
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
cd "`dirname "$0"`"
|
||||
|
||||
java -Xmx1024m -XX:MaxPermSize=384m -jar ./lib/mage-server-${project.version}.jar
|
||||
java -Xmx1024m -jar ./lib/mage-server-${project.version}.jar
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
java -Xmx1024m -XX:MaxPermSize=384m -jar ./lib/mage-server-${project.version}.jar
|
||||
java -Xmx1024m -jar ./lib/mage-server-${project.version}.jar
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ set JAVA_HOME="C:\Program Files (x86)\Java\jre7\"
|
|||
set CLASSPATH=%JAVA_HOME%/bin;%CLASSPATH%
|
||||
set PATH=%JAVA_HOME%/bin;%PATH%
|
||||
:NOJAVADIR
|
||||
java -Xmx1024m -XX:MaxPermSize=384m -jar ./lib/mage-server-${project.version}.jar
|
||||
java -Xmx1024m -jar ./lib/mage-server-${project.version}.jar
|
||||
|
|
@ -1001,10 +1001,18 @@ public class MageServerImpl implements MageServer {
|
|||
}
|
||||
|
||||
public void handleException(Exception ex) throws MageException {
|
||||
if (!ex.getMessage().equals("No message")) {
|
||||
logger.fatal("", ex);
|
||||
if (ex.getMessage() != null && !ex.getMessage().equals("No message")) {
|
||||
throw new MageException("Server error: " + ex.getMessage());
|
||||
}
|
||||
|
||||
if (ex instanceof ConcurrentModificationException) {
|
||||
// how-to fix: game objects must be accessible by game thread only, all other threads must work with copies
|
||||
logger.error("wrong threads sync error", ex);
|
||||
} else {
|
||||
// TODO: on logs spamming (e.g. connection problems) move it inside condition block above
|
||||
logger.error("unknown error", ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public class GameSessionWatcher {
|
|||
if (!killed) {
|
||||
Optional<User> user = userManager.getUser(userId);
|
||||
if (user.isPresent()) {
|
||||
// TODO: can be called outside of the game thread, e.g. user start watching already running game
|
||||
// possible fix: getGameView must use last cached value in non game thread call (split by sessions)
|
||||
user.get().fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INIT, game.getId(), getGameView()));
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-root</artifactId>
|
||||
<version>1.4.55</version>
|
||||
<version>1.4.56</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-sets</artifactId>
|
||||
|
|
|
|||
57
Mage.Sets/src/mage/cards/a/AccursedDuneyard.java
Normal file
57
Mage.Sets/src/mage/cards/a/AccursedDuneyard.java
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.RegenerateTargetEffect;
|
||||
import mage.abilities.mana.ColorlessManaAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sobiech
|
||||
*/
|
||||
public final class AccursedDuneyard extends CardImpl {
|
||||
private static final FilterPermanent filter = new FilterPermanent("Shade, Skeleton, Specter, Spirit, Vampire, Wraith, or Zombie");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
SubType.SHADE.getPredicate(),
|
||||
SubType.SKELETON.getPredicate(),
|
||||
SubType.SPECTER.getPredicate(),
|
||||
SubType.SPIRIT.getPredicate(),
|
||||
SubType.VAMPIRE.getPredicate(),
|
||||
SubType.WRAITH.getPredicate(),
|
||||
SubType.ZOMBIE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public AccursedDuneyard(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
|
||||
|
||||
// {T}: Add {C}.
|
||||
this.addAbility(new ColorlessManaAbility());
|
||||
// {2}, {T}: Regenerate target Shade, Skeleton, Specter, Spirit, Vampire, Wraith, or Zombie.
|
||||
final Ability ability = new SimpleActivatedAbility(new RegenerateTargetEffect(), new ManaCostsImpl<>("{2}"));
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
ability.addCost(new TapSourceCost());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AccursedDuneyard(final AccursedDuneyard card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccursedDuneyard copy() {
|
||||
return new AccursedDuneyard(this);
|
||||
}
|
||||
}
|
||||
66
Mage.Sets/src/mage/cards/a/AdaptiveOmnitool.java
Normal file
66
Mage.Sets/src/mage/cards/a/AdaptiveOmnitool.java
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
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.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.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledArtifactPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @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}");
|
||||
|
||||
this.subtype.add(SubType.EQUIPMENT);
|
||||
|
||||
// Equipped creature gets +1/+1 for each artifact you control.
|
||||
this.addAbility(
|
||||
new SimpleStaticAbility(new BoostEquippedEffect(artifactYouControlCount, artifactYouControlCount)).addHint(hint)
|
||||
);
|
||||
|
||||
// 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.
|
||||
this.addAbility(new AttacksAttachedTriggeredAbility(
|
||||
new LookLibraryAndPickControllerEffect(
|
||||
6,
|
||||
1,
|
||||
StaticFilters.FILTER_CARD_ARTIFACT,
|
||||
PutCards.HAND,
|
||||
PutCards.BOTTOM_RANDOM
|
||||
)
|
||||
));
|
||||
|
||||
// Equip {3}
|
||||
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3), new TargetControlledCreaturePermanent(), false));
|
||||
}
|
||||
|
||||
private AdaptiveOmnitool(final AdaptiveOmnitool card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdaptiveOmnitool copy() {
|
||||
return new AdaptiveOmnitool(this);
|
||||
}
|
||||
}
|
||||
62
Mage.Sets/src/mage/cards/a/AdrenalineJockey.java
Normal file
62
Mage.Sets/src/mage/cards/a/AdrenalineJockey.java
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.ActivateAbilityTriggeredAbility;
|
||||
import mage.abilities.common.SpellCastAllTriggeredAbility;
|
||||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SetTargetPointer;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterSpell;
|
||||
import mage.filter.FilterStackObject;
|
||||
import mage.filter.predicate.other.ExhaustAbilityPredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AdrenalineJockey extends CardImpl {
|
||||
|
||||
private static final FilterSpell filter = new FilterSpell("a spell, if it's not their turn");
|
||||
private static final FilterStackObject filter2 = new FilterStackObject("an exhaust ability");
|
||||
|
||||
static {
|
||||
filter.add(TargetController.INACTIVE.getControllerPredicate());
|
||||
filter2.add(ExhaustAbilityPredicate.instance);
|
||||
}
|
||||
|
||||
public AdrenalineJockey(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
|
||||
|
||||
this.subtype.add(SubType.MINOTAUR);
|
||||
this.subtype.add(SubType.PILOT);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// Whenever a player casts a spell, if it's not their turn, this creature deals 4 damage to them.
|
||||
this.addAbility(new SpellCastAllTriggeredAbility(
|
||||
new DamageTargetEffect(4, true, "them"),
|
||||
filter, false, SetTargetPointer.PLAYER
|
||||
));
|
||||
|
||||
// Whenever you activate an exhaust ability, put a +1/+1 counter on this creature.
|
||||
this.addAbility(new ActivateAbilityTriggeredAbility(
|
||||
new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter2, SetTargetPointer.NONE
|
||||
));
|
||||
}
|
||||
|
||||
private AdrenalineJockey(final AdrenalineJockey card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdrenalineJockey copy() {
|
||||
return new AdrenalineJockey(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
|
||||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.continuous.BecomesCreatureIfVehicleEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
|
|
@ -13,46 +10,39 @@ import mage.abilities.keyword.EnchantAbility;
|
|||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.constants.AttachmentType;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Styxo
|
||||
*/
|
||||
public final class AerialModification extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("creature or Vehicle");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(CardType.CREATURE.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()));
|
||||
}
|
||||
|
||||
public AerialModification(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{W}");
|
||||
|
||||
this.subtype.add(SubType.AURA);
|
||||
|
||||
// Enchant creature or Vehicle
|
||||
TargetPermanent auraTarget = new TargetPermanent(filter);
|
||||
TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE_OR_VEHICLE);
|
||||
this.getSpellAbility().addTarget(auraTarget);
|
||||
this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit));
|
||||
Ability ability = new EnchantAbility(auraTarget);
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new EnchantAbility(auraTarget));
|
||||
|
||||
// As long as enchanted permanent is a Vehicle, it's a creature in addition to its other types.
|
||||
this.addAbility(new SimpleStaticAbility(new BecomesCreatureIfVehicleEffect()));
|
||||
|
||||
// Enchanted creature gets +2/+2 and has flying.
|
||||
Effect effect = new BoostEnchantedEffect(2, 2);
|
||||
effect.setText("Enchanted creature gets +2/+2");
|
||||
ability = new SimpleStaticAbility(effect);
|
||||
effect = new GainAbilityAttachedEffect(FlyingAbility.getInstance(), AttachmentType.AURA);
|
||||
effect.setText(" and has flying");
|
||||
ability.addEffect(effect);
|
||||
Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
|
||||
ability.addEffect(new GainAbilityAttachedEffect(
|
||||
FlyingAbility.getInstance(), AttachmentType.AURA
|
||||
).setText(" and has flying"));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
|
||||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.AttachEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
|
||||
import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect;
|
||||
|
|
@ -13,41 +10,37 @@ import mage.abilities.keyword.EnchantAbility;
|
|||
import mage.abilities.keyword.FlashAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public final class AetherMeltdown extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("creature or Vehicle");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(CardType.CREATURE.getPredicate(), SubType.VEHICLE.getPredicate()));
|
||||
}
|
||||
|
||||
public AetherMeltdown(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{U}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}");
|
||||
this.subtype.add(SubType.AURA);
|
||||
|
||||
// Flash
|
||||
this.addAbility(FlashAbility.getInstance());
|
||||
|
||||
// Enchant creature or vehicle.
|
||||
TargetPermanent auraTarget = new TargetPermanent(filter);
|
||||
TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE_OR_VEHICLE);
|
||||
this.getSpellAbility().addTarget(auraTarget);
|
||||
this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment));
|
||||
Ability ability = new EnchantAbility(auraTarget);
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new EnchantAbility(auraTarget));
|
||||
|
||||
// When Aether Meltdown enters the battlefield, you get {E}{E}.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new GetEnergyCountersControllerEffect(2)));
|
||||
|
||||
// Enchanted permanent gets -4/-0.
|
||||
Effect effect = new BoostEnchantedEffect(-4, 0, Duration.WhileOnBattlefield);
|
||||
this.addAbility(new SimpleStaticAbility(effect));
|
||||
this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(-4, 0).setText("enchanted permanent gets -4/-0")));
|
||||
}
|
||||
|
||||
private AetherMeltdown(final AetherMeltdown card) {
|
||||
|
|
|
|||
74
Mage.Sets/src/mage/cards/a/AetherfluxConduit.java
Normal file
74
Mage.Sets/src/mage/cards/a/AetherfluxConduit.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.costs.common.PayEnergyCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.continuous.CastFromHandWithoutPayingManaCostEffect;
|
||||
import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
|
||||
/**
|
||||
* @author sobiech
|
||||
*/
|
||||
public final class AetherfluxConduit extends CardImpl {
|
||||
|
||||
public AetherfluxConduit(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}");
|
||||
|
||||
// Whenever you cast a spell, you get an amount of {E} equal to the amount of mana spent to cast that spell.
|
||||
this.addAbility(new SpellCastControllerTriggeredAbility(
|
||||
new AetherfluxConduitEffect(),false
|
||||
));
|
||||
|
||||
// {T}, Pay fifty {E}: Draw seven cards. You may cast any number of spells from your hand without paying their mana costs.
|
||||
final Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(7), new TapSourceCost());
|
||||
ability.addCost(new PayEnergyCost(50).setText("Pay fifty {E}"));
|
||||
ability.addEffect(new CastFromHandWithoutPayingManaCostEffect());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AetherfluxConduit(final AetherfluxConduit card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AetherfluxConduit copy() {
|
||||
return new AetherfluxConduit(this);
|
||||
}
|
||||
}
|
||||
class AetherfluxConduitEffect extends OneShotEffect {
|
||||
|
||||
AetherfluxConduitEffect() {
|
||||
super(Outcome.Benefit);
|
||||
this.staticText = "you get an amount of {E} <i>(energy counters)</i> equal to the amount of mana spent to cast that spell";
|
||||
}
|
||||
private AetherfluxConduitEffect(AetherfluxConduitEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AetherfluxConduitEffect copy() {
|
||||
return new AetherfluxConduitEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Optional.ofNullable(this.getValue("spellCast"))
|
||||
.map(Spell.class::cast)
|
||||
.ifPresent(spell -> new GetEnergyCountersControllerEffect(spell.getManaValue()).apply(game, source));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
149
Mage.Sets/src/mage/cards/a/AethericAmplifier.java
Normal file
149
Mage.Sets/src/mage/cards/a/AethericAmplifier.java
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.mana.AnyColorManaAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
/**
|
||||
* @author sobiech
|
||||
*/
|
||||
public final class AethericAmplifier extends CardImpl {
|
||||
|
||||
public AethericAmplifier(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
|
||||
|
||||
// {T}: Add one mana of any color.
|
||||
this.addAbility(new AnyColorManaAbility());
|
||||
|
||||
// {4}, {T}: Choose one. Activate only as a sorcery.
|
||||
// * Double the number of each kind of counter on target permanent.
|
||||
final Ability ability = new ActivateAsSorceryActivatedAbility(new AethericAmplifierDoublePermanentEffect(), new GenericManaCost(4))
|
||||
.withShowActivateText(false);
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addTarget(new TargetPermanent());
|
||||
ability.getModes().setChooseText("choose one. Activate only as a sorcery.");
|
||||
|
||||
// * Double the number of each kind of counter you have.
|
||||
ability.addMode(new Mode(new AethericAmplifierDoubleControllerEffect()));
|
||||
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AethericAmplifier(final AethericAmplifier card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AethericAmplifier copy() {
|
||||
return new AethericAmplifier(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AethericAmplifierDoublePermanentEffect extends OneShotEffect {
|
||||
|
||||
AethericAmplifierDoublePermanentEffect() {
|
||||
super(Outcome.Benefit);
|
||||
this.staticText = "double the number of each kind of counter on target permanent";
|
||||
}
|
||||
|
||||
private AethericAmplifierDoublePermanentEffect(OneShotEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
final Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source));
|
||||
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Set<Counter> counters = permanent
|
||||
.getCounters(game)
|
||||
.values()
|
||||
.stream()
|
||||
.map(counter -> CounterType
|
||||
.findByName(counter.getName())
|
||||
.createInstance(counter.getCount()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (counters.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
counters.forEach(counter -> permanent.addCounters(counter, source, game));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AethericAmplifierDoublePermanentEffect copy() {
|
||||
return new AethericAmplifierDoublePermanentEffect(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AethericAmplifierDoubleControllerEffect extends OneShotEffect {
|
||||
|
||||
AethericAmplifierDoubleControllerEffect() {
|
||||
super(Outcome.Benefit);
|
||||
this.staticText = "double the number of each kind of counter you have";
|
||||
}
|
||||
|
||||
private AethericAmplifierDoubleControllerEffect(OneShotEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
final Player controller = game.getPlayer(source.getControllerId());
|
||||
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Set<Counter> counters = controller.getCountersAsCopy()
|
||||
.values()
|
||||
.stream()
|
||||
.map(counter -> CounterType
|
||||
.findByName(counter.getName())
|
||||
.createInstance(counter.getCount()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (counters.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
counters.forEach(counter -> controller.addCounters(
|
||||
counter,
|
||||
source.getControllerId(),
|
||||
source,
|
||||
game));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AethericAmplifierDoubleControllerEffect copy() {
|
||||
return new AethericAmplifierDoubleControllerEffect(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -14,8 +14,7 @@ import mage.cards.CardSetInfo;
|
|||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -25,15 +24,6 @@ import java.util.UUID;
|
|||
*/
|
||||
public final class AgonasaurRex extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("creature or Vehicle");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
CardType.CREATURE.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public AgonasaurRex(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}");
|
||||
|
||||
|
|
@ -51,7 +41,7 @@ public final class AgonasaurRex extends CardImpl {
|
|||
Ability ability = new CycleTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)));
|
||||
ability.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()).setText("it gains trample"));
|
||||
ability.addEffect(new GainAbilityTargetEffect(IndestructibleAbility.getInstance()).setText("and indestructible until end of turn"));
|
||||
ability.addTarget(new TargetPermanent(0, 1, filter));
|
||||
ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE_OR_VEHICLE));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import mage.abilities.common.SimpleStaticAbility;
|
|||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.continuous.BoostTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
|
@ -33,7 +32,7 @@ public final class AltarOfTheGoyf extends CardImpl {
|
|||
// Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of card types among cards in all graveyards.
|
||||
this.addAbility(new AttacksAloneControlledTriggeredAbility(
|
||||
new BoostTargetEffect(CardTypesInGraveyardCount.ALL, CardTypesInGraveyardCount.ALL, Duration.EndOfTurn),
|
||||
true, false).addHint(CardTypesInGraveyardHint.ALL));
|
||||
true, false).addHint(CardTypesInGraveyardCount.ALL.getHint()));
|
||||
|
||||
// Lhurgoyf creatures you control have trample.
|
||||
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import mage.MageInt;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.DealsDamageSourceTriggeredAbility;
|
||||
import mage.abilities.condition.common.DeliriumCondition;
|
||||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.ExileTargetEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
|
@ -35,7 +35,7 @@ public final class AngelOfDeliverance extends CardImpl {
|
|||
Ability ability = new DealsDamageSourceTriggeredAbility(new ExileTargetEffect())
|
||||
.withInterveningIf(DeliriumCondition.instance);
|
||||
ability.addTarget(new TargetOpponentsCreaturePermanent());
|
||||
ability.addHint(CardTypesInGraveyardHint.YOU);
|
||||
ability.addHint(CardTypesInGraveyardCount.YOU.getHint());
|
||||
this.addAbility(ability.setAbilityWord(AbilityWord.DELIRIUM));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public final class ArniSlaysTheTroll extends CardImpl {
|
|||
"You gain life equal to the greatest power among creatures you control"
|
||||
)
|
||||
);
|
||||
sagaAbility.addHint(GreatestPowerAmongControlledCreaturesValue.getHint());
|
||||
this.addAbility(sagaAbility);
|
||||
}
|
||||
|
||||
|
|
|
|||
53
Mage.Sets/src/mage/cards/a/AutarchMammoth.java
Normal file
53
Mage.Sets/src/mage/cards/a/AutarchMammoth.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.AttacksWhileSaddledTriggeredAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.keyword.SaddleAbility;
|
||||
import mage.abilities.meta.OrTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.permanent.token.ElephantToken;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author jackd149
|
||||
*/
|
||||
public final class AutarchMammoth extends CardImpl {
|
||||
|
||||
public AutarchMammoth(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}");
|
||||
|
||||
this.subtype.add(SubType.ELEPHANT);
|
||||
this.subtype.add(SubType.MOUNT);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// When this creature enters and whenever it attacks while saddled, create a 3/3 green Elephant creature token.
|
||||
this.addAbility(new OrTriggeredAbility(
|
||||
Zone.BATTLEFIELD,
|
||||
new CreateTokenEffect(new ElephantToken(), 1),
|
||||
false,
|
||||
"When this creature enters and whenever it attacks while saddled, ",
|
||||
new EntersBattlefieldTriggeredAbility(null),
|
||||
new AttacksWhileSaddledTriggeredAbility(null)
|
||||
));
|
||||
|
||||
// Saddle 5
|
||||
this.addAbility(new SaddleAbility(5));
|
||||
}
|
||||
|
||||
private AutarchMammoth(final AutarchMammoth card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutarchMammoth copy() {
|
||||
return new AutarchMammoth(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ class AuthorOfShadowsEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
Cards cards = new CardsImpl();
|
||||
game.getOpponents(source.getControllerId())
|
||||
game.getOpponents(source.getControllerId(), true)
|
||||
.stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
|
|
|
|||
|
|
@ -3,20 +3,19 @@ package mage.cards.a;
|
|||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.condition.common.DeliriumCondition;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.MillCardsControllerEffect;
|
||||
import mage.abilities.effects.common.TransformSourceEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.AbilityWord;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.constants.Zone;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
|
|
@ -34,7 +33,7 @@ public final class AutumnalGloom extends CardImpl {
|
|||
this.addAbility(new TransformAbility());
|
||||
Ability ability = new BeginningOfEndStepTriggeredAbility(TargetController.YOU, new TransformSourceEffect(), false, DeliriumCondition.instance);
|
||||
ability.setAbilityWord(AbilityWord.DELIRIUM);
|
||||
ability.addHint(CardTypesInGraveyardHint.YOU);
|
||||
ability.addHint(CardTypesInGraveyardCount.YOU.getHint());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,16 +7,15 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.common.DeliriumCondition;
|
||||
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.continuous.BoostSourceEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
|
|
@ -33,7 +32,7 @@ public final class BackwoodsSurvivalists extends CardImpl {
|
|||
ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new BoostSourceEffect(1, 1, Duration.WhileOnBattlefield), DeliriumCondition.instance, "<i>Delirium</i> — {this} gets +1/+1");
|
||||
Ability ability = new SimpleStaticAbility(effect);
|
||||
ability.addEffect(new ConditionalContinuousEffect(new GainAbilitySourceEffect(TrampleAbility.getInstance()), DeliriumCondition.instance, "and has trample as long as there are four or more card types among cards in your graveyard."));
|
||||
ability.addHint(CardTypesInGraveyardHint.YOU);
|
||||
ability.addHint(CardTypesInGraveyardCount.YOU.getHint());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import java.util.UUID;
|
|||
*/
|
||||
public final class BalladOfTheBlackFlag extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterHistoricCard("historic spells you cast this turn");
|
||||
private static final FilterCard filter = new FilterHistoricCard();
|
||||
|
||||
public BalladOfTheBlackFlag(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{U}");
|
||||
|
|
@ -31,7 +31,7 @@ public final class BalladOfTheBlackFlag extends CardImpl {
|
|||
// I, II, III -- Mill three cards. You may put a historic card from among them into your hand.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III,
|
||||
new MillThenPutInHandEffect(3, filter)
|
||||
new MillThenPutInHandEffect(3, filter).withTextOptions("them")
|
||||
);
|
||||
|
||||
// IV - Historic spells you cast this turn cost {2} less to cast.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import mage.abilities.common.ActivateIfConditionActivatedAbility;
|
|||
import mage.abilities.common.CantBeCounteredSourceAbility;
|
||||
import mage.abilities.condition.common.DeliriumCondition;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldWithCounterEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
|
|
@ -42,7 +42,7 @@ public final class BalustradeWurm extends CardImpl {
|
|||
Zone.GRAVEYARD,
|
||||
new ReturnSourceFromGraveyardToBattlefieldWithCounterEffect(CounterType.FINALITY.createInstance(), false),
|
||||
new ManaCostsImpl<>("{2}{G}{G}"), DeliriumCondition.instance, TimingRule.SORCERY
|
||||
).setAbilityWord(AbilityWord.DELIRIUM).addHint(CardTypesInGraveyardHint.YOU));
|
||||
).setAbilityWord(AbilityWord.DELIRIUM).addHint(CardTypesInGraveyardCount.YOU.getHint()));
|
||||
}
|
||||
|
||||
private BalustradeWurm(final BalustradeWurm card) {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ import java.util.UUID;
|
|||
*/
|
||||
public final class Barrowgoyf extends CardImpl {
|
||||
|
||||
private static final DynamicValue powerValue = CardTypesInGraveyardCount.ALL;
|
||||
|
||||
public Barrowgoyf(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
|
||||
this.subtype.add(SubType.LHURGOYF);
|
||||
|
|
@ -40,7 +38,9 @@ public final class Barrowgoyf extends CardImpl {
|
|||
this.addAbility(LifelinkAbility.getInstance());
|
||||
|
||||
// Barrowgoyf's power is equal to the number of card types among cards in all graveyards and its toughness is equal to that number plus 1.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessPlusOneSourceEffect(powerValue)));
|
||||
this.addAbility(new SimpleStaticAbility(Zone.ALL,
|
||||
new SetBasePowerToughnessPlusOneSourceEffect(CardTypesInGraveyardCount.ALL)
|
||||
).addHint(CardTypesInGraveyardCount.ALL.getHint()));
|
||||
|
||||
// Whenever Barrowgoyf deals combat damage to a player, you may mill that many cards. If you do, you may put a creature card from among them into your hand.
|
||||
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package mage.cards.b;
|
|||
|
||||
import mage.abilities.condition.common.DeliriumCondition;
|
||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
||||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.AbilityWord;
|
||||
|
|
@ -37,7 +37,7 @@ public final class BeastieBeatdown extends CardImpl {
|
|||
DeliriumCondition.instance, AbilityWord.DELIRIUM.formatWord() + "If there are four or more " +
|
||||
"card types among cards in your graveyard, put two +1/+1 counters on the creature you control."
|
||||
).concatBy("<br>"));
|
||||
this.getSpellAbility().addHint(CardTypesInGraveyardHint.YOU);
|
||||
this.getSpellAbility().addHint(CardTypesInGraveyardCount.YOU.getHint());
|
||||
|
||||
// The creature you control deals damage equal to its power to the creature an opponent controls.
|
||||
this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ public final class BecomeImmense extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{5}{G}");
|
||||
|
||||
// Delve (Each card you exile from your graveyard while casting this spell pays for {1}.)
|
||||
this.addAbility(new DelveAbility());
|
||||
this.addAbility(new DelveAbility(false));
|
||||
|
||||
// Target creature gets +6/+6 until end of turn
|
||||
this.getSpellAbility().addEffect(new BoostTargetEffect(6, 6, Duration.EndOfTurn));
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public final class BighornerRancher extends CardImpl {
|
|||
this.addAbility(new DynamicManaAbility(
|
||||
Mana.GreenMana(1), GreatestPowerAmongControlledCreaturesValue.instance, new TapSourceCost(),
|
||||
"Add an amount of {G} equal to the greatest power among creatures you control."
|
||||
));
|
||||
).addHint(GreatestPowerAmongControlledCreaturesValue.getHint()));
|
||||
|
||||
// Sacrifice Bighorner Rancher: You gain life equal to the greatest toughness among other creatures you control.
|
||||
this.addAbility(new SimpleActivatedAbility(
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import mage.abilities.common.CantBlockAbility;
|
|||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.common.DeliriumCondition;
|
||||
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.CascadeAbility;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
|
|
@ -45,7 +45,7 @@ public final class BloodbraidMarauder extends CardImpl {
|
|||
new GainAbilitySourceEffect(new CascadeAbility(), Duration.WhileOnStack, true),
|
||||
DeliriumCondition.instance,
|
||||
"<i>Delirium</i> — This spell has cascade as long as there are four or more card types among cards in your graveyard." + REMINDER_TEXT
|
||||
)).addHint(CardTypesInGraveyardHint.YOU));
|
||||
)).addHint(CardTypesInGraveyardCount.YOU.getHint()));
|
||||
}
|
||||
|
||||
private BloodbraidMarauder(final BloodbraidMarauder card) {
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ public final class BloodtitheCollector extends CardImpl {
|
|||
// Flying
|
||||
this.addAbility(FlyingAbility.getInstance());
|
||||
|
||||
// When Bloodtithe Collector enters the battlefield, if an opponent lost life this turn, each opponent discards a card.
|
||||
// When this creature enters, if an opponent lost life this turn, each opponent discards a card.
|
||||
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
|
||||
new EntersBattlefieldTriggeredAbility(new DiscardEachPlayerEffect(TargetController.OPPONENT)),
|
||||
OpponentsLostLifeCondition.instance, "When {this} enters, " +
|
||||
OpponentsLostLifeCondition.instance, "When this creature enters, " +
|
||||
"if an opponent lost life this turn, each opponent discards a card."
|
||||
).addHint(OpponentsLostLifeHint.instance));
|
||||
}
|
||||
|
|
|
|||
59
Mage.Sets/src/mage/cards/b/Boommobile.java
Normal file
59
Mage.Sets/src/mage/cards/b/Boommobile.java
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.dynamicvalue.common.GetXValue;
|
||||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.effects.mana.AddConditionalManaOfAnyColorEffect;
|
||||
import mage.abilities.effects.mana.ManaEffect;
|
||||
import mage.abilities.keyword.CrewAbility;
|
||||
import mage.abilities.keyword.ExhaustAbility;
|
||||
import mage.abilities.mana.builder.common.ActivatedAbilityManaBuilder;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.target.common.TargetAnyTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author jackd149
|
||||
*/
|
||||
public final class Boommobile extends CardImpl {
|
||||
|
||||
public Boommobile(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}{R}");
|
||||
|
||||
this.subtype.add(SubType.VEHICLE);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// When this Vehicle enters, add four mana of any one color. Spend this mana only to activate abilities.
|
||||
ManaEffect entersEffect = new AddConditionalManaOfAnyColorEffect(4, new ActivatedAbilityManaBuilder());
|
||||
entersEffect.setText("add four mana of any one color. Spend this mana only to activate abilities.");
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(entersEffect));
|
||||
|
||||
// Exhaust -- {X}{2}{R}: This vehicle deals X damage to any target. Put a +1/+1 counter on this Vehicle.
|
||||
Ability exhaustAbility = new ExhaustAbility(new DamageTargetEffect(GetXValue.instance), new ManaCostsImpl<>("{X}{2}{R}"));
|
||||
exhaustAbility.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()));
|
||||
exhaustAbility.addTarget(new TargetAnyTarget());
|
||||
this.addAbility(exhaustAbility);
|
||||
|
||||
// Crew 2
|
||||
this.addAbility(new CrewAbility(2));
|
||||
}
|
||||
|
||||
private Boommobile(final Boommobile card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boommobile copy() {
|
||||
return new Boommobile(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,7 @@ import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -16,21 +14,12 @@ import java.util.UUID;
|
|||
*/
|
||||
public final class BounceOff extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("creature or Vehicle");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
CardType.CREATURE.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public BounceOff(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}");
|
||||
|
||||
// Return target creature or Vehicle to its owner's hand.
|
||||
this.getSpellAbility().addEffect(new ReturnToHandTargetEffect());
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(filter));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE_OR_VEHICLE));
|
||||
}
|
||||
|
||||
private BounceOff(final BounceOff card) {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class BrainstealerDragonExileEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
Cards cards = new CardsImpl();
|
||||
game.getOpponents(source.getControllerId())
|
||||
game.getOpponents(source.getControllerId(), true)
|
||||
.stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public final class BrightfieldMustang extends CardImpl {
|
|||
|
||||
// Whenever this creature attacks while saddled, untap it and put a +1/+1 counter on it.
|
||||
Ability ability = new AttacksWhileSaddledTriggeredAbility(new UntapSourceEffect().setText("untap it"));
|
||||
ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()));
|
||||
ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).setText("and put a +1/+1 counter on it"));
|
||||
this.addAbility(ability);
|
||||
|
||||
// Saddle 1
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public final class BrightglassGearhulk extends CardImpl {
|
|||
// When this creature enters, you may search your library for up to two artifact, creature, and/or enchantment cards with mana value 1 or less, reveal them, put them into your hand, then shuffle.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInHandEffect(
|
||||
new TargetCardInLibrary(0, 2, filter), true
|
||||
)));
|
||||
), true));
|
||||
}
|
||||
|
||||
private BrightglassGearhulk(final BrightglassGearhulk card) {
|
||||
|
|
|
|||
59
Mage.Sets/src/mage/cards/b/BroodheartEngine.java
Normal file
59
Mage.Sets/src/mage/cards/b/BroodheartEngine.java
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
|
||||
import mage.abilities.effects.keyword.SurveilEffect;
|
||||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class BroodheartEngine extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterCard("creature or Vehicle card from your graveyard");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
CardType.CREATURE.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public BroodheartEngine(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{B}{G}");
|
||||
|
||||
// At the beginning of your upkeep, surveil 1.
|
||||
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SurveilEffect(1)));
|
||||
|
||||
// {2}{B}{G}, {T}, Sacrifice this artifact: Return target creature or Vehicle card from your graveyard to the battlefield. Activate only as a sorcery.
|
||||
Ability ability = new ActivateAsSorceryActivatedAbility(
|
||||
new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{2}{B}{G}")
|
||||
);
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addCost(new SacrificeSourceCost());
|
||||
ability.addTarget(new TargetCardInYourGraveyard(filter));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private BroodheartEngine(final BroodheartEngine card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroodheartEngine copy() {
|
||||
return new BroodheartEngine(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import mage.abilities.costs.mana.ManaCostsImpl;
|
|||
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.keyword.SurveilEffect;
|
||||
import mage.abilities.hint.common.CardTypesInGraveyardHint;
|
||||
import mage.abilities.keyword.ReachAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
|
@ -45,7 +44,7 @@ public final class Broodspinner extends CardImpl {
|
|||
new ManaCostsImpl<>("{4}{B}{G}"));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addCost(new SacrificeSourceCost());
|
||||
this.addAbility(ability.addHint(CardTypesInGraveyardHint.YOU));
|
||||
this.addAbility(ability.addHint(CardTypesInGraveyardCount.YOU.getHint()));
|
||||
}
|
||||
|
||||
private Broodspinner(final Broodspinner card) {
|
||||
|
|
|
|||
54
Mage.Sets/src/mage/cards/b/BurnerRocket.java
Normal file
54
Mage.Sets/src/mage/cards/b/BurnerRocket.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package mage.cards.b;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.common.continuous.BoostTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.CrewAbility;
|
||||
import mage.abilities.keyword.FlashAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class BurnerRocket extends CardImpl {
|
||||
|
||||
public BurnerRocket(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}");
|
||||
|
||||
this.subtype.add(SubType.VEHICLE);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// Flash
|
||||
this.addAbility(FlashAbility.getInstance());
|
||||
|
||||
// When this Vehicle enters, target creature you control gets +2/+0 and gains trample until end of turn.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(2, 0)
|
||||
.setText("target creature you control gets +2/+0"));
|
||||
ability.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance())
|
||||
.setText("and gains trample until end of turn"));
|
||||
ability.addTarget(new TargetControlledCreaturePermanent());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Crew 1
|
||||
this.addAbility(new CrewAbility(1));
|
||||
}
|
||||
|
||||
private BurnerRocket(final BurnerRocket card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BurnerRocket copy() {
|
||||
return new BurnerRocket(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ public final class BurnoutBashtronaut extends CardImpl {
|
|||
|
||||
// {2}: This creature gets +1/+0 until end of turn.
|
||||
this.addAbility(new SimpleActivatedAbility(
|
||||
new BoostSourceEffect(2, 0, Duration.EndOfTurn), new GenericManaCost(2)
|
||||
new BoostSourceEffect(1, 0, Duration.EndOfTurn), new GenericManaCost(2)
|
||||
));
|
||||
|
||||
// Max speed -- This creature has double strike.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ import mage.constants.SubType;
|
|||
import mage.constants.SuperType;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
|
||||
|
|
@ -25,17 +24,12 @@ import java.util.UUID;
|
|||
public final class CaradoraHeartOfAlacria extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterCard("Mount or Vehicle card");
|
||||
private static final FilterPermanent filter2 = new FilterControlledPermanent("creature or Vehicle you control");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
SubType.MOUNT.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()
|
||||
));
|
||||
filter2.add(Predicates.or(
|
||||
CardType.CREATURE.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public CaradoraHeartOfAlacria(UUID ownerId, CardSetInfo setInfo) {
|
||||
|
|
@ -49,11 +43,11 @@ public final class CaradoraHeartOfAlacria extends CardImpl {
|
|||
|
||||
// When Caradora enters, you may search your library for a Mount or Vehicle card, reveal it, put it into your hand, then shuffle.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(
|
||||
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true)
|
||||
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), true
|
||||
));
|
||||
|
||||
// If one or more +1/+1 counters would be put on a creature or Vehicle you control, that many plus one +1/+1 counters are put on it instead.
|
||||
this.addAbility(new SimpleStaticAbility(new ModifyCountersAddedEffect(filter2, CounterType.P1P1)));
|
||||
this.addAbility(new SimpleStaticAbility(new ModifyCountersAddedEffect(StaticFilters.FILTER_CONTROLLED_PERMANENT_CREATURE_OR_VEHICLE, CounterType.P1P1)));
|
||||
}
|
||||
|
||||
private CaradoraHeartOfAlacria(final CaradoraHeartOfAlacria card) {
|
||||
|
|
|
|||
93
Mage.Sets/src/mage/cards/c/CarrionCruiser.java
Normal file
93
Mage.Sets/src/mage/cards/c/CarrionCruiser.java
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.MillCardsControllerEffect;
|
||||
import mage.abilities.keyword.CrewAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class CarrionCruiser extends CardImpl {
|
||||
|
||||
public CarrionCruiser(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}");
|
||||
|
||||
this.subtype.add(SubType.VEHICLE);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(2);
|
||||
|
||||
// When this Vehicle enters, mill two cards. Then return a creature or Vehicle card from your graveyard to your hand.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(2));
|
||||
ability.addEffect(new CarrionCruiserEffect());
|
||||
this.addAbility(ability);
|
||||
|
||||
// Crew 1
|
||||
this.addAbility(new CrewAbility(1));
|
||||
}
|
||||
|
||||
private CarrionCruiser(final CarrionCruiser card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CarrionCruiser copy() {
|
||||
return new CarrionCruiser(this);
|
||||
}
|
||||
}
|
||||
|
||||
class CarrionCruiserEffect extends OneShotEffect {
|
||||
|
||||
private static final FilterCard filter = new FilterCard("creature or Vehicle card");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
CardType.CREATURE.getPredicate(),
|
||||
SubType.VEHICLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
CarrionCruiserEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "Then return a creature or Vehicle card from your graveyard to your hand";
|
||||
}
|
||||
|
||||
private CarrionCruiserEffect(final CarrionCruiserEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CarrionCruiserEffect copy() {
|
||||
return new CarrionCruiserEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null || player.getGraveyard().count(filter, game) < 1) {
|
||||
return false;
|
||||
}
|
||||
TargetCard target = new TargetCardInYourGraveyard(filter);
|
||||
target.withNotTarget(true);
|
||||
player.choose(outcome, player.getGraveyard(), target, source, game);
|
||||
Card card = game.getCard(target.getFirstTarget());
|
||||
return card != null && player.moveCards(card, Zone.HAND, source, game);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ public final class CemeteryTampering extends CardImpl {
|
|||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}");
|
||||
|
||||
// Hideaway 5
|
||||
this.addAbility(new HideawayAbility(5));
|
||||
this.addAbility(new HideawayAbility(this, 5));
|
||||
|
||||
// At the beginning of your upkeep, you may mill three cards. Then if there are twenty or more cards in your graveyard, you may play the exiled card without paying its mana cost.
|
||||
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
|
||||
|
|
|
|||
78
Mage.Sets/src/mage/cards/c/ChandraSparkHunter.java
Normal file
78
Mage.Sets/src/mage/cards/c/ChandraSparkHunter.java
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.LoyaltyAbility;
|
||||
import mage.abilities.costs.OrCost;
|
||||
import mage.abilities.costs.common.DiscardCardCost;
|
||||
import mage.abilities.costs.common.SacrificeTargetCost;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.DoIfCostPaid;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.GetEmblemEffect;
|
||||
import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
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.constants.SuperType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.game.command.emblems.ChandraSparkHunterEmblem;
|
||||
import mage.game.permanent.token.VehicleToken;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class ChandraSparkHunter extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.VEHICLE);
|
||||
|
||||
public ChandraSparkHunter(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{R}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.CHANDRA);
|
||||
this.setStartingLoyalty(4);
|
||||
|
||||
// At the beginning of combat on your turn, choose up to one target Vehicle you control. Until end of turn, it becomes an artifact creature and gains haste.
|
||||
Ability ability = new BeginningOfCombatTriggeredAbility(new AddCardTypeTargetEffect(
|
||||
Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE
|
||||
).setText("choose up to one target Vehicle you control. Until end of turn, it becomes an artifact creature"));
|
||||
ability.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()).setText("and gains haste"));
|
||||
ability.addTarget(new TargetPermanent(0, 1, filter));
|
||||
this.addAbility(ability);
|
||||
|
||||
// +2: You may sacrifice an artifact or discard a card. If you do, draw a card.
|
||||
this.addAbility(new LoyaltyAbility(new DoIfCostPaid(
|
||||
new DrawCardSourceControllerEffect(1),
|
||||
new OrCost(
|
||||
"sacrifice an artifact or discard a card",
|
||||
new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ARTIFACT),
|
||||
new DiscardCardCost()
|
||||
)
|
||||
), 2));
|
||||
|
||||
// +0: Create a 3/2 colorless Vehicle artifact token with crew 1.
|
||||
this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new VehicleToken()), 0));
|
||||
|
||||
// -7: You get an emblem with "Whenever an artifact you control enters, this emblem deals 3 damage to any target."
|
||||
this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new ChandraSparkHunterEmblem()), -7));
|
||||
}
|
||||
|
||||
private ChandraSparkHunter(final ChandraSparkHunter card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChandraSparkHunter copy() {
|
||||
return new ChandraSparkHunter(this);
|
||||
}
|
||||
}
|
||||
64
Mage.Sets/src/mage/cards/c/ChitinGravestalker.java
Normal file
64
Mage.Sets/src/mage/cards/c/ChitinGravestalker.java
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount;
|
||||
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.abilities.keyword.CyclingAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.predicate.Predicates;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class ChitinGravestalker extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterCard("artifact and/or creature card");
|
||||
|
||||
static {
|
||||
filter.add(Predicates.or(
|
||||
CardType.ARTIFACT.getPredicate(),
|
||||
CardType.CREATURE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter);
|
||||
private static final Hint hint = new ValueHint("Instant and sorcery card in your graveyard", xValue);
|
||||
|
||||
public ChitinGravestalker(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}");
|
||||
|
||||
this.subtype.add(SubType.INSECT);
|
||||
this.subtype.add(SubType.WARRIOR);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// This spell costs {1} less to cast for each artifact and/or creature card in your graveyard.
|
||||
this.addAbility(new SimpleStaticAbility(
|
||||
Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)
|
||||
).setRuleAtTheTop(true).addHint(hint));
|
||||
|
||||
// Cycling {2}
|
||||
this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}")));
|
||||
}
|
||||
|
||||
private ChitinGravestalker(final ChitinGravestalker card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChitinGravestalker copy() {
|
||||
return new ChitinGravestalker(this);
|
||||
}
|
||||
}
|
||||
133
Mage.Sets/src/mage/cards/c/CloudspireCoordinator.java
Normal file
133
Mage.Sets/src/mage/cards/c/CloudspireCoordinator.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package mage.cards.c;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.keyword.ScryEffect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.EntersTheBattlefieldEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.PilotSaddleCrewToken;
|
||||
import mage.util.CardUtil;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class CloudspireCoordinator extends CardImpl {
|
||||
|
||||
public CloudspireCoordinator(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}");
|
||||
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.PILOT);
|
||||
this.power = new MageInt(3);
|
||||
this.toughness = new MageInt(1);
|
||||
|
||||
// When this creature enters, scry 2.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(2)));
|
||||
|
||||
// {T}: Create X 1/1 colorless Pilot creature tokens, where X is the number of Mounts and/or Vehicles that entered the battlefield under your control this turn. The tokens have "This token saddles Mounts and crews Vehicles as though its power were 2 greater."
|
||||
this.addAbility(new SimpleActivatedAbility(new CreateTokenEffect(
|
||||
new PilotSaddleCrewToken(), CloudspireCoordinatorValue.instance
|
||||
).setText("create X 1/1 colorless Pilot creature tokens, where X is " +
|
||||
"the number of Mounts and/or Vehicles that entered the battlefield " +
|
||||
"under your control this turn. The tokens have \"This token saddles Mounts " +
|
||||
"and crews Vehicles as though its power were 2 greater.\""),
|
||||
new TapSourceCost()
|
||||
).addHint(CloudspireCoordinatorValue.getHint()), new CloudspireCoordinatorWatcher());
|
||||
}
|
||||
|
||||
private CloudspireCoordinator(final CloudspireCoordinator card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudspireCoordinator copy() {
|
||||
return new CloudspireCoordinator(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum CloudspireCoordinatorValue implements DynamicValue {
|
||||
instance;
|
||||
private static final Hint hint = new ValueHint(
|
||||
"Mounts and/or Vehicles that entered under your control this turn", instance
|
||||
);
|
||||
|
||||
public static Hint getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
return CloudspireCoordinatorWatcher.getValue(sourceAbility, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudspireCoordinatorValue copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "X";
|
||||
}
|
||||
}
|
||||
|
||||
class CloudspireCoordinatorWatcher extends Watcher {
|
||||
|
||||
private final Map<UUID, Integer> map = new HashMap<>();
|
||||
|
||||
CloudspireCoordinatorWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
|
||||
return;
|
||||
}
|
||||
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
|
||||
if (permanent != null
|
||||
&& (permanent.hasSubtype(SubType.MOUNT, game)
|
||||
|| permanent.hasSubtype(SubType.VEHICLE, game))) {
|
||||
map.compute(permanent.getControllerId(), CardUtil::setOrIncrementValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
map.clear();
|
||||
}
|
||||
|
||||
static int getValue(Ability source, Game game) {
|
||||
return game
|
||||
.getState()
|
||||
.getWatcher(CloudspireCoordinatorWatcher.class)
|
||||
.map
|
||||
.getOrDefault(source.getControllerId(), 0);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue