diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 1c6d6b01218..376f7c40bf6 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -1578,6 +1578,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { // FIRST GUI CALL (create main window with all prepared frames, dialogs, etc) try { instance = new MageFrame(); + EDTExceptionHandler.registerMainApp(instance); } catch (Throwable e) { LOGGER.fatal("Critical error on start up, app will be closed: " + e.getMessage(), e); System.exit(1); diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java index 614c785fd3c..90a7be54dc3 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java @@ -20,11 +20,13 @@ import mage.view.TableView; import org.apache.log4j.Logger; import javax.swing.*; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; /** * App GUI: create new GAME @@ -35,13 +37,22 @@ public class NewTableDialog extends MageDialog { private static final Logger logger = Logger.getLogger(NewTableDialog.class); + public static final int DEFAULT_COMPUTER_PLAYER_SKILL_LEVEL = 2; + public static final String PLAYER_DATA_DELIMETER_OLD = ","; // need for compatibility with old version + public static final String PLAYER_DATA_DELIMETER_NEW = "@@@"; + private final CustomOptionsDialog customOptions; private TableView table; private UUID playerId; private UUID roomId; private String lastSessionId; private final List players = new ArrayList<>(); + + // temp settings on loading players list private final List prefPlayerTypes = new ArrayList<>(); + private final List prefPlayerSkills = new ArrayList<>(); + private final List prefPlayerDecks = new ArrayList<>(); + private static final String LIMITED = "Limited"; @@ -756,27 +767,56 @@ public class NewTableDialog extends MageDialog { } private void createPlayers(int numPlayers) { - // add missing player panels + // add miss panels if (numPlayers > players.size()) { while (players.size() != numPlayers) { TablePlayerPanel playerPanel = new TablePlayerPanel(); - PlayerType playerType = PlayerType.HUMAN; - if (prefPlayerTypes.size() >= players.size() && !players.isEmpty()) { - playerType = prefPlayerTypes.get(players.size() - 1); - } - playerPanel.init(players.size() + 2, playerType); players.add(playerPanel); playerPanel.addPlayerTypeEventListener( (Listener) event -> drawPlayers() ); } + } - } // remove player panels no longer needed - else if (numPlayers < players.size()) { + // remove un-used panels + if (numPlayers < players.size()) { while (players.size() != numPlayers) { players.remove(players.size() - 1); } } + + // load player data + String prevGoodPlayerDeck = ""; + for (int i = 0; i < players.size(); i++) { + TablePlayerPanel playerPanel = players.get(i); + + // find player type + PlayerType playerType = PlayerType.HUMAN; + if (i < prefPlayerTypes.size()) { + playerType = prefPlayerTypes.get(i); + } + + // find skill level + int playerSkill = DEFAULT_COMPUTER_PLAYER_SKILL_LEVEL; + if (i < prefPlayerSkills.size()) { + playerSkill = prefPlayerSkills.get(i); + } + + // find deck file + String playerDeck = ""; + if (i < prefPlayerDecks.size()) { + playerDeck = prefPlayerDecks.get(i); + // use prev deck if loaded not found + if (playerDeck.isEmpty() || !(new File(playerDeck).exists())) { + playerDeck = prevGoodPlayerDeck; + } else { + prevGoodPlayerDeck = playerDeck; + } + } + + playerPanel.init(i + 2, playerType, playerSkill, playerDeck); + } + drawPlayers(); } @@ -808,11 +848,6 @@ public class NewTableDialog extends MageDialog { cbRange.setModel(new DefaultComboBoxModel(RangeOfInfluence.values())); cbAttackOption.setModel(new DefaultComboBoxModel(MultiplayerAttackOption.values())); cbSkillLevel.setModel(new DefaultComboBoxModel(SkillLevel.values())); - // Update the existing player panels (neccessary if server was changes = new session) - int i = 2; - for (TablePlayerPanel tablePlayerPanel : players) { - tablePlayerPanel.init(i++, tablePlayerPanel.getPlayerType()); - } this.setModal(true); setGameOptions(); this.setLocation(150, 100); @@ -863,11 +898,24 @@ public class NewTableDialog extends MageDialog { txtName.setText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_NAME + versionStr, "Game")); txtPassword.setText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_PASSWORD + versionStr, "")); - String playerTypes = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_TYPES + versionStr, "Human"); + // load player data + // player type + String playerData = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_TYPES + versionStr, "Human"); prefPlayerTypes.clear(); - for (String pType : playerTypes.split(",")) { - prefPlayerTypes.add(PlayerType.getByDescription(pType)); + for (String playerTypeStr : playerData.split(PLAYER_DATA_DELIMETER_OLD)) { + prefPlayerTypes.add(PlayerType.getByDescription(playerTypeStr)); } + // player skill + playerData = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_SKILLS + versionStr, String.valueOf(DEFAULT_COMPUTER_PLAYER_SKILL_LEVEL)); + prefPlayerSkills.clear(); + for (String playerSkillStr : playerData.split(PLAYER_DATA_DELIMETER_NEW)) { + prefPlayerSkills.add(Integer.parseInt(playerSkillStr)); + } + // player deck + playerData = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_DECKS + versionStr, "Human"); + prefPlayerDecks.clear(); + prefPlayerDecks.addAll(Arrays.asList(playerData.split(PLAYER_DATA_DELIMETER_NEW))); + this.spnNumPlayers.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_PLAYERS + versionStr, "2"))); String gameTypeName = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_GAME_TYPE + versionStr, "Two Player Duel"); @@ -968,15 +1016,22 @@ public class NewTableDialog extends MageDialog { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_MINIMUM_RATING + versionStr, Integer.toString(options.getMinimumRating())); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_EDH_POWER_LEVEL + versionStr, Integer.toString(options.getEdhPowerLevel())); - StringBuilder playerTypesString = new StringBuilder(); - for (Object player : players) { - if (playerTypesString.length() > 0) { - playerTypesString.append(','); - } - TablePlayerPanel tpp = (TablePlayerPanel) player; - playerTypesString.append(tpp.getPlayerType()); - } - PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_TYPES + versionStr, playerTypesString.toString()); + // save player data + // player type + String playerData = players.stream() + .map(panel -> panel.getPlayerType().toString()) + .collect(Collectors.joining(PLAYER_DATA_DELIMETER_OLD)); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_TYPES + versionStr, playerData); + // player skill + playerData = players.stream() + .map(panel -> String.valueOf(panel.getPlayerSkill())) + .collect(Collectors.joining(PLAYER_DATA_DELIMETER_NEW)); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_SKILLS + versionStr, playerData); + // player deck + playerData = players.stream() + .map(panel -> String.valueOf(panel.getPlayerDeck())) + .collect(Collectors.joining(PLAYER_DATA_DELIMETER_NEW)); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_PLAYER_DECKS + versionStr, playerData); customOptions.onSaveSettings(version, options); } diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index d45700ec42b..79850e3f76b 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; +import java.util.List; import java.util.stream.Collectors; import javax.swing.*; import mage.cards.decks.Deck; @@ -39,6 +40,10 @@ public class NewTournamentDialog extends MageDialog { private static final Logger logger = Logger.getLogger(NewTournamentDialog.class); + // temp settings on loading players list + private final List prefPlayerTypes = new ArrayList<>(); + private final List prefPlayerSkills = new ArrayList<>(); + private TableView table; // private UUID playerId; private UUID roomId; @@ -70,6 +75,16 @@ public class NewTournamentDialog extends MageDialog { this.spnMinimumRating.setModel(new SpinnerNumberModel(0, 0, 3000, 10)); } + private int getCurrentNumPlayers() { + int res = (Integer) spnNumPlayers.getValue(); + return res > 0 ? res : 2; + } + + private int getCurrentNumSeats() { + int res = (Integer) spnNumSeats.getValue(); + return res > 0 ? res : 2; + } + public void showDialog(UUID roomId) { this.roomId = roomId; if (!lastSessionId.equals(SessionHandler.getSessionId())) { @@ -89,11 +104,6 @@ public class NewTournamentDialog extends MageDialog { .filter(o -> !o.equals(TimingOption.NONE)) .toArray()) ); - // update player types - int i = 2; - for (TournamentPlayerPanel tournamentPlayerPanel : players) { - tournamentPlayerPanel.init(i++); - } cbAllowSpectators.setSelected(true); this.setModal(true); this.setLocation(150, 100); @@ -648,7 +658,7 @@ public class NewTournamentDialog extends MageDialog { }// //GEN-END:initComponents private void cbTournamentTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTournamentTypeActionPerformed - prepareTourneyView((Integer) this.spnNumPlayers.getValue()); + prepareTourneyView(false, prepareVersionStr(-1, false), getCurrentNumPlayers(), getCurrentNumSeats()); jumpstartPacksFilename = ""; if (cbTournamentType.getSelectedItem().toString().matches(".*Jumpstart.*Custom.*")) { @@ -703,6 +713,7 @@ public class NewTournamentDialog extends MageDialog { // join AI for (TournamentPlayerPanel player : players) { if (player.getPlayerType().getSelectedItem() != PlayerType.HUMAN) { + // TODO: add support of multiple deck files per each computer player (can be implemented after combine table/tourney dialog in one) if (!player.joinTournamentTable(roomId, table.getTableId(), DeckImporter.importDeckFromFile(this.player1Panel.getDeckFile(), true))) { // error message must be send by sever SessionHandler.removeTable(roomId, table.getTableId()); @@ -737,7 +748,6 @@ public class NewTournamentDialog extends MageDialog { }//GEN-LAST:event_btnCancelActionPerformed private void updateNumSeats() { - // int numPlayers = (Integer) this.spnNumPlayers.getValue(); int numSeats = (Integer) this.spnNumSeats.getValue(); if (numSeats > 2) { @@ -756,7 +766,7 @@ public class NewTournamentDialog extends MageDialog { } private void spnNumPlayersStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumPlayersStateChanged - int numPlayers = (Integer) this.spnNumPlayers.getValue(); + int numPlayers = getCurrentNumPlayers(); createPlayers(numPlayers - 1); int numSeats = (Integer) this.spnNumSeats.getValue(); if (numSeats > 2 && numPlayers != numSeats) { @@ -771,8 +781,7 @@ public class NewTournamentDialog extends MageDialog { }//GEN-LAST:event_spnNumSeatsStateChanged private void spnNumWinsnumPlayersChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spnNumWinsnumPlayersChanged - int numSeats = (Integer) this.spnNumSeats.getValue(); - int numWins = (Integer) this.spnNumSeats.getValue(); + int numSeats = getCurrentNumSeats(); if (numSeats > 2) { spnNumWins.setValue(1); } @@ -878,32 +887,47 @@ public class NewTournamentDialog extends MageDialog { }//GEN-LAST:event_btnCustomOptionsActionPerformed private void setGameOptions() { - GameTypeView gameType = (GameTypeView) cbGameType.getSelectedItem(); -// int oldValue = (Integer) this.spnNumPlayers.getValue(); -// this.spnNumPlayers.setModel(new SpinnerNumberModel(gameType.getMinPlayers(), gameType.getMinPlayers(), gameType.getMaxPlayers(), 1)); -// this.spnNumPlayers.setEnabled(gameType.getMinPlayers() != gameType.getMaxPlayers()); -// if (oldValue >= gameType.getMinPlayers() && oldValue <= gameType.getMaxPlayers()){ -// this.spnNumPlayers.setBoostedValue(oldValue); -// } - // this.cbAttackOption.setEnabled(gameType.isUseAttackOption()); - // this.cbRange.setEnabled(gameType.isUseRange()); - createPlayers((Integer) spnNumPlayers.getValue() - 1); + createPlayers(getCurrentNumPlayers() - 1); } - private void prepareTourneyView(int numPlayers) { + private void prepareTourneyView(boolean loadPlayerSettings, String versionStr, int numPlayers, int numSeats) { TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); activatePanelElements(tournamentType); - // players if (numPlayers < tournamentType.getMinPlayers() || numPlayers > tournamentType.getMaxPlayers()) { numPlayers = tournamentType.getMinPlayers(); - createPlayers(numPlayers - 1); // ? } this.spnNumPlayers.setModel(new SpinnerNumberModel(numPlayers, tournamentType.getMinPlayers(), tournamentType.getMaxPlayers(), 1)); this.spnNumPlayers.setEnabled(tournamentType.getMinPlayers() != tournamentType.getMaxPlayers()); - createPlayers((Integer) spnNumPlayers.getValue() - 1); this.spnNumSeats.setModel(new SpinnerNumberModel(2, 2, tournamentType.getMaxPlayers(), 1)); + // manual call change events to apply players/seats restrictions and create miss panels + // TODO: refactor to use isLoading and restrictions from a code instead restrictions from a component + this.spnNumPlayers.setValue(numPlayers); + spnNumPlayersStateChanged(null); + this.spnNumSeats.setValue(numSeats); + spnNumSeatsStateChanged(null); + + if (loadPlayerSettings) { + // load player data + // player type + String playerData = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYER_TYPES + versionStr, "Human"); + prefPlayerTypes.clear(); + for (String playerTypeStr : playerData.split(NewTableDialog.PLAYER_DATA_DELIMETER_NEW)) { + prefPlayerTypes.add(PlayerType.getByDescription(playerTypeStr)); + } + // player skill + playerData = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYER_SKILLS + versionStr, String.valueOf(NewTableDialog.DEFAULT_COMPUTER_PLAYER_SKILL_LEVEL)); + prefPlayerSkills.clear(); + for (String playerSkillStr : playerData.split(NewTableDialog.PLAYER_DATA_DELIMETER_NEW)) { + prefPlayerSkills.add(Integer.parseInt(playerSkillStr)); + } + // player deck + // no deck files support here yet (single deck for all computers) + } + + createPlayers(numPlayers - 1); + // packs preparePacksView(tournamentType); } @@ -921,10 +945,10 @@ public class NewTournamentDialog extends MageDialog { } } - private void setNumberOfSwissRoundsMin(int numPlayers) { + private void setNumberOfSwissRoundsMin(int additionalNumPlayers) { // set 3 rounds default if more than 4 players // don't set 4 rounds by default, as 3 rounds generally preferred - int minRounds = (numPlayers + 1 > 4) ? 3 : 2; + int minRounds = (additionalNumPlayers + 1 > 4) ? 3 : 2; int newValue = Math.max((Integer) spnNumRounds.getValue(), minRounds); this.spnNumRounds.setModel(new SpinnerNumberModel(newValue, 2, 10, 1)); this.pack(); @@ -1136,24 +1160,46 @@ public class NewTournamentDialog extends MageDialog { } } - private void createPlayers(int numPlayers) { - // add/remove player panels - if (numPlayers > players.size()) { - while (players.size() != numPlayers) { + private void createPlayers(int additionalNumPlayers) { + // add miss panels + if (additionalNumPlayers > players.size()) { + while (players.size() != additionalNumPlayers) { TournamentPlayerPanel playerPanel = new TournamentPlayerPanel(); - playerPanel.init(players.size() + 2); - players.add(playerPanel); } - } else if (numPlayers < players.size()) { - while (players.size() != numPlayers) { + } + + // remove un-used panels + if (additionalNumPlayers < players.size()) { + while (players.size() != additionalNumPlayers) { players.remove(players.size() - 1); } } + + // load player data + for (int i = 0; i < players.size(); i++) { + TournamentPlayerPanel playerPanel = players.get(i); + + // find player type + PlayerType playerType = PlayerType.HUMAN; + if (i < prefPlayerTypes.size()) { + playerType = prefPlayerTypes.get(i); + } + + // find skill level + int playerSkill = NewTableDialog.DEFAULT_COMPUTER_PLAYER_SKILL_LEVEL; + if (i < prefPlayerSkills.size()) { + playerSkill = prefPlayerSkills.get(i); + } + + // find deck file + // no deck files support here yet (single deck for all computers) + playerPanel.init(i + 2, playerType, playerSkill); + } + drawPlayers(); - setNumberOfSwissRoundsMin(numPlayers); - + setNumberOfSwissRoundsMin(additionalNumPlayers); } private void drawPlayers() { @@ -1347,7 +1393,6 @@ public class NewTournamentDialog extends MageDialog { private void onLoadSettings(int version) { String versionStr = prepareVersionStr(version, false); - int numPlayers; txtName.setText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NAME + versionStr, "Tournament")); txtPassword.setText(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PASSWORD + versionStr, "")); int timeLimit = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_TIME_LIMIT + versionStr, "1500")); @@ -1365,7 +1410,7 @@ public class NewTournamentDialog extends MageDialog { break; } } - String skillLevelDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_SKILL_LEVEL + versionStr, "Casual"); + String skillLevelDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_SKILL_LEVEL + versionStr, "Casual"); for (SkillLevel skillLevel : SkillLevel.values()) { if (skillLevel.toString().equals(skillLevelDefault)) { this.cbSkillLevel.setSelectedItem(skillLevel); @@ -1391,11 +1436,11 @@ public class NewTournamentDialog extends MageDialog { TournamentTypeView tournamentType = (TournamentTypeView) cbTournamentType.getSelectedItem(); activatePanelElements(tournamentType); + int defaultNumberPlayers = 2; + int defaultNumberSeats = 2; if (tournamentType.isLimited()) { if (tournamentType.isDraft()) { - numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_DRAFT + versionStr, "4")); - prepareTourneyView(numPlayers); - + defaultNumberPlayers = 4; if (tournamentType.isRandom() || tournamentType.isRichMan() || tournamentType.isReshuffled()) { loadRandomPacks(version); } else { @@ -1410,8 +1455,6 @@ public class NewTournamentDialog extends MageDialog { } } } else { - numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_SEALED + versionStr, "2")); - prepareTourneyView(numPlayers); loadBoosterPacks(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_SEALED + versionStr, "")); } } @@ -1419,6 +1462,15 @@ public class NewTournamentDialog extends MageDialog { this.chkRollbackTurnsAllowed.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS + versionStr, "Yes").equals("Yes")); this.chkRated.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_RATED + versionStr, "No").equals("Yes")); + String deckFile = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_DECK_FILE + versionStr, ""); + if (deckFile != null && !deckFile.isEmpty() && new File(deckFile).exists()) { + this.player1Panel.setDeckFile(deckFile); + } + + int numPlayers = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_PLAYERS + versionStr, String.valueOf(defaultNumberPlayers))); + int numSeats = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_SEATS + versionStr, String.valueOf(defaultNumberSeats))); + prepareTourneyView(true, versionStr, numPlayers, numSeats); + this.customOptions.onLoadSettings(version); } @@ -1443,20 +1495,14 @@ public class NewTournamentDialog extends MageDialog { if (tOptions.getTournamentType().startsWith("Sealed")) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_SEALED + versionStr, tOptions.getLimitedOptions().getSetCodes().toString()); - PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_SEALED + versionStr, Integer.toString(tOptions.getPlayerTypes().size())); } if (tOptions.getTournamentType().startsWith("Booster")) { DraftOptions draftOptions = (DraftOptions) tOptions.getLimitedOptions(); if (draftOptions != null) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_DRAFT + versionStr, draftOptions.getSetCodes().toString()); - PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYERS_DRAFT + versionStr, Integer.toString(tOptions.getPlayerTypes().size())); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_DRAFT_TIMING + versionStr, draftOptions.getTiming().name()); } - String deckFile = this.player1Panel.getDeckFile(); - if (deckFile != null && !deckFile.isEmpty()) { - PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE + versionStr, deckFile); - } if (tOptions.getLimitedOptions().getIsRandom() || tOptions.getLimitedOptions().getIsRichMan() || tOptions.getLimitedOptions().getIsReshuffled()) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT + versionStr, String.join(";", this.randomPackSelector.getSelectedPacks())); } @@ -1464,6 +1510,30 @@ public class NewTournamentDialog extends MageDialog { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS + versionStr, (tOptions.isWatchingAllowed() ? "Yes" : "No")); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS + versionStr, (tOptions.getMatchOptions().isRollbackTurnsAllowed() ? "Yes" : "No")); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_RATED + versionStr, (tOptions.getMatchOptions().isRated() ? "Yes" : "No")); + + String deckFile = this.player1Panel.getDeckFile(); + if (deckFile != null && !deckFile.isEmpty()) { + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_DECK_FILE + versionStr, deckFile); + } + + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_PLAYERS + versionStr, Integer.toString(tOptions.getPlayerTypes().size())); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_SEATS + versionStr, Integer.toString((Integer) this.spnNumSeats.getValue())); + + // save player data + // player type + String playerData = players.stream() + .map(panel -> ((PlayerType) panel.getPlayerType().getSelectedItem())) + .map(panel -> panel.toString()) + .collect(Collectors.joining(NewTableDialog.PLAYER_DATA_DELIMETER_NEW)); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYER_TYPES + versionStr, playerData); + // player skill + playerData = players.stream() + .map(panel -> String.valueOf(panel.getPlayerSkill())) + .collect(Collectors.joining(NewTableDialog.PLAYER_DATA_DELIMETER_NEW)); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_PLAYER_SKILLS + versionStr, playerData); + // player deck + // no deck files support here yet (single deck for all computers) + customOptions.onSaveSettings(version, tOptions.getMatchOptions()); } diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form index 108df9549fb..c9e24ad94d4 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -2184,7 +2184,7 @@ - + @@ -2219,22 +2219,13 @@ - - - - + + - - - - - - - - + - + @@ -2251,17 +2242,12 @@ - - - - - - - + + @@ -2318,29 +2304,6 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 4d435dbc16d..6239d22f5fe 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -84,8 +84,6 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_CARD_IMAGES_USE_DEFAULT = "cardImagesUseDefault"; public static final String KEY_CARD_IMAGES_PATH = "cardImagesPath"; - public static final String KEY_CARD_IMAGES_THREADS = "cardImagesThreads"; - public static final String KEY_CARD_IMAGES_THREADS_DEFAULT = "3"; public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip"; public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage"; @@ -219,6 +217,8 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TABLE_SKILL_LEVEL = "newTableSkillLevel"; public static final String KEY_NEW_TABLE_NUMBER_PLAYERS = "newTableNumberPlayers"; public static final String KEY_NEW_TABLE_PLAYER_TYPES = "newTablePlayerTypes"; + public static final String KEY_NEW_TABLE_PLAYER_SKILLS = "newTablePlayerSkills"; + public static final String KEY_NEW_TABLE_PLAYER_DECKS = "newTablePlayerDecks"; public static final String KEY_NEW_TABLE_QUIT_RATIO = "newTableQuitRatio"; public static final String KEY_NEW_TABLE_MINIMUM_RATING = "newTableMinimumRating"; public static final String KEY_NEW_TABLE_RATED = "newTableRated"; @@ -233,6 +233,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TOURNAMENT_TIME_LIMIT = "newTournamentTimeLimit"; public static final String KEY_NEW_TOURNAMENT_BUFFER_TIME = "newTournamentBufferTime"; public static final String KEY_NEW_TOURNAMENT_CONSTR_TIME = "newTournamentConstructionTime"; + public static final String KEY_NEW_TOURNAMENT_SKILL_LEVEL = "newTournamentSkillLevel"; public static final String KEY_NEW_TOURNAMENT_TYPE = "newTournamentType"; public static final String KEY_NEW_TOURNAMENT_NUMBER_OF_FREE_MULLIGANS = "newTournamentNumberOfFreeMulligans"; public static final String KEY_NEW_TOURNAMENT_MULLIGUN_TYPE = "newTournamentMulliganType"; @@ -244,8 +245,10 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TOURNAMENT_PACKS_SEALED = "newTournamentPacksSealed"; public static final String KEY_NEW_TOURNAMENT_PACKS_DRAFT = "newTournamentPacksDraft"; public static final String KEY_NEW_TOURNAMENT_PACKS_RANDOM_DRAFT = "newTournamentPacksRandomDraft"; - public static final String KEY_NEW_TOURNAMENT_PLAYERS_SEALED = "newTournamentPlayersSealed"; - public static final String KEY_NEW_TOURNAMENT_PLAYERS_DRAFT = "newTournamentPlayersDraft"; + public static final String KEY_NEW_TOURNAMENT_NUMBER_PLAYERS = "newTournamentNumberPlayers"; + public static final String KEY_NEW_TOURNAMENT_NUMBER_SEATS = "newTournamentNumberSeats"; + public static final String KEY_NEW_TOURNAMENT_PLAYER_TYPES = "newTournamentPlayerTypes"; + public static final String KEY_NEW_TOURNAMENT_PLAYER_SKILLS = "newTournamentPlayerSkills"; public static final String KEY_NEW_TOURNAMENT_DRAFT_TIMING = "newTournamentDraftTiming"; public static final String KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS = "newTournamentAllowSpectators"; public static final String KEY_NEW_TOURNAMENT_PLANE_CHASE = "newTournamentPlaneChase"; @@ -659,10 +662,6 @@ public class PreferencesDialog extends javax.swing.JDialog { return CardLanguage.valueByCode(getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode())); } - public static Integer getPrefDownloadThreads() { - return Integer.parseInt(getCachedValue(KEY_CARD_IMAGES_THREADS, KEY_CARD_IMAGES_THREADS_DEFAULT)); - } - private static class ImageFileFilter extends FileFilter { @Override @@ -759,7 +758,6 @@ public class PreferencesDialog extends javax.swing.JDialog { } cbPreferredImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList())); - cbNumberOfDownloadThreads.setModel(new DefaultComboBoxModel<>(new String[]{"10", "9", "8", "7", "6", "5", "4", "3", "2", "1"})); } private void createSizeSetting(Integer position, String key, Integer defaultValue, boolean useExample, String name, String hint) { @@ -960,9 +958,6 @@ public class PreferencesDialog extends javax.swing.JDialog { cbSaveToZipFiles = new javax.swing.JCheckBox(); cbPreferredImageLanguage = new javax.swing.JComboBox<>(); labelPreferredImageLanguage = new javax.swing.JLabel(); - labelNumberOfDownloadThreads = new javax.swing.JLabel(); - cbNumberOfDownloadThreads = new javax.swing.JComboBox(); - labelHint1 = new javax.swing.JLabel(); panelCardStyles = new javax.swing.JPanel(); cbCardRenderImageFallback = new javax.swing.JCheckBox(); cbCardRenderIconsForAbilities = new javax.swing.JCheckBox(); @@ -2287,13 +2282,6 @@ public class PreferencesDialog extends javax.swing.JDialog { labelPreferredImageLanguage.setText("Default images language:"); labelPreferredImageLanguage.setFocusable(false); - labelNumberOfDownloadThreads.setText("Default download threads:"); - - cbNumberOfDownloadThreads.setMaximumRowCount(20); - cbNumberOfDownloadThreads.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); - - labelHint1.setText("(change it to 1-3 if image source bans your IP for too many connections)"); - org.jdesktop.layout.GroupLayout panelCardImagesLayout = new org.jdesktop.layout.GroupLayout(panelCardImages); panelCardImages.setLayout(panelCardImagesLayout); panelCardImagesLayout.setHorizontalGroup( @@ -2310,17 +2298,11 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(cbSaveToZipFiles) .add(panelCardImagesLayout.createSequentialGroup() - .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(labelNumberOfDownloadThreads) - .add(labelPreferredImageLanguage)) + .add(6, 6, 6) + .add(labelPreferredImageLanguage) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .add(panelCardImagesLayout.createSequentialGroup() - .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(labelHint1))))) - .add(0, 122, Short.MAX_VALUE))) + .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) + .add(0, 480, Short.MAX_VALUE))) .addContainerGap()) ); panelCardImagesLayout.setVerticalGroup( @@ -2332,15 +2314,11 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(btnBrowseImageLocation)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) .add(cbSaveToZipFiles) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) - .add(labelNumberOfDownloadThreads) - .add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .add(labelHint1)) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(labelPreferredImageLanguage) - .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) + .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) ); panelCardStyles.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)")); @@ -2383,7 +2361,7 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(panelCardStyles, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(panelCardImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addContainerGap(244, Short.MAX_VALUE)) + .addContainerGap(306, Short.MAX_VALUE)) ); tabsPanel.addTab("GUI Images", tabGuiImages); @@ -3054,7 +3032,6 @@ public class PreferencesDialog extends javax.swing.JDialog { save(prefs, dialog.cbUseDefaultImageFolder, KEY_CARD_IMAGES_USE_DEFAULT, "true", "false"); saveImagesPath(prefs); save(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true", "false"); - save(prefs, dialog.cbNumberOfDownloadThreads, KEY_CARD_IMAGES_THREADS); save(prefs, dialog.cbPreferredImageLanguage, KEY_CARD_IMAGES_PREF_LANGUAGE); save(prefs, dialog.cbUseDefaultBackground, KEY_BACKGROUND_IMAGE_DEFAULT, "true", "false"); @@ -3505,7 +3482,6 @@ public class PreferencesDialog extends javax.swing.JDialog { updateCache(KEY_CARD_IMAGES_PATH, path); } load(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true"); - dialog.cbNumberOfDownloadThreads.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_THREADS, KEY_CARD_IMAGES_THREADS_DEFAULT)); dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode())); // rendering settings @@ -4080,7 +4056,6 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JCheckBox cbGameJsonLogAutoSave; private javax.swing.JCheckBox cbGameLogAutoSave; private javax.swing.JCheckBox cbLimitedDeckAutoSave; - private javax.swing.JComboBox cbNumberOfDownloadThreads; private javax.swing.JCheckBox cbPassPriorityActivation; private javax.swing.JCheckBox cbPassPriorityCast; private javax.swing.JComboBox cbPreferredImageLanguage; @@ -4196,10 +4171,8 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JLabel labelCancel; private javax.swing.JLabel labelConfirm; private javax.swing.JLabel labelEndStep; - private javax.swing.JLabel labelHint1; private javax.swing.JLabel labelMainStep; private javax.swing.JLabel labelNextTurn; - private javax.swing.JLabel labelNumberOfDownloadThreads; private javax.swing.JLabel labelPreferredImageLanguage; private javax.swing.JLabel labelPriorEnd; private javax.swing.JLabel labelSizeGroup1; diff --git a/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java b/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java index 82977aa47df..6db3835601f 100644 --- a/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/NewPlayerPanel.java @@ -17,19 +17,12 @@ public class NewPlayerPanel extends javax.swing.JPanel { private final JFileChooser fcSelectDeck; - /** - * Creates new form NewPlayerPanel - */ public NewPlayerPanel() { initComponents(); fcSelectDeck = new JFileChooser(); fcSelectDeck.setAcceptAllFileFilterUsed(false); fcSelectDeck.addChoosableFileFilter(new DeckFileFilter("dck", "XMage's deck files (*.dck)")); - String deckPath = MageFrame.getPreferences().get("defaultDeckPath", ""); - if (deckPath.isEmpty()) { - deckPath = ClientDefaultSettings.deckPath; - } - this.txtPlayerDeck.setText(deckPath); + this.txtPlayerDeck.setText(""); this.txtPlayerName.setText(ClientDefaultSettings.computerName); } @@ -62,7 +55,6 @@ public class NewPlayerPanel extends javax.swing.JPanel { return; } this.txtPlayerDeck.setText(path); - MageFrame.getPreferences().put("defaultDeckPath", path); } public String getPlayerName() { @@ -77,7 +69,11 @@ public class NewPlayerPanel extends javax.swing.JPanel { this.txtPlayerDeck.setText(deckFile); } - public int getLevel() { + public void setSkillLevel(int level) { + this.spnLevel.setValue(level); + } + + public int getSkillLevel() { return (Integer) spnLevel.getValue(); } diff --git a/Mage.Client/src/main/java/mage/client/table/TablePlayerPanel.java b/Mage.Client/src/main/java/mage/client/table/TablePlayerPanel.java index 75370abed30..34840279052 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablePlayerPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablePlayerPanel.java @@ -18,16 +18,12 @@ public class TablePlayerPanel extends javax.swing.JPanel { protected final PlayerTypeEventSource playerTypeEventSource = new PlayerTypeEventSource(); - - /** - * Creates new form TablePlayerPanel - */ public TablePlayerPanel() { initComponents(); this.newPlayerPanel.setVisible(false); } - public void init(int playerNum, PlayerType playerType) { + public void init(int playerNum, PlayerType playerType, int playerSkill, String playerDeck) { cbPlayerType.setModel(new DefaultComboBoxModel(SessionHandler.getPlayerTypes())); this.lblPlayerNum.setText("Player " + playerNum); if (ClientDefaultSettings.otherPlayerIndex != null) { @@ -41,11 +37,13 @@ public class TablePlayerPanel extends javax.swing.JPanel { if (playerType != null) { this.cbPlayerType.setSelectedItem(playerType); } + this.newPlayerPanel.setDeckFile(playerDeck); + this.newPlayerPanel.setSkillLevel(playerSkill); } public boolean joinTable(UUID roomId, UUID tableId) throws IOException, ClassNotFoundException { if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) { - return SessionHandler.joinTable(roomId, tableId, this.newPlayerPanel.getPlayerName(), (PlayerType) this.cbPlayerType.getSelectedItem(), this.newPlayerPanel.getLevel(), DeckImporter.importDeckFromFile(this.newPlayerPanel.getDeckFile(), true), ""); + return SessionHandler.joinTable(roomId, tableId, this.newPlayerPanel.getPlayerName(), (PlayerType) this.cbPlayerType.getSelectedItem(), this.newPlayerPanel.getSkillLevel(), DeckImporter.importDeckFromFile(this.newPlayerPanel.getDeckFile(), true), ""); } return true; } @@ -54,6 +52,14 @@ public class TablePlayerPanel extends javax.swing.JPanel { return PlayerType.getByDescription(this.cbPlayerType.getSelectedItem().toString()); } + public int getPlayerSkill() { + return newPlayerPanel.getSkillLevel(); + } + + public String getPlayerDeck() { + return newPlayerPanel.getDeckFile(); + } + public void addPlayerTypeEventListener(Listener listener) { playerTypeEventSource.addListener(listener); } diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 957fc9b991a..d94baacf55f 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -66,7 +66,7 @@ public class TablesPanel extends javax.swing.JPanel { private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 100, 50, 120, 180, 80, 120, 80, 60, 40, 40, 60}; - // ping timeout (warning, must be less than SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS) + // ping timeout (warning, must be less than UserManagerImpl.USER_CONNECTION_TIMEOUTS_CHECK_SECS) public static final int PING_SERVER_SECS = 20; // refresh timeouts for data downloads from server diff --git a/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java b/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java index c9b1fc6e6ff..a6eca85360b 100644 --- a/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TournamentPlayerPanel.java @@ -22,15 +22,31 @@ public class TournamentPlayerPanel extends javax.swing.JPanel { this.pnlPlayerName.setVisible(false); } - public void init(int playerNum) { + public void init(int playerNum, PlayerType playerType, int playerSkill) { cbPlayerType.setModel(new DefaultComboBoxModel(SessionHandler.getPlayerTypes())); this.lblPlayerNum.setText("Player " + playerNum); + if (ClientDefaultSettings.otherPlayerIndex != null) { + Integer index = Integer.parseInt(ClientDefaultSettings.otherPlayerIndex); + if (index >= cbPlayerType.getItemCount()) { + cbPlayerType.setSelectedIndex(cbPlayerType.getItemCount() - 1); + } else { + cbPlayerType.setSelectedIndex(index); + } + } + if (playerType != null) { + this.cbPlayerType.setSelectedItem(playerType); + } + this.spnLevel.setValue(playerSkill); } public JComboBox getPlayerType() { return this.cbPlayerType; } + public int getPlayerSkill() { + return (Integer) this.spnLevel.getValue(); + } + public boolean joinTournamentTable(UUID roomId, UUID tableId, DeckCardLists deckCardLists) { if (this.cbPlayerType.getSelectedItem() != PlayerType.HUMAN) { return SessionHandler.joinTournamentTable( diff --git a/Mage.Client/src/main/java/mage/client/util/ClientDefaultSettings.java b/Mage.Client/src/main/java/mage/client/util/ClientDefaultSettings.java index 528e2b85b4c..07a3c57ed44 100644 --- a/Mage.Client/src/main/java/mage/client/util/ClientDefaultSettings.java +++ b/Mage.Client/src/main/java/mage/client/util/ClientDefaultSettings.java @@ -15,7 +15,6 @@ public final class ClientDefaultSettings { public static final CardDimensions dimensions; public static final CardDimensions dimensionsEnlarged; - public static final String deckPath; public static final String otherPlayerIndex; public static final String computerName; @@ -26,7 +25,6 @@ public final class ClientDefaultSettings { cardScalingFactor = 0.4; cardScalingFactorEnlarged = 0.5; handScalingFactor = 1.3; - deckPath = ""; otherPlayerIndex = "1"; // combobox default, example: 0: Human, 1: Computer - mad, 2: Computer - Draft Bot computerName = "Computer"; dimensions = new CardDimensions(cardScalingFactor); diff --git a/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java b/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java index fb7e198f0c0..f4d09dd8d2d 100644 --- a/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java +++ b/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java @@ -1,17 +1,23 @@ - - package mage.client.util; +import mage.client.MageFrame; import org.apache.log4j.Logger; +import java.util.HashMap; +import java.util.Map; + /** + * GUI helper class - catch all app's unhandled errors * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class EDTExceptionHandler implements Thread.UncaughtExceptionHandler { private static final Logger logger = Logger.getLogger(EDTExceptionHandler.class); + private static final Map foundErrors = new HashMap<>(); + private static MageFrame mainApp = null; // app to show error dialogs + @Override public void uncaughtException(Thread t, Throwable e) { handle(e); @@ -20,8 +26,18 @@ public class EDTExceptionHandler implements Thread.UncaughtExceptionHandler { public void handle(Throwable throwable) { try { logger.fatal("MAGE Client UI error", throwable); - // JOptionPane.showMessageDialog(MageFrame.getDesktop(), throwable, "MAGE Client UI error", JOptionPane.ERROR_MESSAGE); - } catch (Throwable t) {} + + // show error dialog for better users feedback about client side errors + // with some protection from dialogs spam on screen (e.g. on render/graphic errors) + String errorKey = throwable.toString(); + int foundCount = foundErrors.getOrDefault(errorKey, 0); + if (foundCount < 5 && mainApp != null) { + mainApp.showErrorDialog("CLIENT - unhandled error in GUI", throwable); + foundCount++; + } + foundErrors.put(errorKey, foundCount); + } catch (Throwable ignore) { + } } public static void registerExceptionHandler() { @@ -29,4 +45,8 @@ public class EDTExceptionHandler implements Thread.UncaughtExceptionHandler { System.setProperty("sun.awt.exception.handler", EDTExceptionHandler.class.getName()); } + public static void registerMainApp(MageFrame app) { + mainApp = app; + } + } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index f4aadb4917d..a6622c16c62 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -65,6 +65,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements private static final List basicList = Arrays.asList("Plains", "Island", "Swamp", "Mountain", "Forest"); private static final int MAX_ERRORS_COUNT_BEFORE_CANCEL = 50; + private static final int DEFAULT_DOWNLOAD_THREADS = 5; // protect from wrong data save // there are possible land images with small sizes, so must research content in check @@ -78,7 +79,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements private List cardsAll; private List cardsMissing; - private final List cardsDownloadQueue; + private List cardsDownloadQueue; private final List selectedSets = new ArrayList<>(); private CardImageSource selectedSource; @@ -200,7 +201,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements // DOWNLOAD THREADS uiDialog.getDownloadThreadsCombo().setModel(new DefaultComboBoxModel<>(new String[]{"10", "9", "8", "7", "6", "5", "4", "3", "2", "1"})); - uiDialog.getDownloadThreadsCombo().setSelectedItem(PreferencesDialog.getPrefDownloadThreads().toString()); + uiDialog.getDownloadThreadsCombo().setSelectedItem(String.valueOf(DEFAULT_DOWNLOAD_THREADS)); // REDOWNLOAD uiDialog.getRedownloadCheckbox().setSelected(false); @@ -975,9 +976,16 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements }); // remove all downloaded cards, missing must be remains - // TODO: too slow on finished, must be reworked (e.g. run full check instead remove) - this.cardsDownloadQueue.removeAll(downloadedCards); - this.cardsMissing.removeAll(downloadedCards); + // workaround for fast remove + Set finished = new HashSet<>(downloadedCards); + this.cardsDownloadQueue = Collections.synchronizedList(this.cardsDownloadQueue.stream() + .filter(c -> !finished.contains(c)) + .collect(Collectors.toList()) + ); + this.cardsMissing = Collections.synchronizedList(this.cardsMissing.stream() + .filter(c -> !finished.contains(c)) + .collect(Collectors.toList()) + ); if (this.cardsDownloadQueue.isEmpty()) { // stop download diff --git a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java index 6988a14f220..1155facff78 100644 --- a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java +++ b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java @@ -14,8 +14,8 @@ import java.util.UUID; */ public class ClientCallback implements Serializable { - // for debug only: simulate bad connection on client side, use launcher's client param like -Dxmage.badconnection - private static final String SIMULATE_BAD_CONNECTION_PROP = "xmage.badconnection"; + // for debug only: simulate bad connection on client side, use launcher's client param like -Dxmage.badConnection + private static final String SIMULATE_BAD_CONNECTION_PROP = "xmage.badConnection"; public static final boolean SIMULATE_BAD_CONNECTION; static { diff --git a/Mage.Common/src/main/java/mage/remote/SessionImpl.java b/Mage.Common/src/main/java/mage/remote/SessionImpl.java index 1a777ed2355..963c12a4600 100644 --- a/Mage.Common/src/main/java/mage/remote/SessionImpl.java +++ b/Mage.Common/src/main/java/mage/remote/SessionImpl.java @@ -42,6 +42,11 @@ public class SessionImpl implements Session { private static final Logger logger = Logger.getLogger(SessionImpl.class); + // connection validation on client side (jboss's ping implementation, depends on server config's leasePeriod) + // client's validation ping must be less than server's leasePeriod + private static final int SESSION_VALIDATOR_PING_PERIOD_SECS = 4; + private static final int SESSION_VALIDATOR_PING_TIMEOUT_SECS = 3; + public static final String ADMIN_NAME = "Admin"; // if you change here then change in User too public static final String KEEP_MY_OLD_SESSION = "keep_my_old_session"; // for disconnects without active session lose (keep tables/games) @@ -65,16 +70,11 @@ public class SessionImpl implements Session { private static final int PING_CYCLES = 10; private final LinkedList pingTime = new LinkedList<>(); private String lastPingInfo = ""; - private static boolean debugMode = false; private boolean canceled = false; private boolean jsonLogActive = false; private String lastError = ""; - static { - debugMode = System.getProperty("debug.mage") != null; - } - public SessionImpl(MageClient client) { this.client = client; } @@ -381,7 +381,7 @@ public class SessionImpl implements Session { clientMetadata.put("generalizeSocketException", "true"); /* A remoting server also has the capability to detect when a client is no longer available. - * This is done by estabilishing a lease with the remoting clients that connect to a server. + * This is done by establishing a lease with the remoting clients that connect to a server. * On the client side, an org.jboss.remoting.LeasePinger periodically sends PING messages to * the server, and on the server side an org.jboss.remoting.Lease informs registered listeners * if the PING doesn't arrive withing the specified timeout period. */ @@ -437,15 +437,10 @@ public class SessionImpl implements Session { clientMetadata.put(Remoting.USE_CLIENT_CONNECTION_IDENTITY, "true"); callbackClient = new Client(clientLocator, "callback", clientMetadata); + // client side connection validator (jboss's implementation of ping) Map listenerMetadata = new HashMap<>(); - if (debugMode) { - // prevent client from disconnecting while debugging - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "1000000"); - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "900000"); - } else { - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "15000"); - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "13000"); - } + listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, String.valueOf(SESSION_VALIDATOR_PING_PERIOD_SECS * 1000)); + listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, String.valueOf(SESSION_VALIDATOR_PING_TIMEOUT_SECS * 1000)); callbackClient.connect(new MageClientConnectionListener(), listenerMetadata); Map callbackMetadata = new HashMap<>(); @@ -589,13 +584,13 @@ public class SessionImpl implements Session { @Override public synchronized boolean sendFeedback(String title, String type, String message, String email) { - if (isConnected()) { - try { + try { + if (isConnected()) { server.serverAddFeedbackMessage(sessionId, connection.getUsername(), title, type, message, email); return true; - } catch (MageException e) { - logger.error(e); } + } catch (MageException ex) { + handleMageException(ex); } return false; } @@ -1706,10 +1701,12 @@ public class SessionImpl implements Session { @Override public void ping() { try { + // client side connection validator (xmage's implementation) + // // jboss uses lease mechanic for connection check but xmage needs additional data like pings stats // ping must work after login only, all other actions are single call (example: register new user) - // sessionId fills on connection - // serverState fills on good login + // - sessionId fills on connection + // - serverState fills on good login if (!isConnected() || sessionId == null || serverState == null) { return; } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java index 841835a13bd..e2255b9cef6 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/DuelCommander.java @@ -109,6 +109,7 @@ public class DuelCommander extends Commander { bannedCommander.add("Raffine, Scheming Seer"); bannedCommander.add("Rofellos, Llanowar Emissary"); bannedCommander.add("Shorikai, Genesis Engine"); + bannedCommander.add("Tamiyo, Inquisitive Student"); bannedCommander.add("Tasigur, the Golden Fang"); bannedCommander.add("Urza, Lord High Artificer"); bannedCommander.add("Vial Smasher the Fierce"); diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index d23a7b438e2..9503aacb383 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -54,13 +54,13 @@ public final class Main { private static final MageVersion version = new MageVersion(Main.class); // Server threads: - // - worker threads: creates for each connection, controls by maxPoolSize; - // - acceptor threads: processing requests to start a new connection, controls by numAcceptThreads; - // - backlog threads: processing waiting queue if maxPoolSize reached, controls by backlogSize; + // - acceptor threads: processing new connection from a client, controlled by numAcceptThreads; + // - worker threads (server threads): processing income requests from a client, controlled by maxPoolSize; + // - backlog: max size of income queue if maxPoolSize reached, controlled by backlogSize; // Usage hints: // - if maxPoolSize reached then new clients will freeze in connection dialog until backlog queue overflow; - // - so for active server must increase maxPoolSize to big value like "max online * 10" or enable worker idle timeout - // - worker idle time will free unused worker thread, so new client can connect; + // - so for active server must increase maxPoolSize to bigger value like "max online * 20" + // - worker idle timeout will free unused worker thread, so new client can connect again; private static final int SERVER_WORKER_THREAD_IDLE_TIMEOUT_SECS = 5 * 60; // no needs to config, must be enabled for all // arg settings can be setup by run script or IDE's program arguments like -xxx=yyy @@ -85,6 +85,7 @@ public final class Main { // - fast game buttons; // - cheat commands; // - no deck validation; + // - no connection validation by pings (no disconnects on IDE's debugger usage) // - load any deck in sideboarding; // - simplified registration and login (no password check); // - debug main menu for GUI and rendering testing (must use -debug arg for client app); @@ -342,6 +343,8 @@ public final class Main { @Override public void handleConnectionException(Throwable throwable, Client client) { + // called on client disconnect or on failed network (depends on server config's leasePeriod) + String sessionId = client.getSessionId(); Session session = managerFactory.sessionManager().getSession(sessionId).orElse(null); if (session == null) { @@ -357,7 +360,7 @@ public final class Main { } else { sessionInfo.append("[no user]"); } - sessionInfo.append(" at ").append(session.getHost()).append(", sessionId: ").append(session.getId()); + sessionInfo.append(" at ").append(session.getHost()); // check disconnection reason // lease ping is inner jboss feature to check connection status @@ -371,13 +374,13 @@ public final class Main { } else if (throwable == null) { // lease timeout (ping), so server lost connection with a client // must keep tables - logger.info("LOST CONNECTION - " + sessionInfo); + logger.info("LOST CONNECTION (bad network) - " + sessionInfo); logger.debug("- cause: lease expired"); managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection, true); } else { // unknown error // must keep tables - logger.info("LOST CONNECTION - " + sessionInfo); + logger.info("LOST CONNECTION (unknown) - " + sessionInfo); logger.debug("- cause: unknown error - " + throwable); managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection, true); } @@ -396,7 +399,8 @@ public final class Main { connector.addInvocationHandler("callback", serverInvocationHandler); // commands processing // connection monitoring and errors processing - connector.setLeasePeriod(managerFactory.configSettings().getLeasePeriod()); + boolean isTestMode = ((MageServerImpl) target).getServerState().isTestMode(); + connector.setLeasePeriod(isTestMode ? 3600 * 1000 : managerFactory.configSettings().getLeasePeriod()); connector.addConnectionListener(new MageServerConnectionListener(managerFactory)); } diff --git a/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java index a02bc605e63..a3fe09ef6b6 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java @@ -4,6 +4,7 @@ import mage.MageException; import mage.players.net.UserData; import mage.server.managers.ManagerFactory; import mage.server.managers.SessionManager; +import mage.util.ThreadUtils; import org.apache.log4j.Logger; import org.jboss.remoting.callback.InvokerCallbackHandler; @@ -63,8 +64,7 @@ public class SessionManagerImpl implements SessionManager { if (session != null) { String errorMessage = session.connectUser(userName, password, restoreSessionId); if (errorMessage == null) { - logger.info(userName + " connected to server by sessionId " + sessionId - + (restoreSessionId.isEmpty() ? "" : ", restoreSessionId " + restoreSessionId)); + logger.info(userName + " connected to server" + (restoreSessionId.isEmpty() ? "" : " with restored session")); if (detailsMode) { logger.info("- details: " + userInfo); } @@ -153,6 +153,8 @@ public class SessionManagerImpl implements SessionManager { } user.showUserMessage("Admin action", "Your session was disconnected by admin"); + ThreadUtils.sleep(1000); + logger.warn(user.getName() + " disconnected by admin"); disconnect(userSessionId, DisconnectReason.DisconnectedByAdmin, true); admin.showUserMessage("Admin result", "User " + user.getName() + " was disconnected"); } diff --git a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java index 571142a9b8d..82c1ab5ad0e 100644 --- a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java @@ -234,6 +234,7 @@ public class UserManagerImpl implements UserManager { } if (isBadSession) { // full disconnect + logger.info(user.getName() + " disconnected due connection problems"); disconnect(user.getId(), DisconnectReason.SessionExpired); } break; diff --git a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java index a8bb71ae863..a13df85c70d 100644 --- a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java @@ -127,7 +127,7 @@ public enum ServerMessagesUtil { private String getServerStatsMessage() { long current = System.currentTimeMillis(); - long hours = ((current - startDate) / (1000 * 60 * 60)); + long hours = startDate <= 0 ? 0 : ((current - startDate) / (1000 * 60 * 60)); String updated = new Date().toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime().format(DateTimeFormatter.ofPattern("HH:mm:ss")); return String.format("Server uptime: %d hours; max online: %d; active games: %d of %d, tourneys: %d of %d; stats from %s", hours, diff --git a/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java b/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java index 05c89acde16..74aebc13b10 100644 --- a/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java +++ b/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java @@ -16,6 +16,7 @@ import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; import mage.game.events.NumberOfTriggersEvent; +import mage.util.CardUtil; import java.util.UUID; @@ -75,26 +76,25 @@ class AncientGreenwardenEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent)) { - return false; - } - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; + // Only triggers for the source controller if (!source.isControlledBy(event.getPlayerId())) { return false; } - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); + GameEvent sourceEvent = ((NumberOfTriggersEvent) event).getSourceEvent(); + // Only EtB triggers of lands if (sourceEvent == null || sourceEvent.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD || !(sourceEvent instanceof EntersTheBattlefieldEvent) || !((EntersTheBattlefieldEvent) sourceEvent).getTarget().isLand(game)) { return false; } - return game.getPermanent(numberOfTriggersEvent.getSourceId()) != null; + // Only for triggers of permanents + return game.getPermanent(event.getSourceId()) != null; } @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); return false; } } diff --git a/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java b/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java index 452997f951b..94eb69faf2e 100644 --- a/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java +++ b/Mage.Sets/src/mage/cards/a/AnnieJoinsUp.java @@ -12,7 +12,6 @@ import mage.filter.FilterPermanent; import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.util.CardUtil; @@ -79,10 +78,7 @@ class AnnieJoinsUpEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent)) { - return false; - } - Permanent permanent = game.getPermanent(((NumberOfTriggersEvent) event).getSourceId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); return permanent != null && permanent.isControlledBy(source.getControllerId()) && permanent.isLegendary(game) diff --git a/Mage.Sets/src/mage/cards/b/BecomeAnonymous.java b/Mage.Sets/src/mage/cards/b/BecomeAnonymous.java new file mode 100644 index 00000000000..27e56fc651b --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BecomeAnonymous.java @@ -0,0 +1,88 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.ManifestEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BecomeAnonymous extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("nontoken creature you own"); + + static { + filter.add(TokenPredicate.FALSE); + filter.add(TargetController.YOU.getOwnerPredicate()); + } + + public BecomeAnonymous(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{U}"); + + // Exile target nontoken creature you own and the top two cards of your library in a face-down pile, shuffle that pile, then cloak those cards. They enter the battlefield tapped. + this.getSpellAbility().addEffect(new BecomeAnonymousEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + } + + private BecomeAnonymous(final BecomeAnonymous card) { + super(card); + } + + @Override + public BecomeAnonymous copy() { + return new BecomeAnonymous(this); + } +} + +class BecomeAnonymousEffect extends OneShotEffect { + + BecomeAnonymousEffect() { + super(Outcome.Benefit); + staticText = "exile target nontoken creature you own and the top two cards of your library in " + + "a face-down pile, shuffle that pile, then cloak those cards. They enter the battlefield tapped"; + } + + private BecomeAnonymousEffect(final BecomeAnonymousEffect effect) { + super(effect); + } + + @Override + public BecomeAnonymousEffect copy() { + return new BecomeAnonymousEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || permanent == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 2)); + cards.add(permanent); + player.moveCards(cards, Zone.EXILED, source, game); + cards.retainZone(Zone.EXILED, game); + if (cards.isEmpty()) { + return true; + } + Set cardSet = cards.getCards(game); + cardSet.stream().forEach(card -> card.setFaceDown(true, game)); + game.processAction(); + return !ManifestEffect.doManifestCards(game, source, player, cardSet, true, true).isEmpty(); + } +} diff --git a/Mage.Sets/src/mage/cards/b/Boltbender.java b/Mage.Sets/src/mage/cards/b/Boltbender.java new file mode 100644 index 00000000000..a88d773039f --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/Boltbender.java @@ -0,0 +1,49 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; +import mage.abilities.keyword.DisguiseAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetStackObject; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Boltbender extends CardImpl { + + public Boltbender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Disguise {1}{R} + this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{1}{R}"))); + + // When Boltbender is turned face up, you may choose new targets for any number of other spells and/or abilities. + Ability ability = new TurnedFaceUpSourceTriggeredAbility(new ChooseNewTargetsTargetEffect() + .setText("you may choose new targets for any number of other spells and/or abilities")); + ability.addTarget(new TargetStackObject(0, Integer.MAX_VALUE, StaticFilters.FILTER_SPELL_OR_ABILITY)); + this.addAbility(ability); + } + + private Boltbender(final Boltbender card) { + super(card); + } + + @Override + public Boltbender copy() { + return new Boltbender(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java b/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java index d422797b1b5..6288c217a69 100644 --- a/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java +++ b/Mage.Sets/src/mage/cards/c/ChangeOfPlans.java @@ -69,7 +69,7 @@ class ChangeOfPlansEffect extends OneShotEffect { .getTargetPointer() .getTargets(game, source) .stream() - .map(game::getPermanent) + .map(game::getPermanentOrLKIBattlefield) .filter(Objects::nonNull) .collect(Collectors.toSet()); if (permanents.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/c/ClaraOswald.java b/Mage.Sets/src/mage/cards/c/ClaraOswald.java index 53b1863d768..f01bc7bd579 100644 --- a/Mage.Sets/src/mage/cards/c/ClaraOswald.java +++ b/Mage.Sets/src/mage/cards/c/ClaraOswald.java @@ -11,7 +11,6 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; import mage.util.CardUtil; @@ -75,10 +74,7 @@ class ClaraOswaldEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent)) { - return false; - } - Permanent permanent = game.getPermanent(((NumberOfTriggersEvent) event).getSourceId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); return permanent != null && permanent.isControlledBy(source.getControllerId()) && permanent.hasSubtype(SubType.DOCTOR, game); diff --git a/Mage.Sets/src/mage/cards/d/DevouringHellion.java b/Mage.Sets/src/mage/cards/d/DevouringHellion.java index dfe1f3087f7..f9385861199 100644 --- a/Mage.Sets/src/mage/cards/d/DevouringHellion.java +++ b/Mage.Sets/src/mage/cards/d/DevouringHellion.java @@ -27,7 +27,7 @@ public final class DevouringHellion extends CardImpl { // As Devouring Hellion enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers. If you do, it enters with twice that many +1/+1 counters on it. this.addAbility(new SimpleStaticAbility(Zone.ALL, new DevourEffect(2, StaticFilters.FILTER_CONTROLLED_PERMANENT_CREATURE_OR_PLANESWALKER) - .setText("As {this} enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers." + .setText("As {this} enters, you may sacrifice any number of creatures and/or planeswalkers." + " If you do, it enters with twice that many +1/+1 counters on it") )); } diff --git a/Mage.Sets/src/mage/cards/d/DragonhawkFatesTempest.java b/Mage.Sets/src/mage/cards/d/DragonhawkFatesTempest.java new file mode 100644 index 00000000000..4c4cb511396 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragonhawkFatesTempest.java @@ -0,0 +1,127 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; + +import java.util.Set; +import java.util.UUID; + +/** + * @author notgreat + */ +public final class DragonhawkFatesTempest extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creatures you control with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_GREATER, 4)); + } + + public DragonhawkFatesTempest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Dragonhawk enters or attacks, exile the top X cards of your library, where X is the number of creatures you control with power 4 or greater. You may play those cards until your next end step. + // At the beginning of your next end step, Dragonhawk deals 2 damage to each opponent for each of those cards that are still exiled. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DragonhawkExileEffect( + new PermanentsOnBattlefieldCount(filter, null), Duration.UntilYourNextEndStep) + .withTextOptions("those cards", true))); + } + + private DragonhawkFatesTempest(final DragonhawkFatesTempest card) { + super(card); + } + + @Override + public DragonhawkFatesTempest copy() { + return new DragonhawkFatesTempest(this); + } +} + +// Copied from ExileTopXMayPlayUntilEffect but with addDelayedTriggeredAbility +class DragonhawkExileEffect extends ExileTopXMayPlayUntilEffect { + + public DragonhawkExileEffect(DynamicValue amount, Duration duration) { + super(amount, duration); + staticText += ". At the beginning of your next end step, " + DragonhawkFatesTempestDamageEffect.STATIC_TEXT; + } + + private DragonhawkExileEffect(final DragonhawkExileEffect effect) { + super(effect); + } + + @Override + public DragonhawkExileEffect copy() { + return new DragonhawkExileEffect(this); + } + + protected void effectCards(Game game, Ability source, Set cards) { + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new DragonhawkFatesTempestDamageEffect(new FixedTargets(cards, game)), TargetController.YOU), source); + } +} + +class DragonhawkFatesTempestDamageEffect extends OneShotEffect { + FixedTargets cards; + public static String STATIC_TEXT = "{this} deals 2 damage to each opponent for each of those cards that are still exiled"; + + DragonhawkFatesTempestDamageEffect(FixedTargets cards) { + super(Outcome.Benefit); + this.staticText = STATIC_TEXT; + this.cards = cards; + } + + private DragonhawkFatesTempestDamageEffect(final DragonhawkFatesTempestDamageEffect effect) { + super(effect); + cards = effect.cards; + } + + @Override + public DragonhawkFatesTempestDamageEffect copy() { + return new DragonhawkFatesTempestDamageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int count = cards.getTargets(game, source).size(); //Automatically filters out moved cards + if (count < 1) { + return false; + } + for (UUID playerId : game.getOpponents(source.getControllerId())) { + player = game.getPlayer(playerId); + if (playerId == null) { + continue; + } + player.damage(count * 2, source.getSourceId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DryadMilitant.java b/Mage.Sets/src/mage/cards/d/DryadMilitant.java index 7b422087820..0391293b5b8 100644 --- a/Mage.Sets/src/mage/cards/d/DryadMilitant.java +++ b/Mage.Sets/src/mage/cards/d/DryadMilitant.java @@ -1,22 +1,16 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @@ -33,7 +27,7 @@ public final class DryadMilitant extends CardImpl { this.toughness = new MageInt(1); // If an instant or sorcery card would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DryadMilitantReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, false))); } private DryadMilitant(final DryadMilitant card) { @@ -45,42 +39,3 @@ public final class DryadMilitant extends CardImpl { return new DryadMilitant(this); } } - -class DryadMilitantReplacementEffect extends ReplacementEffectImpl { - - DryadMilitantReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Exile); - staticText = "If an instant or sorcery card would be put into a graveyard from anywhere, exile it instead"; - } - - private DryadMilitantReplacementEffect(final DryadMilitantReplacementEffect effect) { - super(effect); - } - - @Override - public DryadMilitantReplacementEffect copy() { - return new DryadMilitantReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (((ZoneChangeEvent)event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && (card.isSorcery(game) || card.isInstant(game))) { - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/e/EchoesOfEternity.java b/Mage.Sets/src/mage/cards/e/EchoesOfEternity.java index 7bb66fb6f2e..c074ba92de9 100644 --- a/Mage.Sets/src/mage/cards/e/EchoesOfEternity.java +++ b/Mage.Sets/src/mage/cards/e/EchoesOfEternity.java @@ -12,7 +12,6 @@ import mage.filter.FilterSpell; import mage.filter.predicate.mageobject.ColorlessPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.util.CardUtil; @@ -78,11 +77,10 @@ class EchoesOfEternityEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent) - || !source.isControlledBy(game.getControllerId(event.getSourceId()))) { + if (!source.isControlledBy(event.getPlayerId())) { return false; } - Permanent permanent = game.getPermanent(event.getSourceId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); if (permanent != null && permanent.getColor(game).isColorless() && !permanent.getId().equals(source.getSourceId())) { return true; diff --git a/Mage.Sets/src/mage/cards/f/FestivalOfEmbers.java b/Mage.Sets/src/mage/cards/f/FestivalOfEmbers.java new file mode 100644 index 00000000000..81cd11c9a27 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FestivalOfEmbers.java @@ -0,0 +1,97 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class FestivalOfEmbers extends CardImpl { + + public FestivalOfEmbers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{R}"); + + + // During your turn, you may cast instant and sorcery spells from your graveyard by paying 1 life in addition to their other costs. + this.addAbility(new SimpleStaticAbility(new FestivalOfEmbersCastEffect())); + + // If a card or token would be put into your graveyard from anywhere, exile it instead. + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, true))); + + // {1}{R}: Sacrifice Festival of Embers. + this.addAbility(new SimpleActivatedAbility(new SacrificeSourceEffect(), new ManaCostsImpl<>("{1}{R}"))); + } + + private FestivalOfEmbers(final FestivalOfEmbers card) { + super(card); + } + + @Override + public FestivalOfEmbers copy() { + return new FestivalOfEmbers(this); + } +} + +//Based on Osteomancer Adept +class FestivalOfEmbersCastEffect extends AsThoughEffectImpl { + + FestivalOfEmbersCastEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.AIDontUseIt); + staticText = "During your turn, you may cast instant and sorcery spells from your graveyard by paying 1 life in addition to their other costs."; + } + + private FestivalOfEmbersCastEffect(final FestivalOfEmbersCastEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public FestivalOfEmbersCastEffect copy() { + return new FestivalOfEmbersCastEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId)) { + return false; + } + Card card = game.getCard(objectId); + Player player = game.getPlayer(affectedControllerId); + if (card == null + || player == null + || !game.getActivePlayerId().equals(affectedControllerId) + || !card.isOwnedBy(affectedControllerId) + || !card.isInstantOrSorcery(game) + || !game.getState().getZone(objectId).match(Zone.GRAVEYARD)) { + return false; + } + Costs newCosts = new CostsImpl<>(); + newCosts.addAll(card.getSpellAbility().getCosts()); + newCosts.add(new PayLifeCost(1)); + player.setCastSourceIdWithAlternateMana( + card.getId(), card.getManaCost(), newCosts + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java b/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java index 91e3653e637..9f334a41037 100644 --- a/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java +++ b/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java @@ -1,10 +1,10 @@ package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -14,12 +14,11 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** * * @author Quercitron @@ -30,9 +29,9 @@ public final class ForbiddenCrypt extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{B}{B}"); // If you would draw a card, return a card from your graveyard to your hand instead. If you can't, you lose the game. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ForbiddenCryptDrawCardReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new ForbiddenCryptDrawCardReplacementEffect())); // If a card would be put into your graveyard from anywhere, exile that card instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ForbiddenCryptPutIntoYourGraveyardReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, false))); } private ForbiddenCrypt(final ForbiddenCrypt card) { @@ -96,47 +95,4 @@ class ForbiddenCryptDrawCardReplacementEffect extends ReplacementEffectImpl { return event.getPlayerId().equals(source.getControllerId()); } -} - -class ForbiddenCryptPutIntoYourGraveyardReplacementEffect extends ReplacementEffectImpl { - - ForbiddenCryptPutIntoYourGraveyardReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere, exile that card instead"; - } - - private ForbiddenCryptPutIntoYourGraveyardReplacementEffect(final ForbiddenCryptPutIntoYourGraveyardReplacementEffect effect) { - super(effect); - } - - @Override - public ForbiddenCryptPutIntoYourGraveyardReplacementEffect copy() { - return new ForbiddenCryptPutIntoYourGraveyardReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GaeasWill.java b/Mage.Sets/src/mage/cards/g/GaeasWill.java index df94ce51f7c..34cc9981afe 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasWill.java +++ b/Mage.Sets/src/mage/cards/g/GaeasWill.java @@ -1,23 +1,19 @@ package mage.cards.g; -import java.util.UUID; - import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.abilities.keyword.SuspendAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author weirddan455 @@ -36,7 +32,7 @@ public final class GaeasWill extends CardImpl { this.getSpellAbility().addEffect(new GaeasWillGraveyardEffect()); // If a card would be put into your graveyard from anywhere this turn, exile that card instead. - this.getSpellAbility().addEffect(new GaeassWillReplacementEffect()); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn))); } private GaeasWill(final GaeasWill card) { @@ -79,45 +75,3 @@ class GaeasWillGraveyardEffect extends ContinuousEffectImpl { return false; } } - -class GaeassWillReplacementEffect extends ReplacementEffectImpl { - - GaeassWillReplacementEffect() { - super(Duration.EndOfTurn, Outcome.Detriment); - this.staticText = "
If a card would be put into your graveyard from anywhere this turn, exile that card instead"; - } - - private GaeassWillReplacementEffect(final GaeassWillReplacementEffect effect) { - super(effect); - } - - @Override - public GaeassWillReplacementEffect copy() { - return new GaeassWillReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/h/HarmonicProdigy.java b/Mage.Sets/src/mage/cards/h/HarmonicProdigy.java index 610a1c86d6b..b2de60e0ab3 100644 --- a/Mage.Sets/src/mage/cards/h/HarmonicProdigy.java +++ b/Mage.Sets/src/mage/cards/h/HarmonicProdigy.java @@ -13,8 +13,8 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; +import mage.util.CardUtil; import java.util.UUID; @@ -72,10 +72,7 @@ class HarmonicProdigyEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent)) { - return false; - } - Permanent permanent = game.getPermanent(((NumberOfTriggersEvent) event).getSourceId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId()); return permanent != null && permanent.isControlledBy(source.getControllerId()) && (permanent.hasSubtype(SubType.SHAMAN, game) @@ -85,7 +82,7 @@ class HarmonicProdigyEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); return false; } } diff --git a/Mage.Sets/src/mage/cards/h/HavocEater.java b/Mage.Sets/src/mage/cards/h/HavocEater.java new file mode 100644 index 00000000000..b7557b22603 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HavocEater.java @@ -0,0 +1,94 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.ForEachOpponentTargetsAdjuster; +import mage.target.targetpointer.EachTargetPointer; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HavocEater extends CardImpl { + + public HavocEater(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{R}{R}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Havoc Eater enters the battlefield, for each opponent, goad up to one target creature that opponent controls. Put X +1/+1 counters on Havoc Eater, where X is the total power of creatures goaded this way. + Ability ability = new EntersBattlefieldTriggeredAbility( + new GoadTargetEffect() + .setText("for each opponent, goad up to one target creature that opponent controls") + .setTargetPointer(new EachTargetPointer())); + ability.addEffect(new HavocEaterEffect()); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability.setTargetAdjuster(new ForEachOpponentTargetsAdjuster())); + } + + private HavocEater(final HavocEater card) { + super(card); + } + + @Override + public HavocEater copy() { + return new HavocEater(this); + } +} + +class HavocEaterEffect extends OneShotEffect { + + HavocEaterEffect() { + super(Outcome.Benefit); + staticText = "put X +1/+1 counters on {this}, where X is the total power of creatures goaded this way"; + this.setTargetPointer(new EachTargetPointer()); + } + + private HavocEaterEffect(final HavocEaterEffect effect) { + super(effect); + } + + @Override + public HavocEaterEffect copy() { + return new HavocEaterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + int amount = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .sum(); + return amount > 0 && permanent.addCounters(CounterType.P1P1.createInstance(amount), source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HotPursuit.java b/Mage.Sets/src/mage/cards/h/HotPursuit.java new file mode 100644 index 00000000000..33e8df8c392 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HotPursuit.java @@ -0,0 +1,111 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.SuspectTargetEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.common.continuous.GainControlAllUntapGainHasteEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.constants.WatcherScope; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.GoadedPredicate; +import mage.filter.predicate.permanent.SuspectedPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HotPursuit extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("goaded and/or suspected creatures"); + + static { + filter.add(Predicates.or( + GoadedPredicate.instance, + SuspectedPredicate.instance + )); + } + + public HotPursuit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + // When Hot Pursuit enters the battlefield, suspect target creature an opponent controls. As long as Hot Pursuit remains on the battlefield, that creature is also goaded. + Ability ability = new EntersBattlefieldTriggeredAbility(new SuspectTargetEffect()); + ability.addEffect(new GoadTargetEffect(Duration.UntilSourceLeavesBattlefield) + .setText("as long as {this} remains on the battlefield, that creature is also goaded")); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + this.addAbility(ability); + + // At the beginning of combat on your turn, if two or more players have lost the game, gain control of all goaded and/or suspected creatures until end of turn. Untap them. They gain haste until end of turn. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new BeginningOfCombatTriggeredAbility( + new GainControlAllUntapGainHasteEffect(filter), + TargetController.YOU, false + ), HotPursuitCondition.instance, "At the beginning of combat on your turn, " + + "if two or more players have lost the game, gain control of all goaded and/or " + + "suspected creatures until end of turn. Untap them. They gain haste until end of turn." + ), new HotPursuitWatcher()); + } + + private HotPursuit(final HotPursuit card) { + super(card); + } + + @Override + public HotPursuit copy() { + return new HotPursuit(this); + } +} + +enum HotPursuitCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return HotPursuitWatcher.checkCondition(game); + } +} + +class HotPursuitWatcher extends Watcher { + + private final Set players = new HashSet<>(); + + HotPursuitWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case BEGINNING_PHASE_PRE: + players.clear(); + return; + case LOST: + players.add(event.getPlayerId()); + } + } + + static boolean checkCondition(Game game) { + return game + .getState() + .getWatcher(HotPursuitWatcher.class) + .players + .size() >= 2; + } +} diff --git a/Mage.Sets/src/mage/cards/j/JackdawSavior.java b/Mage.Sets/src/mage/cards/j/JackdawSavior.java new file mode 100644 index 00000000000..f5db76b14ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JackdawSavior.java @@ -0,0 +1,91 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class JackdawSavior extends CardImpl { + public JackdawSavior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Jackdaw Savior or another creature you control with flying dies, return another target creature card with lesser mana value from your graveyard to the battlefield. + this.addAbility(new JackdawSaviorDiesThisOrAnotherTriggeredAbility()); + } + + private JackdawSavior(final JackdawSavior card) { + super(card); + } + + @Override + public JackdawSavior copy() { + return new JackdawSavior(this); + } +} + +class JackdawSaviorDiesThisOrAnotherTriggeredAbility extends DiesThisOrAnotherTriggeredAbility { + private static final FilterControlledCreaturePermanent flyingFilter = new FilterControlledCreaturePermanent("creature you control with flying"); + + static { + flyingFilter.add(new AbilityPredicate(FlyingAbility.class)); + } + + public JackdawSaviorDiesThisOrAnotherTriggeredAbility() { + super(new ReturnFromGraveyardToBattlefieldTargetEffect().setText( + "return another target creature card with lesser mana value from your graveyard to the battlefield"), + false, flyingFilter); + } + + protected JackdawSaviorDiesThisOrAnotherTriggeredAbility(final JackdawSaviorDiesThisOrAnotherTriggeredAbility ability) { + super(ability); + } + + @Override + public JackdawSaviorDiesThisOrAnotherTriggeredAbility copy() { + return new JackdawSaviorDiesThisOrAnotherTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (super.checkTrigger(event, game)) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + FilterCard filter = new FilterCreatureCard(); + filter.add(Predicates.not(new MageObjectReferencePredicate(zEvent.getTargetId(), game))); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, zEvent.getTarget().getManaValue())); + filter.setMessage("target creature card other than "+zEvent.getTarget().getLogName()+" with mana value less than "+zEvent.getTarget().getManaValue()); + this.getTargets().clear(); + this.addTarget(new TargetCardInYourGraveyard(filter)); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LandscaperColos.java b/Mage.Sets/src/mage/cards/l/LandscaperColos.java index b0e69ca1221..ff626c62d89 100644 --- a/Mage.Sets/src/mage/cards/l/LandscaperColos.java +++ b/Mage.Sets/src/mage/cards/l/LandscaperColos.java @@ -39,7 +39,7 @@ public final class LandscaperColos extends CardImpl { Ability ability = new EntersBattlefieldTriggeredAbility(new PutOnLibraryTargetEffect( false, "put target card from an opponent's graveyard on the bottom of their library" )); - ability.addTarget(new TargetCardInGraveyard()); + ability.addTarget(new TargetCardInGraveyard(filter)); this.addAbility(ability); // Basic landcycling {1}{W} diff --git a/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java b/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java index 812c091acd2..16fc67220fa 100644 --- a/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java +++ b/Mage.Sets/src/mage/cards/m/MageHuntersOnslaught.java @@ -61,7 +61,7 @@ class MageHuntersOnslaughtDelayedTriggeredAbility extends DelayedTriggeredAbilit @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(event.getTargetId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java b/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java index 9478297dbdb..7fb505b88c4 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java @@ -1,7 +1,6 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -9,24 +8,15 @@ import mage.abilities.costs.common.ExileSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author fireshoes @@ -44,7 +34,7 @@ public final class MagusOfTheWill extends CardImpl { // {2}{B}, {T}, Exile Magus of the Will: Until end of turn, you may play cards from your graveyard. // If a card would be put into your graveyard from anywhere else this turn, exile that card instead. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CanPlayCardsFromGraveyardEffect(), new ManaCostsImpl<>("{2}{B}")); - ability.addEffect(new MagusOfTheWillReplacementEffect()); + ability.addEffect(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn)); ability.addCost(new TapSourceCost()); ability.addCost(new ExileSourceCost()); this.addAbility(ability); @@ -91,46 +81,3 @@ class CanPlayCardsFromGraveyardEffect extends ContinuousEffectImpl { } } - -class MagusOfTheWillReplacementEffect extends ReplacementEffectImpl { - - MagusOfTheWillReplacementEffect() { - super(Duration.EndOfTurn, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere else this turn, exile that card instead"; - } - - private MagusOfTheWillReplacementEffect(final MagusOfTheWillReplacementEffect effect) { - super(effect); - } - - @Override - public MagusOfTheWillReplacementEffect copy() { - return new MagusOfTheWillReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/n/Necrodominance.java b/Mage.Sets/src/mage/cards/n/Necrodominance.java index c0ae8beec5f..3aab2e46b60 100644 --- a/Mage.Sets/src/mage/cards/n/Necrodominance.java +++ b/Mage.Sets/src/mage/cards/n/Necrodominance.java @@ -6,18 +6,13 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.SkipDrawStepEffect; import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; import java.util.UUID; @@ -50,7 +45,7 @@ public final class Necrodominance extends CardImpl { )); // If a card or token would be put into your graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(new NecrodominanceReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, true))); } private Necrodominance(final Necrodominance card) { @@ -95,50 +90,4 @@ class NecrodominanceEffect extends OneShotEffect { return true; } -} - -// Inspired by [Rest in Peace] and [Wheel of Sun and Moon] -class NecrodominanceReplacementEffect extends ReplacementEffectImpl { - - NecrodominanceReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Exile); - staticText = "If a card or token would be put into your graveyard from anywhere, exile it instead"; - } - - private NecrodominanceReplacementEffect(final NecrodominanceReplacementEffect effect) { - super(effect); - } - - @Override - public NecrodominanceReplacementEffect copy() { - return new NecrodominanceReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getToZone() != Zone.GRAVEYARD) { - return false; - } - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - return true; - } - Permanent token = game.getPermanent(event.getTargetId()); - if (token != null && token instanceof PermanentToken && token.isOwnedBy(source.getControllerId())) { - return true; - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java b/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java index 6cecc1acc09..931831c102e 100644 --- a/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java +++ b/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java @@ -95,7 +95,7 @@ class NikoLightOfHopeEffect extends OneShotEffect { FilterPermanent filter = new FilterPermanent("shards"); filter.add(SubType.SHARD.getPredicate()); for (Permanent copyTo : game.getBattlefield().getAllActivePermanents(filter, controller.getId(), game)) { - game.copyPermanent(Duration.UntilTheNextEndStep, permanent, copyTo.getId(), source, new EmptyCopyApplier()); + game.copyPermanent(Duration.UntilNextEndStep, permanent, copyTo.getId(), source, new EmptyCopyApplier()); } ExileZone exile = game.getExile().getExileZone(source.getSourceId()); if (exile != null && !exile.isEmpty()) { diff --git a/Mage.Sets/src/mage/cards/o/OverwhelmingRemorse.java b/Mage.Sets/src/mage/cards/o/OverwhelmingRemorse.java index a6c5a9fdd11..77f9dfc67c6 100644 --- a/Mage.Sets/src/mage/cards/o/OverwhelmingRemorse.java +++ b/Mage.Sets/src/mage/cards/o/OverwhelmingRemorse.java @@ -30,7 +30,7 @@ public final class OverwhelmingRemorse extends CardImpl { // This spell costs {1} less to cast for each creature card in your graveyard. this.addAbility(new SimpleStaticAbility( Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) - ).addHint(hint)); + ).addHint(hint).setRuleAtTheTop(true)); // Exile target creature or planeswalker. this.getSpellAbility().addEffect(new ExileTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/r/RansomNote.java b/Mage.Sets/src/mage/cards/r/RansomNote.java new file mode 100644 index 00000000000..764d157ed0d --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RansomNote.java @@ -0,0 +1,58 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.combat.GoadTargetEffect; +import mage.abilities.effects.keyword.ManifestEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RansomNote extends CardImpl { + + public RansomNote(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.CLUE); + + // When Ransom Note enters the battlefield, surveil 1. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SurveilEffect(1))); + + // {2}, Sacrifice Ransom Note: Choose one -- + // * Cloak the top card of your library. + Ability ability = new SimpleActivatedAbility( + new ManifestEffect(StaticValue.get(1), false, true), new GenericManaCost(2) + ); + ability.addCost(new SacrificeSourceCost()); + + // * Goad target creature. + ability.addMode(new Mode(new GoadTargetEffect()).addTarget(new TargetCreaturePermanent())); + + // * Draw a card. + ability.addMode(new Mode(new DrawCardSourceControllerEffect(1))); + this.addAbility(ability); + } + + private RansomNote(final RansomNote card) { + super(card); + } + + @Override + public RansomNote copy() { + return new RansomNote(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RedressFate.java b/Mage.Sets/src/mage/cards/r/RedressFate.java new file mode 100644 index 00000000000..c399b0def19 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RedressFate.java @@ -0,0 +1,72 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MiracleAbility; +import mage.filter.FilterCard; +import mage.filter.common.FilterArtifactOrEnchantmentCard; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author RobertFrosty + */ +public final class RedressFate extends CardImpl { + + public RedressFate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{6}{W}{W}"); + + + // Return all artifact and enchantment cards from your graveyard to the battlefield. + this.getSpellAbility().addEffect(new RedressFateEffect()); + + // Miracle {3}{W} + this.addAbility(new MiracleAbility("{3}{W}")); + + } + + private RedressFate(final RedressFate card) { + super(card); + } + + @Override + public RedressFate copy() { + return new RedressFate(this); + } +} + +class RedressFateEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterArtifactOrEnchantmentCard(); + + RedressFateEffect() { + super(Outcome.Benefit); + staticText = "return all artifact and enchantment cards from your graveyard to the battlefield"; + } + + private RedressFateEffect(final RedressFateEffect effect) { + super(effect); + } + + @Override + public RedressFateEffect copy() { + return new RedressFateEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + return controller.moveCards(controller.getGraveyard().getCards(filter, game), Zone.BATTLEFIELD, source, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RestInPeace.java b/Mage.Sets/src/mage/cards/r/RestInPeace.java index 3fedd0f1478..68abaaf04b8 100644 --- a/Mage.Sets/src/mage/cards/r/RestInPeace.java +++ b/Mage.Sets/src/mage/cards/r/RestInPeace.java @@ -1,25 +1,15 @@ package mage.cards.r; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.ExileGraveyardAllPlayersEffect; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; + +import java.util.UUID; /** * @author LevelX2 @@ -49,7 +39,7 @@ public final class RestInPeace extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new ExileGraveyardAllPlayersEffect())); // If a card or token would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new RestInPeaceReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(false, true))); } private RestInPeace(final RestInPeace card) { @@ -61,36 +51,3 @@ public final class RestInPeace extends CardImpl { return new RestInPeace(this); } } - -class RestInPeaceReplacementEffect extends ReplacementEffectImpl { - - RestInPeaceReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Exile); - staticText = "If a card or token would be put into a graveyard from anywhere, exile it instead"; - } - - private RestInPeaceReplacementEffect(final RestInPeaceReplacementEffect effect) { - super(effect); - } - - @Override - public RestInPeaceReplacementEffect copy() { - return new RestInPeaceReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - return ((ZoneChangeEvent)event).getToZone() == Zone.GRAVEYARD; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RoamingThrone.java b/Mage.Sets/src/mage/cards/r/RoamingThrone.java index 73858795131..08a8c7ce511 100644 --- a/Mage.Sets/src/mage/cards/r/RoamingThrone.java +++ b/Mage.Sets/src/mage/cards/r/RoamingThrone.java @@ -18,8 +18,8 @@ import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.NumberOfTriggersEvent; import mage.game.permanent.Permanent; +import mage.util.CardUtil; import java.util.UUID; @@ -90,14 +90,10 @@ class RoamingThroneReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent)) { - return false; - } - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; if (!source.isControlledBy(event.getPlayerId())) { return false; } - Permanent permanentSource = game.getPermanentOrLKIBattlefield(numberOfTriggersEvent.getSourceId()); + Permanent permanentSource = game.getPermanentOrLKIBattlefield(event.getSourceId()); return permanentSource != null && filter.match(permanentSource, source.getControllerId(), source, game) && permanentSource.hasSubtype(ChooseCreatureTypeEffect.getChosenCreatureType(source.getSourceId(), game), game); @@ -105,7 +101,7 @@ class RoamingThroneReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); return false; } } diff --git a/Mage.Sets/src/mage/cards/s/SeanceBoard.java b/Mage.Sets/src/mage/cards/s/SeanceBoard.java new file mode 100644 index 00000000000..c75276bff5c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeanceBoard.java @@ -0,0 +1,127 @@ +package mage.cards.s; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.common.MorbidCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.ManaEffect; +import mage.abilities.hint.common.MorbidHint; +import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.ConditionalSpellManaBuilder; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.ChoiceColor; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * @author Cguy7777 + */ +public final class SeanceBoard extends CardImpl { + + public SeanceBoard(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // Morbid -- At the beginning of each end step, if a creature died this turn, put a soul counter on Seance Board. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + new AddCountersSourceEffect(CounterType.SOUL.createInstance()), + TargetController.ANY, + MorbidCondition.instance, + false + ).addHint(MorbidHint.instance).setAbilityWord(AbilityWord.MORBID)); + + // {T}: Add X mana of any one color, where X is the number of soul counters on Seance Board. + // Spend this mana only to cast instant, sorcery, Demon, and Spirit spells. + this.addAbility(new SimpleManaAbility(new SeanceBoardManaEffect(), new TapSourceCost())); + } + + private SeanceBoard(final SeanceBoard card) { + super(card); + } + + @Override + public SeanceBoard copy() { + return new SeanceBoard(this); + } +} + +class SeanceBoardManaEffect extends ManaEffect { + + private static final FilterSpell filter = new FilterSpell("instant, sorcery, Demon, and Spirit spells"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate(), + SubType.DEMON.getPredicate(), + SubType.SPIRIT.getPredicate())); + } + + private final ConditionalManaBuilder manaBuilder + = new ConditionalSpellManaBuilder(filter); + + SeanceBoardManaEffect() { + this.staticText = "Add X mana of any one color, where X is the number of soul counters on {this}. " + + manaBuilder.getRule(); + } + + private SeanceBoardManaEffect(final SeanceBoardManaEffect effect) { + super(effect); + } + + @Override + public List getNetMana(Game game, Ability source) { + List netMana = new ArrayList<>(); + if (game == null) { + return netMana; + } + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (permanent != null) { + int soulCounters = permanent.getCounters(game).getCount(CounterType.SOUL); + netMana.add(manaBuilder.setMana(Mana.BlackMana(soulCounters), source, game).build()); + netMana.add(manaBuilder.setMana(Mana.BlueMana(soulCounters), source, game).build()); + netMana.add(manaBuilder.setMana(Mana.RedMana(soulCounters), source, game).build()); + netMana.add(manaBuilder.setMana(Mana.GreenMana(soulCounters), source, game).build()); + netMana.add(manaBuilder.setMana(Mana.WhiteMana(soulCounters), source, game).build()); + } + return netMana; + } + + @Override + public Mana produceMana(Game game, Ability source) { + Mana mana = new Mana(); + if (game == null) { + return mana; + } + + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (controller == null || permanent == null) { + return mana; + } + + ChoiceColor choice = new ChoiceColor(); + if (!controller.choose(Outcome.PutManaInPool, choice, game)) { + return mana; + } + Mana chosen = choice.getMana(permanent.getCounters(game).getCount(CounterType.SOUL)); + return manaBuilder.setMana(chosen, source, game).build(); + } + + @Override + public SeanceBoardManaEffect copy() { + return new SeanceBoardManaEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpymastersVault.java b/Mage.Sets/src/mage/cards/s/SpymastersVault.java index 6a29ec7d8c1..f2c4c70eb09 100644 --- a/Mage.Sets/src/mage/cards/s/SpymastersVault.java +++ b/Mage.Sets/src/mage/cards/s/SpymastersVault.java @@ -39,6 +39,7 @@ public final class SpymastersVault extends CardImpl { // {T}: Add {B}. this.addAbility(new BlackManaAbility()); + // {B}, {T}: Target creature you control connives X, where X is the number of creatures that died this turn. Ability ability = new SimpleActivatedAbility(new SpymastersVaultEffect(), new ManaCostsImpl<>("{B}")); ability.addCost(new TapSourceCost()); @@ -80,7 +81,7 @@ class SpymastersVaultEffect extends OneShotEffect { if (deaths < 1) { return false; } - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getFirstTarget()); if (permanent == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/s/StoneDrake.java b/Mage.Sets/src/mage/cards/s/StoneDrake.java new file mode 100644 index 00000000000..fc0e1516287 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StoneDrake.java @@ -0,0 +1,93 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.Game; +import mage.target.TargetPlayer; +import mage.target.common.TargetLandPermanent; +import mage.watchers.common.CastSpellLastTurnWatcher; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class StoneDrake extends CardImpl { + + private static final Hint hint + = new ValueHint("Spells you've cast this turn", StoneDrakeDynamicValue.instance); + + public StoneDrake(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}"); + + this.subtype.add(SubType.DRAKE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Stone Drake enters the battlefield, choose one — + //• Distract — Tap target land. Draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addEffect(new DrawCardSourceControllerEffect(1)); + ability.addTarget(new TargetLandPermanent()); + ability.withFirstModeFlavorWord("Distract"); + + //• Enthrall — Target player discards a card for each spell you've cast this turn. + ability.addMode(new Mode(new DiscardTargetEffect(StoneDrakeDynamicValue.instance)) + .addTarget(new TargetPlayer()) + .withFlavorWord("Enthrall")); + this.addAbility(ability.addHint(hint)); + } + + private StoneDrake(final StoneDrake card) { + super(card); + } + + @Override + public StoneDrake copy() { + return new StoneDrake(this); + } +} + +enum StoneDrakeDynamicValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getState() + .getWatcher(CastSpellLastTurnWatcher.class) + .getAmountOfSpellsPlayerCastOnCurrentTurn(sourceAbility.getControllerId()); + } + + @Override + public StoneDrakeDynamicValue copy() { + return this; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "spell you've cast this turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheInfamousCruelclaw.java b/Mage.Sets/src/mage/cards/t/TheInfamousCruelclaw.java new file mode 100644 index 00000000000..6ce8b714afa --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheInfamousCruelclaw.java @@ -0,0 +1,118 @@ +package mage.cards.t; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author notgreat + */ +public final class TheInfamousCruelclaw extends CardImpl { + + public TheInfamousCruelclaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WEASEL); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Whenever The Infamous Cruelclaw deals combat damage to a player, exile cards from the top of your library until you exile a nonland card. You may cast that card by discarding a card rather than paying its mana cost. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new InfamousCruelclawEffect())); + } + + private TheInfamousCruelclaw(final TheInfamousCruelclaw card) { + super(card); + } + + @Override + public TheInfamousCruelclaw copy() { + return new TheInfamousCruelclaw(this); + } +} + +//Based on Amped Raptor +class InfamousCruelclawEffect extends OneShotEffect { + + InfamousCruelclawEffect() { + super(Outcome.PlayForFree); + staticText = "exile cards from the top of your library until you exile a nonland card. " + + "You may cast that card by discarding a card rather than paying its mana cost."; + } + + private InfamousCruelclawEffect(final InfamousCruelclawEffect effect) { + super(effect); + } + + @Override + public InfamousCruelclawEffect copy() { + return new InfamousCruelclawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !controller.getLibrary().hasCards()) { + return false; + } + for (Card card : controller.getLibrary().getCards(game)) { + controller.moveCards(card, Zone.EXILED, source, game); + if (!card.isLand(game)) { + List castableComponents = CardUtil.getCastableComponents(card, null, source, controller, game, null, false); + if (castableComponents.isEmpty()) { + break; + } + String partsInfo = castableComponents + .stream() + .map(MageObject::getLogName) + .collect(Collectors.joining(" or ")); + if (!controller.chooseUse(Outcome.PlayForFree, "Cast spell by discarding a card instead of mana (" + partsInfo + ")?", source, game)) { + break; + } + castableComponents.forEach(partCard -> game.getState().setValue("PlayFromNotOwnHandZone" + partCard.getId(), Boolean.TRUE)); + SpellAbility chosenAbility = controller.chooseAbilityForCast(card, game, true); + if (chosenAbility != null) { + Card faceCard = game.getCard(chosenAbility.getSourceId()); + if (faceCard != null) { + // discard instead of mana cost + Costs newCosts = new CostsImpl<>(); + newCosts.add(new DiscardCardCost()); + newCosts.addAll(chosenAbility.getCosts()); + controller.setCastSourceIdWithAlternateMana(faceCard.getId(), null, newCosts); + controller.cast( + chosenAbility, game, true, + new ApprovingObject(source, game) + ); + } + } + castableComponents.forEach(partCard -> game.getState().setValue("PlayFromNotOwnHandZone" + partCard.getId(), null)); + break; + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheMindskinner.java b/Mage.Sets/src/mage/cards/t/TheMindskinner.java index 067e522d99c..9575c061581 100644 --- a/Mage.Sets/src/mage/cards/t/TheMindskinner.java +++ b/Mage.Sets/src/mage/cards/t/TheMindskinner.java @@ -64,6 +64,11 @@ class TheMindskinnerEffect extends PreventionEffectImpl { return new TheMindskinnerEffect(this); } + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGE_PLAYER; + } + @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { int amount = event.getAmount(); @@ -79,6 +84,6 @@ class TheMindskinnerEffect extends PreventionEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return super.applies(event, source, game) && source.isControlledBy(game.getControllerId(event.getSourceId())); + return super.applies(event, source, game) && source.isControlledBy(game.getControllerId(event.getSourceId())) && game.getOpponents(game.getControllerId(event.getSourceId())).contains(event.getTargetId()); } } diff --git a/Mage.Sets/src/mage/cards/w/WicksPatrol.java b/Mage.Sets/src/mage/cards/w/WicksPatrol.java index 281d966e0a6..c68905df1bf 100644 --- a/Mage.Sets/src/mage/cards/w/WicksPatrol.java +++ b/Mage.Sets/src/mage/cards/w/WicksPatrol.java @@ -17,6 +17,7 @@ import mage.constants.SubType; import mage.game.Controllable; import mage.game.Game; import mage.players.Player; +import mage.target.common.TargetOpponentsCreaturePermanent; import java.util.Collection; import java.util.Optional; @@ -75,9 +76,11 @@ class WicksPatrolEffect extends OneShotEffect { if (player == null || player.millCards(3, source, game).size() < 3) { return false; } - game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility( + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( new BoostTargetEffect(WicksPatrolValue.instance, WicksPatrolValue.instance), false - ), source); + ).setTriggerPhrase("When you do, "); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + game.fireReflexiveTriggeredAbility(ability, source); return true; } } @@ -87,7 +90,7 @@ enum WicksPatrolValue implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return Optional + return -1 * Optional .ofNullable(sourceAbility) .map(Controllable::getControllerId) .map(game::getPlayer) @@ -112,6 +115,11 @@ enum WicksPatrolValue implements DynamicValue { @Override public String toString() { - return "X"; + return "-X"; + } + + @Override + public int getSign() { + return -1; } } diff --git a/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java b/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java index 411c15dfe35..3c06fb78394 100644 --- a/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java +++ b/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java @@ -1,29 +1,19 @@ package mage.cards.y; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author LevelX2 @@ -34,11 +24,11 @@ public final class YawgmothsAgenda extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{B}{B}"); // You can't cast more than one spell each turn. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantCastMoreThanOneSpellEffect(TargetController.YOU))); + this.addAbility(new SimpleStaticAbility(new CantCastMoreThanOneSpellEffect(TargetController.YOU))); // You may play cards from your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new YawgmothsAgendaCanPlayCardsFromGraveyardEffect())); + this.addAbility(new SimpleStaticAbility(new YawgmothsAgendaCanPlayCardsFromGraveyardEffect())); // If a card would be put into your graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new YawgmothsAgendaReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, false))); } private YawgmothsAgenda(final YawgmothsAgenda card) { @@ -80,48 +70,4 @@ class YawgmothsAgendaCanPlayCardsFromGraveyardEffect extends ContinuousEffectImp } return false; } - -} - -class YawgmothsAgendaReplacementEffect extends ReplacementEffectImpl { - - YawgmothsAgendaReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere, exile it instead"; - } - - private YawgmothsAgendaReplacementEffect(final YawgmothsAgendaReplacementEffect effect) { - super(effect); - } - - @Override - public YawgmothsAgendaReplacementEffect copy() { - return new YawgmothsAgendaReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - } diff --git a/Mage.Sets/src/mage/cards/y/YawgmothsWill.java b/Mage.Sets/src/mage/cards/y/YawgmothsWill.java index 0d8678a1ae5..f6af0a369a1 100644 --- a/Mage.Sets/src/mage/cards/y/YawgmothsWill.java +++ b/Mage.Sets/src/mage/cards/y/YawgmothsWill.java @@ -1,27 +1,17 @@ package mage.cards.y; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author LevelX2 @@ -35,7 +25,7 @@ public final class YawgmothsWill extends CardImpl { this.getSpellAbility().addEffect(new CanPlayCardsFromGraveyardEffect()); // If a card would be put into your graveyard from anywhere this turn, exile that card instead. - this.getSpellAbility().addEffect(new YawgmothsWillReplacementEffect()); + this.getSpellAbility().addEffect(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn).concatBy("
")); } private YawgmothsWill(final YawgmothsWill card) { @@ -79,46 +69,3 @@ class CanPlayCardsFromGraveyardEffect extends ContinuousEffectImpl { } } - -class YawgmothsWillReplacementEffect extends ReplacementEffectImpl { - - YawgmothsWillReplacementEffect() { - super(Duration.EndOfTurn, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere this turn, exile that card instead"; - } - - private YawgmothsWillReplacementEffect(final YawgmothsWillReplacementEffect effect) { - super(effect); - } - - @Override - public YawgmothsWillReplacementEffect copy() { - return new YawgmothsWillReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((ZoneChangeEvent) event).setToZone(Zone.EXILED); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/sets/Apocalypse.java b/Mage.Sets/src/mage/sets/Apocalypse.java index f84e4b9e75c..fc95e2bcd35 100644 --- a/Mage.Sets/src/mage/sets/Apocalypse.java +++ b/Mage.Sets/src/mage/sets/Apocalypse.java @@ -3,6 +3,13 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; public final class Apocalypse extends ExpansionSet { @@ -167,4 +174,38 @@ public final class Apocalypse extends ExpansionSet { cards.add(new SetCardInfo("Yavimaya's Embrace", 127, Rarity.RARE, mage.cards.y.YavimayasEmbrace.class)); cards.add(new SetCardInfo("Zombie Boa", 54, Rarity.COMMON, mage.cards.z.ZombieBoa.class)); } + + @Override + public BoosterCollator createCollator() { + return new ApocalypseCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/jud.html +// Using US collation - commons only +class ApocalypseCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "49", "81", "103", "19", "66", "118", "4", "37", "125", "90", "28", "101", "57", "14", "117", "37", "78", "119", "19", "57", "109", "13", "53", "101", "78", "27", "109", "66", "14", "119", "54", "81", "117", "27", "71", "103", "4", "53", "120", "90", "22", "93", "64", "3", "125", "49", "85", "93", "28", "71", "122", "3", "54", "120", "85", "22", "118", "64", "13", "122"); + private final CardRun commonB = new CardRun(true, "42", "76", "30", "56", "8", "43", "89", "25", "56", "16", "51", "76", "26", "60", "18", "44", "86", "25", "60", "1", "51", "86", "26", "65", "1", "41", "89", "29", "69", "18", "42", "82", "35", "69", "8", "44", "73", "30", "70", "16", "43", "82", "29", "70", "15", "41", "73", "35", "65", "15"); + private final CardRun uncommon = new CardRun(false, "91", "74", "58", "133", "92", "20", "2", "96", "5", "7", "134", "135", "61", "97", "136", "9", "23", "128", "99", "39", "77", "102", "62", "40", "12", "63", "129", "106", "79", "130", "110", "111", "45", "131", "132", "83", "48", "67", "123", "52", "87", "31", "33", "34"); + private final CardRun rare = new CardRun(false, "75", "139", "55", "140", "21", "94", "95", "6", "38", "59", "10", "98", "100", "11", "104", "105", "24", "80", "107", "137", "108", "141", "138", "112", "46", "113", "84", "114", "47", "50", "115", "116", "68", "142", "17", "121", "124", "88", "32", "126", "36", "72", "143", "127"); + + private final BoosterStructure AAAAAABBBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, commonB + ); + private final BoosterStructure U3 = new BoosterStructure(uncommon, uncommon, uncommon); + private final BoosterStructure R1 = new BoosterStructure(rare); + + private final RarityConfiguration commonRuns = new RarityConfiguration(AAAAAABBBBB); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(U3); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 3150e3f8845..531396e3e2c 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -37,6 +37,7 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Basim Ibn Ishaq", 49, Rarity.RARE, mage.cards.b.BasimIbnIshaq.class)); cards.add(new SetCardInfo("Battlefield Improvisation", 276, Rarity.COMMON, mage.cards.b.BattlefieldImprovisation.class)); cards.add(new SetCardInfo("Bayek of Siwa", 50, Rarity.RARE, mage.cards.b.BayekOfSiwa.class)); + cards.add(new SetCardInfo("Become Anonymous", 14, Rarity.UNCOMMON, mage.cards.b.BecomeAnonymous.class)); cards.add(new SetCardInfo("Black Market Connections", 87, Rarity.RARE, mage.cards.b.BlackMarketConnections.class)); cards.add(new SetCardInfo("Bleeding Effect", 51, Rarity.UNCOMMON, mage.cards.b.BleedingEffect.class)); cards.add(new SetCardInfo("Brotherhood Ambushers", 285, Rarity.UNCOMMON, mage.cards.b.BrotherhoodAmbushers.class)); diff --git a/Mage.Sets/src/mage/sets/Bloomburrow.java b/Mage.Sets/src/mage/sets/Bloomburrow.java index 4ce257ae16d..40212886700 100644 --- a/Mage.Sets/src/mage/sets/Bloomburrow.java +++ b/Mage.Sets/src/mage/sets/Bloomburrow.java @@ -80,6 +80,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Diresight", 91, Rarity.COMMON, mage.cards.d.Diresight.class)); cards.add(new SetCardInfo("Dour Port-Mage", 47, Rarity.RARE, mage.cards.d.DourPortMage.class)); cards.add(new SetCardInfo("Downwind Ambusher", 92, Rarity.UNCOMMON, mage.cards.d.DownwindAmbusher.class)); + cards.add(new SetCardInfo("Dragonhawk, Fate's Tempest", 132, Rarity.MYTHIC, mage.cards.d.DragonhawkFatesTempest.class)); cards.add(new SetCardInfo("Dreamdew Entrancer", 211, Rarity.RARE, mage.cards.d.DreamdewEntrancer.class)); cards.add(new SetCardInfo("Driftgloom Coyote", 11, Rarity.UNCOMMON, mage.cards.d.DriftgloomCoyote.class)); cards.add(new SetCardInfo("Druid of the Spade", 170, Rarity.COMMON, mage.cards.d.DruidOfTheSpade.class)); @@ -94,6 +95,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Fecund Greenshell", 362, Rarity.RARE, mage.cards.f.FecundGreenshell.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Feed the Cycle", 94, Rarity.UNCOMMON, mage.cards.f.FeedTheCycle.class)); cards.add(new SetCardInfo("Fell", 95, Rarity.UNCOMMON, mage.cards.f.Fell.class)); + cards.add(new SetCardInfo("Festival of Embers", 134, Rarity.RARE, mage.cards.f.FestivalOfEmbers.class)); cards.add(new SetCardInfo("Finch Formation", 50, Rarity.COMMON, mage.cards.f.FinchFormation.class)); cards.add(new SetCardInfo("Finneas, Ace Archer", 212, Rarity.RARE, mage.cards.f.FinneasAceArcher.class)); cards.add(new SetCardInfo("Fireglass Mentor", 213, Rarity.UNCOMMON, mage.cards.f.FireglassMentor.class)); @@ -135,6 +137,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Intrepid Rabbit", 17, Rarity.COMMON, mage.cards.i.IntrepidRabbit.class)); cards.add(new SetCardInfo("Iridescent Vinelasher", 99, Rarity.RARE, mage.cards.i.IridescentVinelasher.class)); cards.add(new SetCardInfo("Island", 266, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Jackdaw Savior", 18, Rarity.RARE, mage.cards.j.JackdawSavior.class)); cards.add(new SetCardInfo("Jolly Gerbils", 19, Rarity.UNCOMMON, mage.cards.j.JollyGerbils.class)); cards.add(new SetCardInfo("Junkblade Bruiser", 220, Rarity.COMMON, mage.cards.j.JunkbladeBruiser.class)); cards.add(new SetCardInfo("Kastral, the Windcrested", 221, Rarity.RARE, mage.cards.k.KastralTheWindcrested.class)); @@ -259,6 +262,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Teapot Slinger", 157, Rarity.UNCOMMON, mage.cards.t.TeapotSlinger.class)); cards.add(new SetCardInfo("Tempest Angler", 235, Rarity.COMMON, mage.cards.t.TempestAngler.class)); cards.add(new SetCardInfo("Tender Wildguide", 196, Rarity.RARE, mage.cards.t.TenderWildguide.class)); + cards.add(new SetCardInfo("The Infamous Cruelclaw", 219, Rarity.MYTHIC, mage.cards.t.TheInfamousCruelclaw.class)); cards.add(new SetCardInfo("Thieving Otter", 390, Rarity.COMMON, mage.cards.t.ThievingOtter.class)); cards.add(new SetCardInfo("Thistledown Players", 35, Rarity.COMMON, mage.cards.t.ThistledownPlayers.class)); cards.add(new SetCardInfo("Thornplate Intimidator", 117, Rarity.COMMON, mage.cards.t.ThornplateIntimidator.class)); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index aefc2b6eddb..a9a0c7f2350 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -61,7 +61,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Burnished Hart", 243, Rarity.UNCOMMON, mage.cards.b.BurnishedHart.class)); cards.add(new SetCardInfo("Cackling Counterpart", 72, Rarity.RARE, mage.cards.c.CacklingCounterpart.class)); cards.add(new SetCardInfo("Canyon Slough", 266, Rarity.RARE, mage.cards.c.CanyonSlough.class)); - cards.add(new SetCardInfo("Carrion Grub", 134, Rarity.COMMON, mage.cards.c.CarrionGrub.class)); + cards.add(new SetCardInfo("Carrion Grub", 134, Rarity.UNCOMMON, mage.cards.c.CarrionGrub.class)); cards.add(new SetCardInfo("Cast Out", 98, Rarity.UNCOMMON, mage.cards.c.CastOut.class)); cards.add(new SetCardInfo("Castle Vantress", 267, Rarity.RARE, mage.cards.c.CastleVantress.class)); cards.add(new SetCardInfo("Caves of Koilos", 268, Rarity.RARE, mage.cards.c.CavesOfKoilos.class)); @@ -115,8 +115,8 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Giant Adephage", 179, Rarity.MYTHIC, mage.cards.g.GiantAdephage.class)); cards.add(new SetCardInfo("Gleeful Arsonist", 27, Rarity.RARE, mage.cards.g.GleefulArsonist.class)); cards.add(new SetCardInfo("Gnarlwood Dryad", 180, Rarity.UNCOMMON, mage.cards.g.GnarlwoodDryad.class)); - cards.add(new SetCardInfo("Golgari Rot Farm", 279, Rarity.COMMON, mage.cards.g.GolgariRotFarm.class)); - cards.add(new SetCardInfo("Golgari Signet", 246, Rarity.COMMON, mage.cards.g.GolgariSignet.class)); + cards.add(new SetCardInfo("Golgari Rot Farm", 279, Rarity.UNCOMMON, mage.cards.g.GolgariRotFarm.class)); + cards.add(new SetCardInfo("Golgari Signet", 246, Rarity.UNCOMMON, mage.cards.g.GolgariSignet.class)); cards.add(new SetCardInfo("Goryo's Vengeance", 372, Rarity.MYTHIC, mage.cards.g.GoryosVengeance.class)); cards.add(new SetCardInfo("Grapple with the Past", 82, Rarity.COMMON, mage.cards.g.GrappleWithThePast.class)); cards.add(new SetCardInfo("Graven Cairns", 280, Rarity.RARE, mage.cards.g.GravenCairns.class)); @@ -205,6 +205,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Read the Bones", 154, Rarity.COMMON, mage.cards.r.ReadTheBones.class)); cards.add(new SetCardInfo("Reality Shift", 125, Rarity.UNCOMMON, mage.cards.r.RealityShift.class)); cards.add(new SetCardInfo("Reanimate", 155, Rarity.RARE, mage.cards.r.Reanimate.class)); + cards.add(new SetCardInfo("Redress Fate", 9, Rarity.RARE, mage.cards.r.RedressFate.class)); cards.add(new SetCardInfo("Reliquary Tower", 295, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); cards.add(new SetCardInfo("Retreat to Coralhelm", 126, Rarity.UNCOMMON, mage.cards.r.RetreatToCoralhelm.class)); cards.add(new SetCardInfo("Return to Dust", 102, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); @@ -213,6 +214,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Scavenging Ooze", 196, Rarity.RARE, mage.cards.s.ScavengingOoze.class)); cards.add(new SetCardInfo("Scroll of Fate", 251, Rarity.RARE, mage.cards.s.ScrollOfFate.class)); cards.add(new SetCardInfo("Scute Swarm", 197, Rarity.RARE, mage.cards.s.ScuteSwarm.class)); + cards.add(new SetCardInfo("Seance Board", 40, Rarity.RARE, mage.cards.s.SeanceBoard.class)); cards.add(new SetCardInfo("Shadowblood Ridge", 296, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); cards.add(new SetCardInfo("Shark Typhoon", 127, Rarity.RARE, mage.cards.s.SharkTyphoon.class)); cards.add(new SetCardInfo("Shigeki, Jukai Visionary", 198, Rarity.RARE, mage.cards.s.ShigekiJukaiVisionary.class)); @@ -262,7 +264,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("They Came from the Pipes", 14, Rarity.RARE, mage.cards.t.TheyCameFromThePipes.class)); cards.add(new SetCardInfo("Thirst for Meaning", 129, Rarity.COMMON, mage.cards.t.ThirstForMeaning.class)); cards.add(new SetCardInfo("Thornwood Falls", 314, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); - cards.add(new SetCardInfo("Thought Vessel", 256, Rarity.UNCOMMON, mage.cards.t.ThoughtVessel.class)); + cards.add(new SetCardInfo("Thought Vessel", 256, Rarity.COMMON, mage.cards.t.ThoughtVessel.class)); cards.add(new SetCardInfo("Thriving Heath", 315, Rarity.COMMON, mage.cards.t.ThrivingHeath.class)); cards.add(new SetCardInfo("Thriving Isle", 316, Rarity.COMMON, mage.cards.t.ThrivingIsle.class)); cards.add(new SetCardInfo("Thriving Moor", 317, Rarity.COMMON, mage.cards.t.ThrivingMoor.class)); diff --git a/Mage.Sets/src/mage/sets/Judgment.java b/Mage.Sets/src/mage/sets/Judgment.java index cd8c93471b1..8a15c2a0d2d 100644 --- a/Mage.Sets/src/mage/sets/Judgment.java +++ b/Mage.Sets/src/mage/sets/Judgment.java @@ -1,9 +1,15 @@ - package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; /** * @@ -29,6 +35,7 @@ public final class Judgment extends ExpansionSet { this.numBoosterRare = 1; this.ratioBoosterMythic = 0; this.hasUnbalancedColors = true; + cards.add(new SetCardInfo("Ancestor's Chosen", 1, Rarity.UNCOMMON, mage.cards.a.AncestorsChosen.class)); cards.add(new SetCardInfo("Anger", 77, Rarity.UNCOMMON, mage.cards.a.Anger.class)); cards.add(new SetCardInfo("Anurid Barkripper", 104, Rarity.COMMON, mage.cards.a.AnuridBarkripper.class)); @@ -172,4 +179,39 @@ public final class Judgment extends ExpansionSet { cards.add(new SetCardInfo("Wormfang Newt", 59, Rarity.COMMON, mage.cards.w.WormfangNewt.class)); cards.add(new SetCardInfo("Wormfang Turtle", 60, Rarity.UNCOMMON, mage.cards.w.WormfangTurtle.class)); } + + @Override + public BoosterCollator createCollator() { + return new JudgmentCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/jud.html +// Using US collation - commons only +class JudgmentCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "22", "59", "118", "100", "4", "76", "126", "59", "95", "17", "57", "120", "28", "63", "106", "4", "91", "129", "42", "22", "89", "125", "47", "63", "57", "123", "86", "43", "5", "106", "33", "89", "30", "104", "43", "17", "34", "6", "123", "79", "76", "125", "42", "95", "126", "33", "100", "129", "28", "79", "120", "30", "86", "47", "118", "6", "91", "5", "104", "34"); + private final CardRun commonB = new CardRun(true, "121", "102", "108", "45", "71", "132", "14", "87", "41", "109", "13", "115", "39", "78", "18", "74", "20", "65", "46", "10", "121", "38", "20", "80", "132", "10", "87", "71", "41", "102", "136", "74", "7", "80", "109", "39", "13", "136", "46", "108", "7", "94", "18", "45", "115", "78", "65", "38", "94", "14"); + private final CardRun uncommon = new CardRun(false, "1", "77", "105", "2", "3", "107", "82", "62", "36", "8", "85", "111", "114", "66", "88", "40", "116", "67", "119", "92", "122", "141", "44", "142", "127", "16", "97", "49", "143", "131", "25", "99", "26", "27", "72", "101", "75", "135", "31", "32", "53", "54", "56", "60"); + // Shaman's Trance (rare, #98) not implemented, so has been omitted + private final CardRun rare = new CardRun(false, "137", "61", "81", "83", "35", "9", "110", "37", "64", "84", "112", "113", "90", "117", "11", "12", "68", "138", "93", "96", "124", "69", "139", "48", "70", "15", "128", "140", "19", "50", "130", "21", "23", "24", "51", "73", "133", "52", "29", "134", "103", "55", "58"); + + private final BoosterStructure AAAAAABBBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, commonB + ); + private final BoosterStructure U3 = new BoosterStructure(uncommon, uncommon, uncommon); + private final BoosterStructure R1 = new BoosterStructure(rare); + + private final RarityConfiguration commonRuns = new RarityConfiguration(AAAAAABBBBB); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(U3); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java index b9e201f507f..067dd103e5c 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManorCommander.java @@ -46,6 +46,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Black Sun's Zenith", 126, Rarity.RARE, mage.cards.b.BlackSunsZenith.class)); cards.add(new SetCardInfo("Bloodthirsty Blade", 225, Rarity.UNCOMMON, mage.cards.b.BloodthirstyBlade.class)); cards.add(new SetCardInfo("Bojuka Bog", 250, Rarity.COMMON, mage.cards.b.BojukaBog.class)); + cards.add(new SetCardInfo("Boltbender", 30, Rarity.RARE, mage.cards.b.Boltbender.class)); cards.add(new SetCardInfo("Boros Garrison", 251, Rarity.UNCOMMON, mage.cards.b.BorosGarrison.class)); cards.add(new SetCardInfo("Boros Reckoner", 201, Rarity.RARE, mage.cards.b.BorosReckoner.class)); cards.add(new SetCardInfo("Brainstorm", 96, Rarity.COMMON, mage.cards.b.Brainstorm.class)); @@ -125,10 +126,12 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Graf Mole", 170, Rarity.UNCOMMON, mage.cards.g.GrafMole.class)); cards.add(new SetCardInfo("Grave Titan", 129, Rarity.MYTHIC, mage.cards.g.GraveTitan.class)); cards.add(new SetCardInfo("Gruul Turf", 265, Rarity.UNCOMMON, mage.cards.g.GruulTurf.class)); + cards.add(new SetCardInfo("Havoc Eater", 31, Rarity.RARE, mage.cards.h.HavocEater.class)); cards.add(new SetCardInfo("Hidden Dragonslayer", 69, Rarity.RARE, mage.cards.h.HiddenDragonslayer.class)); cards.add(new SetCardInfo("Hooded Hydra", 171, Rarity.MYTHIC, mage.cards.h.HoodedHydra.class)); cards.add(new SetCardInfo("Hornet Queen", 172, Rarity.RARE, mage.cards.h.HornetQueen.class)); cards.add(new SetCardInfo("Hostile Desert", 266, Rarity.RARE, mage.cards.h.HostileDesert.class)); + cards.add(new SetCardInfo("Hot Pursuit", 32, Rarity.RARE, mage.cards.h.HotPursuit.class)); cards.add(new SetCardInfo("Hydroid Krasis", 212, Rarity.RARE, mage.cards.h.HydroidKrasis.class)); cards.add(new SetCardInfo("Idol of Oblivion", 229, Rarity.RARE, mage.cards.i.IdolOfOblivion.class)); cards.add(new SetCardInfo("Imperial Hellkite", 155, Rarity.RARE, mage.cards.i.ImperialHellkite.class)); @@ -212,6 +215,7 @@ public final class MurdersAtKarlovManorCommander extends ExpansionSet { cards.add(new SetCardInfo("Prisoner's Dilemma", 34, Rarity.RARE, mage.cards.p.PrisonersDilemma.class)); cards.add(new SetCardInfo("Promise of Loyalty", 79, Rarity.RARE, mage.cards.p.PromiseOfLoyalty.class)); cards.add(new SetCardInfo("Psychosis Crawler", 234, Rarity.RARE, mage.cards.p.PsychosisCrawler.class)); + cards.add(new SetCardInfo("Ransom Note", 45, Rarity.RARE, mage.cards.r.RansomNote.class)); cards.add(new SetCardInfo("Ravenous Chupacabra", 136, Rarity.UNCOMMON, mage.cards.r.RavenousChupacabra.class)); cards.add(new SetCardInfo("Reanimate", 137, Rarity.RARE, mage.cards.r.Reanimate.class)); cards.add(new SetCardInfo("Redemption Arc", 13, Rarity.RARE, mage.cards.r.RedemptionArc.class)); diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 8b1778af764..74fcfd4e4e4 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -232,6 +232,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Static Orb", 234, Rarity.RARE, mage.cards.s.StaticOrb.class)); cards.add(new SetCardInfo("Stifle", 173, Rarity.RARE, mage.cards.s.Stifle.class)); cards.add(new SetCardInfo("Stitcher's Supplier", 48, Rarity.UNCOMMON, mage.cards.s.StitchersSupplier.class)); + cards.add(new SetCardInfo("Stone Drake", 363, Rarity.RARE, mage.cards.s.StoneDrake.class)); cards.add(new SetCardInfo("Stony Silence", 21, Rarity.RARE, mage.cards.s.StonySilence.class)); cards.add(new SetCardInfo("Street Wraith", 49, Rarity.COMMON, mage.cards.s.StreetWraith.class)); cards.add(new SetCardInfo("Summoner's Pact", 74, Rarity.RARE, mage.cards.s.SummonersPact.class)); diff --git a/Mage.Sets/src/mage/sets/Odyssey.java b/Mage.Sets/src/mage/sets/Odyssey.java index 910c52eef78..fdf79b25f11 100644 --- a/Mage.Sets/src/mage/sets/Odyssey.java +++ b/Mage.Sets/src/mage/sets/Odyssey.java @@ -3,6 +3,14 @@ package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; +import mage.util.RandomUtil; + +import java.util.ArrayList; +import java.util.List; /** * @author North @@ -379,4 +387,127 @@ public final class Odyssey extends ExpansionSet { cards.add(new SetCardInfo("Zombify", 171, Rarity.UNCOMMON, mage.cards.z.Zombify.class)); cards.add(new SetCardInfo("Zoologist", 285, Rarity.RARE, mage.cards.z.Zoologist.class)); } + + @Override + public BoosterCollator createCollator() { + return new OdysseyCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/ody.html +// Using US collation - commons only +class OdysseyCollator implements BoosterCollator { + + private final CardRun commonA = new CardRun(true, "189", "93", "269", "136", "44", "199", "59", "233", "119", "14", "213", "109", "262", "145", "30", "185", "93", "268", "148", "56", "189", "109", "284", "136", "14", "199", "99", "269", "154", "44", "185", "87", "284", "145", "56", "225", "99", "262", "154", "16", "213", "59", "268", "119", "30", "225", "87", "233", "148", "16"); + private final CardRun commonB = new CardRun(true, "184", "312", "251", "150", "38", "172", "330", "247", "115", "7", "324", "95", "272", "151", "3", "314", "83", "251", "167", "312", "172", "103", "272", "115", "314", "190", "70", "246", "324", "27", "196", "103", "247", "325", "38", "184", "95", "246", "151", "7", "196", "70", "325", "150", "27", "190", "83", "330", "167", "3"); + private final CardRun commonC = new CardRun(true, "45", "140", "194", "63", "237", "8", "138", "224", "72", "253", "46", "139", "175", "98", "237", "45", "131", "181", "63", "263", "22", "125", "215", "65", "283", "25", "131", "193", "74", "253", "22", "138", "194", "98", "263", "36", "140", "181", "65", "282", "8", "139", "215", "74", "240", "25", "156", "193", "91", "282", "46", "125", "175", "72", "283", "36", "156", "224", "91", "240"); + private final CardRun commonD = new CardRun(true, "60", "232", "168", "42", "202", "114", "265", "130", "41", "173", "84", "252", "169", "40", "219", "60", "249", "124", "15", "202", "107", "252", "146", "41", "211", "114", "232", "124", "35", "217", "107", "238", "130", "15", "178", "100", "264", "137", "42", "217", "84", "238", "168", "5", "178", "81", "249", "146", "40", "211", "100", "265", "137", "5", "219", "81", "264", "169", "35", "173"); + private final CardRun uncommon = new CardRun(false, "4", "62", "6", "64", "66", "67", "68", "313", "176", "177", "230", "11", "12", "118", "315", "122", "316", "71", "317", "76", "123", "78", "235", "299", "128", "236", "80", "183", "129", "239", "186", "188", "191", "133", "134", "135", "86", "195", "23", "241", "244", "144", "88", "200", "301", "201", "289", "31", "203", "147", "250", "302", "206", "304", "34", "254", "258", "37", "322", "260", "155", "306", "291", "261", "94", "214", "292", "96", "97", "216", "43", "266", "158", "159", "307", "293", "160", "308", "221", "161", "162", "309", "270", "47", "222", "48", "49", "50", "51", "52", "53", "274", "102", "310", "223", "275", "311", "277", "54", "55", "295", "104", "106", "111", "279", "281", "226", "228", "170", "171"); + private final CardRun rare = new CardRun(false, "58", "1", "61", "2", "174", "286", "9", "10", "69", "229", "116", "179", "117", "180", "120", "121", "231", "13", "297", "73", "75", "182", "298", "234", "77", "318", "79", "126", "319", "127", "287", "17", "320", "18", "82", "19", "20", "187", "21", "132", "192", "85", "24", "141", "242", "142", "143", "243", "197", "288", "245", "300", "198", "26", "28", "248", "29", "204", "32", "149", "205", "303", "207", "152", "321", "208", "33", "290", "255", "256", "209", "153", "257", "259", "210", "305", "212", "89", "90", "92", "323", "39", "157", "218", "220", "267", "326", "294", "101", "327", "271", "273", "163", "276", "328", "164", "329", "278", "105", "108", "165", "110", "166", "112", "113", "296", "280", "227", "57", "285"); + + // either A then B, or B then A + private final BoosterStructure AAABB = new BoosterStructure( + commonA, commonA, commonA, + commonB, commonB + ); + private final BoosterStructure AABBB = new BoosterStructure( + commonA, commonA, + commonB, commonB, commonB + ); + private final BoosterStructure AAAAB = new BoosterStructure( + commonA, commonA, commonA, commonA, + commonB + ); + private final BoosterStructure ABBBB = new BoosterStructure( + commonA, + commonB, commonB, commonB, commonB + ); + private final BoosterStructure BBBAA = new BoosterStructure( + commonB, commonB, commonB, + commonA, commonA + ); + private final BoosterStructure BBAAA = new BoosterStructure( + commonB, commonB, + commonA, commonA, commonA + ); + private final BoosterStructure BBBBA = new BoosterStructure( + commonB, commonB, commonB, commonB, + commonA + ); + private final BoosterStructure BAAAA = new BoosterStructure( + commonB, + commonA, commonA, commonA, commonA + ); + + // either C then D, or D then C + private final BoosterStructure CCCDDD = new BoosterStructure( + commonC, commonC, commonC, + commonD, commonD, commonD + ); + private final BoosterStructure CCCCDD = new BoosterStructure( + commonC, commonC, commonC, commonC, + commonD, commonD + ); + private final BoosterStructure CCDDDD = new BoosterStructure( + commonC, commonC, + commonD, commonD, commonD, commonD + ); + private final BoosterStructure DDDCCC = new BoosterStructure( + commonD, commonD, commonD, + commonC, commonC, commonC + ); + private final BoosterStructure DDDDCC = new BoosterStructure( + commonD, commonD, commonD, commonD, + commonC, commonC + ); + private final BoosterStructure DDCCCC = new BoosterStructure( + commonD, commonD, + commonC, commonC, commonC, commonC + ); + + private final BoosterStructure U3 = new BoosterStructure(uncommon, uncommon, uncommon); + private final BoosterStructure R1 = new BoosterStructure(rare); + + // no definitive ratio here, just "most" packs 3-2 + private final RarityConfiguration commonRunsAB = new RarityConfiguration( + AAAAB, + AAABB, AAABB, AAABB, AAABB, AAABB, AAABB, AAABB, AAABB, AAABB, + AABBB, AABBB, AABBB, AABBB, AABBB, AABBB, AABBB, AABBB, AABBB, + ABBBB, + BBBBA, + BBBAA, BBBAA, BBBAA, BBBAA, BBBAA, BBBAA, BBBAA, BBBAA, BBBAA, + BBAAA, BBAAA, BBAAA, BBAAA, BBAAA, BBAAA, BBAAA, BBAAA, BBAAA, + BAAAA + ); + + // similar collation structure for INV suggests some evidence for 1/6 packs 4-2 + private final RarityConfiguration commonRunsCD = new RarityConfiguration( + CCCCDD, + CCCDDD, CCCDDD, CCCDDD, CCCDDD, CCCDDD, + CCCDDD, CCCDDD, CCCDDD, CCCDDD, CCCDDD, + CCDDDD, + DDDDCC, + DDDCCC, DDDCCC, DDDCCC, DDDCCC, DDDCCC, + DDDCCC, DDDCCC, DDDCCC, DDDCCC, DDDCCC, + DDCCCC + ); + + private final RarityConfiguration uncommonRuns = new RarityConfiguration(U3); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + // either A/B or C/D can be first in the pack + if (RandomUtil.nextBoolean()) { + booster.addAll(commonRunsAB.getNext().makeRun()); + booster.addAll(commonRunsCD.getNext().makeRun()); + } else { + booster.addAll(commonRunsCD.getNext().makeRun()); + booster.addAll(commonRunsAB.getNext().makeRun()); + } + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/Onslaught.java b/Mage.Sets/src/mage/sets/Onslaught.java index d6c97612d39..267d4cdda31 100644 --- a/Mage.Sets/src/mage/sets/Onslaught.java +++ b/Mage.Sets/src/mage/sets/Onslaught.java @@ -5,6 +5,13 @@ import mage.cards.CardGraphicInfo; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; public final class Onslaught extends ExpansionSet { @@ -24,6 +31,7 @@ public final class Onslaught extends ExpansionSet { this.numBoosterUncommon = 3; this.numBoosterRare = 1; this.ratioBoosterMythic = 0; + cards.add(new SetCardInfo("Accursed Centaur", 123, Rarity.COMMON, mage.cards.a.AccursedCentaur.class)); cards.add(new SetCardInfo("Aether Charge", 184, Rarity.UNCOMMON, mage.cards.a.AetherCharge.class)); cards.add(new SetCardInfo("Aggravated Assault", 185, Rarity.RARE, mage.cards.a.AggravatedAssault.class)); @@ -374,4 +382,55 @@ public final class Onslaught extends ExpansionSet { cards.add(new SetCardInfo("Words of Worship", 61, Rarity.RARE, mage.cards.w.WordsOfWorship.class)); cards.add(new SetCardInfo("Wretched Anurid", 183, Rarity.COMMON, mage.cards.w.WretchedAnurid.class)); } + + @Override + public BoosterCollator createCollator() { + return new OnslaughtCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/ons.html +// Using US collation +class OnslaughtCollator implements BoosterCollator { + private final CardRun commonAC = new CardRun(true, "154", "50", "243", "77", "275", "138", "33", "188", "111", "304", "168", "49", "243", "114", "296", "166", "21", "210", "111", "253", "168", "29", "201", "106", "296", "172", "21", "202", "112", "253", "138", "18", "188", "99", "275", "154", "29", "215", "106", "304", "125", "18", "201", "114", "293", "166", "50", "202", "99", "302", "172", "33", "215", "77", "293", "125", "49", "210", "112", "302", "145", "9", "221", "70", "248", "162", "32", "209", "62", "260", "148", "9", "216", "94", "248", "124", "52", "220", "91", "301", "148", "19", "221", "62", "288", "145", "52", "194", "91", "290", "134", "31", "220", "74", "260", "162", "19", "209", "94", "288", "124", "31", "216", "70", "301", "134", "32", "194", "74", "290"); + private final CardRun commonB = new CardRun(true, "183", "35", "191", "113", "257", "176", "58", "208", "78", "256", "175", "25", "190", "88", "303", "139", "24", "191", "78", "255", "183", "23", "237", "119", "257", "139", "16", "208", "113", "300", "176", "23", "230", "90", "273", "158", "35", "196", "119", "255", "123", "25", "237", "115", "303", "175", "24", "190", "90", "300", "123", "58", "230", "115", "256", "158", "16", "196", "88", "273"); + private final CardRun commonD = new CardRun(true, "165", "26", "228", "68", "326", "312", "20", "227", "81", "284", "135", "34", "317", "68", "272", "159", "324", "234", "93", "246", "174", "26", "235", "320", "283", "159", "47", "317", "87", "272", "135", "20", "235", "81", "326", "165", "324", "227", "87", "284", "312", "34", "228", "93", "283", "174", "47", "234", "320", "246"); + private final CardRun uncommonA = new CardRun(true, "130", "41", "186", "98", "280", "157", "37", "193", "96", "292", "329", "126", "1", "241", "83", "251", "127", "41", "200", "95", "292", "315", "137", "37", "195", "83", "277", "128", "11", "233", "79", "262", "325", "170", "22", "203", "92", "277", "163", "11", "241", "85", "254", "323", "130", "38", "193", "76", "280", "129", "40", "203", "69", "298", "318", "137", "48", "242", "85", "262", "157", "22", "232", "96", "294", "329", "149", "40", "233", "98", "295", "170", "17", "186", "76", "287", "315", "149", "48", "232", "79", "295", "163", "8", "195", "82", "287", "325", "128", "1", "187", "95", "298", "126", "17", "242", "69", "251", "323", "127", "8", "187", "92", "254", "129", "38", "200", "82", "294", "318"); + private final CardRun uncommonB = new CardRun(true, "132", "12", "204", "107", "250", "144", "56", "226", "109", "249", "167", "15", "184", "65", "269", "171", "4", "199", "80", "258", "156", "51", "222", "73", "276", "180", "27", "184", "109", "258", "171", "5", "197", "110", "271", "177", "12", "217", "107", "279", "156", "54", "225", "64", "276", "131", "51", "231", "110", "252", "147", "27", "236", "100", "267", "167", "42", "225", "86", "265", "144", "54", "204", "63", "252", "131", "4", "240", "64", "269", "177", "45", "236", "63", "263", "151", "5", "199", "100", "279", "147", "45", "231", "105", "250", "181", "60", "217", "65", "267", "151", "56", "240", "105", "271", "180", "15", "197", "80", "249", "132", "60", "222", "86", "263", "181", "42", "226", "73", "265"); + // Artificial Evolution (rare, #67) not implemented, so removed from the run + private final CardRun rare = new CardRun(false, "75", "153", "213", "245", "13", "118", "140", "39", "198", "286", "322", "30", "291", "244", "161", "101", "36", "308", "274", "212", "160", "66", "316", "43", "247", "207", "306", "164", "102", "28", "270", "214", "178", "84", "313", "3", "285", "185", "97", "136", "229", "282", "310", "53", "71", "182", "319", "238", "261", "55", "281", "141", "108", "307", "57", "297", "205", "143", "104", "330", "179", "61", "211", "103", "305", "173", "10", "189", "120", "268", "155", "327", "7", "223", "116", "278", "309", "146", "2", "218", "72", "289", "152", "328", "44", "239", "122", "264", "133", "46", "219", "117", "311", "259", "150", "321", "59", "192", "121", "266", "142", "14", "224", "299", "169", "6", "314", "206", "89"); + + private final BoosterStructure AAAAABBBDDD = new BoosterStructure( + commonAC, commonAC, commonAC, commonAC, commonAC, + commonB, commonB, commonB, + commonD, commonD, commonD + ); + private final BoosterStructure AAAAAABBBDD = new BoosterStructure( + commonAC, commonAC, commonAC, commonAC, commonAC, commonAC, + commonB, commonB, commonB, + commonD, commonD + ); + private final BoosterStructure ABB = new BoosterStructure(uncommonA, uncommonB, uncommonB); + private final BoosterStructure AAB = new BoosterStructure(uncommonA, uncommonA, uncommonB); + + private final BoosterStructure R1 = new BoosterStructure(rare); + + private final RarityConfiguration commonRuns = new RarityConfiguration( + AAAAABBBDDD, AAAAAABBBDD + ); + + private final RarityConfiguration uncommonRuns = new RarityConfiguration( + AAB, ABB + ); + + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Sets/src/mage/sets/StarWars.java b/Mage.Sets/src/mage/sets/StarWars.java index 1cdbd4539d5..b2f364c873b 100644 --- a/Mage.Sets/src/mage/sets/StarWars.java +++ b/Mage.Sets/src/mage/sets/StarWars.java @@ -174,7 +174,7 @@ public final class StarWars extends ExpansionSet { cards.add(new SetCardInfo("Hidden Base", 733, Rarity.UNCOMMON, mage.cards.h.HiddenBase.class)); cards.add(new SetCardInfo("Hold Captive", 723, Rarity.COMMON, mage.cards.h.HoldCaptive.class)); cards.add(new SetCardInfo("Holochess", 708, Rarity.UNCOMMON, mage.cards.h.Holochess.class)); - cards.add(new SetCardInfo("Hot Pursuit", 188, Rarity.RARE, mage.cards.h.HotPursuitStarWars.class)); + cards.add(new SetCardInfo("Hot Pursuit (Star Wars)", 188, Rarity.RARE, mage.cards.h.HotPursuitStarWars.class)); cards.add(new SetCardInfo("Hungry Dragonsnake", 138, Rarity.COMMON, mage.cards.h.HungryDragonsnake.class)); cards.add(new SetCardInfo("Hunt to Extinction", 189, Rarity.RARE, mage.cards.h.HuntToExtinction.class)); cards.add(new SetCardInfo("Hutt Crime Lord", 139, Rarity.UNCOMMON, mage.cards.h.HuttCrimeLord.class)); diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index 392cf4d1165..43694b2b05b 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -557,9 +557,12 @@ class TheLostCavernsOfIxalanCollator implements BoosterCollator { private final BoosterStructure L1 = new BoosterStructure(land); // In order for equal numbers of each common to exist, the average booster must contain: - // 2.06250 A commons (66 / 32) + // 2.06250 A commons (66 / 32) whilst paper 3A packs not observed, 6¼% 3A packs required where foils are ignored // 3.46875 B commons (111 / 32) // 3.46875 C commons (111 / 32) + // Because xmage doesn't consider foils displacing commons, this requires the possibility of packs with 3 A commons + // Taking into account foils changes the numbers to 1.986 A, 3.340 B, 3.340 C + // which would mean 3x FABBBBCCC,3x FABBBCCCC,138x FAABBBCCC,144x AABBBBCCC,144x AABBBCCCC private final RarityConfiguration commonRuns = new RarityConfiguration( AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, AABBBCCCC, diff --git a/Mage.Sets/src/mage/sets/Torment.java b/Mage.Sets/src/mage/sets/Torment.java index fbc8089ce63..347b91f2b51 100644 --- a/Mage.Sets/src/mage/sets/Torment.java +++ b/Mage.Sets/src/mage/sets/Torment.java @@ -1,9 +1,15 @@ - package mage.sets; import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import mage.collation.BoosterCollator; +import mage.collation.BoosterStructure; +import mage.collation.CardRun; +import mage.collation.RarityConfiguration; + +import java.util.ArrayList; +import java.util.List; /** * @@ -29,6 +35,7 @@ public final class Torment extends ExpansionSet { this.numBoosterRare = 1; this.ratioBoosterMythic = 0; this.hasUnbalancedColors = true; + cards.add(new SetCardInfo("Accelerate", 90, Rarity.COMMON, mage.cards.a.Accelerate.class)); cards.add(new SetCardInfo("Acorn Harvest", 118, Rarity.COMMON, mage.cards.a.AcornHarvest.class)); cards.add(new SetCardInfo("Ambassador Laquatus", 23, Rarity.RARE, mage.cards.a.AmbassadorLaquatus.class)); @@ -172,4 +179,39 @@ public final class Torment extends ExpansionSet { cards.add(new SetCardInfo("Waste Away", 88, Rarity.COMMON, mage.cards.w.WasteAway.class)); cards.add(new SetCardInfo("Zombie Trailblazer", 89, Rarity.UNCOMMON, mage.cards.z.ZombieTrailblazer.class)); } + + @Override + public BoosterCollator createCollator() { + return new TormentCollator(); + } +} + +// Booster collation info from https://www.lethe.xyz/mtg/collation/tor.html +// Using US collation - commons only +class TormentCollator implements BoosterCollator { + private final CardRun commonA = new CardRun(true, "102", "9", "58", "123", "39", "81", "103", "15", "75", "123", "47", "88", "103", "5", "53", "36", "121", "69", "93", "5", "85", "129", "32", "77", "97", "2", "60", "132", "24", "88", "102", "11", "60", "129", "36", "62", "93", "9", "75", "132", "32", "69", "107", "15", "53", "125", "39", "62", "97", "11", "85", "125", "47", "81", "107", "2", "58", "121", "24", "77"); + private final CardRun commonB = new CardRun(true, "99", "30", "6", "87", "96", "43", "130", "78", "90", "27", "12", "79", "43", "109", "51", "118", "35", "54", "115", "6", "79", "27", "109", "76", "128", "38", "54", "92", "12", "78", "30", "130", "115", "87", "41", "99", "18", "51", "92", "41", "128", "52", "90", "35", "18", "76", "96", "38", "118", "52"); + private final CardRun uncommon = new CardRun(false, "119", "120", "25", "50", "26", "139", "55", "122", "28", "29", "57", "33", "3", "34", "94", "124", "4", "98", "61", "63", "40", "7", "70", "72", "134", "105", "106", "110", "112", "138", "83", "84", "16", "17", "86", "48", "140", "141", "142", "143", "116", "19", "117", "89"); + // Alter Reality (rare, #22) not implemented, so has been omitted + private final CardRun rare = new CardRun(false, "23", "1", "91", "31", "56", "59", "95", "37", "100", "126", "101", "64", "65", "66", "127", "67", "68", "42", "8", "10", "71", "73", "131", "133", "74", "135", "104", "136", "108", "44", "45", "111", "137", "13", "113", "14", "46", "80", "82", "114", "20", "49", "21"); + + private final BoosterStructure AAAAAABBBBB = new BoosterStructure( + commonA, commonA, commonA, commonA, commonA, commonA, + commonB, commonB, commonB, commonB, commonB + ); + private final BoosterStructure U3 = new BoosterStructure(uncommon, uncommon, uncommon); + private final BoosterStructure R1 = new BoosterStructure(rare); + + private final RarityConfiguration commonRuns = new RarityConfiguration(AAAAAABBBBB); + private final RarityConfiguration uncommonRuns = new RarityConfiguration(U3); + private final RarityConfiguration rareRuns = new RarityConfiguration(R1); + + @Override + public List makeBooster() { + List booster = new ArrayList<>(); + booster.addAll(commonRuns.getNext().makeRun()); + booster.addAll(uncommonRuns.getNext().makeRun()); + booster.addAll(rareRuns.getNext().makeRun()); + return booster; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BlitzTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BlitzTest.java index a3f74a9f86f..fd551527b89 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BlitzTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BlitzTest.java @@ -77,7 +77,7 @@ public class BlitzTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decoy + withBlitz); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major",decoy); - setChoice(playerA, ""); //stack triggers + setChoice(playerA, "At the beginning of the next end step"); // x2 triggers setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java index faa29cfe838..540539aa1c9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java @@ -3,11 +3,23 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; +import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * @author Alex-Vasile - * To have a creature connive, draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature. + * @author Alex-Vasile, JayDi85 + *

+ * Once an ability that causes a creature to connive begins to resolve, no player may take any other actions until it's done. Notably, opponents can't try to remove the conniving creature after you discard cards but before it receives +1/+1 counters, if any. + * (2024-06-07) + *

+ * To have a creature connive, draw a card, then discard a card. If you discarded a nonland card, + * put a +1/+1 counter on that creature. + *

+ * If no cards are discarded, most likely because that player's hand is empty and an effect says they can't draw cards, the conniving creature does not receive any +1/+1 counters. + * (2024-06-07) + *

+ * If a resolving spell or ability instructs a specific creature to connive but that creature has left the battlefield, the creature still connives, although you can't put any +1/+1 counters on it. Abilities that trigger "when [that creature] connives" will trigger. + * (2024-06-07) */ public class ConniveTest extends CardTestPlayerBase { @@ -16,7 +28,7 @@ public class ConniveTest extends CardTestPlayerBase { * Creature should not get a +1/+1 counter. */ @Test - public void conniveDiscardLand() { + public void test_OnLandDiscard() { // P/T : 1/2 // {3}: Hypnotic Grifter connives addCard(Zone.BATTLEFIELD, playerA, "Hypnotic Grifter"); @@ -41,7 +53,7 @@ public class ConniveTest extends CardTestPlayerBase { * Creature should get a +1/+1 counter. */ @Test - public void conniveDiscardCreature() { + public void test_OnCreatureDiscard() { // P/T : 1/2 // {3}: Hypnotic Grifter connives addCard(Zone.BATTLEFIELD, playerA, "Hypnotic Grifter"); @@ -66,7 +78,7 @@ public class ConniveTest extends CardTestPlayerBase { * Creature should get a +1/+1 counter and the madness card gets played. */ @Test - public void conniveMadness() { + public void test_Madness() { // P/T : 1/2 // {3}: Hypnotic Grifter connives addCard(Zone.BATTLEFIELD, playerA, "Hypnotic Grifter"); @@ -95,10 +107,10 @@ public class ConniveTest extends CardTestPlayerBase { /** * Obscura Confluence allows you to connive a creature you don't control. - * It's the only card that causes you to coonnive a creature you don't control. + * It's the only card that causes you to connive a creature you don't control. */ @Test - public void conniveNonControlledCreature() { + public void test_OpponentCreature() { // Choose three. You may choose the same mode more than once. //• Until end of turn, target creature loses all abilities and has base power and toughness 1/1. //• Target creature connives. (Draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature.) @@ -135,16 +147,16 @@ public class ConniveTest extends CardTestPlayerBase { /** * Reported bug: https://github.com/magefree/mage/issues/9252 * Connive fizzles if the creature that connived leaves the battlefield before connive resolves. - * + *

* Ruling: - * If a resolving spell or ability instructs a specific creature to connive but that creature has left the battlefield, - * the creature still connives. - * If you discard a nonland card this way, you won’t put a +1/+1 counter on anything. - * Abilities that trigger “when [that creature] connives” will trigger. - * (2022-04-29) + * If a resolving spell or ability instructs a specific creature to connive but that creature has left the battlefield, + * the creature still connives. + * If you discard a nonland card this way, you won’t put a +1/+1 counter on anything. + * Abilities that trigger “when [that creature] connives” will trigger. + * (2022-04-29) */ @Test - public void conniveDoesNotFizzle() { + public void test_LKI_PsychicPickpocket() { // {4}{U} // 3/2 // When Psychic Pickpocket enters the battlefield, it connives. @@ -175,4 +187,187 @@ public class ConniveTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Psychic Pickpocket", 1); // Destroyed by Lightning Bolt assertGraveyardCount(playerB, "Lightning Bolt", 1); } + + @Test + public void test_LKI_Source_Normal() { + // When Raffine’s Silencer enters, it connives. + addCard(Zone.HAND, playerA, "Raffine's Silencer");// {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raffine's Silencer"); + setChoice(playerA, "Grizzly Bears"); // to discard + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, "Raffine's Silencer", 1 + 1, 1 + 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } + + @Test + public void test_LKI_Source_Dies() { + // When Raffine’s Silencer enters, it connives. + addCard(Zone.HAND, playerA, "Raffine's Silencer");// {2}{B} + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + // cast and kill raffine before cannive resolve + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 3); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Raffine's Silencer"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); // resolve raffine + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt"); + addTarget(playerA, "Raffine's Silencer"); // to kill + setChoice(playerA, "Grizzly Bears"); // to discard + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Raffine's Silencer", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } + + @Test + public void test_LKI_SpymastersVault_Normal() { + // {B}, {T}: Target creature you control connives X, where X is the number of creatures that died this turn. + addCard(Zone.BATTLEFIELD, playerA, "Spymaster's Vault"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + // + addCustomEffect_DestroyTarget(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + // prepare X = 1 from died amount + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Razorclaw Bear"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkGraveyardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Razorclaw Bear", 1); + + // cannive lion + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}, {T}: Target creature", "Silvercoat Lion"); + setChoice(playerA, "Grizzly Bears"); // to discard + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } + + @Test + public void test_LKI_SpymastersVault_Fizzle() { + // {B}, {T}: Target creature you control connives X, where X is the number of creatures that died this turn. + addCard(Zone.BATTLEFIELD, playerA, "Spymaster's Vault"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + // + addCustomEffect_DestroyTarget(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + // prepare X = 1 from died amount + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Razorclaw Bear"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkGraveyardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Razorclaw Bear", 1); + + // cannive lion + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}, {T}: Target creature", "Silvercoat Lion"); + // destroy lion before cannive + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // no connive due invalid target (ability fizzled) + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 0); + } + + @Test + public void test_LKI_ChangeOfPlans_Normal() { + // Each of X target creatures you control connive. You may have any number of them phase out. + addCard(Zone.HAND, playerA, "Change of Plans"); // {X}{1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + // connive lion + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); + setChoice(playerA, "X=1"); + addTarget(playerA, "Silvercoat Lion"); + setChoice(playerA, "Grizzly Bears"); // to discard + setChoice(playerA, TestPlayer.CHOICE_SKIP); // no phase out + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } + + @Test + public void test_LKI_ChangeOfPlans_Fizzle() { + // Each of X target creatures you control connive. You may have any number of them phase out. + addCard(Zone.HAND, playerA, "Change of Plans"); // {X}{1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + // + addCustomEffect_DestroyTarget(playerA); + + // connive lion + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); + setChoice(playerA, "X=1"); + addTarget(playerA, "Silvercoat Lion"); + // destroy lion before connive resolve + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // no connive due invalid target (ability fizzled) + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + } + + @Test + public void test_LKI_ChangeOfPlans_MultipleTargets() { + // Each of X target creatures you control connive. You may have any number of them phase out. + addCard(Zone.HAND, playerA, "Change of Plans"); // {X}{1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, "Grizzly Bears", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + // + addCustomEffect_DestroyTarget(playerA); + + // connive lion + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); + setChoice(playerA, "X=2"); + addTarget(playerA, "Silvercoat Lion^Razorclaw Bear"); + // destroy lion before connive resolve + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Silvercoat Lion"); + setChoice(playerA, "Grizzly Bears"); // to discard first conni + setChoice(playerA, TestPlayer.CHOICE_SKIP); // no phase out + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // spell is valid for 1 target + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertPowerToughness(playerA, "Razorclaw Bear", 3 + 1, 3 + 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java index cf4e984f4b0..62d366ecb66 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DisguiseTest.java @@ -67,7 +67,7 @@ public class DisguiseTest extends CardTestPlayerBase { }); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkPermanentCount("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dog Walker", 0); - checkPermanentCount("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + checkPermanentCount("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); runCode("after face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { // server side Permanent permanent = currentGame.getBattlefield().getAllPermanents() @@ -145,7 +145,7 @@ public class DisguiseTest extends CardTestPlayerBase { activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R/W}{R/W}: Turn"); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA); checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dog Walker", 1); - checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); checkPermanentCount("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dog Token", 2); runCode("after face up", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> { Permanent permanent = currentGame.getBattlefield().getAllPermanents() @@ -193,7 +193,7 @@ public class DisguiseTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); assertLife(playerA, 20); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java index 833f64b98e6..093d7ec92fa 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java @@ -88,8 +88,7 @@ public class EchoTest extends CardTestPlayerBase { setChoice(playerA, true); setChoice(playerA, "Deranged Hermit"); - setChoice(playerA, ""); //stack triggers - setChoice(playerA, ""); + setChoice(playerA, "Echo {3}{G}{G}", 2); // x3 triggers from Deranged Hermit setChoice(playerA, true); //Pay echo costs setChoice(playerA, true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java index 58609af216e..fcf4f29ebb7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java @@ -36,7 +36,7 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -54,7 +54,7 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -74,8 +74,8 @@ public class ManifestTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -89,32 +89,32 @@ public class ManifestTest extends CardTestPlayerBase { }); // turn 1 - checkPermanentCount("turn 1.A - no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); - checkPermanentCount("turn 1.B - no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("turn 1.A - no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); + checkPermanentCount("turn 1.B - no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); // turn 2 - checkPermanentCount("turn 2.A - +1 face down", 2, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - checkPermanentCount("turn 2.B - no face down", 2, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("turn 2.A - +1 face down", 2, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + checkPermanentCount("turn 2.B - no face down", 2, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); // turn 3 - checkPermanentCount("turn 3.A - +1 face down", 3, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - checkPermanentCount("turn 3.B - no face down", 3, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("turn 3.A - +1 face down", 3, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + checkPermanentCount("turn 3.B - no face down", 3, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); // turn 4 - checkPermanentCount("turn 4.A - +2 face down", 4, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); - checkPermanentCount("turn 4.B - no face down", 4, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("turn 4.A - +2 face down", 4, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); + checkPermanentCount("turn 4.B - no face down", 4, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); // turn 5 - checkPermanentCount("turn 5.A - +2 face down", 5, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); - checkPermanentCount("turn 5.B - no face down", 5, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("turn 5.A - +2 face down", 5, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); + checkPermanentCount("turn 5.B - no face down", 5, PhaseStep.PRECOMBAT_MAIN, playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); setStrictChooseMode(true); setStopAt(5, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); Assert.assertEquals("manifested cards must be taken from opponent's library", 2, playerA.getLibrary().size() - playerB.getLibrary().size()); } @@ -136,12 +136,12 @@ public class ManifestTest extends CardTestPlayerBase { // manifest castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Summons"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - checkPermanentCount("need face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + checkPermanentCount("need face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); // blink - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - checkPermanentCount("need no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + checkPermanentCount("need no face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); runCode("after blink", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { if (cardAfterBlink == null) { @@ -238,10 +238,10 @@ public class ManifestTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); // not tapped - assertTapped(EmptyNames.FACE_DOWN_CREATURE.toString(), false); + assertTapped(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), false); } /** @@ -270,8 +270,8 @@ public class ManifestTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); // PlayerB's Silvercoat Lion should not have get -1/-1/ assertPermanentCount(playerB, "Silvercoat Lion", 1); assertPowerToughness(playerB, "Silvercoat Lion", 2, 2); @@ -307,8 +307,8 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reality Shift", 1); assertExileCount("Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); // PlayerA's Pillarfield Ox should not have get -1/-1/ assertPermanentCount(playerB, "Pillarfield Ox", 1); assertPowerToughness(playerB, "Pillarfield Ox", 2, 4); @@ -345,8 +345,8 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reality Shift", 1); assertExileCount("Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); } @@ -381,8 +381,8 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reality Shift", 1); assertExileCount("Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); assertPowerToughness(playerA, "Foundry Street Denizen", 1, 1); } @@ -412,7 +412,7 @@ public class ManifestTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion"); // showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); // showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silence the Believers", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silence the Believers", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -424,7 +424,7 @@ public class ManifestTest extends CardTestPlayerBase { assertExileCount("Silvercoat Lion", 1); assertExileCount("Gore Swine", 1); // no facedown creature is on the battlefield - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); for (Card card : currentGame.getExile().getAllCards(currentGame)) { if (card.hasName("Gore Swine", currentGame)) { @@ -463,7 +463,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); // a facedown creature is on the battlefield - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @@ -503,7 +503,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerB, "Aerie Bowmasters", 1); assertPowerToughness(playerB, "Aerie Bowmasters", 4, 5); // 3/4 and the +1/+1 counter from Megamorph Permanent aerie = getPermanent("Aerie Bowmasters", playerB); @@ -541,7 +541,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerB, "Aerie Bowmasters", 1); assertPowerToughness(playerB, "Aerie Bowmasters", 3, 4); // 3/4 without counter (megamorph not used) Permanent aerie = getPermanent("Aerie Bowmasters", playerB); @@ -581,7 +581,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Silvercoat Lion", 1); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @@ -618,7 +618,7 @@ public class ManifestTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Whisperwood Elemental", 1); assertGraveyardCount(playerB, "Silvercoat Lion", 2); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); } @@ -652,7 +652,7 @@ public class ManifestTest extends CardTestPlayerBase { setChoice(playerB, "Silvercoat Lion"); activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Sacrifice a creature"); - setChoice(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); + setChoice(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); @@ -703,7 +703,7 @@ public class ManifestTest extends CardTestPlayerBase { waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN, playerB); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cloudshift", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStrictChooseMode(true); setStopAt(2, PhaseStep.END_TURN); @@ -843,8 +843,8 @@ public class ManifestTest extends CardTestPlayerBase { execute(); assertGraveyardCount(playerA, excommunicate, 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); assertLife(playerA, 20); assertLife(playerB, 18); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 80ecdce4f92..e67e776fc16 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -58,8 +58,8 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); } @@ -73,7 +73,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker using Morph"); - attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up."); setStopAt(3, PhaseStep.END_TURN); @@ -81,7 +81,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 18); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); @@ -104,8 +104,8 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker using Morph"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Icefeather Aven using Morph", TestPlayer.NO_TARGET, "Pine Walker", StackClause.WHILE_NOT_ON_STACK); - attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); - attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerA, "{1}{G}{U}: Turn this face-down permanent face up."); setChoice(playerA, false); // Don't use return permanent to hand effect @@ -117,7 +117,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Pine Walker", 0); assertHandCount(playerA, "Icefeather Aven", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertPermanentCount(playerA, "Icefeather Aven", 1); assertTapped("Icefeather Aven", true); @@ -148,7 +148,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 20); // and not 21 - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertPermanentCount(playerB, "Soldier of the Pantheon", 1); } @@ -175,10 +175,10 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); } /** @@ -212,7 +212,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Pine Walker", 0); assertHandCount(playerB, "Doomwake Giant", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerB, "Doomwake Giant", 1); assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 4, 4); @@ -252,7 +252,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Ponyback Brigade", 0); assertHandCount(playerB, "Doomwake Giant", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerA, "Goblin Token", 3); assertPowerToughness(playerA, "Goblin Token", 1, 1, Filter.ComparisonScope.Any); assertPermanentCount(playerB, "Doomwake Giant", 1); @@ -323,7 +323,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Sagu Mauler", 0); assertHandCount(playerB, "Disdainful Stroke", 1); // can't be cast - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @@ -351,7 +351,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler using Morph", TestPlayer.NO_TARGET, "Sagu Mauler", StackClause.WHILE_NOT_ON_STACK); // showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Echoing Decay", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Echoing Decay", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); // showBattlefield("A battle after", 1, PhaseStep.END_TURN, playerA); setStopAt(1, PhaseStep.END_TURN); @@ -364,7 +364,7 @@ public class MorphTest extends CardTestPlayerBase { assertHandCount(playerA, "Sagu Mauler", 0); assertHandCount(playerB, "Echoing Decay", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertGraveyardCount(playerA, "Sagu Mauler", 1); } @@ -410,7 +410,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Birchlore Rangers using Morph"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Swords to Plowshares", ""); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Swords to Plowshares", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -449,7 +449,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix using Morph"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -489,7 +489,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ashcloud Phoenix using Morph"); attack(2, playerB, "Mirri, Cat Warrior"); - block(2, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), "Mirri, Cat Warrior"); + block(2, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), "Mirri, Cat Warrior"); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); @@ -528,7 +528,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury using Morph"); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form"); - addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString()); + addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -536,13 +536,13 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 20); assertHandCount(playerA, "Akroma, Angel of Fury", 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerA, "Akroma, Angel of Fury", 0); assertGraveyardCount(playerB, "Supplant Form", 1); assertPermanentCount(playerB, "Akroma, Angel of Fury", 0); - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_TOKEN.toString(), 1); - assertPowerToughness(playerB, EmptyNames.FACE_DOWN_TOKEN.toString(), 2, 2); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_TOKEN.getTestCommand(), 1); + assertPowerToughness(playerB, EmptyNames.FACE_DOWN_TOKEN.getTestCommand(), 2, 2); } /** @@ -567,7 +567,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerA, 20); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @@ -591,7 +591,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pine Walker using Morph"); - attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + attack(3, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "{4}{G}: Turn this face-down permanent face up."); setStopAt(3, PhaseStep.END_TURN); @@ -599,7 +599,7 @@ public class MorphTest extends CardTestPlayerBase { assertLife(playerB, 18); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); @@ -652,7 +652,7 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Reflector Mage", 1); assertPermanentCount(playerB, "Rattleclaw Mystic", 0); assertHandCount(playerB, "Rattleclaw Mystic", 1); // can't play - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); // don't try as morph + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); // don't try as morph } @Test @@ -685,7 +685,7 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Reflector Mage", 1); assertPermanentCount(playerB, "Rattleclaw Mystic", 0); assertHandCount(playerB, "Rattleclaw Mystic", 0); // able cast as morph - assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerB, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } /** @@ -809,7 +809,7 @@ public class MorphTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Fatal Push", 1); assertGraveyardCount(playerA, "Pine Walker", 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); } @@ -842,7 +842,7 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertHandCount(playerA, 0); assertTappedCount("Island", true, 3); @@ -877,7 +877,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Quicksilver Dragon using Morph"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStopAt(2, PhaseStep.UPKEEP); execute(); @@ -959,7 +959,7 @@ public class MorphTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Zoetic Cavern", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -984,7 +984,7 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Island", 1); assertPermanentCount(playerA, "Zoetic Cavern", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -1007,7 +1007,7 @@ public class MorphTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Zoetic Cavern", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -1048,7 +1048,7 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -1069,7 +1069,7 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -1099,7 +1099,7 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(3, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -1122,7 +1122,7 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); } @Test @@ -1134,10 +1134,10 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Monastery Flock using Morph"); - checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{U}: Turn this", true); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tamiyo's Compleation", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tamiyo's Compleation", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); checkPlayableAbility("unmorph", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{U}: Turn this", false); @@ -1145,9 +1145,9 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertTapped(EmptyNames.FACE_DOWN_CREATURE.toString(), true); - assertAttachedTo(playerA, "Tamiyo's Compleation", EmptyNames.FACE_DOWN_CREATURE.toString(), true); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + assertTapped(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), true); + assertAttachedTo(playerA, "Tamiyo's Compleation", EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), true); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); } @Test @@ -1162,10 +1162,10 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sage-Eye Harrier using Morph"); - checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{3}{W}: Turn this", true); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Minimus Containment", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Minimus Containment", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); checkPlayableAbility("unmorph", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}{W}: Turn this", false); @@ -1175,10 +1175,10 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(2, PhaseStep.UPKEEP); execute(); - assertSubtype(EmptyNames.FACE_DOWN_CREATURE.toString(), SubType.TREASURE); - assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.ARTIFACT, true); - assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.CREATURE, false); - assertAttachedTo(playerA, "Minimus Containment", EmptyNames.FACE_DOWN_CREATURE.toString(), true); + assertSubtype(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), SubType.TREASURE); + assertType(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), CardType.ARTIFACT, true); + assertType(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), CardType.CREATURE, false); + assertAttachedTo(playerA, "Minimus Containment", EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), true); } @Test @@ -1190,7 +1190,7 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Monastery Flock using Morph"); - checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{U}: Turn this", true); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Mycosynth Lattice"); @@ -1201,10 +1201,10 @@ public class MorphTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.ARTIFACT, true); - assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.CREATURE, true); - assertNotSubtype(EmptyNames.FACE_DOWN_CREATURE.toString(), SubType.BIRD); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + assertType(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), CardType.ARTIFACT, true); + assertType(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), CardType.CREATURE, true); + assertNotSubtype(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), SubType.BIRD); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); } @Test @@ -1216,17 +1216,17 @@ public class MorphTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Monastery Flock using Morph"); - checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{U}: Turn this", true); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.ARTIFACT, true); - assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.CREATURE, true); - assertNotSubtype(EmptyNames.FACE_DOWN_CREATURE.toString(), SubType.BIRD); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + assertType(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), CardType.ARTIFACT, true); + assertType(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), CardType.CREATURE, true); + assertNotSubtype(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), SubType.BIRD); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); } private void assertMorphedFaceDownColor(String info, String needColor) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java index c862a87d9fb..edbd795c334 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/CastFromGraveyardOnceTest.java @@ -128,7 +128,7 @@ public class CastFromGraveyardOnceTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java index eb67cfdf475..1dbeff49706 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java @@ -50,7 +50,7 @@ public class UnboundFlourishingTest extends CardTestPlayerBase { // cast with X=3, but double it twice castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One"); setChoice(playerA, "X=3"); - setChoice(playerA, ""); //stack triggers + setChoice(playerA, "Whenever you cast a permanent spell"); // x2 triggers from Unbound Flourishing checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Endless One", CounterType.P1P1, 3 * 2 * 2); setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java index 9fdff5e76f2..6695ce20845 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/ExileAndReturnUnderYourControl.java @@ -105,8 +105,8 @@ public class ExileAndReturnUnderYourControl extends CardTestPlayerBase { assertExileCount("Secret Plans", 0); assertPermanentCount(playerA, "Secret Plans", 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 3); } /** diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/GontiLordOfLuxuryEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/GontiLordOfLuxuryEffectTest.java index 343226076d2..12801f91ccb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/GontiLordOfLuxuryEffectTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/GontiLordOfLuxuryEffectTest.java @@ -225,6 +225,6 @@ public class GontiLordOfLuxuryEffectTest extends CardTestPlayerBase { setStopAt(4, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/AlteredEgoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/AlteredEgoTest.java index f49fd47ef5f..b2782baca65 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/AlteredEgoTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/AlteredEgoTest.java @@ -72,7 +72,7 @@ public class AlteredEgoTest extends CardTestPlayerBase { setChoice(playerB, "X=3"); setChoice(playerB, true); // use copy setChoice(playerB, "Endless One"); // copy target - setChoice(playerB, ""); // Order place counters effects + setChoice(playerB, "Endless One"); // x2 replacement effects from Endless One setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CloneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CloneTest.java index 0ea639c0771..e446e1037e3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CloneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CloneTest.java @@ -271,7 +271,7 @@ public class CloneTest extends CardTestPlayerBase { castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone"); setChoice(playerA, true); - setChoice(playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + setChoice(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStrictChooseMode(true); setStopAt(3, PhaseStep.BEGIN_COMBAT); @@ -283,8 +283,8 @@ public class CloneTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Ixidron", 1); assertPowerToughness(playerA, "Ixidron", 1, 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2, Filter.ComparisonScope.All); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2, Filter.ComparisonScope.All); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/DefilersTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/DefilersTest.java index 0bd2e742ca9..eb30f797425 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/DefilersTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/DefilersTest.java @@ -63,7 +63,7 @@ public class DefilersTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear); setChoice(playerA, true); setChoice(playerA, true); - setChoice(playerA, ""); + setChoice(playerA, "Whenever you cast a green permanent spell"); // x2 triggers from Defiler of Vigor setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); @@ -83,7 +83,7 @@ public class DefilersTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tusker); setChoice(playerA, true); setChoice(playerA, true); - setChoice(playerA, ""); + setChoice(playerA, "Whenever you cast a green permanent spell"); // x2 triggers from Defiler of Vigor setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); @@ -103,7 +103,7 @@ public class DefilersTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, goblin); setChoice(playerA, true); setChoice(playerA, true); - setChoice(playerA, ""); + setChoice(playerA, "Whenever you cast a green permanent spell"); // x2 triggers from red and green defilers addTarget(playerA, playerB); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/RoleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/RoleTest.java index eb1ebac5d1a..d5d62d9d3f8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/RoleTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/RoleTest.java @@ -163,7 +163,7 @@ public class RoleTest extends CardTestPlayerBase { addTarget(playerA, bear + "^" + wardens); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, become); - setChoice(playerA, ""); // order triggers + setChoice(playerA, "Constellation"); // x2 triggers from Nexus Wardens setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java index a6e4cdf030e..09528972de7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java @@ -51,7 +51,7 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase { execute(); assertGraveyardCount(playerA, "Thopter Spy Network", 0); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2, Filter.ComparisonScope.All); // the manifested cards assertPermanentCount(playerA, "Starfield of Nyx", 1); assertPowerToughness(playerA, "Thopter Spy Network", 4, 4, Filter.ComparisonScope.All); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java index b516159d9b8..49106437084 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/GhastlyConscriptionTest.java @@ -38,7 +38,7 @@ public class GhastlyConscriptionTest extends CardTestPlayerBase { assertLife(playerB, 20); assertGraveyardCount(playerA, 2); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java index 6c3931a1a9b..e28c470d73a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/ObscuringAetherTest.java @@ -33,7 +33,7 @@ public class ObscuringAetherTest extends CardTestPlayerBase { assertHandCount(playerA, "Obscuring Aether", 0); assertGraveyardCount(playerA, "Obscuring Aether", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java index bd9cbcca7b9..660d1d1a225 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/PrimordialMistTest.java @@ -35,7 +35,7 @@ public class PrimordialMistTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Exile a face-down permanent you control"); - setChoice(playerA, EmptyNames.FACE_DOWN_CREATURE.toString()); + setChoice(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Brine Elemental"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java index dac1fd0950f..c7e82c786f3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/facedown/TriggerTest.java @@ -41,7 +41,7 @@ public class TriggerTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java index 059d81948ed..6177edc5bec 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/entersBattlefield/PrimalClayTest.java @@ -3,6 +3,7 @@ package org.mage.test.cards.replacement.entersBattlefield; import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HasteAbility; +import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.SubType; import mage.constants.Zone; @@ -220,7 +221,7 @@ public class PrimalClayTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aquamorph+" using Morph"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Savage Swipe"); - addTarget(playerA, ""); // morph + addTarget(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); // morph addTarget(playerA, "Siege Mastodon"); // 2/2 becomes 4/4, fights 3/5, neither dies waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/restriction/MeddlingMageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/restriction/MeddlingMageTest.java index 969dd1b8b69..c788d07067a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/restriction/MeddlingMageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/restriction/MeddlingMageTest.java @@ -89,7 +89,7 @@ public class MeddlingMageTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Meddling Mage", 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertHandCount(playerA, "Meddling Mage", 0); assertHandCount(playerA, "Ainok Tracker", 0); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java index 3bd2bd83a44..ec176dec90a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/CantCastTest.java @@ -136,7 +136,7 @@ public class CantCastTest extends CardTestPlayerBase { } } - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertHandCount(playerA, "Pine Walker", 1); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/rules/NamePredicateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/rules/NamePredicateTest.java index ac8037ba789..08c3d8f2267 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/rules/NamePredicateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/rules/NamePredicateTest.java @@ -15,6 +15,9 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class NamePredicateTest extends CardTestPlayerBase { private void assertNamePredicate(String checkName, int needAmount, String needName, boolean ignoreMtgRules) { + //Assert.assertNotEquals("", needName); empty strings also testing here, so no need to assert it + needName = EmptyNames.replaceTestCommandByObjectName(needName); + FilterPermanent filter = new FilterPermanent(); filter.add(new NamePredicate(needName, ignoreMtgRules)); Assert.assertEquals(checkName, needAmount, currentGame.getBattlefield().countAll(filter, playerA.getId(), currentGame)); @@ -33,20 +36,20 @@ public class NamePredicateTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, 3 + 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); // use mtg rules for name searching assertNamePredicate("by rules - empty choice must return zero", 0, "", false); - assertNamePredicate("by rules - face down choice must return zero", 0, EmptyNames.FACE_DOWN_CREATURE.toString(), false); + assertNamePredicate("by rules - face down choice must return zero", 0, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), false); assertNamePredicate("by rules - non existing name must return zero", 0, "Island", false); assertNamePredicate("by rules - existing name must work", 3, "Forest", false); // use inner engine for name searching (e.g. must find face down permanents with empty names) - if (!EmptyNames.FACE_DOWN_CREATURE.toString().isEmpty()) { + if (!EmptyNames.FACE_DOWN_CREATURE.getObjectName().isEmpty()) { // if face down permanents gets inner name someday then empty choice must ignore it assertNamePredicate("by inner - empty choice must return zero", 0, "", true); } - assertNamePredicate("by inner - face down choice must work", 1, EmptyNames.FACE_DOWN_CREATURE.toString(), true); + assertNamePredicate("by inner - face down choice must work", 1, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), true); assertNamePredicate("by inner - non existing name must return zero", 0, "Island", true); assertNamePredicate("by inner - existing name must work", 3, "Forest", true); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/JackdawSaviorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/JackdawSaviorTest.java new file mode 100644 index 00000000000..9bad5634ebc --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/JackdawSaviorTest.java @@ -0,0 +1,90 @@ +package org.mage.test.cards.single.blb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author notgreat + */ +public class JackdawSaviorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.j.JackdawSavior Jackdaw Savior} {2}{W} + * Creature — Bird Cleric + * Flying + * Whenever Jackdaw Savior or another creature you control with flying dies, + * return another target creature card with lesser mana value from your graveyard to the battlefield. + *

+ * This card is unusual in that "another" here refers to "other than the creature that just died", + * which xmage does not natively support. + */ + + @Test + public void test_Simultaneous() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Jackdaw Savior"); //MV = 3, flying + addCard(Zone.BATTLEFIELD, playerA, "Air Elemental"); //MV = 5, flying + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); //MV = 1 + + addCard(Zone.HAND, playerA, "Damnation"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Damnation"); + setChoice(playerA, "Whenever {this} or another creature you control"); // x2 triggers from Jackdaw Savior + addTarget(playerA, "Jackdaw Savior"); + addTarget(playerA, "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, 2); //Air Elemental+Damnation + assertPermanentCount(playerA, "Jackdaw Savior", 1); + assertPermanentCount(playerA, "Memnite", 1); + } + + @Test + public void test_Clones() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Jackdaw Savior"); //MV = 3, flying + addCard(Zone.BATTLEFIELD, playerA, "Air Elemental"); //MV = 5, flying + addCard(Zone.BATTLEFIELD, playerA, "Spidersilk Armor"); //+0/+1 to all + + addCard(Zone.HAND, playerA, "Phantasmal Image", 2); //MV = 2, clone + addCard(Zone.HAND, playerA, "Murder", 2); + + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); + setChoice(playerA, "Air Elemental"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder"); + addTarget(playerA, "Air Elemental[only copy]"); + // Can't target itself, no valid targets + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); + setChoice(playerA, "Air Elemental"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder"); + addTarget(playerA, "Air Elemental[only copy]"); + addTarget(playerA, "Phantasmal Image"); // Target the previous one + setChoice(playerA, false); //Don't copy, stay on battlefield as 0/1 + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Murder", 2); + assertGraveyardCount(playerA, "Phantasmal Image", 1); + assertPermanentCount(playerA, "Phantasmal Image", 1); + assertPermanentCount(playerA, "Jackdaw Savior", 1); + assertPermanentCount(playerA, "Air Elemental", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java index 05efa5c57b2..ace5bb1b2a0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dtk/QarsiDeceiverTest.java @@ -34,7 +34,7 @@ public class QarsiDeceiverTest extends CardTestPlayerBase { castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, mystic + " using Morph"); - checkPT("morph", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + checkPT("morph", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U}: Turn"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java index d5fb689cf27..2c46156c061 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fut/MuragandaPetroglyphsTest.java @@ -56,8 +56,8 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 4, 4); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 4, 4); } @Test @@ -77,9 +77,9 @@ public class MuragandaPetroglyphsTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); - //assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); // no boost (permanent have haste) - assertAbility(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), HasteAbility.getInstance(), true); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); + //assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestName(), 2, 2); // no boost (permanent have haste) + assertAbility(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), HasteAbility.getInstance(), true); } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java index bb24539b9a5..64008416185 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m21/AlpineHoundmasterTest.java @@ -66,7 +66,6 @@ public class AlpineHoundmasterTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpine Houndmaster"); setChoice(playerA, true); addTarget(playerA, "Igneous Cur^Alpine Watchdog"); - //addTarget(playerA, ""); setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java index 569eb5850d0..1decc61f509 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/AssembleThePlayersTest.java @@ -48,7 +48,7 @@ public class AssembleThePlayersTest extends CardTestPlayerBase { setStopAt(3, PhaseStep.END_TURN); execute(); - assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 2, 2); assertPowerToughness(playerA, merfolk, 2, 1); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CrypticCoatTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CrypticCoatTest.java index 3a0bfcac749..212877bdcae 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CrypticCoatTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mkm/CrypticCoatTest.java @@ -33,9 +33,9 @@ public class CrypticCoatTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, coat); - checkPT("Cloaked is 3/2", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 3, 2); + checkPT("Cloaked is 3/2", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 3, 2); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.toString()); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", EmptyNames.FACE_DOWN_CREATURE.getTestCommand()); setChoice(playerB, true); // pay for ward activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{U}{U}: Turn this face-down permanent face up."); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java index 27cbdcefe33..a6b01128763 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ths/PurphorosGodOfTheForgeTest.java @@ -1,6 +1,5 @@ package org.mage.test.cards.single.ths; -import mage.constants.CardType; import mage.constants.EmptyNames; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -48,7 +47,7 @@ public class PurphorosGodOfTheForgeTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Reach of Shadows", 1); assertPermanentCount(playerA, "Ashcloud Phoenix", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); Permanent purphorosGodOfTheForge = getPermanent("Purphoros, God of the Forge", playerA); Assert.assertFalse("Purphoros may not be a creature but it is", purphorosGodOfTheForge.isCreature(currentGame)); @@ -79,7 +78,7 @@ public class PurphorosGodOfTheForgeTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Ashcloud Phoenix", 0); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertLife(playerA, 18); // 2 damage from Eidolon of the Great Revel assertLife(playerB, 18); // 2 damage from Purphoros for the morphed Phoenix diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java index 01107363d6e..1f7838a1efb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/vow/CemeteryIlluminatorTest.java @@ -98,7 +98,7 @@ public class CemeteryIlluminatorTest extends CardTestPlayerBase { assertExileCount(playerA, creature, 1); assertPermanentCount(playerA, ci, 1); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertPermanentCount(playerA, whetwheel, 0); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java index bd0e93c924a..0d3877ce713 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/ThijarianWitnessTest.java @@ -119,7 +119,7 @@ public class ThijarianWitnessTest extends CardTestPlayerBase { attack(1, playerA, tiny); block(1, playerB, tiny, tiny); - setChoice(playerA, ""); //stack triggers + setChoice(playerA, "Bear Witness"); // x2 triggers from Thijarian Witness setStopAt(1, PhaseStep.END_TURN); execute(); @@ -158,8 +158,8 @@ public class ThijarianWitnessTest extends CardTestPlayerBase { attack(1, playerA, tiny); block(1, playerB, tiny, tiny); - setChoice(playerA, ""); //stack triggers - setChoice(playerB, ""); + setChoice(playerA, "Bear Witness"); // x2 triggers from Thijarian Witness + setChoice(playerB, "Bear Witness"); // x2 triggers from Thijarian Witness setStopAt(1, PhaseStep.END_TURN); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java index e9e02ea67b0..66e9f93c3d3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java @@ -40,7 +40,7 @@ public class WhisperwoodElementalTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Silvercoat Lion", 1); // Manifested creature from dying Silvercoat Lion - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 57fc6ec6d13..65f5cbcb63e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -154,6 +154,13 @@ public class TestPlayer implements Player { } public void addChoice(String choice) { + // prepare face down + // how-to fix: + // * for face down choices: use EmptyNames.XXX.getTestCommand instead toString + // * for replacement/triggers choices: comment choice command, look at logs for triggers list and use starting text in the choice instead empty + Assert.assertNotEquals("Choice can't be empty", "", choice); + choice = EmptyNames.replaceTestCommandByObjectName(choice); + choices.add(choice); } @@ -182,6 +189,12 @@ public class TestPlayer implements Player { } public void addTarget(String target) { + // prepare face down + // how-to fix: if it's a face down object then use getTestCommand instead toString + Assert.assertNotEquals("Target can't be empty", "", target); + + target = EmptyNames.replaceTestCommandByObjectName(target); + targets.add(target); } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java index b9e842c270c..8f645ad543f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/TokenImagesTest.java @@ -300,7 +300,7 @@ public class TokenImagesTest extends CardTestPlayerBase { Assert.assertNotEquals(prefix + " - wrong image number", Integer.valueOf(0), object.getImageNumber()); // characteristic checks instead new test - Assert.assertEquals(prefix + " - wrong name", EmptyNames.FACE_DOWN_CREATURE.toString(), object.getName()); + Assert.assertEquals(prefix + " - wrong name", EmptyNames.FACE_DOWN_CREATURE.getObjectName(), object.getName()); Assert.assertEquals(prefix + " - wrong power", 2, object.getPower().getValue()); Assert.assertEquals(prefix + " - wrong toughness", 2, object.getToughness().getValue()); Assert.assertEquals(prefix + " - wrong color", "", object.getColor(currentGame).toString()); @@ -746,7 +746,7 @@ public class TokenImagesTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), faceDownAmount); assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3)); } @@ -765,7 +765,7 @@ public class TokenImagesTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), faceDownAmount); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), faceDownAmount); assert_FaceDownMorphImageNumber(Arrays.asList(1, 2, 3)); } @@ -844,7 +844,7 @@ public class TokenImagesTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); } @Test @@ -856,16 +856,16 @@ public class TokenImagesTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aerie Bowmasters using Morph"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); runCode("on face down", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 1); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 1); assertPermanentCount(playerA, "Aerie Bowmasters", 0); - Permanent permanent = getPermanent(EmptyNames.FACE_DOWN_CREATURE.toString(), playerA); + Permanent permanent = getPermanent(EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), playerA); assertFaceDownCharacteristics("permanent", permanent, TokenRepository.XMAGE_IMAGE_NAME_FACE_DOWN_MORPH); }); // face up it and find counter activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}{G}: Turn this"); runCode("on face up", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { - assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 0); + assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.getTestCommand(), 0); assertPermanentCount(playerA, "Aerie Bowmasters", 1); assertCounterCount(playerA, "Aerie Bowmasters", CounterType.P1P1, 1); }); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index bcaeb708287..21a35cb89a9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -32,7 +32,9 @@ import mage.utils.SystemUtil; import mage.util.CardUtil; import mage.view.GameView; import org.junit.Assert; + import static org.junit.Assert.assertTrue; + import org.junit.Before; import org.mage.test.player.PlayerAction; import org.mage.test.player.TestPlayer; @@ -349,12 +351,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkPT(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer power, Integer toughness) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_PT, permanentName, power.toString(), toughness.toString()); } public void checkDamage(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer damage) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_DAMAGE, permanentName, damage.toString()); } @@ -367,7 +373,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkAbility(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Class abilityClass, Boolean mustHave) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_ABILITY, permanentName, abilityClass.getName(), mustHave.toString()); } @@ -412,12 +420,13 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) { - //Assert.assertNotEquals("", permanentName); checkPermanentCount(checkName, turnNum, step, player, player, permanentName, count); } public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, TestPlayer targetPlayer, String permanentName, Integer count) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_PERMANENT_COUNT, targetPlayer.getId().toString(), permanentName, count.toString()); } @@ -438,17 +447,23 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkExileCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + check(checkName, turnNum, step, player, CHECK_COMMAND_EXILE_COUNT, cardName, count.toString()); } public void checkGraveyardCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + check(checkName, turnNum, step, player, CHECK_COMMAND_GRAVEYARD_COUNT, cardName, count.toString()); } public void checkLibraryCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + check(checkName, turnNum, step, player, CHECK_COMMAND_LIBRARY_COUNT, cardName, count.toString()); } @@ -457,32 +472,44 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void checkHandCardCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + check(checkName, turnNum, step, player, CHECK_COMMAND_HAND_CARD_COUNT, cardName, count.toString()); } public void checkCommandCardCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String cardName, Integer count) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + check(checkName, turnNum, step, player, CHECK_COMMAND_COMMAND_CARD_COUNT, cardName, count.toString()); } public void checkEmblemCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String emblemName, Integer count) { - //Assert.assertNotEquals("", emblemName); + Assert.assertNotEquals("", emblemName); + emblemName = EmptyNames.replaceTestCommandByObjectName(emblemName); + check(checkName, turnNum, step, player, CHECK_COMMAND_EMBLEM_COUNT, emblemName, count.toString()); } public void checkColor(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, String colors, Boolean mustHave) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_COLOR, permanentName, colors, mustHave.toString()); } public void checkType(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, CardType type, Boolean mustHave) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_TYPE, permanentName, type.toString(), mustHave.toString()); } public void checkSubType(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, SubType subType, Boolean mustHave) { - //Assert.assertNotEquals("", permanentName); + Assert.assertNotEquals("", permanentName); + permanentName = EmptyNames.replaceTestCommandByObjectName(permanentName); + check(checkName, turnNum, step, player, CHECK_COMMAND_SUBTYPE, permanentName, subType.toString(), mustHave.toString()); } @@ -850,7 +877,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertPowerToughness(Player player, String cardName, int powerNeeded, int toughnessNeeded, Filter.ComparisonScope scope, boolean checkBaseValues) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int count = 0; int fit = 0; int foundPower = 0; @@ -908,7 +937,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertAbilities(Player player, String cardName, List abilities) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int count = 0; Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { @@ -944,7 +975,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @throws AssertionError */ public void assertAbility(Player player, String cardName, Ability ability, boolean mustHave, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int foundCount = 0; Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { @@ -970,6 +1003,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void assertAbilityCount(Player player, String cardName, Class searchedAbility, int amount) { + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents(player.getId())) { if (isObjectHaveTargetNameOrAlias(player, permanent, cardName)) { @@ -1010,7 +1046,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement */ @Override public void assertPermanentCount(Player player, String cardName, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int actualCount = 0; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent.getControllerId().equals(player.getId())) { @@ -1024,7 +1062,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertTokenCount(Player player, String tokenName, int count) throws AssertionError { - //Assert.assertNotEquals("", tokenName); + Assert.assertNotEquals("", tokenName); + tokenName = EmptyNames.replaceTestCommandByObjectName(tokenName); + int actualCount = 0; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (permanent instanceof PermanentToken) { @@ -1040,7 +1080,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement @Override public void assertCommandZoneCount(Player player, String commandZoneObjectName, int count) throws AssertionError { - //Assert.assertNotEquals("", commandZoneObjectName); + Assert.assertNotEquals("", commandZoneObjectName); + commandZoneObjectName = EmptyNames.replaceTestCommandByObjectName(commandZoneObjectName); + int actualCount = 0; for (CommandObject commandObject : currentGame.getState().getCommand()) { if (commandObject.getControllerId().equals(player.getId()) && isObjectHaveTargetNameOrAlias(player, commandObject, commandZoneObjectName)) { @@ -1101,7 +1143,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertCounterCount(Player player, String cardName, String counterName, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { if (isObjectHaveTargetNameOrAlias(player, permanent, cardName) && (player == null || permanent.getControllerId().equals(player.getId()))) { @@ -1121,7 +1165,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertCounterOnExiledCardCount(String cardName, CounterType type, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Card found = null; for (Card card : currentGame.getExile().getAllCards(currentGame)) { @@ -1154,11 +1200,13 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param mustHave true if creature should have type, false if it should not */ public void assertType(String cardName, CardType type, boolean mustHave) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { - if (permanent.getName().equals(cardName)) { + if (CardUtil.haveSameNames(permanent.getName(), cardName, true)) { found = permanent; break; } @@ -1179,7 +1227,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param subType a subtype to test for */ public void assertType(String cardName, CardType type, SubType subType) throws AssertionError { - //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); assertTrue("(Battlefield) card type not found (" + cardName + ':' + type + ')', found.getCardType(currentGame).contains(type)); if (subType != null) { @@ -1194,7 +1241,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param type A type to test for */ public void assertNotType(String cardName, CardType type) throws AssertionError { - //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); Assert.assertFalse("(Battlefield) card type found (" + cardName + ':' + type + ')', found.getCardType(currentGame).contains(type)); } @@ -1206,7 +1252,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param subType a subtype to test for */ public void assertNotSubtype(String cardName, SubType subType) throws AssertionError { - //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); if (subType != null) { Assert.assertFalse("(Battlefield) card sub-type equal (" + cardName + ':' + subType.getDescription() + ')', found.hasSubtype(subType, currentGame)); @@ -1220,7 +1265,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param subType a subtype to test for */ public void assertSubtype(String cardName, SubType subType) throws AssertionError { - //Assert.assertNotEquals("", cardName); Permanent found = getPermanent(cardName); if (subType != null) { assertTrue("(Battlefield) card sub-type equal (" + cardName + ':' + subType.getDescription() + ')', found.hasSubtype(subType, currentGame)); @@ -1236,7 +1280,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param mustHave must or not must have that colors */ public void assertColor(Player player, String cardName, ObjectColor searchColors, boolean mustHave) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Assert.assertNotEquals("must setup colors to search", 0, searchColors.getColorCount()); Permanent card = getPermanent(cardName, player); @@ -1271,7 +1317,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param tapped Whether the permanent is tapped or not */ public void assertTapped(String cardName, boolean tapped) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, true); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { @@ -1299,12 +1347,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count The number of these permanents that should be tapped */ public void assertTappedCount(String cardName, boolean tapped, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); int tappedAmount = 0; Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { - if (permanent.getName().equals(cardName)) { + if (CardUtil.haveSameNames(permanent.getName(), cardName, true)) { if (permanent.isTapped() == tapped) { tappedAmount++; } @@ -1322,11 +1372,13 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param attacking Whether the permanent is attacking or not */ public void assertAttacking(String cardName, boolean attacking) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); Permanent found = null; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { - if (permanent.getName().equals(cardName)) { + if (CardUtil.haveSameNames(permanent.getName(), cardName, true)) { found = permanent; } } @@ -1355,7 +1407,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertHandCount(Player player, String cardName, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int actual; if (cardName.contains("//")) { // special logic for checked split cards, because in game logic of card name filtering is different from in test actual = 0; @@ -1414,6 +1468,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param subType Expected subtype. */ public void assertExiledCardSubtype(String cardName, SubType subType) throws AssertionError { + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + boolean found = false; for (ExileZone exile : currentGame.getExile().getExileZones()) { for (Card card : exile.getCards(currentGame)) { @@ -1433,7 +1490,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertExileCount(String cardName, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int actualCount = 0; for (ExileZone exile : currentGame.getExile().getExileZones()) { for (Card card : exile.getCards(currentGame)) { @@ -1473,12 +1532,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertExileCount(Player owner, String cardName, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); int actualCount = 0; for (ExileZone exile : currentGame.getExile().getExileZones()) { for (Card card : exile.getCards(currentGame)) { - if (card.isOwnedBy(owner.getId()) && card.getName().equals(cardName)) { + if (card.isOwnedBy(owner.getId()) && CardUtil.haveSameNames(card.getName(), cardName, true)) { actualCount++; } } @@ -1486,20 +1547,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement Assert.assertEquals("(Exile " + owner.getName() + ") Card counts are not equal (" + cardName + ").", count, actualCount); } - /** - * Assert card count in a specific exile zone. - * - * @param exileZoneName Name of the exile zone to be counted. - * @param count Expected count. - * @throws AssertionError - */ - public void assertExileZoneCount(String exileZoneName, int count) throws AssertionError { - ExileZone exileZone = currentGame.getExile().getExileZone(CardUtil.getExileZoneId(exileZoneName, currentGame)); - int actualCount = exileZone == null ? 0 : exileZone.getCards(currentGame).size(); - - Assert.assertEquals("(Exile \"" + exileZoneName + "\") Card counts are not equal.", count, actualCount); - } - /** * Assert card count in player's graveyard. * @@ -1508,8 +1555,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertGraveyardCount(Player player, String cardName, int count) throws AssertionError { + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, true); - //Assert.assertNotEquals("", cardName); + int actualCount = 0; for (Card card : player.getGraveyard().getCards(currentGame)) { if (isObjectHaveTargetNameOrAlias(player, card, cardName)) { @@ -1540,7 +1590,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertLibraryCount(Player player, String cardName, int count) throws AssertionError { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + int actualCount = 0; for (Card card : player.getLibrary().getCards(currentGame)) { if (CardUtil.haveSameNames(card.getName(), cardName, true)) { @@ -1614,9 +1666,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param isAttached true => assertIsAttachedTo, false => assertIsNotAttachedTo */ public void assertAttachedTo(TestPlayer thePlayer, String theAttachment, String thePermanent, boolean isAttached) { + Assert.assertNotEquals("", theAttachment); + theAttachment = EmptyNames.replaceTestCommandByObjectName(theAttachment); + Assert.assertNotEquals("", thePermanent); + thePermanent = EmptyNames.replaceTestCommandByObjectName(thePermanent); + + String needPermanent = thePermanent; + String needAttachment = theAttachment; List permanents = currentGame.getBattlefield().getAllActivePermanents().stream() .filter(permanent -> permanent.isControlledBy(thePlayer.getId())) - .filter(permanent -> permanent.getName().equals(thePermanent)) + .filter(permanent -> CardUtil.haveSameNames(permanent.getName(), needPermanent, true)) .collect(Collectors.toList()); assertTrue(theAttachment + " was " + (!isAttached ? "" : "not") + " attached to " + thePermanent, !isAttached ^ @@ -1624,13 +1683,16 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement .anyMatch(permanent -> permanent.getAttachments() .stream() .map(id -> currentGame.getCard(id)) - .map(MageObject::getName) - .collect(Collectors.toList()).contains(theAttachment))); + .filter(Objects::nonNull) + .anyMatch(o -> CardUtil.haveSameNames(o.getName(), needAttachment, true)))); } public Permanent getPermanent(String cardName, UUID controller) { assertAliaseSupportInActivateCommand(cardName, false); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Permanent found = null; Pattern indexedName = Pattern.compile("^([\\w| ]+):(\\d+)$"); // Ends with <:number> Matcher indexedMatcher = indexedName.matcher(cardName); @@ -1641,7 +1703,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement index = Integer.parseInt(indexedMatcher.group(2)); } for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { - if (permanent.getName().equals(cardName)) { + if (CardUtil.haveSameNames(permanent.getName(), cardName, true)) { if (controller == null || permanent.getControllerId().equals(controller)) { found = permanent; if (count != index) { @@ -1664,13 +1726,17 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void playLand(int turnNum, PhaseStep step, TestPlayer player, String cardName) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); addPlayerAction(player, turnNum, step, ACTIVATE_PLAY + cardName); } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName); } @@ -1683,14 +1749,18 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, Player target) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + // warning, target in spell cast command setups without choose target call assertAliaseSupportInActivateCommand(cardName, false); addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName()); } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, Player target, int manaInPool) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + assertAliaseSupportInActivateCommand(cardName, false); addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName + "$targetPlayer=" + target.getName() + "$manaInPool=" + manaInPool); } @@ -1810,9 +1880,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * warning, do not support cards with target adjusters - use addTarget instead */ public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Assert.assertNotEquals("", targetName); + targetName = EmptyNames.replaceTestCommandByObjectName(targetName); + assertAliaseSupportInActivateCommand(cardName, true); assertAliaseSupportInActivateCommand(targetName, true); + addPlayerAction(player, turnNum, step, ACTIVATE_CAST + cardName + "$target=" + targetName); } @@ -1863,7 +1938,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param clause */ public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack, StackClause clause) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Assert.assertNotEquals("", targetName); + targetName = EmptyNames.replaceTestCommandByObjectName(targetName); + assertAliaseSupportInActivateCommand(cardName, true); assertAliaseSupportInActivateCommand(targetName, true); assertAliaseSupportInActivateCommand(spellOnStack, false); @@ -1891,7 +1970,11 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack, String spellOnTopOfStack) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + Assert.assertNotEquals("", targetName); + targetName = EmptyNames.replaceTestCommandByObjectName(targetName); + assertAliaseSupportInActivateCommand(cardName, false); assertAliaseSupportInActivateCommand(targetName, false); assertAliaseSupportInActivateCommand(spellOnStack, false); @@ -1961,6 +2044,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param clause */ public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack, StackClause clause) { + Assert.assertNotEquals("", targetName); + targetName = EmptyNames.replaceTestCommandByObjectName(targetName); + assertAliaseSupportInActivateCommand(ability, true); assertAliaseSupportInActivateCommand(targetName, true); StringBuilder sb = new StringBuilder(ACTIVATE_ABILITY).append(ability); @@ -1986,24 +2072,32 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void addCounters(int turnNum, PhaseStep step, TestPlayer player, String cardName, CounterType type, int count) { - //Assert.assertNotEquals("", cardName); + Assert.assertNotEquals("", cardName); + cardName = EmptyNames.replaceTestCommandByObjectName(cardName); + addPlayerAction(player, turnNum, step, "addCounters:" + cardName + '$' + type.getName() + '$' + count); } public void attack(int turnNum, TestPlayer player, String attacker) { - //Assert.assertNotEquals("", attacker); + Assert.assertNotEquals("", attacker); + attacker = EmptyNames.replaceTestCommandByObjectName(attacker); + assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker); } public void attack(int turnNum, TestPlayer player, String attacker, TestPlayer defendingPlayer) { - //Assert.assertNotEquals("", attacker); + Assert.assertNotEquals("", attacker); + attacker = EmptyNames.replaceTestCommandByObjectName(attacker); + assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$defendingPlayer=" + defendingPlayer.getName()); } public void attack(int turnNum, TestPlayer player, String attacker, String permanent) { - //Assert.assertNotEquals("", attacker); + Assert.assertNotEquals("", attacker); + attacker = EmptyNames.replaceTestCommandByObjectName(attacker); + assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index assertAliaseSupportInActivateCommand(permanent, false); addPlayerAction(player, turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$permanent=" + permanent); @@ -2014,15 +2108,18 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void block(int turnNum, TestPlayer player, String blocker, String attacker) { - //Assert.assertNotEquals("", blocker); - //Assert.assertNotEquals("", attacker); + Assert.assertNotEquals("", attacker); + attacker = EmptyNames.replaceTestCommandByObjectName(attacker); + Assert.assertNotEquals("", blocker); + blocker = EmptyNames.replaceTestCommandByObjectName(blocker); + assertAliaseSupportInActivateCommand(blocker, false); // it uses old special notation like card_name:index assertAliaseSupportInActivateCommand(attacker, false); addPlayerAction(player, turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker); } public void blockSkip(int turnNum, TestPlayer player) { - block(turnNum, player, TestPlayer.BLOCK_SKIP, ""); + block(turnNum, player, TestPlayer.BLOCK_SKIP, TestPlayer.ATTACK_SKIP); } /** @@ -2193,7 +2290,6 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } public void assertDamageReceived(Player player, String cardName, int expected) { - //Assert.assertNotEquals("", cardName); Permanent p = getPermanent(cardName, player); if (p != null) { Assert.assertEquals("Wrong damage received: ", expected, p.getDamage()); @@ -2239,7 +2335,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement if (player != null) { // TODO: remove null check and replace all null-player calls in tests by player return testPlayer.hasObjectTargetNameOrAlias(object, nameOrAlias); } else { - return object.getName().equals(nameOrAlias); + return CardUtil.haveSameNames(object.getName(), nameOrAlias, true); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java index 97c8b2acf2b..7e9cd397b47 100644 --- a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java @@ -511,6 +511,15 @@ public class BoosterGenerationTest extends MageTestPlayerBase { } } + // String output formatter for the below debug test + private static String getManaCostOrColorIdentity(Card card) { + String result = card.getManaCost().getText(); + if (result.isEmpty()) { + result = "[" + card.getColorIdentity().toString().replace("{", "").replace("}", "") + "]"; + } + return result; + } + @Ignore // debug only: collect info about cards in boosters, see https://github.com/magefree/mage/issues/8081 @Test public void test_CollectBoosterStats() { @@ -524,7 +533,12 @@ public class BoosterGenerationTest extends MageTestPlayerBase { List booster = setToAnalyse.createBooster(); totalCards += booster.size(); booster.forEach(card -> { - String code = String.format("%s %s %s", card.getExpansionSetCode(), card.getRarity().toString().charAt(0), card.getName()); + String code = String.format("%s %s %3s %-32s %18s", + card.getExpansionSetCode(), + card.getRarity().toString().charAt(0), + card.getCardNumber(), + card.getName(), + getManaCostOrColorIdentity(card)); resRatio.putIfAbsent(code, 0); resRatio.computeIfPresent(code, (u, count) -> count + 1); }); @@ -536,14 +550,14 @@ public class BoosterGenerationTest extends MageTestPlayerBase { .map(Map.Entry::getValue) .collect(Collectors.toList()); if (!rarityCounts.isEmpty()) { - System.out.println(rarity + String.format(": %s unique, min %s, max %s, total %s", + System.out.println(rarity + String.format(": %3s unique, min %5s, max %5s, total %7s", rarityCounts.size(), Collections.min(rarityCounts), Collections.max(rarityCounts), rarityCounts.stream().mapToInt(x -> x).sum())); } } List info = resRatio.entrySet().stream() .sorted((o1, o2) -> Integer.compare(o2.getValue(), o1.getValue())) - .map(e -> String.format("%s: %d", + .map(e -> String.format("%s: %5d", e.getKey(), e.getValue() )) diff --git a/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java b/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java index bd4f1c06f19..f91eb3683d8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/testapi/AliasesApiTest.java @@ -20,32 +20,32 @@ public class AliasesApiTest extends CardTestPlayerBase { public void test_NamesEquals() { // // empty names for face down cards // Assert.assertTrue(CardUtil.haveEmptyName("")); -// Assert.assertTrue(CardUtil.haveEmptyName(EmptyNames.FACE_DOWN_CREATURE.toString())); +// Assert.assertTrue(CardUtil.haveEmptyName(EmptyNames.FACE_DOWN_CREATURE.getObjectName())); // Assert.assertFalse(CardUtil.haveEmptyName(" ")); // Assert.assertFalse(CardUtil.haveEmptyName("123")); // Assert.assertFalse(CardUtil.haveEmptyName("Sample Name")); // // // same names (empty names can't be same) // Assert.assertFalse(CardUtil.haveSameNames("", "")); -// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.toString(), "")); -// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.toString(), EmptyNames.FACE_DOWN_CREATURE.toString())); -// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_TOKEN.toString(), "")); -// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_TOKEN.toString(), EmptyNames.FACE_DOWN_CREATURE.toString())); +// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.getObjectName(), "")); +// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.getObjectName(), EmptyNames.FACE_DOWN_CREATURE.getObjectName())); +// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_TOKEN.getObjectName(), "")); +// Assert.assertFalse(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_TOKEN.getObjectName(), EmptyNames.FACE_DOWN_CREATURE.getObjectName())); // Assert.assertTrue(CardUtil.haveSameNames("Name", "Name")); // Assert.assertFalse(CardUtil.haveSameNames("Name", "")); // Assert.assertFalse(CardUtil.haveSameNames("Name", " ")); // Assert.assertFalse(CardUtil.haveSameNames("Name", "123")); -// Assert.assertFalse(CardUtil.haveSameNames("Name", EmptyNames.FACE_DOWN_CREATURE.toString())); +// Assert.assertFalse(CardUtil.haveSameNames("Name", EmptyNames.FACE_DOWN_CREATURE.getObjectName())); // Assert.assertFalse(CardUtil.haveSameNames("Name1", "Name2")); // // // ignore mtg rules (empty names must be same) // Assert.assertTrue(CardUtil.haveSameNames("", "", true)); -// Assert.assertTrue(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.toString(), EmptyNames.FACE_DOWN_CREATURE.toString(), true)); +// Assert.assertTrue(CardUtil.haveSameNames(EmptyNames.FACE_DOWN_CREATURE.getObjectName(), EmptyNames.FACE_DOWN_CREATURE.getObjectName(), true)); // Assert.assertTrue(CardUtil.haveSameNames("Name", "Name", true)); // Assert.assertFalse(CardUtil.haveSameNames("Name", "", true)); // Assert.assertFalse(CardUtil.haveSameNames("Name", " ", true)); // Assert.assertFalse(CardUtil.haveSameNames("Name", "123", true)); -// Assert.assertFalse(CardUtil.haveSameNames("Name", EmptyNames.FACE_DOWN_CREATURE.toString(), true)); +// Assert.assertFalse(CardUtil.haveSameNames("Name", EmptyNames.FACE_DOWN_CREATURE.getObjectName(), true)); // Assert.assertFalse(CardUtil.haveSameNames("Name1", "Name2", true)); // // // name with split card @@ -68,12 +68,12 @@ public class AliasesApiTest extends CardTestPlayerBase { // // normal spell // Assert.assertFalse(CardUtil.haveSameNames(normalSpell, "", currentGame)); // Assert.assertFalse(CardUtil.haveSameNames(normalSpell, "Other", currentGame)); -// Assert.assertFalse(CardUtil.haveSameNames(normalSpell, EmptyNames.FACE_DOWN_CREATURE.toString(), currentGame)); +// Assert.assertFalse(CardUtil.haveSameNames(normalSpell, EmptyNames.FACE_DOWN_CREATURE.getObjectName(), currentGame)); // Assert.assertTrue(CardUtil.haveSameNames(normalSpell, "Balduvian Bears", currentGame)); // // face down spell // Assert.assertFalse(CardUtil.haveSameNames(faceDownSpell, "", currentGame)); // Assert.assertFalse(CardUtil.haveSameNames(faceDownSpell, "Other", currentGame)); -// Assert.assertFalse(CardUtil.haveSameNames(faceDownSpell, EmptyNames.FACE_DOWN_CREATURE.toString(), currentGame)); +// Assert.assertFalse(CardUtil.haveSameNames(faceDownSpell, EmptyNames.FACE_DOWN_CREATURE.getObjectName(), currentGame)); // Assert.assertFalse(CardUtil.haveSameNames(faceDownSpell, "Balduvian Bears", currentGame)); } diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java index 056f001c92f..8a19656b323 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java @@ -56,19 +56,12 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.isDiesEvent()) { - if (zEvent.getTarget() != null) { - if (!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId())) { - // TODO: remove this workaround for Basri's Lieutenant - return true; - } else { - if (filter.match(zEvent.getTarget(), getControllerId(), this, game)) { - return true; - } - } - } + if (!zEvent.isDiesEvent() || zEvent.getTarget() == null) { + return false; } - return false; + // TODO: remove applyFilterOnSource workaround for Basri's Lieutenant + return ((!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId())) + || filter.match(zEvent.getTarget(), getControllerId(), this, game)); } @Override diff --git a/Mage/src/main/java/mage/abilities/common/EnduringGlimmerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EnduringGlimmerTriggeredAbility.java index 2051be69acf..acf578c8e87 100644 --- a/Mage/src/main/java/mage/abilities/common/EnduringGlimmerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EnduringGlimmerTriggeredAbility.java @@ -61,7 +61,7 @@ class EnduringGlimmerReturnEffect extends OneShotEffect { } game.addEffect(new EnduringGlimmerTypeEffect() .setTargetPointer(new FixedTarget(new MageObjectReference(card, game, 1))), source); - return player.moveCards(card, Zone.BATTLEFIELD, source, game); + return player.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null); } } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java index 3b0cddcbf60..c1456292c37 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java @@ -74,8 +74,6 @@ public interface ContinuousEffect extends Effect { boolean isYourNextEndStep(Game game); - boolean isTheNextEndStep(Game game); - boolean isYourNextUpkeepStep(Game game); @Override diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index 901dc44d102..c8a5b78dd0e 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -56,11 +56,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu // until your next turn or until end of your next turn private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability) - private UUID activePlayerId; // Player whose turn the effect started on private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active private int effectStartingOnTurn = 0; // turn the effect started - private int effectControllerStartingEndStep = 0; - private int effectActivePlayerStartingEndStep = 0; + private int effectStartingEndStep = 0; private int nextTurnNumber = Integer.MAX_VALUE; // effect is waiting for a step during your next turn, we store it if found. // set to the turn number on your next turn. private int effectStartingStepNum = 0; // Some continuous are waiting for the next step of a kind. @@ -95,7 +93,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.startingControllerId = effect.startingControllerId; this.startingTurnWasActive = effect.startingTurnWasActive; this.effectStartingOnTurn = effect.effectStartingOnTurn; - this.effectControllerStartingEndStep = effect.effectControllerStartingEndStep; + this.effectStartingEndStep = effect.effectStartingEndStep; this.dependencyTypes = effect.dependencyTypes; this.dependendToTypes = effect.dependendToTypes; this.characterDefining = effect.characterDefining; @@ -253,12 +251,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId) { this.startingControllerId = startingController; - this.activePlayerId = activePlayerId; this.startingTurnWasActive = activePlayerId != null && activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too this.effectStartingOnTurn = game.getTurnNum(); - this.effectControllerStartingEndStep = EndStepCountWatcher.getCount(startingController, game); - this.effectActivePlayerStartingEndStep = EndStepCountWatcher.getCount(activePlayerId, game); + this.effectStartingEndStep = EndStepCountWatcher.getCount(startingController, game); this.effectStartingStepNum = game.getState().getStepNum(); } @@ -270,12 +266,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public boolean isYourNextEndStep(Game game) { - return EndStepCountWatcher.getCount(startingControllerId, game) > effectControllerStartingEndStep; - } - - @Override - public boolean isTheNextEndStep(Game game) { - return EndStepCountWatcher.getCount(activePlayerId, game) > effectActivePlayerStartingEndStep; + return EndStepCountWatcher.getCount(startingControllerId, game) > effectStartingEndStep; } public boolean isEndCombatOfYourNextTurn(Game game) { @@ -300,14 +291,13 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public boolean isInactive(Ability source, Game game) { - // YOUR turn checks + // YOUR turn checks, players who left the game // until end of turn - must be checked on cleanup step, see rules 514.2 // other must checked here (active and leave players), see rules 800.4 switch (duration) { case UntilYourNextTurn: case UntilEndOfYourNextTurn: case UntilYourNextEndStep: - case UntilTheNextEndStep: case UntilEndCombatOfYourNextTurn: case UntilYourNextUpkeepStep: break; @@ -352,10 +342,6 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu return this.isYourNextEndStep(game); } break; - case UntilTheNextEndStep: - if (player != null && player.isInGame()) { - return this.isTheNextEndStep(game); - } case UntilEndCombatOfYourNextTurn: if (player != null && player.isInGame()) { return this.isEndCombatOfYourNextTurn(game); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 35a309dff83..8dd4e6cf9ea 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -120,8 +120,8 @@ public class ContinuousEffects implements Serializable { preventionEffects.removeEndOfCombatEffects(); requirementEffects.removeEndOfCombatEffects(); restrictionEffects.removeEndOfCombatEffects(); - for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) { - asThoughtlist.removeEndOfCombatEffects(); + for (ContinuousEffectsList asThoughlist : asThoughEffectsMap.values()) { + asThoughlist.removeEndOfCombatEffects(); } costModificationEffects.removeEndOfCombatEffects(); spliceCardEffects.removeEndOfCombatEffects(); @@ -134,13 +134,27 @@ public class ContinuousEffects implements Serializable { preventionEffects.removeEndOfTurnEffects(game); requirementEffects.removeEndOfTurnEffects(game); restrictionEffects.removeEndOfTurnEffects(game); - for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) { - asThoughtlist.removeEndOfTurnEffects(game); + for (ContinuousEffectsList asThoughlist : asThoughEffectsMap.values()) { + asThoughlist.removeEndOfTurnEffects(game); } costModificationEffects.removeEndOfTurnEffects(game); spliceCardEffects.removeEndOfTurnEffects(game); } + public synchronized void removeBeginningOfEndStepEffects(Game game) { + layeredEffects.removeBeginningOfEndStepEffects(game); + continuousRuleModifyingEffects.removeBeginningOfEndStepEffects(game); + replacementEffects.removeBeginningOfEndStepEffects(game); + preventionEffects.removeBeginningOfEndStepEffects(game); + requirementEffects.removeBeginningOfEndStepEffects(game); + restrictionEffects.removeBeginningOfEndStepEffects(game); + for (ContinuousEffectsList asThoughlist : asThoughEffectsMap.values()) { + asThoughlist.removeBeginningOfEndStepEffects(game); + } + costModificationEffects.removeBeginningOfEndStepEffects(game); + spliceCardEffects.removeBeginningOfEndStepEffects(game); + } + public synchronized void removeInactiveEffects(Game game) { layeredEffects.removeInactiveEffects(game); continuousRuleModifyingEffects.removeInactiveEffects(game); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java index a75c476971b..5b517f58f2a 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java @@ -71,6 +71,29 @@ public class ContinuousEffectsList extends ArrayList } } + public void removeBeginningOfEndStepEffects(Game game) { + // calls every turn on beginning of end step + // rules 514.2 + for (Iterator i = this.iterator(); i.hasNext(); ) { + T entry = i.next(); + boolean canRemove; + switch (entry.getDuration()) { + case UntilNextEndStep: + canRemove = true; + break; + case UntilYourNextEndStep: + canRemove = entry.isYourNextEndStep(game); + break; + default: + canRemove = false; + } + if (canRemove) { + i.remove(); + effectAbilityMap.remove(entry.getId()); + } + } + } + public void removeEndOfCombatEffects() { for (Iterator i = this.iterator(); i.hasNext(); ) { T entry = i.next(); @@ -156,7 +179,6 @@ public class ContinuousEffectsList extends ArrayList case UntilEndOfYourNextTurn: case UntilEndCombatOfYourNextTurn: case UntilYourNextEndStep: - case UntilTheNextEndStep: case UntilYourNextUpkeepStep: // until your turn effects continue until real turn reached, their used it's own inactive method // 514.2 Second, the following actions happen simultaneously: all damage marked on permanents @@ -170,6 +192,7 @@ public class ContinuousEffectsList extends ArrayList } break; case EndOfTurn: + case UntilNextEndStep: // end of turn discards on cleanup steps // 514.2 break; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java index 0180438e7e7..7efa8bccbce 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseNewTargetsTargetEffect.java @@ -7,6 +7,8 @@ import mage.constants.Outcome; import mage.game.Game; import mage.game.stack.StackObject; +import java.util.UUID; + /** * @author BetaSteward_at_googlemail.com */ @@ -39,11 +41,15 @@ public class ChooseNewTargetsTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - StackObject stackObject = game.getStack().getStackObject(source.getFirstTarget()); - if (stackObject != null) { - return stackObject.chooseNewTargets(game, source.getControllerId(), forceChange, onlyOneTarget, null); + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + StackObject stackObject = game.getStack().getStackObject(targetId); + if (stackObject != null) { + stackObject.chooseNewTargets( + game, source.getControllerId(), forceChange, onlyOneTarget, null + ); + } } - return false; + return true; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java index 800c54452b5..30cada36483 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java @@ -125,6 +125,10 @@ public class DevourEffect extends ReplacementEffectImpl { @Override public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + String text = "Devour "; String filterMessage = filterDevoured.getMessage(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java index f0850a3d560..23ff219ca5a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java @@ -32,7 +32,7 @@ public class ExileTopXMayPlayUntilEffect extends OneShotEffect { makeText(amount.toString().equals("1") ? "that card" : "those cards", duration == Duration.EndOfTurn); } - private ExileTopXMayPlayUntilEffect(final ExileTopXMayPlayUntilEffect effect) { + protected ExileTopXMayPlayUntilEffect(final ExileTopXMayPlayUntilEffect effect) { super(effect); this.amount = effect.amount.copy(); this.duration = effect.duration; @@ -60,10 +60,15 @@ public class ExileTopXMayPlayUntilEffect extends OneShotEffect { if (!cards.isEmpty()) { game.addEffect(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, duration) .setTargetPointer(new FixedTargets(cards, game)), source); + effectCards(game, source, cards); } return true; } + protected void effectCards(Game game, Ability source, Set cards) { + //Do nothing, used for derived classes + } + /** * [Until end of turn, ] you may play [refCardText] [this turn] */ @@ -76,7 +81,11 @@ public class ExileTopXMayPlayUntilEffect extends OneShotEffect { String text = "exile the top "; boolean singular = amount.toString().equals("1"); text += singular ? "card" : CardUtil.numberToText(amount.toString()) + " cards"; - text += " of your library. "; + if (amount.toString().equals("X")) { + text += " of your library, where X is " + amount.getMessage() + ". "; + } else { + text += " of your library. "; + } if (durationRuleAtEnd) { text += "You may play " + refCardText + ' ' + (duration == Duration.EndOfTurn ? "this turn" : duration.toString()); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java index a570d04c5d1..dc944f0add3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/GoadTargetEffect.java @@ -26,7 +26,11 @@ public class GoadTargetEffect extends ContinuousEffectImpl { * each combat if able and attacks a player other than that player if able. */ public GoadTargetEffect() { - super(Duration.UntilYourNextTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment); + this(Duration.UntilYourNextTurn); + } + + public GoadTargetEffect(Duration duration) { + super(duration, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment); } private GoadTargetEffect(final GoadTargetEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java index 24ec41ea6f7..099abc9d1ac 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java @@ -251,7 +251,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { String originalObjectInfo = object.toString(); // warning, it's a direct changes to the object (without game state, so no game param here) - object.setName(EmptyNames.FACE_DOWN_CREATURE.toString()); + object.setName(EmptyNames.FACE_DOWN_CREATURE.getObjectName()); object.removeAllSuperTypes(); object.getSubtype().clear(); object.removeAllCardTypes(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggerControlledETBReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggerControlledETBReplacementEffect.java index 38267422654..c4b4b464a24 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggerControlledETBReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/AdditionalTriggerControlledETBReplacementEffect.java @@ -8,6 +8,7 @@ import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; import mage.game.events.NumberOfTriggersEvent; +import mage.util.CardUtil; /** * @author TheElk801 @@ -36,15 +37,11 @@ public class AdditionalTriggerControlledETBReplacementEffect extends Replacement @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!(event instanceof NumberOfTriggersEvent)) { - return false; - } - NumberOfTriggersEvent numberOfTriggersEvent = (NumberOfTriggersEvent) event; // Only triggers for the source controller if (!source.isControlledBy(event.getPlayerId())) { return false; } - GameEvent sourceEvent = numberOfTriggersEvent.getSourceEvent(); + GameEvent sourceEvent = ((NumberOfTriggersEvent) event).getSourceEvent(); // Only EtB triggers if (sourceEvent == null || sourceEvent.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD @@ -52,12 +49,12 @@ public class AdditionalTriggerControlledETBReplacementEffect extends Replacement return false; } // Only for triggers of permanents - return game.getPermanent(numberOfTriggersEvent.getSourceId()) != null; + return game.getPermanent(event.getSourceId()) != null; } @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(event.getAmount() + 1); + event.setAmount(CardUtil.overflowInc(event.getAmount(), 1)); return false; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java new file mode 100644 index 00000000000..979c26e60a6 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java @@ -0,0 +1,92 @@ +package mage.abilities.effects.common.replacement; + +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.util.CardUtil; + +/** + * @author notgreat + */ +public class GraveyardFromAnywhereExileReplacementEffect extends ReplacementEffectImpl { + + private final FilterCard filter; + private final boolean onlyYou; + private final boolean tokens; + + public GraveyardFromAnywhereExileReplacementEffect(FilterCard filter, boolean onlyYou) { + this(Duration.WhileOnBattlefield, filter, onlyYou, false); + } + + public GraveyardFromAnywhereExileReplacementEffect(boolean onlyYou, boolean tokens) { + this(Duration.WhileOnBattlefield, StaticFilters.FILTER_CARD_A, onlyYou, tokens); + } + + public GraveyardFromAnywhereExileReplacementEffect(Duration duration) { + this(duration, StaticFilters.FILTER_CARD_A, true, false); + } + protected GraveyardFromAnywhereExileReplacementEffect(Duration duration, FilterCard filter, boolean onlyYou, boolean tokens) { + super(duration, Outcome.Exile); + this.filter = filter; + this.onlyYou = onlyYou; + this.tokens = tokens; + this.setText(); + } + + private GraveyardFromAnywhereExileReplacementEffect(final GraveyardFromAnywhereExileReplacementEffect effect) { + super(effect); + this.filter = effect.filter; + this.onlyYou = effect.onlyYou; + this.tokens = effect.tokens; + } + + private void setText() { + this.staticText = "If " + CardUtil.addArticle(filter.getMessage()) + (tokens ? " or token" : "") + + " would be put into " + (onlyYou ? "your" : "a") + " graveyard from anywhere" + + (duration == Duration.EndOfTurn ? " this turn" : "") + ", exile " + + ((filter == StaticFilters.FILTER_CARD_A && !tokens) ? "that card" : "it") + " instead"; + } + + @Override + public GraveyardFromAnywhereExileReplacementEffect copy() { + return new GraveyardFromAnywhereExileReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + ((ZoneChangeEvent) event).setToZone(Zone.EXILED); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getToZone() != Zone.GRAVEYARD) { + return false; + } + Card card = game.getCard(event.getTargetId()); + if (card != null && (!onlyYou || card.isOwnedBy(source.getControllerId())) && (filter == null || filter.match(card, game))) { + return true; + } + Permanent token = game.getPermanent(event.getTargetId()); + if (tokens && (token instanceof PermanentToken && (!onlyYou || token.isOwnedBy(source.getControllerId())))) { + return true; + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java index 1d1b9954d18..aba3e39d53c 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java @@ -18,7 +18,10 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.CardUtil; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; /** * Manifest @@ -79,7 +82,7 @@ public class ManifestEffect extends OneShotEffect { this(amount, isPlural, false); } - private ManifestEffect(DynamicValue amount, boolean isPlural, boolean cloakNotManifest) { + public ManifestEffect(DynamicValue amount, boolean isPlural, boolean cloakNotManifest) { super(Outcome.PutCreatureInPlay); this.amount = amount; this.isPlural = isPlural; @@ -115,6 +118,10 @@ public class ManifestEffect extends OneShotEffect { } public static List doManifestCards(Game game, Ability source, Player manifestPlayer, Set cardsToManifest, boolean cloakNotManifest) { + return doManifestCards(game, source, manifestPlayer, cardsToManifest, cloakNotManifest, false); + } + + public static List doManifestCards(Game game, Ability source, Player manifestPlayer, Set cardsToManifest, boolean cloakNotManifest, boolean tapped) { if (cardsToManifest.isEmpty()) { return Collections.emptyList(); } @@ -149,7 +156,7 @@ public class ManifestEffect extends OneShotEffect { List manifested = new ArrayList<>(); // move cards to battlefield as face down // TODO: possible buggy for multiple cards, see rule 701.34e - it require manifest one by one (card to check: Omarthis, Ghostfire Initiate) - manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, false, true, false, null); + manifestPlayer.moveCards(cardsToManifest, Zone.BATTLEFIELD, source, game, tapped, true, false, null); for (Card card : cardsToManifest) { Card battlefieldCard = BecomesFaceDownCreatureEffect.findDefaultCardSideForFaceDown(game, card); diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 651baee0613..fa2462a09ed 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -870,7 +870,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public String getLogName() { if (name.isEmpty()) { - return GameLog.getNeutralColoredText(EmptyNames.FACE_DOWN_CREATURE.toString()); + return GameLog.getNeutralColoredText(EmptyNames.FACE_DOWN_CREATURE.getObjectName()); } else { return GameLog.getColoredObjectIdName(this); } diff --git a/Mage/src/main/java/mage/constants/Duration.java b/Mage/src/main/java/mage/constants/Duration.java index da37dd1e973..a65221b40ad 100644 --- a/Mage/src/main/java/mage/constants/Duration.java +++ b/Mage/src/main/java/mage/constants/Duration.java @@ -13,7 +13,7 @@ public enum Duration { EndOfTurn("until end of turn", true, true), UntilYourNextTurn("until your next turn", true, true), UntilYourNextEndStep("until your next end step", true, true), - UntilTheNextEndStep("until your next end step", true, true), + UntilNextEndStep("until the beginning of the next end step", true, true), UntilEndCombatOfYourNextTurn("until end of combat on your next turn", true, true), UntilYourNextUpkeepStep("until your next upkeep", true, true), UntilEndOfYourNextTurn("until the end of your next turn", true, true), diff --git a/Mage/src/main/java/mage/constants/EmptyNames.java b/Mage/src/main/java/mage/constants/EmptyNames.java index 96696c533c5..350607b8f46 100644 --- a/Mage/src/main/java/mage/constants/EmptyNames.java +++ b/Mage/src/main/java/mage/constants/EmptyNames.java @@ -1,33 +1,50 @@ package mage.constants; +import java.util.Arrays; + /** - * * @author JayDi85 */ public enum EmptyNames { - - // TODO: make names for that cards and enable Assert.assertNotEquals("", permanentName); for assertXXX tests // TODO: replace all getName().equals to haveSameNames and haveEmptyName - FACE_DOWN_CREATURE(""), // "Face down creature" - FACE_DOWN_TOKEN(""), // "Face down token" - FACE_DOWN_CARD(""); // "Face down card" + FACE_DOWN_CREATURE("", "[face_down_creature]"), // "Face down creature" + FACE_DOWN_TOKEN("", "[face_down_token]"), // "Face down token" + FACE_DOWN_CARD("", "[face_down_card]"); // "Face down card" public static final String EMPTY_NAME_IN_LOGS = "face down object"; - private final String cardName; + private final String objectName; // for mtg rules + private final String testCommand; // for unit tests - EmptyNames(String cardName) { - this.cardName = cardName; + EmptyNames(String objectName, String testCommand) { + this.objectName = objectName; + this.testCommand = testCommand; } @Override public String toString() { - return cardName; + return objectName; + } + + /** + * Face down choice for unit tests (use it instead empty string) + */ + public String getTestCommand() { + return this.testCommand; + } + + public String getObjectName() { + return this.objectName; } public static boolean isEmptyName(String objectName) { - return objectName.equals(FACE_DOWN_CREATURE.toString()) - || objectName.equals(FACE_DOWN_TOKEN.toString()) - || objectName.equals(FACE_DOWN_CARD.toString()); + return objectName.equals(FACE_DOWN_CREATURE.getObjectName()) + || objectName.equals(FACE_DOWN_TOKEN.getObjectName()) + || objectName.equals(FACE_DOWN_CARD.getObjectName()); + } + + public static String replaceTestCommandByObjectName(String searchCommand) { + EmptyNames res = Arrays.stream(values()).filter(e -> e.testCommand.equals(searchCommand)).findAny().orElse(null); + return res == null ? searchCommand : res.objectName; } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 400daff1cd7..43cacfdd583 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -19,6 +19,7 @@ import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.keyword.FinalityCounterEffect; import mage.abilities.effects.keyword.ShieldCounterEffect; import mage.abilities.effects.keyword.StunCounterEffect; +import mage.abilities.hint.common.DayNightHint; import mage.abilities.keyword.*; import mage.abilities.mana.DelayedTriggeredManaAbility; import mage.abilities.mana.TriggeredManaAbility; @@ -1443,6 +1444,9 @@ public abstract class GameImpl implements Game { getState().addWatcher(bloodthirstWatcher); } + /** + * Add source of some global effects (as hidden emblems), so users will see good image in stack and logs + */ public void initGameDefaultHelperEmblems() { // Rad Counter's trigger source @@ -1456,6 +1460,7 @@ public abstract class GameImpl implements Game { // global card hints for better UX for (UUID playerId : state.getPlayerList(startingPlayerId)) { state.addHelperEmblem(new XmageHelperEmblem().withCardHint("storm counter", StormAbility.getHint()), playerId); + state.addHelperEmblem(new XmageHelperEmblem().withCardHint("day or night", DayNightHint.instance), playerId); } } diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index e9238b39ab5..889ec20af27 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -692,6 +692,11 @@ public class GameState implements Serializable, Copyable { game.applyEffects(); } + // remove beginning of end step effects + public void removeBoESEffects(Game game) { + effects.removeBeginningOfEndStepEffects(game); + } + public void removeTurnStartEffect(Game game) { delayed.removeStartOfNewTurn(game); } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index ab9258fb39e..ccb394e13a4 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -248,7 +248,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { public String getName() { if (name.isEmpty()) { if (faceDown) { - return EmptyNames.FACE_DOWN_CREATURE.toString(); + return EmptyNames.FACE_DOWN_CREATURE.getObjectName(); } else { return ""; } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index e51643b1d1a..36825c296a0 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -71,7 +71,7 @@ public class PermanentToken extends PermanentImpl { @Override public String getName() { if (name.isEmpty()) { - return EmptyNames.FACE_DOWN_TOKEN.toString(); + return EmptyNames.FACE_DOWN_TOKEN.getObjectName(); } else { return name; } diff --git a/Mage/src/main/java/mage/game/turn/EndStep.java b/Mage/src/main/java/mage/game/turn/EndStep.java index d23ef87d7c8..26d32f20e17 100644 --- a/Mage/src/main/java/mage/game/turn/EndStep.java +++ b/Mage/src/main/java/mage/game/turn/EndStep.java @@ -3,8 +3,11 @@ package mage.game.turn; import mage.constants.PhaseStep; +import mage.game.Game; import mage.game.events.GameEvent.EventType; +import java.util.UUID; + /** * @author BetaSteward_at_googlemail.com */ @@ -26,4 +29,10 @@ public class EndStep extends Step { return new EndStep(this); } + @Override + public void beginStep(Game game, UUID activePlayerId) { + super.beginStep(game, activePlayerId); + + game.getState().removeBoESEffects(game); + } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index c16aeb3f188..1e4628f2e34 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -5199,7 +5199,7 @@ public abstract class PlayerImpl implements Player, Serializable { } visibleName = card.getLogName() + (card.isCopy() ? " (Copy)" : ""); } else { - visibleName = "a " + GameLog.getNeutralObjectIdName(EmptyNames.FACE_DOWN_CARD.toString(), card.getId()); + visibleName = "a " + GameLog.getNeutralObjectIdName(EmptyNames.FACE_DOWN_CARD.getObjectName(), card.getId()); } game.informPlayers(this.getLogName() + " moves " + visibleName + (fromZone != null ? " from " + fromZone.toString().toLowerCase(Locale.ENGLISH) : "")