Merge pull request #22 from magefree/master

Merge https://github.com/magefree/mage
This commit is contained in:
Zzooouhh 2017-12-02 18:34:34 +01:00 committed by GitHub
commit 6454033c5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2130 additions and 612 deletions

View file

@ -141,6 +141,18 @@
<artifactId>balloontip</artifactId> <artifactId>balloontip</artifactId>
<version>1.2.4.1</version> <version>1.2.4.1</version>
</dependency> </dependency>
<!-- svg support start -->
<dependency>
<groupId>batik</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.6-1</version>
</dependency>
<!-- svg support end -->
<dependency>
<groupId>org.ocpsoft.prettytime</groupId>
<artifactId>prettytime</artifactId>
<version>3.2.7.Final</version>
</dependency>
</dependencies> </dependencies>
<!-- to get the reference to local repository with com\googlecode\jspf\jspf-core\0.9.1\ --> <!-- to get the reference to local repository with com\googlecode\jspf\jspf-core\0.9.1\ -->

View file

@ -1,6 +1,5 @@
XMage.de 1 (Europe/Germany) fast :xmage.de:17171 XMage.de 1 (Europe/Germany) fast :xmage.de:17171
woogerworks (North America/USA) :xmage.woogerworks.com:17171 woogerworks (North America/USA) :xmage.woogerworks.com:17171
xmage.lukeskywalk.com (North America) :xmage.lukeskywalk.com:17171
play.xmage.net (North America/Canada) :play.xmage.net:17171 play.xmage.net (North America/Canada) :play.xmage.net:17171
XMageBr. (South America/Brazil) :magic.ncs3sistemas.com.br:17171 XMageBr. (South America/Brazil) :magic.ncs3sistemas.com.br:17171
XMage.tahiti :xmage.tahiti.one:443 XMage.tahiti :xmage.tahiti.one:443

View file

@ -92,6 +92,8 @@ import org.mage.plugins.card.images.DownloadPictures;
import org.mage.plugins.card.info.CardInfoPaneImpl; import org.mage.plugins.card.info.CardInfoPaneImpl;
import org.mage.plugins.card.utils.impl.ImageManagerImpl; import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */

View file

@ -48,6 +48,7 @@ import mage.view.CardView;
import mage.view.CardsView; import mage.view.CardsView;
import mage.view.SimpleCardView; import mage.view.SimpleCardView;
import org.mage.card.arcane.CardPanel; import org.mage.card.arcane.CardPanel;
import org.mage.card.arcane.ManaSymbolsCellRenderer;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
@ -164,6 +165,9 @@ public class CardsList extends javax.swing.JPanel implements MouseListener, ICar
mainTable.getColumnModel().getColumn(6).setPreferredWidth(15); mainTable.getColumnModel().getColumn(6).setPreferredWidth(15);
mainTable.getColumnModel().getColumn(7).setPreferredWidth(15); mainTable.getColumnModel().getColumn(7).setPreferredWidth(15);
// new mana render (svg support)
mainTable.getColumnModel().getColumn(mainModel.COLUMN_INDEX_COST).setCellRenderer(new ManaSymbolsCellRenderer());
if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DRAFT_VIEW, "cardView").equals("listView")) { if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_DRAFT_VIEW, "cardView").equals("listView")) {
jToggleListView.setSelected(true); jToggleListView.setSelected(true);
panelCardArea.setViewportView(mainTable); panelCardArea.setViewportView(mainTable);

View file

@ -27,6 +27,7 @@
*/ */
package mage.client.constants; package mage.client.constants;
import java.awt.*;
import java.io.File; import java.io.File;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.border.Border; import javax.swing.border.Border;
@ -72,18 +73,45 @@ public final class Constants {
public static final double SCALE_FACTOR = 0.5; public static final double SCALE_FACTOR = 0.5;
public static final String PLUGINS_DIRECTORY = "plugins/"; // cards render
public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149);
public static final Rectangle THUMBNAIL_SIZE_FULL = new Rectangle(102, 146);
public static final String RESOURCE_PATH_MANA_LARGE = IO.imageBaseDir + "symbols" + File.separator + "large"; // resources - default images
public static final String RESOURCE_PATH_MANA_MEDIUM = IO.imageBaseDir + "symbols" + File.separator + "medium"; public static final String RESOURCE_PATH_DEFAUL_IMAGES = File.separator + "default";
public static final String RESOURCE_PATH_SET = IO.imageBaseDir + "sets" + File.separator;
public static final String RESOURCE_PATH_SET_SMALL = RESOURCE_PATH_SET + File.separator + "small" + File.separator; // resources - symbols
public static final String BASE_SOUND_PATH = "sounds" + File.separator; public static final String RESOURCE_PATH_SYMBOLS = File.separator + "symbols";
public static final String RESOURCE_SYMBOL_FOLDER_SMALL = "small";
public static final String RESOURCE_SYMBOL_FOLDER_MEDIUM = "medium";
public static final String RESOURCE_SYMBOL_FOLDER_LARGE = "large";
public static final String RESOURCE_SYMBOL_FOLDER_SVG = "svg";
public static final String RESOURCE_SYMBOL_FOLDER_PNG = "png";
public enum ResourceSymbolSize {
SMALL,
MEDIUM,
LARGE,
SVG,
PNG
}
// resources - sets
public static final String RESOURCE_PATH_SETS = File.separator + "sets";
public static final String RESOURCE_SET_FOLDER_SMALL = "small";
public static final String RESOURCE_SET_FOLDER_MEDIUM = ""; // empty, medium images laydown in "sets" folder, TODO: delete that and auto gen, use png for html, not gif
public static final String RESOURCE_SET_FOLDER_SVG = "svg";
public enum ResourceSetSize {
SMALL,
MEDIUM,
SVG
}
// sound
public static final String BASE_SOUND_PATH = "sounds" + File.separator; // TODO: check path with File.separator
public static final String BASE_MUSICS_PATH = "music" + File.separator; public static final String BASE_MUSICS_PATH = "music" + File.separator;
public interface IO { public interface IO {
String DEFAULT_IMAGES_DIR = "plugins" + File.separator + "images" + File.separator;
String imageBaseDir = "plugins" + File.separator + "images" + File.separator;
String IMAGE_PROPERTIES_FILE = "image.url.properties"; String IMAGE_PROPERTIES_FILE = "image.url.properties";
} }

View file

@ -64,6 +64,7 @@ import mage.filter.predicate.other.CardTextPredicate;
import mage.filter.predicate.other.ExpansionSetPredicate; import mage.filter.predicate.other.ExpansionSetPredicate;
import mage.view.CardView; import mage.view.CardView;
import mage.view.CardsView; import mage.view.CardsView;
import org.mage.card.arcane.ManaSymbolsCellRenderer;
/** /**
* *
@ -133,6 +134,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene
mainTable.getColumnModel().getColumn(6).setPreferredWidth(15); mainTable.getColumnModel().getColumn(6).setPreferredWidth(15);
mainTable.getColumnModel().getColumn(7).setPreferredWidth(15); mainTable.getColumnModel().getColumn(7).setPreferredWidth(15);
// new mana render (svg support)
mainTable.getColumnModel().getColumn(mainModel.COLUMN_INDEX_COST).setCellRenderer(new ManaSymbolsCellRenderer());
// mainTable.setToolTipText(cardSelectorScrollPane.getToolTipText()); // mainTable.setToolTipText(cardSelectorScrollPane.getToolTipText());
cardSelectorScrollPane.setViewportView(mainTable); cardSelectorScrollPane.setViewportView(mainTable);
mainTable.setOpaque(false); mainTable.setOpaque(false);

View file

@ -204,7 +204,7 @@ public class MageBook extends JComponent {
if (setImage != null) { if (setImage != null) {
tab.setOverlayImage(setImage); tab.setOverlayImage(setImage);
} else { } else {
System.out.println("Couldn't find symbol image: " + "/plugins/images/sets/" + set + "-C.jpg"); System.out.println("Couldn't find symbol image: " + set + "-C.jpg");
} }
tab.setSet(set); tab.setSet(set);
tab.setBounds(0, y, 39, 120); tab.setBounds(0, y, 39, 120);

View file

@ -79,6 +79,7 @@ public class TableModel extends AbstractTableModel implements ICardGrid {
private UpdateCountsCallback updateCountsCallback; private UpdateCountsCallback updateCountsCallback;
private final String column[] = {"Qty", "Name", "Cost", "Color", "Type", "Stats", "Rarity", "Set", "#"}; private final String column[] = {"Qty", "Name", "Cost", "Color", "Type", "Stats", "Rarity", "Set", "#"};
public final int COLUMN_INDEX_COST = 2;
private SortSetting sortSetting; private SortSetting sortSetting;
private int recentSortedColumn; private int recentSortedColumn;
@ -239,6 +240,10 @@ public class TableModel extends AbstractTableModel implements ICardGrid {
case 1: case 1:
return c.getName(); return c.getName();
case 2: case 2:
// new svg images version
return ManaSymbols.getStringManaCost(c.getManaCost());
/*
// old html images version
String manaCost = ""; String manaCost = "";
for (String m : c.getManaCost()) { for (String m : c.getManaCost()) {
manaCost += m; manaCost += m;
@ -246,6 +251,8 @@ public class TableModel extends AbstractTableModel implements ICardGrid {
String castingCost = UI.getDisplayManaCost(manaCost); String castingCost = UI.getDisplayManaCost(manaCost);
castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, ManaSymbols.Type.TABLE); castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, ManaSymbols.Type.TABLE);
return "<html>" + castingCost + "</html>"; return "<html>" + castingCost + "</html>";
return castingCost;
*/
case 3: case 3:
return c.getColorText(); return c.getColorText();
case 4: case 4:

View file

@ -32,9 +32,11 @@ import org.apache.log4j.Logger;
import org.mage.plugins.card.CardPluginImpl; import org.mage.plugins.card.CardPluginImpl;
import org.mage.plugins.theme.ThemePluginImpl; import org.mage.plugins.theme.ThemePluginImpl;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
public enum Plugins implements MagePlugins { public enum Plugins implements MagePlugins {
instance; instance;
public static final String PLUGINS_DIRECTORY = "plugins/"; public static final String PLUGINS_DIRECTORY = "plugins";
private static final Logger LOGGER = Logger.getLogger(Plugins.class); private static final Logger LOGGER = Logger.getLogger(Plugins.class);
private static PluginManager pm; private static PluginManager pm;
@ -48,9 +50,10 @@ public enum Plugins implements MagePlugins {
@Override @Override
public void loadPlugins() { public void loadPlugins() {
LOGGER.info("Loading plugins..."); LOGGER.info("Loading plugins...");
pm = PluginManagerFactory.createPluginManager(); pm = PluginManagerFactory.createPluginManager();
pm.addPluginsFrom(new File(PLUGINS_DIRECTORY).toURI()); pm.addPluginsFrom(new File(PLUGINS_DIRECTORY + File.separator).toURI());
this.cardPlugin = new CardPluginImpl(); this.cardPlugin = new CardPluginImpl();
this.counterPlugin = pm.getPlugin(CounterPlugin.class); this.counterPlugin = pm.getPlugin(CounterPlugin.class);
this.themePlugin = new ThemePluginImpl(); this.themePlugin = new ThemePluginImpl();
@ -131,10 +134,8 @@ public enum Plugins implements MagePlugins {
@Override @Override
public void downloadSymbols() { public void downloadSymbols() {
String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true");
String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
if (this.cardPlugin != null) { if (this.cardPlugin != null) {
this.cardPlugin.downloadSymbols(path); this.cardPlugin.downloadSymbols(getImagesDir());
} }
} }

View file

@ -46,6 +46,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import mage.cards.decks.importer.DeckImporterUtil; import mage.cards.decks.importer.DeckImporterUtil;
import mage.client.MageFrame; import mage.client.MageFrame;
import mage.client.SessionHandler; import mage.client.SessionHandler;
@ -54,7 +57,6 @@ import mage.client.components.MageComponents;
import mage.client.dialog.*; import mage.client.dialog.*;
import static mage.client.dialog.PreferencesDialog.KEY_TABLES_COLUMNS_ORDER; import static mage.client.dialog.PreferencesDialog.KEY_TABLES_COLUMNS_ORDER;
import static mage.client.dialog.PreferencesDialog.KEY_TABLES_COLUMNS_WIDTH; import static mage.client.dialog.PreferencesDialog.KEY_TABLES_COLUMNS_WIDTH;
import static mage.client.table.TablesPanel.PASSWORDED;
import mage.client.util.ButtonColumn; import mage.client.util.ButtonColumn;
import mage.client.util.GUISizeHelper; import mage.client.util.GUISizeHelper;
import mage.client.util.IgnoreList; import mage.client.util.IgnoreList;
@ -70,6 +72,9 @@ import mage.view.RoomUsersView;
import mage.view.TableView; import mage.view.TableView;
import mage.view.UserRequestMessage; import mage.view.UserRequestMessage;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.ocpsoft.prettytime.PrettyTime;
import org.ocpsoft.prettytime.units.JustNow;
import org.ocpsoft.prettytime.Duration;
/** /**
* *
@ -80,7 +85,6 @@ public class TablesPanel extends javax.swing.JPanel {
private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); private static final Logger LOGGER = Logger.getLogger(TablesPanel.class);
private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 120, 180, 80, 120, 80, 60, 40, 40, 60}; private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 120, 180, 80, 120, 80, 60, 40, 40, 60};
public static final String PASSWORDED = "***";
private final TableTableModel tableModel; private final TableTableModel tableModel;
private final MatchesTableModel matchesModel; private final MatchesTableModel matchesModel;
private UUID roomId; private UUID roomId;
@ -94,12 +98,62 @@ public class TablesPanel extends javax.swing.JPanel {
private java.util.List<String> messages; private java.util.List<String> messages;
private int currentMessage; private int currentMessage;
private final MageTableRowSorter activeTablesSorter; private final MageTableRowSorter activeTablesSorter;
private final MageTableRowSorter completedTablesSorter;
private final ButtonColumn actionButton1; private final ButtonColumn actionButton1;
private final ButtonColumn actionButton2; private final ButtonColumn actionButton2;
final JToggleButton[] filterButtons; final JToggleButton[] filterButtons;
// time formater
private PrettyTime timeFormater = new PrettyTime();
// time ago renderer
TableCellRenderer timeAgoCellRenderer = new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Date d = (Date)value;
label.setText(timeFormater.format(d));
return label;
}
};
// duration renderer
TableCellRenderer durationCellRenderer = new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Long ms = (Long)value;
if(ms != 0){
Duration dur = timeFormater.approximateDuration(new Date(ms));
label.setText((timeFormater.formatDuration(dur)));
}else{
label.setText("");
}
return label;
}
};
// datetime render
TableCellRenderer datetimeCellRenderer = new DefaultTableCellRenderer() {
DateFormat datetimeFormater = new SimpleDateFormat("HH:mm:ss");
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Date d = (Date)value;
if(d != null){
label.setText(datetimeFormater.format(d));
}else{
label.setText("");
}
return label;
}
};
/** /**
* Creates new form TablesPanel * Creates new form TablesPanel
*/ */
@ -112,20 +166,56 @@ public class TablesPanel extends javax.swing.JPanel {
initComponents(); initComponents();
// tableModel.setSession(session); // tableModel.setSession(session);
tableTables.createDefaultColumnsFromModel(); // formater
timeFormater.setLocale(Locale.ENGLISH);
JustNow jn = timeFormater.getUnit(JustNow.class);
jn.setMaxQuantity(1000L * 30L); // 30 seconds gap (show "just now" from 0 to 30 secs)
// 1. TABLE CURRENT
tableTables.createDefaultColumnsFromModel();
activeTablesSorter = new MageTableRowSorter(tableModel); activeTablesSorter = new MageTableRowSorter(tableModel);
tableTables.setRowSorter(activeTablesSorter); tableTables.setRowSorter(activeTablesSorter);
// time ago
tableTables.getColumnModel().getColumn(TableTableModel.COLUMN_CREATED).setCellRenderer(timeAgoCellRenderer);
/* date sorter (not need, default is good - see getColumnClass)
activeTablesSorter.setComparator(TableTableModel.COLUMN_CREATED, new Comparator<Date>() {
@Override
public int compare(Date v1, Date v2) {
return v1.compareTo(v2);
}
});*/
// default sort by created date (last games from above)
ArrayList list = new ArrayList();
list.add(new RowSorter.SortKey(TableTableModel.COLUMN_CREATED, SortOrder.DESCENDING));
activeTablesSorter.setSortKeys(list);
TableUtil.setColumnWidthAndOrder(tableTables, DEFAULT_COLUMNS_WIDTH, TableUtil.setColumnWidthAndOrder(tableTables, DEFAULT_COLUMNS_WIDTH,
PreferencesDialog.KEY_TABLES_COLUMNS_WIDTH, PreferencesDialog.KEY_TABLES_COLUMNS_ORDER); PreferencesDialog.KEY_TABLES_COLUMNS_WIDTH, PreferencesDialog.KEY_TABLES_COLUMNS_ORDER); // TODO: is sort order save and restore after app restart/window open?
tableCompleted.setRowSorter(new MageTableRowSorter(matchesModel));
// 2. TABLE COMPLETED
completedTablesSorter = new MageTableRowSorter(matchesModel);
tableCompleted.setRowSorter(completedTablesSorter);
// duration
tableCompleted.getColumnModel().getColumn(MatchesTableModel.COLUMN_DURATION).setCellRenderer(durationCellRenderer);
// start-end
tableCompleted.getColumnModel().getColumn(MatchesTableModel.COLUMN_START).setCellRenderer(datetimeCellRenderer);
tableCompleted.getColumnModel().getColumn(MatchesTableModel.COLUMN_END).setCellRenderer(datetimeCellRenderer);
// default sort by ended date (last games from above)
ArrayList list2 = new ArrayList();
list2.add(new RowSorter.SortKey(MatchesTableModel.COLUMN_END, SortOrder.DESCENDING));
completedTablesSorter.setSortKeys(list2);
// 3. CHAT
chatPanelMain.getUserChatPanel().useExtendedView(ChatPanelBasic.VIEW_MODE.NONE); chatPanelMain.getUserChatPanel().useExtendedView(ChatPanelBasic.VIEW_MODE.NONE);
chatPanelMain.getUserChatPanel().setBorder(null); chatPanelMain.getUserChatPanel().setBorder(null);
chatPanelMain.getUserChatPanel().setChatType(ChatPanelBasic.ChatType.TABLES); chatPanelMain.getUserChatPanel().setChatType(ChatPanelBasic.ChatType.TABLES);
// 4. BUTTONS
filterButtons = new JToggleButton[]{btnStateWaiting, btnStateActive, btnStateFinished, filterButtons = new JToggleButton[]{btnStateWaiting, btnStateActive, btnStateFinished,
btnTypeMatch, btnTypeTourneyConstructed, btnTypeTourneyLimited, btnTypeMatch, btnTypeTourneyConstructed, btnTypeTourneyLimited,
btnFormatBlock, btnFormatStandard, btnFormatModern, btnFormatLegacy, btnFormatVintage, btnFormatCommander, btnFormatTinyLeader, btnFormatLimited, btnFormatOther, btnFormatBlock, btnFormatStandard, btnFormatModern, btnFormatLegacy, btnFormatVintage, btnFormatCommander, btnFormatTinyLeader, btnFormatLimited, btnFormatOther,
@ -181,7 +271,7 @@ public class TablesPanel extends javax.swing.JPanel {
if (isTournament) { if (isTournament) {
LOGGER.info("Joining tournament " + tableId); LOGGER.info("Joining tournament " + tableId);
if (deckType.startsWith("Limited")) { if (deckType.startsWith("Limited")) {
if (PASSWORDED.equals(pwdColumn)) { if (TableTableModel.PASSWORD_VALUE_YES.equals(pwdColumn)) {
joinTableDialog.showDialog(roomId, tableId, true, deckType.startsWith("Limited")); joinTableDialog.showDialog(roomId, tableId, true, deckType.startsWith("Limited"));
} else { } else {
SessionHandler.joinTournamentTable(roomId, tableId, SessionHandler.getUserName(), PlayerType.HUMAN, 1, null, ""); SessionHandler.joinTournamentTable(roomId, tableId, SessionHandler.getUserName(), PlayerType.HUMAN, 1, null, "");
@ -225,7 +315,7 @@ public class TablesPanel extends javax.swing.JPanel {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
int modelRow = Integer.valueOf(e.getActionCommand()); int modelRow = Integer.valueOf(e.getActionCommand());
String action = (String) matchesModel.getValueAt(modelRow, MatchesTableModel.ACTION_COLUMN); String action = (String) matchesModel.getValueAt(modelRow, MatchesTableModel.COLUMN_ACTION);
switch (action) { switch (action) {
case "Replay": case "Replay":
java.util.List<UUID> gameList = matchesModel.getListofGames(modelRow); java.util.List<UUID> gameList = matchesModel.getListofGames(modelRow);
@ -250,7 +340,7 @@ public class TablesPanel extends javax.swing.JPanel {
// !!!! adds action buttons to the table panel (don't delete this) // !!!! adds action buttons to the table panel (don't delete this)
actionButton1 = new ButtonColumn(tableTables, openTableAction, tableTables.convertColumnIndexToView(TableTableModel.ACTION_COLUMN)); actionButton1 = new ButtonColumn(tableTables, openTableAction, tableTables.convertColumnIndexToView(TableTableModel.ACTION_COLUMN));
actionButton2 = new ButtonColumn(tableCompleted, closedTableAction, tableCompleted.convertColumnIndexToView(MatchesTableModel.ACTION_COLUMN)); actionButton2 = new ButtonColumn(tableCompleted, closedTableAction, tableCompleted.convertColumnIndexToView(MatchesTableModel.COLUMN_ACTION));
// !!!! // !!!!
} }
@ -583,21 +673,27 @@ public class TablesPanel extends javax.swing.JPanel {
skillFilterList.add(RowFilter.regexFilter(SkillLevel.SERIOUS.toString(), TableTableModel.COLUMN_SKILL)); skillFilterList.add(RowFilter.regexFilter(SkillLevel.SERIOUS.toString(), TableTableModel.COLUMN_SKILL));
} }
String ratedMark = TableTableModel.RATED_VALUE_YES;
java.util.List<RowFilter<Object, Object>> ratingFilterList = new ArrayList<>(); java.util.List<RowFilter<Object, Object>> ratingFilterList = new ArrayList<>();
if (btnRated.isSelected()) { if (btnRated.isSelected()) {
ratingFilterList.add(RowFilter.regexFilter("^Rated", TableTableModel.COLUMN_RATING)); // yes word
ratingFilterList.add(RowFilter.regexFilter("^" + ratedMark, TableTableModel.COLUMN_RATING));
} }
if (btnUnrated.isSelected()) { if (btnUnrated.isSelected()) {
ratingFilterList.add(RowFilter.regexFilter("^Unrated", TableTableModel.COLUMN_RATING)); // not yes word, see https://stackoverflow.com/a/406408/1276632
ratingFilterList.add(RowFilter.regexFilter("^((?!" + ratedMark + ").)*$", TableTableModel.COLUMN_RATING));
} }
// Password // Password
String passwordMark = TableTableModel.PASSWORD_VALUE_YES;
java.util.List<RowFilter<Object, Object>> passwordFilterList = new ArrayList<>(); java.util.List<RowFilter<Object, Object>> passwordFilterList = new ArrayList<>();
if (btnOpen.isSelected()) {
passwordFilterList.add(RowFilter.regexFilter("^$", TableTableModel.COLUMN_PASSWORD));
}
if (btnPassword.isSelected()) { if (btnPassword.isSelected()) {
passwordFilterList.add(RowFilter.regexFilter("^\\*\\*\\*$", TableTableModel.COLUMN_PASSWORD)); // yes
passwordFilterList.add(RowFilter.regexFilter("^" + passwordMark, TableTableModel.COLUMN_PASSWORD));
}
if (btnOpen.isSelected()) {
// no
passwordFilterList.add(RowFilter.regexFilter("^((?!" + passwordMark + ").)*$", TableTableModel.COLUMN_PASSWORD));
} }
// Hide games of ignored players // Hide games of ignored players
@ -1281,16 +1377,24 @@ class TableTableModel extends AbstractTableModel {
public static final int COLUMN_QUIT_RATIO = 10; public static final int COLUMN_QUIT_RATIO = 10;
public static final int ACTION_COLUMN = 11; // column the action is located (starting with 0) public static final int ACTION_COLUMN = 11; // column the action is located (starting with 0)
public static final String RATED_VALUE_YES = "YES";
public static final String RATED_VALUE_NO = "";
public static final String PASSWORD_VALUE_YES = "YES";
private final String[] columnNames = new String[]{"M/T", "Deck Type", "Owner / Players", "Game Type", "Info", "Status", "Password", "Created / Started", "Skill Level", "Rating", "Quit %", "Action"}; private final String[] columnNames = new String[]{"M/T", "Deck Type", "Owner / Players", "Game Type", "Info", "Status", "Password", "Created / Started", "Skill Level", "Rating", "Quit %", "Action"};
private TableView[] tables = new TableView[0]; private TableView[] tables = new TableView[0];
private static final DateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss");
TableTableModel() {
}
public void loadData(Collection<TableView> tables) throws MageRemoteException { public void loadData(Collection<TableView> tables) throws MageRemoteException {
this.tables = tables.toArray(new TableView[0]); this.tables = tables.toArray(new TableView[0]);
this.fireTableDataChanged(); this.fireTableDataChanged();
} }
@Override @Override
public int getRowCount() { public int getRowCount() {
return tables.length; return tables.length;
@ -1317,13 +1421,13 @@ class TableTableModel extends AbstractTableModel {
case 5: case 5:
return tables[arg0].getTableStateText(); return tables[arg0].getTableStateText();
case 6: case 6:
return tables[arg0].isPassworded() ? PASSWORDED : ""; return tables[arg0].isPassworded() ? PASSWORD_VALUE_YES : "";
case 7: case 7:
return timeFormatter.format(tables[arg0].getCreateTime()); return tables[arg0].getCreateTime(); // use cell render, not format here
case 8: case 8:
return tables[arg0].getSkillLevel(); return tables[arg0].getSkillLevel();
case 9: case 9:
return tables[arg0].isRated() ? "Rated" : "Unrated"; return tables[arg0].isRated() ? RATED_VALUE_YES : RATED_VALUE_NO;
case 10: case 10:
return tables[arg0].getQuitRatio(); return tables[arg0].getQuitRatio();
case 11: case 11:
@ -1384,6 +1488,8 @@ class TableTableModel extends AbstractTableModel {
return Icon.class; return Icon.class;
case COLUMN_SKILL: case COLUMN_SKILL:
return SkillLevel.class; return SkillLevel.class;
case COLUMN_CREATED:
return Date.class;
default: default:
return String.class; return String.class;
} }
@ -1486,17 +1592,22 @@ class UpdatePlayersTask extends SwingWorker<Void, Collection<RoomUsersView>> {
class MatchesTableModel extends AbstractTableModel { class MatchesTableModel extends AbstractTableModel {
public static final int ACTION_COLUMN = 7; // column the action is located (starting with 0) private final String[] columnNames = new String[]{"Deck Type", "Players", "Game Type", "Rating", "Result", "Duration", "Start Time", "End Time", "Action"};
public static final int GAMES_LIST_COLUMN = 8; public static final int COLUMN_DURATION = 5;
private final String[] columnNames = new String[]{"Deck Type", "Players", "Game Type", "Rating", "Result", "Start Time", "End Time", "Action"}; public static final int COLUMN_START = 6;
public static final int COLUMN_END = 7;
public static final int COLUMN_ACTION = 8; // column the action is located (starting with 0)
private MatchView[] matches = new MatchView[0]; private MatchView[] matches = new MatchView[0];
private static final DateFormat timeFormatter = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
public void loadData(Collection<MatchView> matches) throws MageRemoteException { public void loadData(Collection<MatchView> matches) throws MageRemoteException {
this.matches = matches.toArray(new MatchView[0]); this.matches = matches.toArray(new MatchView[0]);
this.fireTableDataChanged(); this.fireTableDataChanged();
} }
MatchesTableModel(){
}
@Override @Override
public int getRowCount() { public int getRowCount() {
return matches.length; return matches.length;
@ -1517,22 +1628,20 @@ class MatchesTableModel extends AbstractTableModel {
case 2: case 2:
return matches[arg0].getGameType(); return matches[arg0].getGameType();
case 3: case 3:
return matches[arg0].isRated() ? "Rated" : "Unrated"; return matches[arg0].isRated() ? TableTableModel.RATED_VALUE_YES : TableTableModel.RATED_VALUE_NO;
case 4: case 4:
return matches[arg0].getResult(); return matches[arg0].getResult();
case 5: case 5:
if (matches[arg0].getStartTime() != null) { if (matches[arg0].getEndTime() != null) {
return timeFormatter.format(matches[arg0].getStartTime()); return matches[arg0].getEndTime().getTime() - matches[arg0].getStartTime().getTime() + new Date().getTime();
} else { } else {
return ""; return 0L;
} }
case 6: case 6:
if (matches[arg0].getEndTime() != null) { return matches[arg0].getStartTime();
return timeFormatter.format(matches[arg0].getEndTime());
} else {
return "";
}
case 7: case 7:
return matches[arg0].getEndTime();
case 8:
if (matches[arg0].isTournament()) { if (matches[arg0].isTournament()) {
return "Show"; return "Show";
} else if (matches[arg0].isReplayAvailable()) { } else if (matches[arg0].isReplayAvailable()) {
@ -1540,7 +1649,7 @@ class MatchesTableModel extends AbstractTableModel {
} else { } else {
return "None"; return "None";
} }
case 8: case 9:
return matches[arg0].getGames(); return matches[arg0].getGames();
} }
return ""; return "";
@ -1575,12 +1684,21 @@ class MatchesTableModel extends AbstractTableModel {
@Override @Override
public Class getColumnClass(int columnIndex) { public Class getColumnClass(int columnIndex) {
return String.class; switch (columnIndex) {
case COLUMN_DURATION:
return Long.class;
case COLUMN_START:
return Date.class;
case COLUMN_END:
return Date.class;
default:
return String.class;
}
} }
@Override @Override
public boolean isCellEditable(int rowIndex, int columnIndex) { public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == ACTION_COLUMN; return columnIndex == COLUMN_ACTION;
} }
} }

View file

@ -13,23 +13,21 @@ import mage.view.CardView;
import mage.view.CounterView; import mage.view.CounterView;
import mage.view.PermanentView; import mage.view.PermanentView;
import mage.view.StackAbilityView; import mage.view.StackAbilityView;
import net.java.truevfs.access.TFile;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.images.ImageCache; import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.impl.ImageManagerImpl; import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import mage.client.constants.Constants;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Map; import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.UUID; import java.util.UUID;
import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL;
/** /**
* Class for drawing the mage card object by using a form based JComponent * Class for drawing the mage card object by using a form based JComponent
* approach * approach
@ -479,8 +477,10 @@ public class CardPanelComponentImpl extends CardPanel {
if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth(), hasImage)) { if (getShowCastingCost() && !isAnimationPanel() && canShowCardIcons(getCardWidth(), hasImage)) {
int symbolMarginX = 2; // 2 px between icons
String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost());
int manaWidth = getManaWidth(manaCost); int manaWidth = getManaWidth(manaCost, symbolMarginX);
// right top corner with margin (sizes from any sample card, length from black border to mana icon) // right top corner with margin (sizes from any sample card, length from black border to mana icon)
int manaMarginRight = Math.round(22f / 672f * getCardWidth()); int manaMarginRight = Math.round(22f / 672f * getCardWidth());
@ -489,25 +489,19 @@ public class CardPanelComponentImpl extends CardPanel {
int manaX = getCardXOffset() + getCardWidth() - manaMarginRight - manaWidth; int manaX = getCardXOffset() + getCardWidth() - manaMarginRight - manaWidth;
int manaY = getCardYOffset() + manaMarginTop; int manaY = getCardYOffset() + manaMarginTop;
if (hasImage) { ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth(), Color.black, symbolMarginX);
// top right corner if have image like real card
ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth());
} else {
// old version - bottom left corner if haven't card
// ManaSymbols.draw(g, manaCost, getCardXOffset() + manaMarginRight, getCardYOffset() + getCardHeight() - manaMarginTop - getSymbolWidth(), getSymbolWidth());
// new version - like a normal image (it's best to view and construct decks)
ManaSymbols.draw(g, manaCost, manaX, manaY, getSymbolWidth());
}
} }
} }
private int getManaWidth(String manaCost) { private int getManaWidth(String manaCost, int symbolMarginX) {
int width = 0; int width = 0;
manaCost = manaCost.replace("\\", ""); manaCost = manaCost.replace("\\", "");
StringTokenizer tok = new StringTokenizer(manaCost, " "); StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) { while (tok.hasMoreTokens()) {
tok.nextToken(); tok.nextToken();
if(width != 0) {
width += symbolMarginX;
}
width += getSymbolWidth(); width += getSymbolWidth();
} }
return width; return width;
@ -654,7 +648,7 @@ public class CardPanelComponentImpl extends CardPanel {
final BufferedImage srcImage; final BufferedImage srcImage;
if (gameCard.isFaceDown()) { if (gameCard.isFaceDown()) {
srcImage = getFaceDownImage(); srcImage = getFaceDownImage();
} else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { } else if (getCardWidth() > Constants.THUMBNAIL_SIZE_FULL.width) {
srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight());
} else { } else {
srcImage = ImageCache.getThumbnail(gameCard); srcImage = ImageCache.getThumbnail(gameCard);

View file

@ -14,6 +14,7 @@ import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload; import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.images.ImageCache; import org.mage.plugins.card.images.ImageCache;
import mage.client.constants.Constants;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -21,8 +22,6 @@ import java.io.File;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL;
public class CardPanelRenderImpl extends CardPanel { public class CardPanelRenderImpl extends CardPanel {
private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class); private static final Logger LOGGER = Logger.getLogger(CardPanelRenderImpl.class);
@ -341,7 +340,7 @@ public class CardPanelRenderImpl extends CardPanel {
// Nothing to do // Nothing to do
srcImage = null; srcImage = null;
faceArtSrcImage = null; faceArtSrcImage = null;
} else if (getCardWidth() > THUMBNAIL_SIZE_FULL.width) { } else if (getCardWidth() > Constants.THUMBNAIL_SIZE_FULL.width) {
srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight());
faceArtSrcImage = ImageCache.getFaceImage(gameCard, getCardWidth(), getCardHeight()); faceArtSrcImage = ImageCache.getFaceImage(gameCard, getCardWidth(), getCardHeight());
} else { } else {

View file

@ -1,11 +1,13 @@
package org.mage.card.arcane; package org.mage.card.arcane;
import java.awt.Dimension; import java.awt.*;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
@ -16,27 +18,37 @@ import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap; import java.util.*;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*;
import mage.cards.repository.ExpansionRepository; import mage.cards.repository.ExpansionRepository;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.GUISizeHelper; import mage.client.util.GUISizeHelper;
import mage.client.util.ImageHelper; import mage.client.util.ImageHelper;
import mage.client.util.gui.BufferedImageBuilder; import mage.client.util.gui.BufferedImageBuilder;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.util.SVGConstants;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import mage.client.constants.Constants;
import mage.client.constants.Constants.ResourceSymbolSize;
import mage.client.constants.Constants.ResourceSetSize;
import org.jdesktop.swingx.graphics.ShadowRenderer;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.utils.CardImageUtils;
public final class ManaSymbols { public final class ManaSymbols {
private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class);
private static final Map<Integer, Map<String, BufferedImage>> manaImages = new HashMap<>(); private static final Map<Integer, Map<String, BufferedImage>> manaImages = new HashMap<>();
private static boolean smallSymbolsFound = false;
private static boolean mediumSymbolsFound = false;
private static final Map<String, Map<String, Image>> setImages = new HashMap<>(); private static final Map<String, Map<String, Image>> setImages = new HashMap<>();
@ -63,11 +75,46 @@ public final class ManaSymbols {
"BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU",
"WP", "UP", "BP", "RP", "GP", "X", "C", "E"}; "WP", "UP", "BP", "RP", "GP", "X", "C", "E"};
public static void loadImages() { private static final JLabel labelRender = new JLabel(); // render mana text
renameSymbols(getSymbolsPath() + File.separator + "symbols");
smallSymbolsFound = loadSymbolsImages(15);
mediumSymbolsFound = loadSymbolsImages(25);
public static void loadImages() {
// TODO: delete files rename jpg->gif (it was for backward compatibility for one of the old version?)
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.SMALL));
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.MEDIUM));
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.LARGE));
//renameSymbols(getSymbolsPath(ResourceSymbolSize.SVG)); // not need
// TODO: remove medium sets files to "medium" folder like symbols above?
// preload symbol images
loadSymbolImages(15);
loadSymbolImages(25);
loadSymbolImages(50);
// save symbol images in png for html replacement in texts
// you can add bigger size for better quality
Map<String, BufferedImage> pngImages = manaImages.get(50);
if (pngImages != null){
File pngPath = new File(getResourceSymbolsPath(ResourceSymbolSize.PNG));
if (!pngPath.exists()) {
pngPath.mkdirs();
}
for(String symbol: symbols){
try
{
BufferedImage image = pngImages.get(symbol);
if (image != null){
File newFile = new File(pngPath.getPath() + File.separator + symbol + ".png");
ImageIO.write(image, "png", newFile);
}
}catch (Exception e) {
LOGGER.warn("Can't generate png image for symbol:" + symbol);
}
}
}
// preload set images
List<String> setCodes = ExpansionRepository.instance.getSetCodes(); List<String> setCodes = ExpansionRepository.instance.getSetCodes();
if (setCodes == null) { if (setCodes == null) {
// the cards db file is probaly not included in the client. It will be created after the first connect to a server. // the cards db file is probaly not included in the client. It will be created after the first connect to a server.
@ -75,9 +122,11 @@ public final class ManaSymbols {
return; return;
} }
for (String set : setCodes) { for (String set : setCodes) {
if (withoutSymbols.contains(set)) { if (withoutSymbols.contains(set)) {
continue; continue;
} }
String[] codes; String[] codes;
if (onlyMythics.contains(set)) { if (onlyMythics.contains(set)) {
codes = new String[]{"M"}; codes = new String[]{"M"};
@ -88,8 +137,9 @@ public final class ManaSymbols {
Map<String, Image> rarityImages = new HashMap<>(); Map<String, Image> rarityImages = new HashMap<>();
setImages.put(set, rarityImages); setImages.put(set, rarityImages);
// load medium size
for (String rarityCode : codes) { for (String rarityCode : codes) {
File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + '-' + rarityCode + ".jpg"); File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + rarityCode + ".jpg");
try { try {
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
int width = image.getWidth(null); int width = image.getWidth(null);
@ -107,18 +157,19 @@ public final class ManaSymbols {
} }
} }
// generate small size
try { try {
File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM));
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
for (String code : codes) { for (String code : codes) {
file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + '-' + code + ".png"); file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png");
if (file.exists()) { if (file.exists()) {
continue; continue;
} }
file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + '-' + code + ".jpg"); file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg");
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
try { try {
int width = image.getWidth(null); int width = image.getWidth(null);
@ -130,7 +181,7 @@ public final class ManaSymbols {
} }
Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width));
BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r);
File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + '-' + code + ".png"); File newFile = new File(getResourceSetsPath(ResourceSetSize.SMALL) + set + '-' + code + ".png");
ImageIO.write(resized, "png", newFile); ImageIO.write(resized, "png", newFile);
} }
} catch (Exception e) { } catch (Exception e) {
@ -144,13 +195,15 @@ public final class ManaSymbols {
} }
} }
// mark loaded images
// TODO: delete that code, images draw-show must dynamicly
File file; File file;
for (String set : ExpansionRepository.instance.getSetCodes()) { for (String set : ExpansionRepository.instance.getSetCodes()) {
file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); file = new File(getResourceSetsPath(ResourceSetSize.SMALL));
if (!file.exists()) { if (!file.exists()) {
break; break;
} }
file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); file = new File(getResourceSetsPath(ResourceSetSize.SMALL) + set + "-C.png");
try { try {
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
int width = image.getWidth(null); int width = image.getWidth(null);
@ -161,42 +214,253 @@ public final class ManaSymbols {
} }
} }
private static boolean loadSymbolsImages(int size) { public static BufferedImage loadSVG(File svgFile, int resizeToWidth, int resizeToHeight, boolean useShadow) throws IOException {
boolean fileErrors = false;
HashMap<String, BufferedImage> sizedSymbols = new HashMap<>();
String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL;
if (size > 25) {
resourcePath = Constants.RESOURCE_PATH_MANA_LARGE;
} else if (size > 15) {
resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM;
}
for (String symbol : symbols) {
File file = new File(getSymbolsPath() + resourcePath + '/' + symbol + ".gif");
try {
if (size == 15 || size == 25) { // debug: disable shadow gen, need to test it
BufferedImage notResized = ImageIO.read(file); useShadow = false;
sizedSymbols.put(symbol, notResized);
} else { // load SVG image
Rectangle r = new Rectangle(size, size); // base loader code: https://stackoverflow.com/questions/11435671/how-to-get-a-buffererimage-from-a-svg
//Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); // resize code: https://vibranttechie.wordpress.com/2015/05/15/svg-loading-to-javafx-stage-and-auto-scaling-when-stage-resize/
BufferedImage image = ImageIO.read(file);
//BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); if (useShadow && ((resizeToWidth <= 0) || (resizeToHeight <= 0))){
if (image != null) { throw new IllegalArgumentException("Must use non zero sizes for shadow.");
BufferedImage resized = ImageHelper.getResizedImage(image, r); }
sizedSymbols.put(symbol, resized);
} final BufferedImage[] imagePointer = new BufferedImage[1];
// Rendering hints can't be set programatically, so
// we override defaults with a temporary stylesheet.
// These defaults emphasize quality and precision, and
// are more similar to the defaults of other SVG viewers.
// SVG documents can still override these defaults.
String css = "svg {" +
"shape-rendering: geometricPrecision;" +
"text-rendering: geometricPrecision;" +
"color-rendering: optimizeQuality;" +
"image-rendering: optimizeQuality;" +
"}";
File cssFile = File.createTempFile("batik-default-override-", ".css");
FileWriter w = new FileWriter(cssFile);
w.write(css);
w.close();
TranscodingHints transcoderHints = new TranscodingHints();
// resize
int shadowX = 0;
int shadowY = 0;
if(useShadow) {
// shadow size (16px image: 1px left, 2px bottom)
shadowX = 1 * Math.round(1f / 16f * resizeToWidth);
shadowY = 2 * Math.round(1f / 16f * resizeToHeight);
resizeToWidth = resizeToWidth - shadowX;
resizeToHeight = resizeToHeight - shadowY;
};
if(resizeToWidth > 0){
transcoderHints.put(ImageTranscoder.KEY_WIDTH, (float)resizeToWidth); //your image width
}
if(resizeToHeight > 0){
transcoderHints.put(ImageTranscoder.KEY_HEIGHT, (float)resizeToHeight); //your image height
}
transcoderHints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE);
transcoderHints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION,
SVGDOMImplementation.getDOMImplementation());
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,
SVGConstants.SVG_NAMESPACE_URI);
transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, "svg");
transcoderHints.put(ImageTranscoder.KEY_USER_STYLESHEET_URI, cssFile.toURI().toString());
try {
TranscoderInput input = new TranscoderInput(new FileInputStream(svgFile));
ImageTranscoder t = new ImageTranscoder() {
@Override
public BufferedImage createImage(int w, int h) {
return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
} }
} catch (IOException e) {
LOGGER.error("Error for symbol:" + symbol); @Override
public void writeImage(BufferedImage image, TranscoderOutput out)
throws TranscoderException {
imagePointer[0] = image;
}
};
t.setTranscodingHints(transcoderHints);
t.transcode(input, null);
}
catch (TranscoderException ex) {
// Requires Java 6
ex.printStackTrace();
throw new IOException("Couldn't convert " + svgFile);
}
finally {
cssFile.delete();
}
BufferedImage originImage = imagePointer[0];
if(useShadow && (originImage.getWidth() > 0)){
// draw shadow
// origin image was reduces in sizes to fit shadow
// see https://stackoverflow.com/a/40833715/1276632
// a filter which converts all colors except 0 to black
ImageProducer prod = new FilteredImageSource(originImage.getSource(), new RGBImageFilter() {
@Override
public int filterRGB(int x, int y, int rgb) {
if (rgb == 0)
return 0;
else
return 0xff000000;
}
});
// create whe black image
Image shadow = Toolkit.getDefaultToolkit().createImage(prod);
// result
BufferedImage result = new BufferedImage(originImage.getWidth() + shadowX, originImage.getHeight() + shadowY, originImage.getType());
Graphics2D g = (Graphics2D) result.getGraphics();
// draw shadow with offset (left bottom)
g.drawImage(shadow, -1 * shadowX, shadowY, null);
// draw original image
g.drawImage(originImage, 0, 0, null);
return result;
}else{
// return origin image without shadow
return originImage;
}
/*
BufferedImage base = GraphicsUtilities.createCompatibleTranslucentImage(w, h);
Graphics2D g2 = base.createGraphics();
g2.setColor(Color.WHITE);
g2.fillRoundRect(0, 0, image.getWidth(), image.getHeight(), 10, 10);
g2.dispose();
ShadowRenderer renderer = new ShadowRenderer(shadowSize, 0.5f,
Color.GRAY);
return renderer.createShadow(base);
*/
//imagePointer[0];
}
private static File getSymbolFileNameAsSVG(String symbol){
return new File(getResourceSymbolsPath(ResourceSymbolSize.SVG) + symbol + ".svg");
}
private static BufferedImage loadSymbolAsSVG(String symbol, int resizeToWidth, int resizeToHeight){
File sourceFile = getSymbolFileNameAsSVG(symbol);
return loadSymbolAsSVG(sourceFile, resizeToWidth, resizeToHeight);
}
private static BufferedImage loadSymbolAsSVG(File sourceFile, int resizeToWidth, int resizeToHeight){
try{
// no need to resize svg (lib already do it on load)
return loadSVG(sourceFile, resizeToWidth, resizeToHeight, true);
} catch (Exception e) {
LOGGER.error("Can't load svg symbol: " + sourceFile.getPath());
return null;
}
}
private static File getSymbolFileNameAsGIF(String symbol, int size){
ResourceSymbolSize needSize = null;
if (size <= 15){
needSize = ResourceSymbolSize.SMALL;
}else if (size <= 25){
needSize = ResourceSymbolSize.MEDIUM;
} else {
needSize = ResourceSymbolSize.LARGE;
}
return new File(getResourceSymbolsPath(needSize) + symbol + ".gif");
}
private static BufferedImage loadSymbolAsGIF(String symbol, int resizeToWidth, int resizeToHeight){
File file = getSymbolFileNameAsGIF(symbol, resizeToWidth);
return loadSymbolAsGIF(file, resizeToWidth, resizeToHeight);
}
private static BufferedImage loadSymbolAsGIF(File sourceFile, int resizeToWidth, int resizeToHeight){
BufferedImage image = null;
try {
if ((resizeToWidth == 15) || (resizeToWidth == 25)){
// normal size
image = ImageIO.read(sourceFile);
}else{
// resize size
image = ImageIO.read(sourceFile);
if (image != null) {
Rectangle r = new Rectangle(resizeToWidth, resizeToHeight);
image = ImageHelper.getResizedImage(image, r);
}
}
} catch (IOException e) {
LOGGER.error("Can't load gif symbol: " + sourceFile.getPath());
return null;
}
return image;
}
private static boolean loadSymbolImages(int size) {
// load all symbols to cash
// priority: SVG -> GIF
// gif remain for backward compatibility
boolean fileErrors = false;
HashMap<String, BufferedImage> sizedSymbols = new HashMap<>();
for (String symbol : symbols) {
BufferedImage image = null;
File file = null;
// svg
file = getSymbolFileNameAsSVG(symbol);
if (file.exists()) {
image = loadSymbolAsSVG(file, size, size);
}
// gif
if (image == null) {
//LOGGER.info("SVG symbol can't be load: " + file.getPath());
file = getSymbolFileNameAsGIF(symbol, size);
if (file.exists()) {
image = loadSymbolAsGIF(file, size, size);
}
}
// save
if (image != null) {
sizedSymbols.put(symbol, image);
} else {
fileErrors = true; fileErrors = true;
LOGGER.warn("SVG or GIF symbol can't be load: " + symbol);
} }
} }
manaImages.put(size, sizedSymbols); manaImages.put(size, sizedSymbols);
return !fileErrors; return !fileErrors;
} }
private static void renameSymbols(String path) { private static void renameSymbols(String path) {
File file = new File(path);
if (!file.exists()){
return;
}
final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg");
try { try {
Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() { Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
@ -210,60 +474,184 @@ public final class ManaSymbols {
} }
}); });
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Couldn't rename mana symbols!"); LOGGER.error("Couldn't rename mana symbols on " + path, e);
} }
} }
private static String getSymbolsPath() { private static String getResourceSymbolsPath(ResourceSymbolSize needSize){
return getSymbolsPath(false); // return real path to symbols (default or user defined)
String path = CardImageUtils.getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS + File.separator;
// folder by sizes
switch (needSize) {
case SMALL:
path = path + Constants.RESOURCE_SYMBOL_FOLDER_SMALL;
break;
case MEDIUM:
path = path + Constants.RESOURCE_SYMBOL_FOLDER_MEDIUM;
break;
case LARGE:
path = path + Constants.RESOURCE_SYMBOL_FOLDER_LARGE;
break;
case SVG:
path = path + Constants.RESOURCE_SYMBOL_FOLDER_SVG;
break;
case PNG:
path = path + Constants.RESOURCE_SYMBOL_FOLDER_PNG;
break;
default:
throw new java.lang.IllegalArgumentException(
"ResourceSymbolSize value is unknown");
}
// fix double separator if size folder is not set
while(path.endsWith(File.separator))
{
path = path.substring(0, path.length() - 1);
}
return path + File.separator;
} }
private static String getSymbolsPath(boolean forHtmlCode) { private static String getResourceSetsPath(ResourceSetSize needSize){
String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); // return real path to sets icons (default or user defined)
String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
if (path == null) { String path = CardImageUtils.getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS + File.separator;
if (forHtmlCode) {
// for html code we need to use double '//' symbols // folder by sizes
// and seems it should be hard coded - as it is not the same as using File.separator switch (needSize) {
return "plugins/images/"; case SMALL:
} else { path = path + Constants.RESOURCE_SET_FOLDER_SMALL;
return mage.client.constants.Constants.IO.imageBaseDir; break;
} case MEDIUM:
path = path + Constants.RESOURCE_SET_FOLDER_MEDIUM;
break;
case SVG:
path = path + Constants.RESOURCE_SET_FOLDER_SVG;
break;
default:
throw new java.lang.IllegalArgumentException(
"ResourceSetSize value is unknown");
} }
if (forHtmlCode) {
if (cachedPath != null) { // fix double separator if size folder is not set
return cachedPath; while(path.endsWith(File.separator))
} {
if (path.contains("\\")) { path = path.substring(0, path.length() - 1);
cachedPath = path.replaceAll("[\\\\]", "/");
return cachedPath;
}
} }
return path;
return path + File.separator;
} }
public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) {
draw(g, manaCost, x, y, symbolWidth, Color.white, 0);
}
public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth, Color symbolsTextColor, int symbolMarginX) {
if (!manaImages.containsKey(symbolWidth)) { if (!manaImages.containsKey(symbolWidth)) {
loadSymbolsImages(symbolWidth); loadSymbolImages(symbolWidth);
} }
// TODO: replace with jlabel render (look at table rendere)?
/*
// NEW version with component draw
JPanel manaPanel = new JPanel();
// icons size with margin
int symbolHorizontalMargin = 2;
// create each mana symbol as child label
manaPanel.removeAll();
manaPanel.setLayout(new BoxLayout(manaPanel, BoxLayout.X_AXIS));
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
String symbol = tok.nextToken();
JLabel symbolLabel = new JLabel();
//symbolLabel.setBorder(new LineBorder(new Color(150, 150, 150))); // debug
symbolLabel.setBorder(new EmptyBorder(0, symbolHorizontalMargin,0, 0));
BufferedImage image = ManaSymbols.getSizedManaSymbol(symbol, symbolWidth);
if (image != null){
// icon
symbolLabel.setIcon(new ImageIcon(image));
}else
{
// text
symbolLabel.setText("{" + symbol + "}");
//symbolLabel.setOpaque(baseLabel.isOpaque());
//symbolLabel.setForeground(baseLabel.getForeground());
//symbolLabel.setBackground(baseLabel.getBackground());
}
manaPanel.add(symbolLabel);
}
// draw result
Dimension d = manaPanel.getPreferredSize();
BufferedImage image = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D gg = image.createGraphics();
manaPanel.paint(gg);
g.drawImage(image, x, y, null);
*/
// OLD version with custom draw
Map<String, BufferedImage> sizedSymbols = manaImages.get(symbolWidth); Map<String, BufferedImage> sizedSymbols = manaImages.get(symbolWidth);
if (manaCost.isEmpty()) { if (manaCost.isEmpty()) {
return; return;
} }
manaCost = manaCost.replace("\\", ""); manaCost = manaCost.replace("\\", "");
manaCost = UI.getDisplayManaCost(manaCost); manaCost = UI.getDisplayManaCost(manaCost);
StringTokenizer tok = new StringTokenizer(manaCost, " "); StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) { while (tok.hasMoreTokens()) {
String symbol = tok.nextToken(); String symbol = tok.nextToken();
// Check and load symbol in the width
Image image = sizedSymbols.get(symbol); Image image = sizedSymbols.get(symbol);
if (image == null) { if (image == null) {
//log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); // TEXT draw
continue;
labelRender.setText("{" + symbol + "}");
labelRender.setForeground(symbolsTextColor);
labelRender.setSize(symbolWidth, symbolWidth);
labelRender.setVerticalAlignment(SwingConstants.CENTER);
labelRender.setHorizontalAlignment(SwingConstants.CENTER);
//labelRender.setBorder(new LineBorder(new Color(125, 250, 250), 1));
// fix font size for mana text
// work for labels WITHOUT borders
// https://stackoverflow.com/questions/2715118/how-to-change-the-size-of-the-font-of-a-jlabel-to-take-the-maximum-size
Font labelFont = labelRender.getFont();
String labelText = "{W}"; //labelRender.getText(); // need same font size for all -- use max symbol ever, not current text
int stringWidth = labelRender.getFontMetrics(labelFont).stringWidth(labelText);
int componentWidth = labelRender.getWidth();
// Find out how much the font can grow in width.
double widthRatio = (double)componentWidth / (double)stringWidth;
int newFontSize = (int)(labelFont.getSize() * widthRatio);
int componentHeight = labelRender.getHeight();
// Pick a new font size so it will not be larger than the height of label.
int fontSizeToUse = Math.min(newFontSize, componentHeight);
// Set the label's font size to the newly determined size.
labelRender.setFont(new Font(labelFont.getName(), Font.PLAIN + Font.BOLD, fontSizeToUse - 1)); // - for "..." fix in text
// render component to new position
// need to copy graphics, overvise it draw at top left corner
// https://stackoverflow.com/questions/4974268/java-paint-problem
Graphics2D labelG = (Graphics2D)g.create(x, y, symbolWidth, symbolWidth);
labelG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
labelG.fillOval(x + 1, y + 1, symbolWidth - 2, symbolWidth - 2);
labelRender.paint(labelG);
}else {
// ICON draw
g.drawImage(image, x, y, null);
} }
g.drawImage(image, x, y, null); x += symbolWidth + symbolMarginX;
x += symbolWidth;
} }
} }
public static String getStringManaCost(List<String> manaCost) { public static String getStringManaCost(List<String> manaCost) {
@ -281,11 +669,21 @@ public final class ManaSymbols {
TOOLTIP, TOOLTIP,
} }
private static String filePathToUrl(String path){
// convert file path to uri path (for html docs)
if((path != null) && (!path.equals(""))){
File file = new File(path);
return file.toURI().toString();
}else{
return null;
}
}
public static synchronized String replaceSymbolsWithHTML(String value, Type type) { public static synchronized String replaceSymbolsWithHTML(String value, Type type) {
value = value.replace("{source}", "|source|");
value = value.replace("{this}", "|this|"); // mana cost to HTML images (urls to files)
String replaced = value; // do not use it for new code - try to suppotr svg render
boolean symbolFilesFound;
int symbolSize; int symbolSize;
switch (type) { switch (type) {
case TABLE: case TABLE:
@ -304,21 +702,37 @@ public final class ManaSymbols {
symbolSize = 11; symbolSize = 11;
break; break;
} }
String resourcePath = "small";
symbolFilesFound = smallSymbolsFound; // auto size
if (symbolSize > 25) { ResourceSymbolSize needSize = null;
resourcePath = "large"; if (symbolSize <= 15){
} else if (symbolSize > 15) { needSize = ResourceSymbolSize.SMALL;
resourcePath = "medium"; }else if (symbolSize <= 25){
symbolFilesFound = mediumSymbolsFound; needSize = ResourceSymbolSize.MEDIUM;
} } else {
if (symbolFilesFound) { needSize = ResourceSymbolSize.LARGE;
replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll(
"<img src='file:" + getSymbolsPath(true) + "/symbols/" + resourcePath + "/$1$2.gif' alt='$1$2' width=" + symbolSize
+ " height=" + symbolSize + '>');
} }
// replace every {symbol} to <img> link
// ignore data backup
String replaced = value
.replace("{source}", "|source|")
.replace("{this}", "|this|");
// not need to add different images (width and height do the work)
// use best png size (generated on startup) TODO: add reload images after update
String htmlImagesPath = getResourceSymbolsPath(ResourceSymbolSize.PNG);
replaced = REPLACE_SYMBOLS_PATTERN.matcher(replaced).replaceAll(
"<img src='" + filePathToUrl(htmlImagesPath) + "$1$2" + ".png' alt='$1$2' width="
+ symbolSize + " height=" + symbolSize + '>');
// ignore data restore
replaced = replaced.replace("|source|", "{source}"); replaced = replaced.replace("|source|", "{source}");
replaced = replaced.replace("|this|", "{this}"); replaced = replaced.replace("|this|", "{this}");
return replaced; return replaced;
} }
@ -328,7 +742,7 @@ public final class ManaSymbols {
int factor = size / 15 + 1; int factor = size / 15 + 1;
Integer width = setImagesExist.get(_set).width * factor; Integer width = setImagesExist.get(_set).width * factor;
Integer height = setImagesExist.get(_set).height * factor; Integer height = setImagesExist.get(_set).height * factor;
return "<img src='file:" + getSymbolsPath() + "/sets/small/" + _set + '-' + rarity + ".png' alt='" + rarity + "' height='" + height + "' width='" + width + "' >"; return "<img src='" + filePathToUrl(getResourceSetsPath(ResourceSetSize.SMALL)) + _set + '-' + rarity + ".png' alt='" + rarity + "' height='" + height + "' width='" + width + "' >";
} else { } else {
return set; return set;
} }
@ -353,9 +767,10 @@ public final class ManaSymbols {
public static BufferedImage getSizedManaSymbol(String symbol, int size) { public static BufferedImage getSizedManaSymbol(String symbol, int size) {
if (!manaImages.containsKey(size)) { if (!manaImages.containsKey(size)) {
loadSymbolsImages(size); loadSymbolImages(size);
} }
Map<String, BufferedImage> sizedSymbols = manaImages.get(size); Map<String, BufferedImage> sizedSymbols = manaImages.get(size);
return sizedSymbols.get(symbol); return sizedSymbols.get(symbol);
} }
} }

View file

@ -0,0 +1,64 @@
package org.mage.card.arcane;
import mage.client.util.GUISizeHelper;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.StringTokenizer;
public final class ManaSymbolsCellRenderer extends DefaultTableCellRenderer {
// base panel to render
private JPanel manaPanel = new JPanel();
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
// get table text cell settings
DefaultTableCellRenderer baseRenderer = (DefaultTableCellRenderer) table.getDefaultRenderer(String.class);
JLabel baseLabel = (JLabel)baseRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
// apply settings to mana panel from parent
manaPanel.setOpaque(baseLabel.isOpaque());
manaPanel.setForeground(baseLabel.getForeground());
manaPanel.setBackground(baseLabel.getBackground());
// icons size with margin
int symbolWidth = GUISizeHelper.symbolTableSize;
int symbolHorizontalMargin = 2;
// create each mana symbol as child label
String manaCost = (String)value;
manaPanel.removeAll();
manaPanel.setLayout(new BoxLayout(manaPanel, BoxLayout.X_AXIS));
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
String symbol = tok.nextToken();
JLabel symbolLabel = new JLabel();
//symbolLabel.setBorder(new LineBorder(new Color(150, 150, 150))); // debug
symbolLabel.setBorder(new EmptyBorder(0, symbolHorizontalMargin,0, 0));
BufferedImage image = ManaSymbols.getSizedManaSymbol(symbol, symbolWidth);
if (image != null){
// icon
symbolLabel.setIcon(new ImageIcon(image));
}else
{
// text
symbolLabel.setText("{" + symbol + "}");
symbolLabel.setOpaque(baseLabel.isOpaque());
symbolLabel.setForeground(baseLabel.getForeground());
symbolLabel.setBackground(baseLabel.getBackground());
}
manaPanel.add(symbolLabel);
}
return manaPanel;
}
}

View file

@ -561,7 +561,7 @@ public class ModernCardRenderer extends CardRenderer {
// Draw the mana symbols // Draw the mana symbols
if (!cardView.isAbility() && !cardView.isFaceDown()) { if (!cardView.isAbility() && !cardView.isFaceDown()) {
ManaSymbols.draw(g, manaCost, x + w - manaCostWidth, y + boxTextOffset, boxTextHeight); ManaSymbols.draw(g, manaCost, x + w - manaCostWidth, y + boxTextOffset, boxTextHeight, Color.black, 2);
} }
} }
@ -844,7 +844,7 @@ public class ModernCardRenderer extends CardRenderer {
String symbs = symbol; String symbs = symbol;
int symbHeight = (int) (0.8 * h); int symbHeight = (int) (0.8 * h);
int manaCostWidth = CardRendererUtils.getManaCostWidth(symbs, symbHeight); int manaCostWidth = CardRendererUtils.getManaCostWidth(symbs, symbHeight);
ManaSymbols.draw(g, symbs, x + (w - manaCostWidth) / 2, y + (h - symbHeight) / 2, symbHeight); ManaSymbols.draw(g, symbs, x + (w - manaCostWidth) / 2, y + (h - symbHeight) / 2, symbHeight, Color.black, 2);
} }
// Get the first line of the textbox, the keyword string // Get the first line of the textbox, the keyword string

View file

@ -6,7 +6,6 @@ import mage.client.dialog.PreferencesDialog;
import mage.client.util.GUISizeHelper; import mage.client.util.GUISizeHelper;
import mage.constants.Rarity; import mage.constants.Rarity;
import mage.interfaces.plugin.CardPlugin; import mage.interfaces.plugin.CardPlugin;
import mage.utils.CardUtil;
import mage.view.CardView; import mage.view.CardView;
import mage.view.CounterView; import mage.view.CounterView;
import mage.view.PermanentView; import mage.view.PermanentView;
@ -19,7 +18,6 @@ import org.mage.card.arcane.*;
import org.mage.plugins.card.dl.DownloadGui; import org.mage.plugins.card.dl.DownloadGui;
import org.mage.plugins.card.dl.DownloadJob; import org.mage.plugins.card.dl.DownloadJob;
import org.mage.plugins.card.dl.Downloader; import org.mage.plugins.card.dl.Downloader;
import org.mage.plugins.card.dl.sources.CardFrames;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload; import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.dl.sources.GathererSets; import org.mage.plugins.card.dl.sources.GathererSets;
import org.mage.plugins.card.dl.sources.GathererSymbols; import org.mage.plugins.card.dl.sources.GathererSymbols;
@ -525,30 +523,30 @@ public class CardPluginImpl implements CardPlugin {
/** /**
* Download various symbols (mana, tap, set). * Download various symbols (mana, tap, set).
* *
* @param imagesPath Path to check in and store symbols to. Can be null, in * @param imagesDir Path to check in and store symbols to. Can't be null.
* such case default path should be used.
*/ */
@Override @Override
public void downloadSymbols(String imagesPath) { public void downloadSymbols(String imagesDir) {
final DownloadGui g = new DownloadGui(new Downloader()); final DownloadGui g = new DownloadGui(new Downloader());
Iterable<DownloadJob> it = new GathererSymbols(imagesPath); Iterable<DownloadJob> it = new GathererSymbols();
for (DownloadJob job : it) { for (DownloadJob job : it) {
g.getDownloader().add(job); g.getDownloader().add(job);
} }
it = new GathererSets(imagesPath); it = new GathererSets();
for (DownloadJob job : it) { for (DownloadJob job : it) {
g.getDownloader().add(job); g.getDownloader().add(job);
} }
it = new CardFrames(imagesPath); /*
it = new CardFrames(imagesDir); // TODO: delete frames download (not need now)
for (DownloadJob job : it) { for (DownloadJob job : it) {
g.getDownloader().add(job); g.getDownloader().add(job);
} }
*/
it = new DirectLinksForDownload(imagesPath); it = new DirectLinksForDownload();
for (DownloadJob job : it) { for (DownloadJob job : it) {
g.getDownloader().add(job); g.getDownloader().add(job);
} }
@ -560,6 +558,7 @@ public class CardPluginImpl implements CardPlugin {
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
g.getDownloader().dispose(); g.getDownloader().dispose();
ManaSymbols.loadImages(); ManaSymbols.loadImages();
// TODO: check reload process after download (icons do not update)
} }
}); });
d.setLayout(new BorderLayout()); d.setLayout(new BorderLayout());

View file

@ -1,26 +0,0 @@
package org.mage.plugins.card.constants;
import java.awt.Rectangle;
import java.io.File;
public final class Constants {
public static final String RESOURCE_PATH_SET = File.separator + "sets" + File.separator;
public static final String RESOURCE_PATH_MANA_SMALL = File.separator + "symbols" + File.separator + "small";
public static final String RESOURCE_PATH_MANA_LARGE = File.separator + "symbols" + File.separator + "large";
public static final String RESOURCE_PATH_MANA_MEDIUM = File.separator + "symbols" + File.separator + "medium";
public static final String RESOURCE_PATH_SET_SMALL = RESOURCE_PATH_SET + File.separator + "small" + File.separator;
public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149);
public static final Rectangle THUMBNAIL_SIZE_FULL = new Rectangle(102, 146);
public interface IO {
String imageBaseDir = "plugins" + File.separator + "images";
String IMAGE_PROPERTIES_FILE = "image.url.properties";
}
public static final String CARD_IMAGE_PATH_TEMPLATE = '.' + File.separator + "plugins" + File.separator + "images/{set}/{name}.{collector}.full.jpg";
}

View file

@ -11,9 +11,12 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import mage.client.constants.Constants;
import org.mage.plugins.card.dl.DownloadJob; import org.mage.plugins.card.dl.DownloadJob;
import static org.mage.plugins.card.dl.DownloadJob.fromURL; import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile; import static org.mage.plugins.card.dl.DownloadJob.toFile;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
/** /**
* Used when we need to point to direct links to download resources from. * Used when we need to point to direct links to download resources from.
@ -33,15 +36,13 @@ public class DirectLinksForDownload implements Iterable<DownloadJob> {
directLinks.put(cardbackFilename, backsideUrl); directLinks.put(cardbackFilename, backsideUrl);
} }
private static final String DEFAULT_IMAGES_PATH = File.separator + "default"; public static File outDir;
private static final File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + DEFAULT_IMAGES_PATH);
public static File outDir = DEFAULT_OUT_DIR;
public DirectLinksForDownload(String path) { public DirectLinksForDownload() {
if (path == null) { outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_DEFAUL_IMAGES);
useDefaultDir();
} else { if (!outDir.exists()){
changeOutDir(path); outDir.mkdirs();
} }
} }
@ -55,20 +56,4 @@ public class DirectLinksForDownload implements Iterable<DownloadJob> {
} }
return jobs.iterator(); return jobs.iterator();
} }
private void changeOutDir(String path) {
File file = new File(path + DEFAULT_IMAGES_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
} }

View file

@ -9,11 +9,14 @@ import java.util.Iterator;
import mage.cards.ExpansionSet; import mage.cards.ExpansionSet;
import mage.cards.Sets; import mage.cards.Sets;
import mage.client.constants.Constants;
import mage.constants.Rarity; import mage.constants.Rarity;
import org.mage.plugins.card.dl.DownloadJob; import org.mage.plugins.card.dl.DownloadJob;
import static org.mage.plugins.card.dl.DownloadJob.fromURL; import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile; import static org.mage.plugins.card.dl.DownloadJob.toFile;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
public class GathererSets implements Iterable<DownloadJob> { public class GathererSets implements Iterable<DownloadJob> {
@ -36,13 +39,11 @@ public class GathererSets implements Iterable<DownloadJob> {
} }
} }
private static File outDir;
private static final int DAYS_BEFORE_RELEASE_TO_DOWNLOAD = +14; // Try to load the symbolsBasic eralies 14 days before release date private static final int DAYS_BEFORE_RELEASE_TO_DOWNLOAD = +14; // Try to load the symbolsBasic eralies 14 days before release date
private static final Logger logger = Logger.getLogger(GathererSets.class); private static final Logger logger = Logger.getLogger(GathererSets.class);
private static final String SETS_PATH = File.separator + "sets";
private static final File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + SETS_PATH);
private static File outDir = DEFAULT_OUT_DIR;
private static final String[] symbolsBasic = {"10E", "9ED", "8ED", "7ED", "6ED", "5ED", "4ED", "3ED", "2ED", "LEB", "LEA", private static final String[] symbolsBasic = {"10E", "9ED", "8ED", "7ED", "6ED", "5ED", "4ED", "3ED", "2ED", "LEB", "LEA",
"HOP", "HOP",
"ARN", "ATQ", "LEG", "DRK", "FEM", "HML", "ARN", "ATQ", "LEG", "DRK", "FEM", "HML",
@ -150,11 +151,12 @@ public class GathererSets implements Iterable<DownloadJob> {
codeReplacements.put("CHR", "CH"); codeReplacements.put("CHR", "CH");
} }
public GathererSets(String path) { public GathererSets() {
if (path == null) {
useDefaultDir(); outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS);
} else {
changeOutDir(path); if (!outDir.exists()){
outDir.mkdirs();
} }
} }
@ -311,20 +313,4 @@ public class GathererSets implements Iterable<DownloadJob> {
String url = "http://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity; String url = "http://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity;
return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst)); return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst));
} }
private void changeOutDir(String path) {
File file = new File(path + SETS_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
} }

View file

@ -9,9 +9,12 @@ import com.google.common.collect.AbstractIterator;
import java.io.File; import java.io.File;
import static java.lang.String.format; import static java.lang.String.format;
import java.util.Iterator; import java.util.Iterator;
import mage.client.constants.Constants;
import org.mage.plugins.card.dl.DownloadJob; import org.mage.plugins.card.dl.DownloadJob;
import static org.mage.plugins.card.dl.DownloadJob.fromURL; import static org.mage.plugins.card.dl.DownloadJob.fromURL;
import static org.mage.plugins.card.dl.DownloadJob.toFile; import static org.mage.plugins.card.dl.DownloadJob.toFile;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
/** /**
* The class GathererSymbols. * The class GathererSymbols.
@ -23,9 +26,7 @@ public class GathererSymbols implements Iterable<DownloadJob> {
//TODO chaos and planeswalker symbol //TODO chaos and planeswalker symbol
//chaos: http://gatherer.wizards.com/Images/Symbols/chaos.gif //chaos: http://gatherer.wizards.com/Images/Symbols/chaos.gif
private static final String SYMBOLS_PATH = File.separator + "symbols"; private static File outDir;
private static final File DEFAULT_OUT_DIR = new File("plugins" + File.separator + "images" + SYMBOLS_PATH);
private static File outDir = DEFAULT_OUT_DIR;
private static final String urlFmt = "http://gatherer.wizards.com/handlers/image.ashx?size=%1$s&name=%2$s&type=symbol"; private static final String urlFmt = "http://gatherer.wizards.com/handlers/image.ashx?size=%1$s&name=%2$s&type=symbol";
@ -38,11 +39,11 @@ public class GathererSymbols implements Iterable<DownloadJob> {
"X", "S", "T", "Q", "C", "E"}; "X", "S", "T", "Q", "C", "E"};
private static final int minNumeric = 0, maxNumeric = 16; private static final int minNumeric = 0, maxNumeric = 16;
public GathererSymbols(String path) { public GathererSymbols() {
if (path == null) { outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS);
useDefaultDir();
} else { if (!outDir.exists()){
changeOutDir(path); outDir.mkdirs();
} }
} }
@ -115,20 +116,4 @@ public class GathererSymbols implements Iterable<DownloadJob> {
} }
}; };
} }
private void changeOutDir(String path) {
File file = new File(path + SYMBOLS_PATH);
if (file.exists()) {
outDir = file;
} else {
file.mkdirs();
if (file.exists()) {
outDir = file;
}
}
}
private void useDefaultDir() {
outDir = DEFAULT_OUT_DIR;
}
} }

View file

@ -27,13 +27,9 @@ import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo; import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository; import mage.cards.repository.CardRepository;
import mage.client.MageFrame; import mage.client.MageFrame;
import mage.client.constants.Constants;
import mage.client.dialog.PreferencesDialog; import mage.client.dialog.PreferencesDialog;
import mage.client.util.sets.ConstructedFormats; import mage.client.util.sets.ConstructedFormats;
import mage.remote.Connection; import mage.remote.Connection;
import static mage.remote.Connection.ProxyType.HTTP;
import static mage.remote.Connection.ProxyType.NONE;
import static mage.remote.Connection.ProxyType.SOCKS;
import net.java.truevfs.access.TFile; import net.java.truevfs.access.TFile;
import net.java.truevfs.access.TFileOutputStream; import net.java.truevfs.access.TFileOutputStream;
import net.java.truevfs.access.TVFS; import net.java.truevfs.access.TVFS;
@ -43,6 +39,8 @@ import org.mage.plugins.card.dl.sources.*;
import org.mage.plugins.card.properties.SettingsManager; import org.mage.plugins.card.properties.SettingsManager;
import org.mage.plugins.card.utils.CardImageUtils; import org.mage.plugins.card.utils.CardImageUtils;
import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable { public class DownloadPictures extends DefaultBoundedRangeModel implements Runnable {
private static DownloadPictures instance; private static DownloadPictures instance;
@ -462,7 +460,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
*/ */
List<CardDownloadData> cardsToDownload = Collections.synchronizedList(new ArrayList<>()); List<CardDownloadData> cardsToDownload = Collections.synchronizedList(new ArrayList<>());
allCardsUrls.parallelStream().forEach(card -> { allCardsUrls.parallelStream().forEach(card -> {
TFile file = new TFile(CardImageUtils.generateImagePath(card)); File file = new TFile(CardImageUtils.buildImagePathToCard(card));
logger.debug(card.getName() + " (is_token=" + card.isToken() + "). Image is here:" + file.getAbsolutePath() + " (exists=" + file.exists() + ')'); logger.debug(card.getName() + " (is_token=" + card.isToken() + "). Image is here:" + file.getAbsolutePath() + " (exists=" + file.exists() + ')');
if (!file.exists()) { if (!file.exists()) {
logger.debug("Missing: " + file.getAbsolutePath()); logger.debug("Missing: " + file.getAbsolutePath());
@ -544,7 +542,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
public void run() { public void run() {
this.cardIndex = 0; this.cardIndex = 0;
File base = new File(Constants.IO.imageBaseDir); File base = new File(getImagesDir());
if (!base.exists()) { if (!base.exists()) {
base.mkdir(); base.mkdir();
} }
@ -678,38 +676,78 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
@Override @Override
public void run() { public void run() {
StringBuilder filePath = new StringBuilder(); if (cancel) {
File temporaryFile = null; synchronized (sync) {
TFile outputFile = null; update(cardIndex + 1, count);
}
return;
}
TFile fileTempImage;
TFile destFile;
try { try {
filePath.append(Constants.IO.imageBaseDir);
if (!useSpecifiedPaths && card != null) {
filePath.append(card.hashCode()).append('.').append(card.getName().replace(":", "").replace("//", "-")).append(".jpg");
temporaryFile = new File(filePath.toString());
}
String imagePath;
if (useSpecifiedPaths) {
if (card != null && card.isToken()) {
imagePath = CardImageUtils.getTokenBasePath() + actualFilename;
} else if (card != null) {
imagePath = CardImageUtils.getImageBasePath() + actualFilename;
} else {
imagePath = Constants.IO.imageBaseDir;
}
String tmpFile = filePath + "temporary" + actualFilename; if (card == null){
temporaryFile = new File(tmpFile); synchronized (sync) {
if (!temporaryFile.exists()) { update(cardIndex + 1, count);
temporaryFile.getParentFile().mkdirs();
} }
} else { return;
imagePath = CardImageUtils.generateImagePath(card);
} }
outputFile = new TFile(imagePath); // gen temp file (download to images folder)
if (!outputFile.exists()) { String tempPath = getImagesDir() + File.separator + "downloading" + File.separator;
outputFile.getParentFile().mkdirs(); if(useSpecifiedPaths){
fileTempImage = new TFile(tempPath + actualFilename + "-" + card.hashCode() + ".jpg");
}else{
fileTempImage = new TFile(tempPath + CardImageUtils.prepareCardNameForFile(card.getName()) + "-" + card.hashCode() + ".jpg");
} }
if(!fileTempImage.getParentFile().exists()){
fileTempImage.getParentFile().mkdirs();
}
// gen dest file name
if(useSpecifiedPaths)
{
if(card.isToken()){
destFile = new TFile(CardImageUtils.buildImagePathToSet(card) + actualFilename + ".jpg");
}else{
destFile = new TFile(CardImageUtils.buildImagePathToTokens() + actualFilename + ".jpg");
}
}else{
destFile = new TFile(CardImageUtils.buildImagePathToCard(card));
}
// FILE already exists (in zip or in dir)
if (destFile.exists()){
synchronized (sync) {
update(cardIndex + 1, count);
}
return;
}
// zip can't be read
TFile testArchive = destFile.getTopLevelArchive();
if (testArchive != null && testArchive.exists()) {
try {
testArchive.list();
} catch (Exception e) {
logger.error("Error reading archive, may be it was corrapted. Try to delete it: " + testArchive.toString());
synchronized (sync) {
update(cardIndex + 1, count);
}
return;
}
}
/*
if(!destFile.getParentFile().exists()){
destFile.getParentFile().mkdirs();
}
*/
/*
// WTF start?! TODO: wtf
File existingFile = new File(imagePath.replaceFirst("\\w{3}.zip", "")); File existingFile = new File(imagePath.replaceFirst("\\w{3}.zip", ""));
if (existingFile.exists()) { if (existingFile.exists()) {
try { try {
@ -727,7 +765,86 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
} }
return; return;
} }
// WTF end?!
*/
// START to download
cardImageSource.doPause(url.getPath());
URLConnection httpConn = url.openConnection(p);
httpConn.connect();
int responseCode = ((HttpURLConnection) httpConn).getResponseCode();
if (responseCode == 200){
// download OK
// save data to temp
BufferedOutputStream out;
try (BufferedInputStream in = new BufferedInputStream(httpConn.getInputStream())) {
out = new BufferedOutputStream(new TFileOutputStream(fileTempImage));
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) {
// user cancelled
if (cancel) {
in.close();
out.flush();
out.close();
// stop download, save current state and exit
TFile archive = destFile.getTopLevelArchive();
///* not need to unmout/close - it's auto action
if (archive != null && archive.exists()){
logger.info("User canceled download. Closing archive file: " + destFile.toString());
try {
TVFS.umount(archive);
}catch (Exception e) {
logger.error("Can't close archive file: " + e.getMessage(), e);
}
}//*/
try {
TFile.rm(fileTempImage);
}catch (Exception e) {
logger.error("Can't delete temp file: " + e.getMessage(), e);
}
return;
}
out.write(buf, 0, len);
}
}
// TODO: remove to finnaly section?
out.flush();
out.close();
// TODO: add two faces card correction? (WTF)
// SAVE final data
if (fileTempImage.exists()) {
if (!destFile.getParentFile().exists()){
destFile.getParentFile().mkdirs();
}
new TFile(fileTempImage).cp_rp(destFile);
try {
TFile.rm(fileTempImage);
}catch (Exception e) {
logger.error("Can't delete temp file: " + e.getMessage(), e);
}
}
}else{
// download ERROR
logger.warn("Image download for " + card.getName()
+ (!card.getDownloadName().equals(card.getName()) ? " downloadname: " + card.getDownloadName() : "")
+ '(' + card.getSet() + ") failed - responseCode: " + responseCode + " url: " + url.toString()
);
if (logger.isDebugEnabled()) {
// Shows the returned html from the request to the web server
logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream()));
}
}
/*
// Logger.getLogger(this.getClass()).info(url.toString()); // Logger.getLogger(this.getClass()).info(url.toString());
boolean useTempFile = false; boolean useTempFile = false;
int responseCode = 0; int responseCode = 0;
@ -736,7 +853,6 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
if (temporaryFile != null && temporaryFile.length() > 100) { if (temporaryFile != null && temporaryFile.length() > 100) {
useTempFile = true; useTempFile = true;
} else { } else {
cardImageSource.doPause(url.getPath()); cardImageSource.doPause(url.getPath());
httpConn = url.openConnection(p); httpConn = url.openConnection(p);
httpConn.connect(); httpConn.connect();
@ -768,6 +884,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
out.close(); out.close();
} }
// TODO: WTF?! start
if (card != null && card.isTwoFacedCard()) { if (card != null && card.isTwoFacedCard()) {
BufferedImage image = ImageIO.read(temporaryFile); BufferedImage image = ImageIO.read(temporaryFile);
if (image.getHeight() == 470) { if (image.getHeight() == 470) {
@ -790,6 +907,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
outputFile.getParentFile().mkdirs(); outputFile.getParentFile().mkdirs();
new TFile(temporaryFile).cp_rp(outputFile); new TFile(temporaryFile).cp_rp(outputFile);
} }
// WTF?! end
} else { } else {
if (card != null && !useSpecifiedPaths) { if (card != null && !useSpecifiedPaths) {
logger.warn("Image download for " + card.getName() logger.warn("Image download for " + card.getName()
@ -800,16 +918,15 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream())); logger.debug("Returned HTML ERROR:\n" + convertStreamToString(((HttpURLConnection) httpConn).getErrorStream()));
} }
} }
*/
} catch (AccessDeniedException e) { } catch (AccessDeniedException e) {
logger.error("The file " + (outputFile != null ? outputFile.toString() : "to add the image of " + card.getName() + '(' + card.getSet() + ')') + " can't be accessed. Try rebooting your system to remove the file lock."); logger.error("Can't access to files: " + card.getName() + "(" + card.getSet() + "). Try rebooting your system to remove the file lock.");
} catch (Exception e) { } catch (Exception e) {
logger.error(e, e); logger.error(e.getMessage(), e);
} finally { } finally {
if (temporaryFile != null) {
temporaryFile.delete();
}
} }
synchronized (sync) { synchronized (sync) {
update(cardIndex + 1, count); update(cardIndex + 1, count);
} }
@ -823,7 +940,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(0.96f); iwp.setCompressionQuality(0.96f);
File tempFile = new File(Constants.IO.imageBaseDir + File.separator + image.hashCode() + file.getName()); File tempFile = new File(getImagesDir() + File.separator + image.hashCode() + file.getName());
FileImageOutputStream output = new FileImageOutputStream(tempFile); FileImageOutputStream output = new FileImageOutputStream(tempFile);
writer.setOutput(output); writer.setOutput(output);
IIOImage image2 = new IIOImage(image, null, null); IIOImage image2 = new IIOImage(image, null, null);
@ -846,7 +963,7 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab
} else { } else {
List<CardDownloadData> remainingCards = Collections.synchronizedList(new ArrayList<>()); List<CardDownloadData> remainingCards = Collections.synchronizedList(new ArrayList<>());
DownloadPictures.this.allCardsMissingImage.parallelStream().forEach(cardDownloadData -> { DownloadPictures.this.allCardsMissingImage.parallelStream().forEach(cardDownloadData -> {
TFile file = new TFile(CardImageUtils.generateImagePath(cardDownloadData)); TFile file = new TFile(CardImageUtils.buildImagePathToCard(cardDownloadData));
if (!file.exists()) { if (!file.exists()) {
remainingCards.add(cardDownloadData); remainingCards.add(cardDownloadData);
} }

View file

@ -20,9 +20,9 @@ import net.java.truevfs.access.TFile;
import net.java.truevfs.access.TFileInputStream; import net.java.truevfs.access.TFileInputStream;
import net.java.truevfs.access.TFileOutputStream; import net.java.truevfs.access.TFileOutputStream;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload; import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.utils.CardImageUtils; import org.mage.plugins.card.utils.CardImageUtils;
import mage.client.constants.Constants;
/** /**
* This class stores ALL card images in a cache with soft values. this means * This class stores ALL card images in a cache with soft values. this means
@ -94,7 +94,7 @@ public final class ImageCache {
path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; // TODO: replace empty token by other default card, not cardback path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; // TODO: replace empty token by other default card, not cardback
} }
} else { } else {
path = CardImageUtils.generateImagePath(info); path = CardImageUtils.buildImagePathToCard(info);
} }
if (path == null) { if (path == null) {
@ -263,7 +263,7 @@ public final class ImageCache {
path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; // TODO: replace empty token by other default card, not cardback path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename; // TODO: replace empty token by other default card, not cardback
} }
} else { } else {
path = CardImageUtils.generateImagePath(info); path = CardImageUtils.buildImagePathToCard(info);
} }
if (thumbnail && path.endsWith(".jpg")) { if (thumbnail && path.endsWith(".jpg")) {
@ -340,7 +340,7 @@ public final class ImageCache {
// image draw to buffer // image draw to buffer
gg.setComposite(AlphaComposite.SrcAtop); gg.setComposite(AlphaComposite.SrcAtop);
gg.drawImage(image, 0, 0, null); gg.drawImage(image, 0, 0, null);
//gg.dispose(); gg.dispose();
return cornerImage; return cornerImage;
} else { } else {

View file

@ -6,7 +6,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import org.mage.plugins.card.constants.Constants; import mage.client.constants.Constants;
public class SettingsManager { public class SettingsManager {

View file

@ -1,5 +1,6 @@
package org.mage.plugins.card.utils; package org.mage.plugins.card.utils;
import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.util.HashMap; import java.util.HashMap;
@ -63,7 +64,7 @@ public final class CardImageUtils {
} }
private static String getTokenImagePath(CardDownloadData card) { private static String getTokenImagePath(CardDownloadData card) {
String filename = generateImagePath(card); String filename = buildImagePathToCard(card);
TFile file = new TFile(filename); TFile file = new TFile(filename);
if (!file.exists()) { if (!file.exists()) {
@ -74,12 +75,12 @@ public final class CardImageUtils {
if (!file.exists()) { if (!file.exists()) {
CardDownloadData updated = new CardDownloadData(card); CardDownloadData updated = new CardDownloadData(card);
updated.setName(card.getName() + " 1"); updated.setName(card.getName() + " 1");
filename = generateImagePath(updated); filename = buildImagePathToCard(updated);
file = new TFile(filename); file = new TFile(filename);
if (!file.exists()) { if (!file.exists()) {
updated = new CardDownloadData(card); updated = new CardDownloadData(card);
updated.setName(card.getName() + " 2"); updated.setName(card.getName() + " 2");
filename = generateImagePath(updated); filename = buildImagePathToCard(updated);
} }
} }
@ -121,102 +122,127 @@ public final class CardImageUtils {
return set; return set;
} }
private static String getImageDir(CardDownloadData card, String imagesPath) { public static String prepareCardNameForFile(String cardName){
return cardName.replace(":", "").replace("\"", "").replace("//", "-");
}
public static String getImagesDir(){
// return real images dir (path without separator)
String path = null;
// user path
if (!PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true").equals("true")){
path = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
}
// default path
if (path == null) {
path = Constants.IO.DEFAULT_IMAGES_DIR;
}
while(path.endsWith(File.separator))
{
path = path.substring(0, path.length() - 1);
}
return path;
}
public static String buildImagePathToTokens() {
String imagesPath = getImagesDir() + File.separator;
if (PreferencesDialog.isSaveImagesToZip()) {
return imagesPath + "TOK.zip" + File.separator;
} else {
return imagesPath + "TOK" + File.separator;
}
}
private static String buildImagePathToTokenDescriptor(CardDownloadData card) {
return buildImagePathToTokens() + card.getTokenDescriptor() + ".full.jpg";
}
public static String buildImagePathToSet(CardDownloadData card) {
if (card.getSet() == null) { if (card.getSet() == null) {
return ""; throw new IllegalArgumentException("Card " + card.getName() + " have empty set.");
} }
String set = updateSet(card.getSet(), false).toUpperCase();
String imagesDir = (imagesPath != null ? imagesPath : Constants.IO.imageBaseDir); String set = updateSet(card.getSet(), false).toUpperCase(); // TODO: research auto-replace... old code?
if (card.isToken()) { if (card.isToken()) {
return buildTokenPath(imagesDir, set); return buildImagePathToSetAsToken(set);
} else { } else {
return buildPath(imagesDir, set); return buildImagePathToSetAsCard(set);
} }
} }
public static String getImageBasePath() { private static String buildImagePathToSetAsCard(String set) {
String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); String imagesPath = getImagesDir() + File.separator;
String imagesPath = Objects.equals(useDefault, "true") ? Constants.IO.imageBaseDir : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
if (imagesPath != null && !imagesPath.endsWith(TFile.separator)) {
imagesPath += TFile.separator;
}
return imagesPath;
}
public static String getTokenBasePath() {
String imagesPath = getImageBasePath();
String finalPath = "";
if (PreferencesDialog.isSaveImagesToZip()) { if (PreferencesDialog.isSaveImagesToZip()) {
finalPath = imagesPath + "TOK" + ".zip" + TFile.separator; return imagesPath + set + ".zip" + File.separator + set + File.separator;
} else { } else {
finalPath = imagesPath + "TOK" + TFile.separator; return imagesPath + set + File.separator;
}
return finalPath;
}
private static String getTokenDescriptorImagePath(CardDownloadData card) {
return getTokenBasePath() + card.getTokenDescriptor() + ".full.jpg";
}
private static String buildTokenPath(String imagesDir, String set) {
if (PreferencesDialog.isSaveImagesToZip()) {
return imagesDir + TFile.separator + "TOK" + ".zip" + TFile.separator + set;
} else {
return imagesDir + TFile.separator + "TOK" + TFile.separator + set;
} }
} }
private static String buildPath(String imagesDir, String set) { private static String buildImagePathToSetAsToken(String set) {
if (PreferencesDialog.isSaveImagesToZip()) { return buildImagePathToTokens() + set + File.separator;
return imagesDir + TFile.separator + set + ".zip" + TFile.separator + set;
} else {
return imagesDir + TFile.separator + set;
}
} }
public static String generateImagePath(CardDownloadData card) { public static String buildImagePathToCard(CardDownloadData card) {
String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true");
String imagesPath = Objects.equals(useDefault, "true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
String imageDir = getImageDir(card, imagesPath); String setPath = buildImagePathToSet(card);
String imageName;
String type = card.getType() != 0 ? ' ' + Integer.toString(card.getType()) : ""; String prefixType = "";
String name = card.getFileName().isEmpty() ? card.getName().replace(":", "").replace("\"", "").replace("//", "-") : card.getFileName(); if(card.getType() != 0){
prefixType = " " + Integer.toString(card.getType());
if (card.getUsesVariousArt()) {
imageName = name + '.' + card.getCollectorId() + ".full.jpg";
} else {
imageName = name + type + ".full.jpg";
} }
if (new TFile(imageDir).exists() && !new TFile(imageDir + TFile.separator + imageName).exists()) { String cardName = card.getFileName();
for (String fileName : new TFile(imageDir).list()) { if (cardName.isEmpty()){
if (fileName.toLowerCase().equals(imageName.toLowerCase())) { cardName = prepareCardNameForFile(card.getName());
imageName = fileName; }
break;
String finalFileName = "";
if (card.getUsesVariousArt()){
finalFileName = cardName + '.' + card.getCollectorId() + ".full.jpg";
}else{
finalFileName = cardName + prefixType + ".full.jpg";
}
// if image file exists, correct name (for case sensitive systems)
// use TFile for zips
TFile dirFile = new TFile(setPath);
TFile imageFile = new TFile(setPath + finalFileName);
// warning, zip files can be broken
try{
if (dirFile.exists() && !imageFile.exists()) {
// search like names
for (String fileName: dirFile.list()) {
if (fileName.toLowerCase().equals(finalFileName.toLowerCase())) {
finalFileName = fileName;
break;
}
} }
} }
}catch (Exception ex) {
log.error("Can't read card name from file, may be it broken: " + setPath);
} }
return imageDir + TFile.separator + imageName; return setPath + finalFileName;
} }
public static String generateFaceImagePath(String cardname, String set) { public static String generateFaceImagePath(String cardname, String set) {
String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); return getImagesDir() + File.separator + "FACE" + File.separator + set + File.separator + prepareCardNameForFile(cardname) + File.separator + ".jpg";
String imagesPath = Objects.equals(useDefault, "true") ? Constants.IO.imageBaseDir : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
String imageDir = imagesPath;
String imageName = set + TFile.separator + cardname + ".jpg";
return imageDir + TFile.separator + "FACE" + TFile.separator + imageName;
} }
public static String generateTokenDescriptorImagePath(CardDownloadData card) { public static String generateTokenDescriptorImagePath(CardDownloadData card) {
// String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true");
// String imagesPath = Objects.equals(useDefault, "true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null);
String straightImageFile = getTokenDescriptorImagePath(card); String straightImageFile = buildImagePathToTokenDescriptor(card);
TFile file = new TFile(straightImageFile); TFile file = new TFile(straightImageFile);
if (file.exists()) { if (file.exists()) {
return straightImageFile; return straightImageFile;

View file

@ -32,10 +32,9 @@ public interface CardPlugin extends Plugin {
/** /**
* Download various symbols (mana, tap, set). * Download various symbols (mana, tap, set).
* *
* @param imagesPath Path to check in and store symbols to. Can be null, in * @param imagesDir Path to check in and store symbols to. Can't be null.
* such case default path should be used.
*/ */
void downloadSymbols(String imagesPath); void downloadSymbols(String imagesDir);
void onAddCard(MagePermanent card, int count); void onAddCard(MagePermanent card, int count);

View file

@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
public final static int MAGE_VERSION_MAJOR = 1; public final static int MAGE_VERSION_MAJOR = 1;
public final static int MAGE_VERSION_MINOR = 4; public final static int MAGE_VERSION_MINOR = 4;
public final static int MAGE_VERSION_PATCH = 26; public final static int MAGE_VERSION_PATCH = 26;
public final static String MAGE_VERSION_MINOR_PATCH = "V8"; public final static String MAGE_VERSION_MINOR_PATCH = "V9b";
public final static String MAGE_VERSION_INFO = ""; public final static String MAGE_VERSION_INFO = "";
private final int major; private final int major;

View file

@ -44,6 +44,7 @@ public class DuelCommander extends Commander {
banned.add("Eidolon of the Great Revel"); banned.add("Eidolon of the Great Revel");
banned.add("Emrakul, the Aeons Torn"); banned.add("Emrakul, the Aeons Torn");
banned.add("Entomb"); banned.add("Entomb");
banned.add("Fastbond");
banned.add("Fireblast"); banned.add("Fireblast");
banned.add("Food Chain"); banned.add("Food Chain");
banned.add("Gaea's Cradle"); banned.add("Gaea's Cradle");

View file

@ -2261,6 +2261,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
break; break;
} }
} }
} else if (opponents.size() == 1) {
randomOpponentId = game.getOpponents(abilityControllerId).iterator().next();
} }
return randomOpponentId; return randomOpponentId;
} }

View file

@ -0,0 +1,76 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.b;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.HalfValue;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageControllerEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.target.common.TargetCreatureOrPlayer;
/**
*
* @author L_J
*/
public class Banshee extends CardImpl {
public Banshee(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}{B}");
this.subtype.add(SubType.SPIRIT);
this.power = new MageInt(0);
this.toughness = new MageInt(1);
// {X}, {T}: Banshee deals half X damage, rounded down, to target creature or player, and half X damage, rounded up, to you.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(new HalfValue(new ManacostVariableValue(), false)).setText("Banshee deals half X damage, rounded down, to target creature or player,"), new ManaCostsImpl("{X}"));
ability.addCost(new TapSourceCost());
ability.addEffect(new DamageControllerEffect(new HalfValue(new ManacostVariableValue(), true)).setText(" and half X damage, rounded up, to you"));
ability.addTarget(new TargetCreatureOrPlayer());
this.addAbility(ability);
}
public Banshee(final Banshee card) {
super(card);
}
@Override
public Banshee copy() {
return new Banshee(this);
}
}

View file

@ -34,7 +34,7 @@ import mage.abilities.effects.common.DynamicManaEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.StaticFilters;
/** /**
* *
@ -43,11 +43,10 @@ import mage.filter.common.FilterControlledCreaturePermanent;
public class BattleHymn extends CardImpl { public class BattleHymn extends CardImpl {
public BattleHymn(UUID ownerId, CardSetInfo setInfo) { public BattleHymn(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{R}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}");
// Add {R} to your mana pool for each creature you control. // Add {R} to your mana pool for each creature you control.
this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), new PermanentsOnBattlefieldCount(new FilterControlledCreaturePermanent()))); this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_CREATURE)));
} }
public BattleHymn(final BattleHymn card) { public BattleHymn(final BattleHymn card) {

View file

@ -0,0 +1,110 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.b;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.players.Player;
/**
*
* @author L_J
*/
public class BloodOfTheMartyr extends CardImpl {
public BloodOfTheMartyr(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{W}{W}{W}");
// Until end of turn, if damage would be dealt to any creature, you may have that damage dealt to you instead.
this.getSpellAbility().addEffect(new BloodOfTheMartyrEffect());
}
public BloodOfTheMartyr(final BloodOfTheMartyr card) {
super(card);
}
@Override
public BloodOfTheMartyr copy() {
return new BloodOfTheMartyr(this);
}
}
class BloodOfTheMartyrEffect extends ReplacementEffectImpl {
public BloodOfTheMartyrEffect() {
super(Duration.EndOfTurn, Outcome.RedirectDamage);
staticText = "Until end of turn, if damage would be dealt to any creature, you may have that damage dealt to you instead";
}
public BloodOfTheMartyrEffect(final BloodOfTheMartyrEffect effect) {
super(effect);
}
@Override
public BloodOfTheMartyrEffect copy() {
return new BloodOfTheMartyrEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGE_CREATURE;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
DamageEvent damageEvent = (DamageEvent) event;
if (controller != null) {
controller.damage(damageEvent.getAmount(), damageEvent.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), damageEvent.getAppliedEffects());
return true;
}
return false;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
DamageEvent damageEvent = (DamageEvent) event;
return controller != null
&& controller.chooseUse(outcome, "Would you like to have " + damageEvent.getAmount() + " damage dealt to you instead of " + game.getPermanentOrLKIBattlefield(damageEvent.getTargetId()).getLogName() + "?", source, game);
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.e;
import java.util.UUID;
import mage.abilities.dynamicvalue.common.HalfValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.DamageControllerEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.target.common.TargetOpponent;
/**
*
* @author L_J
*/
public class EternalFlame extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent("Mountains you control");
static {
filter.add(new SubtypePredicate(SubType.MOUNTAIN));
}
public EternalFlame(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{R}{R}");
// Eternal Flame deals X damage to target opponent, where X is the number of Mountains you control. It deals half X damage, rounded up, to you.);
this.getSpellAbility().addEffect(new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter)).setText("{this} deals X damage to target opponent, where X is the number of Mountains you control"));
this.getSpellAbility().addEffect(new DamageControllerEffect(new HalfValue(new PermanentsOnBattlefieldCount(filter), true)).setText("It deals half X damage, rounded up, to you"));
this.getSpellAbility().addTarget(new TargetOpponent());
}
public EternalFlame(final EternalFlame card) {
super(card);
}
@Override
public EternalFlame copy() {
return new EternalFlame(this);
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.g;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
*
* @author LevelX2
*/
public class GhaltaPrimalHunger extends CardImpl {
public GhaltaPrimalHunger(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{10}{G}{G}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.ELDER);
this.subtype.add(SubType.DINOSAUR);
this.power = new MageInt(12);
this.toughness = new MageInt(12);
// Ghalta, Primal Hunger costs {X} less to cast, where X is the total power of creatures you control.
this.addAbility(new SimpleStaticAbility(Zone.STACK, new GhaltaPrimalHungerCostReductionEffect()));
// Trample
this.addAbility(TrampleAbility.getInstance());
}
public GhaltaPrimalHunger(final GhaltaPrimalHunger card) {
super(card);
}
@Override
public GhaltaPrimalHunger copy() {
return new GhaltaPrimalHunger(this);
}
}
class GhaltaPrimalHungerCostReductionEffect extends CostModificationEffectImpl {
private static final FilterPermanent filter = new FilterControlledArtifactPermanent("noncreature artifacts you control");
static {
filter.add(Predicates.not(new CardTypePredicate(CardType.CREATURE)));
}
GhaltaPrimalHungerCostReductionEffect() {
super(Duration.Custom, Outcome.Benefit, CostModificationType.REDUCE_COST);
staticText = "{this} costs {X} less to cast, where X is the total power of creatures you control";
}
GhaltaPrimalHungerCostReductionEffect(final GhaltaPrimalHungerCostReductionEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
int totalPower = 0;
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) {
if (permanent.isCreature()) {
totalPower += permanent.getPower().getValue();
}
}
CardUtil.reduceCost(abilityToModify, totalPower);
return true;
}
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility);
}
@Override
public GhaltaPrimalHungerCostReductionEffect copy() {
return new GhaltaPrimalHungerCostReductionEffect(this);
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.g;
import java.util.Set;
import java.util.UUID;
import mage.MageObjectReference;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.common.combat.CantAttackUnlessDefenderControllsPermanent;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterLandPermanent;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.watchers.common.AttackedLastTurnWatcher;
/**
*
* @author L_J
*/
public class GoblinRockSled extends CardImpl {
private static final FilterLandPermanent filter = new FilterLandPermanent("a Mountain");
static {
filter.add(new SubtypePredicate(SubType.MOUNTAIN));
}
public GoblinRockSled(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{R}");
this.subtype.add(SubType.GOBLIN);
this.power = new MageInt(3);
this.toughness = new MageInt(1);
// Goblin Rock Sled doesn't untap during your untap step if it attacked during your last turn.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DontUntapIfAttackedLastTurnSourceEffect()), new AttackedLastTurnWatcher());
// Goblin Rock Sled can't attack unless defending player controls a Mountain.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantAttackUnlessDefenderControllsPermanent(filter)));
}
public GoblinRockSled(final GoblinRockSled card) {
super(card);
}
@Override
public GoblinRockSled copy() {
return new GoblinRockSled(this);
}
}
class DontUntapIfAttackedLastTurnSourceEffect extends ContinuousRuleModifyingEffectImpl {
public DontUntapIfAttackedLastTurnSourceEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment, false, true);
staticText = "{this} doesn't untap during your untap step if it attacked during your last turn";
}
public DontUntapIfAttackedLastTurnSourceEffect(final DontUntapIfAttackedLastTurnSourceEffect effect) {
super(effect);
}
@Override
public DontUntapIfAttackedLastTurnSourceEffect copy() {
return new DontUntapIfAttackedLastTurnSourceEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == EventType.UNTAP;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (game.getTurn().getStepType() == PhaseStep.UNTAP
&& event.getTargetId().equals(source.getSourceId())) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null && permanent.getControllerId().equals(game.getActivePlayerId())) {
AttackedLastTurnWatcher watcher = (AttackedLastTurnWatcher) game.getState().getWatchers().get(AttackedLastTurnWatcher.class.getSimpleName());
if (watcher != null) {
Set<MageObjectReference> attackingCreatures = watcher.getAttackedLastTurnCreatures(permanent.getControllerId());
MageObjectReference mor = new MageObjectReference(permanent, game);
if (attackingCreatures.contains(mor)) {
return true;
}
}
}
}
return false;
}
}

View file

@ -45,7 +45,7 @@ import mage.constants.SuperType;
import mage.constants.TargetController; import mage.constants.TargetController;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.StaticFilters;
import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.CardTypePredicate;
/** /**
@ -62,7 +62,7 @@ public class GrowingRitesOfItlimoc extends CardImpl {
public GrowingRitesOfItlimoc(UUID ownerId, CardSetInfo setInfo) { public GrowingRitesOfItlimoc(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
this.addSuperType(SuperType.LEGENDARY); this.addSuperType(SuperType.LEGENDARY);
this.transformable = true; this.transformable = true;
@ -77,7 +77,7 @@ public class GrowingRitesOfItlimoc extends CardImpl {
this.addAbility(new TransformAbility()); this.addAbility(new TransformAbility());
this.addAbility(new ConditionalTriggeredAbility( this.addAbility(new ConditionalTriggeredAbility(
new BeginningOfEndStepTriggeredAbility(new TransformSourceEffect(true), TargetController.YOU, false), new BeginningOfEndStepTriggeredAbility(new TransformSourceEffect(true), TargetController.YOU, false),
new PermanentsOnTheBattlefieldCondition(new FilterControlledCreaturePermanent(), ComparisonType.MORE_THAN, 3), new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_CONTROLLED_A_CREATURE, ComparisonType.MORE_THAN, 3),
"At the beginning of your end step, if you control four or more creatures, transform {this}")); "At the beginning of your end step, if you control four or more creatures, transform {this}"));
} }

View file

@ -95,8 +95,11 @@ class InvasionPlansEffect extends ContinuousRuleModifyingEffectImpl {
public boolean applies(GameEvent event, Ability source, Game game) { public boolean applies(GameEvent event, Ability source, Game game) {
Player blockController = game.getPlayer(game.getCombat().getAttackingPlayerId()); Player blockController = game.getPlayer(game.getCombat().getAttackingPlayerId());
if (blockController != null) { if (blockController != null) {
game.getCombat().selectBlockers(blockController, game); // temporary workaround for AI bugging out while choosing blockers
return event.getPlayerId().equals(game.getCombat().getAttackingPlayerId()); if (blockController.isHuman()) {
game.getCombat().selectBlockers(blockController, game);
return event.getPlayerId().equals(game.getCombat().getAttackingPlayerId());
}
} }
return false; return false;
} }

View file

@ -48,6 +48,8 @@ import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.common.FilterOwnedCard; import mage.filter.common.FilterOwnedCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.game.ExileZone; import mage.game.ExileZone;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
@ -62,6 +64,13 @@ public class IzzetChemister extends CardImpl {
private static final FilterCard filter = new FilterOwnedCard("instant or sorcery card from your graveyard"); private static final FilterCard filter = new FilterOwnedCard("instant or sorcery card from your graveyard");
static {
filter.add(Predicates.or(
new CardTypePredicate(CardType.INSTANT),
new CardTypePredicate(CardType.SORCERY))
);
}
public IzzetChemister(UUID ownerId, CardSetInfo setInfo) { public IzzetChemister(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");

View file

@ -46,14 +46,12 @@ import mage.constants.Layer;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubLayer; import mage.constants.SubLayer;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate; import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.other.CounterCardPredicate; import mage.filter.predicate.other.CounterCardPredicate;
import mage.filter.predicate.other.OwnerPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
@ -149,7 +147,6 @@ class MairsilThePretenderGainAbilitiesEffect extends ContinuousEffectImpl {
static { static {
filter.add(new CounterCardPredicate(CounterType.CAGE)); filter.add(new CounterCardPredicate(CounterType.CAGE));
filter.add(new OwnerPredicate(TargetController.YOU));
} }
public MairsilThePretenderGainAbilitiesEffect() { public MairsilThePretenderGainAbilitiesEffect() {
@ -164,21 +161,21 @@ class MairsilThePretenderGainAbilitiesEffect extends ContinuousEffectImpl {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Permanent perm = game.getPermanent(source.getSourceId()); Permanent perm = game.getPermanent(source.getSourceId());
if (perm != null) { if (perm == null) {
for (Card card : game.getExile().getAllCards(game)) { return false;
if (filter.match(card, game)) { }
for (Ability ability : card.getAbilities()) { for (Card card : game.getExile().getAllCards(game)) {
if (ability instanceof ActivatedAbility) { if (filter.match(card, game) && card.getOwnerId() == perm.getControllerId()) {
ActivatedAbilityImpl copyAbility = (ActivatedAbilityImpl) ability.copy(); for (Ability ability : card.getAbilities()) {
copyAbility.setMaxActivationsPerTurn(1); if (ability instanceof ActivatedAbility) {
perm.addAbility(copyAbility, card.getId(), game); ActivatedAbilityImpl copyAbility = (ActivatedAbilityImpl) ability.copy();
} copyAbility.setMaxActivationsPerTurn(1);
perm.addAbility(copyAbility, card.getId(), game);
} }
} }
} }
return true;
} }
return false; return true;
} }
@Override @Override

View file

@ -32,19 +32,14 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
/** /**
@ -54,11 +49,10 @@ import mage.game.permanent.Permanent;
public class NaturesWill extends CardImpl { public class NaturesWill extends CardImpl {
public NaturesWill(UUID ownerId, CardSetInfo setInfo) { public NaturesWill(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{G}{G}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}{G}");
// Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. // Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control.
this.addAbility(new NaturesWillTriggeredAbility()); this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(new NaturesWillEffect()));
} }
public NaturesWill(final NaturesWill card) { public NaturesWill(final NaturesWill card) {
@ -71,63 +65,6 @@ public class NaturesWill extends CardImpl {
} }
} }
class NaturesWillTriggeredAbility extends TriggeredAbilityImpl {
private boolean madeDamge = false;
private Set<UUID> damagedPlayers = new HashSet<>();
public NaturesWillTriggeredAbility() {
super(Zone.BATTLEFIELD, new NaturesWillEffect(), false);
}
public NaturesWillTriggeredAbility(final NaturesWillTriggeredAbility ability) {
super(ability);
this.madeDamge = ability.madeDamge;
this.damagedPlayers = new HashSet<>();
this.damagedPlayers.addAll(ability.damagedPlayers);
}
@Override
public NaturesWillTriggeredAbility copy() {
return new NaturesWillTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DAMAGED_PLAYER || event.getType() == EventType.COMBAT_DAMAGE_STEP_POST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == EventType.DAMAGED_PLAYER) {
DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event;
Permanent p = game.getPermanent(event.getSourceId());
if (damageEvent.isCombatDamage() && p != null && p.getControllerId().equals(this.getControllerId())) {
madeDamge = true;
damagedPlayers.add(event.getPlayerId());
}
}
if (event.getType() == EventType.COMBAT_DAMAGE_STEP_POST) {
if (madeDamge) {
Set<UUID> damagedPlayersCopy = new HashSet<>();
damagedPlayersCopy.addAll(damagedPlayers);
for(Effect effect: this.getEffects()) {
effect.setValue("damagedPlayers", damagedPlayersCopy);
}
damagedPlayers.clear();
madeDamge = false;
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever one or more creatures you control deal combat damage to a player, " + super.getRule();
}
}
class NaturesWillEffect extends OneShotEffect { class NaturesWillEffect extends OneShotEffect {
public NaturesWillEffect() { public NaturesWillEffect() {
@ -160,7 +97,6 @@ class NaturesWillEffect extends OneShotEffect {
} }
} }
return false; return false;
} }
} }

View file

@ -27,11 +27,9 @@
*/ */
package mage.cards.o; package mage.cards.o;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.ExileFromGraveCost; import mage.abilities.costs.common.ExileFromGraveCost;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
@ -42,10 +40,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
/** /**
@ -58,7 +52,7 @@ public class OngoingInvestigation extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}");
// Whenever one or more creatures you control deal combat damage to a player, investigate. // Whenever one or more creatures you control deal combat damage to a player, investigate.
this.addAbility(new OngoingInvestigationTriggeredAbility()); this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(new InvestigateEffect()));
// {1}{G}, Exile a creature card from your graveyard: Investigate. You gain 2 life. // {1}{G}, Exile a creature card from your graveyard: Investigate. You gain 2 life.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new InvestigateEffect(), new ManaCostsImpl("{1}{G}")); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new InvestigateEffect(), new ManaCostsImpl("{1}{G}"));
@ -76,50 +70,3 @@ public class OngoingInvestigation extends CardImpl {
return new OngoingInvestigation(this); return new OngoingInvestigation(this);
} }
} }
class OngoingInvestigationTriggeredAbility extends TriggeredAbilityImpl {
List<UUID> damagedPlayerIds = new ArrayList<>();
public OngoingInvestigationTriggeredAbility() {
super(Zone.BATTLEFIELD, new InvestigateEffect(), false);
}
public OngoingInvestigationTriggeredAbility(final OngoingInvestigationTriggeredAbility ability) {
super(ability);
}
@Override
public OngoingInvestigationTriggeredAbility copy() {
return new OngoingInvestigationTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER
|| event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) {
if (((DamagedPlayerEvent) event).isCombatDamage()) {
Permanent creature = game.getPermanent(event.getSourceId());
if (creature != null && creature.getControllerId().equals(controllerId)
&& !damagedPlayerIds.contains(event.getTargetId())) {
damagedPlayerIds.add(event.getTargetId());
return true;
}
}
}
if (event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST) {
damagedPlayerIds.clear();
}
return false;
}
@Override
public String getRule() {
return "Whenever one or more creatures you control deal combat damage to a player, investigate";
}
}

View file

@ -25,14 +25,11 @@
* authors and should not be interpreted as representing official policies, either expressed * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package mage.cards.p; package mage.cards.p;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect;
@ -41,22 +38,14 @@ import mage.abilities.keyword.BloodrushAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
public class PyrewildShaman extends CardImpl { public class PyrewildShaman extends CardImpl {
public PyrewildShaman (UUID ownerId, CardSetInfo setInfo) { public PyrewildShaman(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
this.subtype.add(SubType.GOBLIN); this.subtype.add(SubType.GOBLIN);
this.subtype.add(SubType.SHAMAN); this.subtype.add(SubType.SHAMAN);
@ -64,14 +53,16 @@ public class PyrewildShaman extends CardImpl {
this.toughness = new MageInt(1); this.toughness = new MageInt(1);
// Bloodrush {1}{R}, Discard Pyrewild Shaman: Target attacking creature gets +3/+1 until end of turn. // Bloodrush {1}{R}, Discard Pyrewild Shaman: Target attacking creature gets +3/+1 until end of turn.
this.addAbility(new BloodrushAbility("{1}{R}", new BoostTargetEffect(3,1,Duration.EndOfTurn))); this.addAbility(new BloodrushAbility("{1}{R}", new BoostTargetEffect(3, 1, Duration.EndOfTurn)));
// Whenever one or more creatures you control deal combat damage to a player, if Pyrewild Shaman is in your graveyard, you may pay {3}. If you do, return Pyrewild Shaman to your hand. // Whenever one or more creatures you control deal combat damage to a player, if Pyrewild Shaman is in your graveyard, you may pay {3}. If you do, return Pyrewild Shaman to your hand.
this.addAbility(new PyrewildShamanTriggeredAbility()); this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(Zone.GRAVEYARD,
new DoIfCostPaid(new ReturnToHandSourceEffect(), new ManaCostsImpl("{3}"))
.setText("if {this} is in your graveyard, you may pay {3}. If you do, return {this} to your hand")));
} }
public PyrewildShaman (final PyrewildShaman card) { public PyrewildShaman(final PyrewildShaman card) {
super(card); super(card);
} }
@ -81,56 +72,3 @@ public class PyrewildShaman extends CardImpl {
} }
} }
class PyrewildShamanTriggeredAbility extends TriggeredAbilityImpl {
List<UUID> damagedPlayerIds = new ArrayList<>();
public PyrewildShamanTriggeredAbility() {
super(Zone.GRAVEYARD, new DoIfCostPaid(new ReturnToHandSourceEffect(), new ManaCostsImpl("{3}")), false);
}
public PyrewildShamanTriggeredAbility(final PyrewildShamanTriggeredAbility ability) {
super(ability);
}
@Override
public PyrewildShamanTriggeredAbility copy() {
return new PyrewildShamanTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DAMAGED_PLAYER
|| event.getType() == EventType.END_COMBAT_STEP_POST
|| event.getType() == EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) {
if (((DamagedEvent) event).isCombatDamage()) {
Permanent creature = game.getPermanent(event.getSourceId());
if (creature != null && creature.getControllerId().equals(controllerId) && !damagedPlayerIds.contains(event.getTargetId())) {
damagedPlayerIds.add(event.getTargetId());
return true;
}
}
}
if (event.getType() == EventType.END_COMBAT_STEP_POST){
damagedPlayerIds.clear();
}
if (event.getType() == EventType.ZONE_CHANGE && event.getTargetId().equals(getSourceId())){
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getFromZone() == Zone.GRAVEYARD) {
damagedPlayerIds.clear();
}
}
return false;
}
@Override
public String getRule() {
return "Whenever one or more creatures you control deal combat damage to a player, if {this} is in your graveyard, you may pay {3}. If you do, return {this} to your hand.";
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.s;
import java.util.UUID;
import mage.abilities.common.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.ControlledCreaturesDealCombatDamagePlayerTriggeredAbility;
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.v.VaultOfCatlacan;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.filter.StaticFilters;
import mage.game.permanent.token.TreasureToken;
/**
*
* @author LevelX2
*/
public class StormTheVault extends CardImpl {
public StormTheVault(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{R}");
this.addSuperType(SuperType.LEGENDARY);
this.transformable = true;
this.secondSideCardClazz = VaultOfCatlacan.class;
// Whenever one or more creatures you control deal combat damage to a player, create a colorless Treasure artifact token with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."
this.addAbility(new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(new CreateTokenEffect(new TreasureToken())));
// At the beginning of your end step, if you control five or more artifacts, transform Storm the Vault.
this.addAbility(new TransformAbility());
this.addAbility(new ConditionalTriggeredAbility(
new BeginningOfEndStepTriggeredAbility(new TransformSourceEffect(true), TargetController.YOU, false),
new PermanentsOnTheBattlefieldCondition(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT, ComparisonType.MORE_THAN, 4),
"At the beginning of your end step, if you control five or more artifacts, transform {this}"));
}
public StormTheVault(final StormTheVault card) {
super(card);
}
@Override
public StormTheVault copy() {
return new StormTheVault(this);
}
}

View file

@ -0,0 +1,150 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.t;
import java.util.Set;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.TapEnchantedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.PhaseStep;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.watchers.common.AttackedLastTurnWatcher;
/**
*
* @author L_J
*/
public class TangleKelp extends CardImpl {
public TangleKelp(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment));
Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability);
// When Tangle Kelp enters the battlefield, tap enchanted creature.
this.addAbility(new EntersBattlefieldTriggeredAbility(new TapEnchantedEffect()));
// Enchanted creature doesn't untap during its controller's untap step if it attacked during its controller's last turn.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DontUntapIfAttackedLastTurnEnchantedEffect()), new AttackedLastTurnWatcher());
}
public TangleKelp(final TangleKelp card) {
super(card);
}
@Override
public TangleKelp copy() {
return new TangleKelp(this);
}
}
class DontUntapIfAttackedLastTurnEnchantedEffect extends ContinuousRuleModifyingEffectImpl {
public DontUntapIfAttackedLastTurnEnchantedEffect() {
super(Duration.WhileOnBattlefield, Outcome.Detriment, false, true);
staticText = "Enchanted creature doesn't untap during its controller's untap step if it attacked during its controller's last turn";
}
public DontUntapIfAttackedLastTurnEnchantedEffect(final DontUntapIfAttackedLastTurnEnchantedEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public DontUntapIfAttackedLastTurnEnchantedEffect copy() {
return new DontUntapIfAttackedLastTurnEnchantedEffect(this);
}
@Override
public String getInfoMessage(Ability source, GameEvent event, Game game) {
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment != null && enchantment.getAttachedTo() != null) {
Permanent enchanted = game.getPermanent(enchantment.getAttachedTo());
if (enchanted != null) {
return enchanted.getLogName() + " doesn't untap during its controller's untap step (" + enchantment.getLogName() + ')';
}
}
return null;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.UNTAP;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (game.getTurn().getStepType() == PhaseStep.UNTAP) {
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment != null && enchantment.getAttachedTo() != null && event.getTargetId().equals(enchantment.getAttachedTo())) {
Permanent permanent = game.getPermanent(enchantment.getAttachedTo());
if (permanent != null && permanent.getControllerId().equals(game.getActivePlayerId())) {
AttackedLastTurnWatcher watcher = (AttackedLastTurnWatcher) game.getState().getWatchers().get(AttackedLastTurnWatcher.class.getSimpleName());
if (watcher != null) {
Set<MageObjectReference> attackingCreatures = watcher.getAttackedLastTurnCreatures(permanent.getControllerId());
MageObjectReference mor = new MageObjectReference(permanent, game);
if (attackingCreatures.contains(mor)) {
return true;
}
}
}
}
}
return false;
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.v;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.DynamicManaEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.mana.AnyColorManaAbility;
import mage.abilities.mana.SimpleManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
/**
*
* @author LevelX2
*/
public class VaultOfCatlacan extends CardImpl {
public VaultOfCatlacan(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
this.addSuperType(SuperType.LEGENDARY);
this.nightCard = true;
// <i>(Transforms from Storm the Vault.)</i>
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect("<i>(Transforms from Storm the Vault.)</i>"));
ability.setRuleAtTheTop(true);
this.addAbility(ability);
// {T}: Add one mana of any color to your mana pool.
this.addAbility(new AnyColorManaAbility());
// {T}: Add {U} to your mana pool for each artifact you control.
this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD,
new DynamicManaEffect(Mana.BlueMana(1), new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT)),
new TapSourceCost()));
}
public VaultOfCatlacan(final VaultOfCatlacan card) {
super(card);
}
@Override
public VaultOfCatlacan copy() {
return new VaultOfCatlacan(this);
}
}

View file

@ -65,9 +65,11 @@ public class Chronicles extends ExpansionSet {
cards.add(new SetCardInfo("Ashnod's Transmogrant", 73, Rarity.COMMON, mage.cards.a.AshnodsTransmogrant.class)); cards.add(new SetCardInfo("Ashnod's Transmogrant", 73, Rarity.COMMON, mage.cards.a.AshnodsTransmogrant.class));
cards.add(new SetCardInfo("Axelrod Gunnarson", 107, Rarity.RARE, mage.cards.a.AxelrodGunnarson.class)); cards.add(new SetCardInfo("Axelrod Gunnarson", 107, Rarity.RARE, mage.cards.a.AxelrodGunnarson.class));
cards.add(new SetCardInfo("Azure Drake", 15, Rarity.UNCOMMON, mage.cards.a.AzureDrake.class)); cards.add(new SetCardInfo("Azure Drake", 15, Rarity.UNCOMMON, mage.cards.a.AzureDrake.class));
cards.add(new SetCardInfo("Banshee", 1, Rarity.UNCOMMON, mage.cards.b.Banshee.class));
cards.add(new SetCardInfo("Barl's Cage", 74, Rarity.RARE, mage.cards.b.BarlsCage.class)); cards.add(new SetCardInfo("Barl's Cage", 74, Rarity.RARE, mage.cards.b.BarlsCage.class));
cards.add(new SetCardInfo("Beasts of Bogardan", 45, Rarity.UNCOMMON, mage.cards.b.BeastsOfBogardan.class)); cards.add(new SetCardInfo("Beasts of Bogardan", 45, Rarity.UNCOMMON, mage.cards.b.BeastsOfBogardan.class));
cards.add(new SetCardInfo("Blood Moon", 46, Rarity.RARE, mage.cards.b.BloodMoon.class)); cards.add(new SetCardInfo("Blood Moon", 46, Rarity.RARE, mage.cards.b.BloodMoon.class));
cards.add(new SetCardInfo("Blood of the Martyr", 60, Rarity.UNCOMMON, mage.cards.b.BloodOfTheMartyr.class));
cards.add(new SetCardInfo("Bog Rats", 2, Rarity.COMMON, mage.cards.b.BogRats.class)); cards.add(new SetCardInfo("Bog Rats", 2, Rarity.COMMON, mage.cards.b.BogRats.class));
cards.add(new SetCardInfo("Book of Rass", 75, Rarity.RARE, mage.cards.b.BookOfRass.class)); cards.add(new SetCardInfo("Book of Rass", 75, Rarity.RARE, mage.cards.b.BookOfRass.class));
cards.add(new SetCardInfo("Boomerang", 16, Rarity.COMMON, mage.cards.b.Boomerang.class)); cards.add(new SetCardInfo("Boomerang", 16, Rarity.COMMON, mage.cards.b.Boomerang.class));

View file

@ -66,6 +66,7 @@ public class MastersEditionIII extends ExpansionSet {
cards.add(new SetCardInfo("Ashes to Ashes", 58, Rarity.UNCOMMON, mage.cards.a.AshesToAshes.class)); cards.add(new SetCardInfo("Ashes to Ashes", 58, Rarity.UNCOMMON, mage.cards.a.AshesToAshes.class));
cards.add(new SetCardInfo("Astrolabe", 189, Rarity.COMMON, mage.cards.a.Astrolabe.class)); cards.add(new SetCardInfo("Astrolabe", 189, Rarity.COMMON, mage.cards.a.Astrolabe.class));
cards.add(new SetCardInfo("Axelrod Gunnarson", 143, Rarity.UNCOMMON, mage.cards.a.AxelrodGunnarson.class)); cards.add(new SetCardInfo("Axelrod Gunnarson", 143, Rarity.UNCOMMON, mage.cards.a.AxelrodGunnarson.class));
cards.add(new SetCardInfo("Banshee", 59, Rarity.UNCOMMON, mage.cards.b.Banshee.class));
cards.add(new SetCardInfo("Barktooth Warbeard", 144, Rarity.COMMON, mage.cards.b.BarktoothWarbeard.class)); cards.add(new SetCardInfo("Barktooth Warbeard", 144, Rarity.COMMON, mage.cards.b.BarktoothWarbeard.class));
cards.add(new SetCardInfo("Barl's Cage", 190, Rarity.RARE, mage.cards.b.BarlsCage.class)); cards.add(new SetCardInfo("Barl's Cage", 190, Rarity.RARE, mage.cards.b.BarlsCage.class));
cards.add(new SetCardInfo("Bartel Runeaxe", 145, Rarity.UNCOMMON, mage.cards.b.BartelRuneaxe.class)); cards.add(new SetCardInfo("Bartel Runeaxe", 145, Rarity.UNCOMMON, mage.cards.b.BartelRuneaxe.class));

View file

@ -28,6 +28,7 @@
package mage.sets; package mage.sets;
import mage.cards.ExpansionSet; import mage.cards.ExpansionSet;
import mage.constants.Rarity;
import mage.constants.SetType; import mage.constants.SetType;
/** /**
@ -53,5 +54,10 @@ public class RivalsOfIxalan extends ExpansionSet {
this.numBoosterUncommon = 3; this.numBoosterUncommon = 3;
this.numBoosterRare = 1; this.numBoosterRare = 1;
this.ratioBoosterMythic = 8; this.ratioBoosterMythic = 8;
cards.add(new SetCardInfo("Ghalta, Primal Hunger", 130, Rarity.RARE, mage.cards.g.GhaltaPrimalHunger.class));
cards.add(new SetCardInfo("Silvergill Adept", 53, Rarity.UNCOMMON, mage.cards.s.SilvergillAdept.class));
cards.add(new SetCardInfo("Storm the Vault", 173, Rarity.RARE, mage.cards.s.StormTheVault.class));
cards.add(new SetCardInfo("Vault of Catlacan", 173, Rarity.RARE, mage.cards.v.VaultOfCatlacan.class));
} }
} }

View file

@ -57,8 +57,10 @@ public class TheDark extends ExpansionSet {
cards.add(new SetCardInfo("Apprentice Wizard", 20, Rarity.RARE, mage.cards.a.ApprenticeWizard.class)); cards.add(new SetCardInfo("Apprentice Wizard", 20, Rarity.RARE, mage.cards.a.ApprenticeWizard.class));
cards.add(new SetCardInfo("Ashes to Ashes", 1, Rarity.COMMON, mage.cards.a.AshesToAshes.class)); cards.add(new SetCardInfo("Ashes to Ashes", 1, Rarity.COMMON, mage.cards.a.AshesToAshes.class));
cards.add(new SetCardInfo("Ball Lightning", 56, Rarity.RARE, mage.cards.b.BallLightning.class)); cards.add(new SetCardInfo("Ball Lightning", 56, Rarity.RARE, mage.cards.b.BallLightning.class));
cards.add(new SetCardInfo("Banshee", 2, Rarity.RARE, mage.cards.b.Banshee.class));
cards.add(new SetCardInfo("Barl's Cage", 93, Rarity.RARE, mage.cards.b.BarlsCage.class)); cards.add(new SetCardInfo("Barl's Cage", 93, Rarity.RARE, mage.cards.b.BarlsCage.class));
cards.add(new SetCardInfo("Blood Moon", 57, Rarity.RARE, mage.cards.b.BloodMoon.class)); cards.add(new SetCardInfo("Blood Moon", 57, Rarity.RARE, mage.cards.b.BloodMoon.class));
cards.add(new SetCardInfo("Blood of the Martyr", 75, Rarity.RARE, mage.cards.b.BloodOfTheMartyr.class));
cards.add(new SetCardInfo("Bog Imp", 3, Rarity.COMMON, mage.cards.b.BogImp.class)); cards.add(new SetCardInfo("Bog Imp", 3, Rarity.COMMON, mage.cards.b.BogImp.class));
cards.add(new SetCardInfo("Bog Rats", 4, Rarity.COMMON, mage.cards.b.BogRats.class)); cards.add(new SetCardInfo("Bog Rats", 4, Rarity.COMMON, mage.cards.b.BogRats.class));
cards.add(new SetCardInfo("Bone Flute", 94, Rarity.UNCOMMON, mage.cards.b.BoneFlute.class)); cards.add(new SetCardInfo("Bone Flute", 94, Rarity.UNCOMMON, mage.cards.b.BoneFlute.class));
@ -80,6 +82,7 @@ public class TheDark extends ExpansionSet {
cards.add(new SetCardInfo("Eater of the Dead", 6, Rarity.UNCOMMON, mage.cards.e.EaterOfTheDead.class)); cards.add(new SetCardInfo("Eater of the Dead", 6, Rarity.UNCOMMON, mage.cards.e.EaterOfTheDead.class));
cards.add(new SetCardInfo("Electric Eel", 24, Rarity.UNCOMMON, mage.cards.e.ElectricEel.class)); cards.add(new SetCardInfo("Electric Eel", 24, Rarity.UNCOMMON, mage.cards.e.ElectricEel.class));
cards.add(new SetCardInfo("Elves of Deep Shadow", 39, Rarity.UNCOMMON, mage.cards.e.ElvesOfDeepShadow.class)); cards.add(new SetCardInfo("Elves of Deep Shadow", 39, Rarity.UNCOMMON, mage.cards.e.ElvesOfDeepShadow.class));
cards.add(new SetCardInfo("Eternal Flame", 60, Rarity.RARE, mage.cards.e.EternalFlame.class));
cards.add(new SetCardInfo("Exorcist", 79, Rarity.RARE, mage.cards.e.Exorcist.class)); cards.add(new SetCardInfo("Exorcist", 79, Rarity.RARE, mage.cards.e.Exorcist.class));
cards.add(new SetCardInfo("Fellwar Stone", 99, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Fellwar Stone", 99, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class));
cards.add(new SetCardInfo("Festival", 81, Rarity.COMMON, mage.cards.f.Festival.class)); cards.add(new SetCardInfo("Festival", 81, Rarity.COMMON, mage.cards.f.Festival.class));
@ -92,8 +95,10 @@ public class TheDark extends ExpansionSet {
cards.add(new SetCardInfo("Goblin Caves", 63, Rarity.COMMON, mage.cards.g.GoblinCaves.class)); cards.add(new SetCardInfo("Goblin Caves", 63, Rarity.COMMON, mage.cards.g.GoblinCaves.class));
cards.add(new SetCardInfo("Goblin Digging Team", 64, Rarity.COMMON, mage.cards.g.GoblinDiggingTeam.class)); cards.add(new SetCardInfo("Goblin Digging Team", 64, Rarity.COMMON, mage.cards.g.GoblinDiggingTeam.class));
cards.add(new SetCardInfo("Goblin Hero", 65, Rarity.COMMON, mage.cards.g.GoblinHero.class)); cards.add(new SetCardInfo("Goblin Hero", 65, Rarity.COMMON, mage.cards.g.GoblinHero.class));
cards.add(new SetCardInfo("Goblins of the Flarg", 69, Rarity.COMMON, mage.cards.g.GoblinsOfTheFlarg.class)); cards.add(new SetCardInfo("Goblin Rock Sled", 66, Rarity.COMMON, mage.cards.g.GoblinRockSled.class));
cards.add(new SetCardInfo("Goblin Shrine", 67, Rarity.COMMON, mage.cards.g.GoblinShrine.class));
cards.add(new SetCardInfo("Goblin Wizard", 68, Rarity.RARE, mage.cards.g.GoblinWizard.class)); cards.add(new SetCardInfo("Goblin Wizard", 68, Rarity.RARE, mage.cards.g.GoblinWizard.class));
cards.add(new SetCardInfo("Goblins of the Flarg", 69, Rarity.COMMON, mage.cards.g.GoblinsOfTheFlarg.class));
cards.add(new SetCardInfo("Grave Robbers", 8, Rarity.RARE, mage.cards.g.GraveRobbers.class)); cards.add(new SetCardInfo("Grave Robbers", 8, Rarity.RARE, mage.cards.g.GraveRobbers.class));
cards.add(new SetCardInfo("Hidden Path", 41, Rarity.RARE, mage.cards.h.HiddenPath.class)); cards.add(new SetCardInfo("Hidden Path", 41, Rarity.RARE, mage.cards.h.HiddenPath.class));
cards.add(new SetCardInfo("Holy Light", 83, Rarity.COMMON, mage.cards.h.HolyLight.class)); cards.add(new SetCardInfo("Holy Light", 83, Rarity.COMMON, mage.cards.h.HolyLight.class));
@ -133,6 +138,7 @@ public class TheDark extends ExpansionSet {
cards.add(new SetCardInfo("Standing Stones", 107, Rarity.UNCOMMON, mage.cards.s.StandingStones.class)); cards.add(new SetCardInfo("Standing Stones", 107, Rarity.UNCOMMON, mage.cards.s.StandingStones.class));
cards.add(new SetCardInfo("Stone Calendar", 108, Rarity.RARE, mage.cards.s.StoneCalendar.class)); cards.add(new SetCardInfo("Stone Calendar", 108, Rarity.RARE, mage.cards.s.StoneCalendar.class));
cards.add(new SetCardInfo("Sunken City", 35, Rarity.COMMON, mage.cards.s.SunkenCity.class)); cards.add(new SetCardInfo("Sunken City", 35, Rarity.COMMON, mage.cards.s.SunkenCity.class));
cards.add(new SetCardInfo("Tangle Kelp", 36, Rarity.RARE, mage.cards.t.TangleKelp.class));
cards.add(new SetCardInfo("Tivadar's Crusade", 91, Rarity.UNCOMMON, mage.cards.t.TivadarsCrusade.class)); cards.add(new SetCardInfo("Tivadar's Crusade", 91, Rarity.UNCOMMON, mage.cards.t.TivadarsCrusade.class));
cards.add(new SetCardInfo("Tormod's Crypt", 109, Rarity.UNCOMMON, mage.cards.t.TormodsCrypt.class)); cards.add(new SetCardInfo("Tormod's Crypt", 109, Rarity.UNCOMMON, mage.cards.t.TormodsCrypt.class));
cards.add(new SetCardInfo("Tower of Coireall", 110, Rarity.RARE, mage.cards.t.TowerOfCoireall.class)); cards.add(new SetCardInfo("Tower of Coireall", 110, Rarity.RARE, mage.cards.t.TowerOfCoireall.class));

View file

@ -0,0 +1,114 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.common;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
/**
*
* @author LevelX2
*/
public class ControlledCreaturesDealCombatDamagePlayerTriggeredAbility extends TriggeredAbilityImpl {
private boolean madeDamage = false;
private Set<UUID> damagedPlayerIds = new HashSet<>();
public ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(Effect effect) {
this(Zone.BATTLEFIELD, effect);
}
public ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(Zone zone, Effect effect) {
super(zone, effect, false);
}
public ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(final ControlledCreaturesDealCombatDamagePlayerTriggeredAbility ability) {
super(ability);
this.madeDamage = ability.madeDamage;
this.damagedPlayerIds = new HashSet<>();
this.damagedPlayerIds.addAll(ability.damagedPlayerIds);
}
@Override
public ControlledCreaturesDealCombatDamagePlayerTriggeredAbility copy() {
return new ControlledCreaturesDealCombatDamagePlayerTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == EventType.DAMAGED_PLAYER
|| event.getType() == EventType.END_COMBAT_STEP_POST
|| event.getType() == EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == EventType.DAMAGED_PLAYER) {
DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event;
Permanent p = game.getPermanent(event.getSourceId());
if (damageEvent.isCombatDamage() && p != null && p.getControllerId().equals(this.getControllerId())) {
madeDamage = true;
damagedPlayerIds.add(event.getPlayerId());
}
}
if (event.getType() == EventType.END_COMBAT_STEP_POST) {
if (madeDamage) {
Set<UUID> damagedPlayersCopy = new HashSet<>();
damagedPlayersCopy.addAll(damagedPlayerIds);
for (Effect effect : this.getEffects()) {
effect.setValue("damagedPlayers", damagedPlayersCopy);
}
damagedPlayerIds.clear();
madeDamage = false;
return true;
}
}
if (event.getType() == EventType.ZONE_CHANGE && event.getTargetId().equals(getSourceId())) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getFromZone() == Zone.GRAVEYARD) {
damagedPlayerIds.clear();
}
}
return false;
}
@Override
public String getRule() {
return "Whenever one or more creatures you control deal combat damage to a player, " + super.getRule();
}
}

View file

@ -56,6 +56,7 @@ public enum CounterType {
DIVINITY("divinity"), DIVINITY("divinity"),
DOOM("doom"), DOOM("doom"),
DREAM("dream"), DREAM("dream"),
ECHO("echo"),
ELIXIR("elixir"), ELIXIR("elixir"),
ENERGY("energy"), ENERGY("energy"),
EON("eon"), EON("eon"),

View file

@ -48,6 +48,7 @@ public final class StaticFilters {
public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT = new FilterPermanent("artifact an opponent controls"); public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT = new FilterPermanent("artifact an opponent controls");
public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature an opponent controls"); public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature an opponent controls");
public static final FilterPermanent FILTER_CONTROLLED_CREATURE = new FilterControlledCreaturePermanent();
public static final FilterPermanent FILTER_CONTROLLED_A_CREATURE = new FilterControlledCreaturePermanent("a creature you control"); public static final FilterPermanent FILTER_CONTROLLED_A_CREATURE = new FilterControlledCreaturePermanent("a creature you control");
public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_ANOTHER_CREATURE = new FilterControlledCreaturePermanent("another creature"); public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_ANOTHER_CREATURE = new FilterControlledCreaturePermanent("another creature");
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent"); public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent");

View file

@ -32696,3 +32696,9 @@ Forest|Duel Decks: Mind vs. Might|65|L||Basic Land - Forest|||{T}: Add {G} to yo
Chandra, Gremlin Wrangler|Heroes of the Realm|1|M|{2}{R}{R}|Legendary Planeswalker - Chandra|3||+1: Create a 2/2 red Gremlin creature token.$-2:Chandra, Gremlin Wrangler deals X damage to target creature or player, where X is the number of Gremlins you control.| Chandra, Gremlin Wrangler|Heroes of the Realm|1|M|{2}{R}{R}|Legendary Planeswalker - Chandra|3||+1: Create a 2/2 red Gremlin creature token.$-2:Chandra, Gremlin Wrangler deals X damage to target creature or player, where X is the number of Gremlins you control.|
Dungeon Master|Heroes of the Realm|1|M|{2}{W}{U}|Legendary Planeswalker - Dungeon Master|||+1: Target opponent creates a 1/1 black Skeleton creature token with “When this creature dies, each opponent loses 2 life.”$+1: Roll a d20. If you roll a 1, skip your next turn. If you roll a 12 or higher, draw a card.$-6: You get an adventuring party. (Your party is a 3/3 red Fighter with first strike, a 1/1 white Cleric with lifelink, a 2/2 black Rogue with hexproof, and a 1/1 blue Wizard with flying.)| Dungeon Master|Heroes of the Realm|1|M|{2}{W}{U}|Legendary Planeswalker - Dungeon Master|||+1: Target opponent creates a 1/1 black Skeleton creature token with “When this creature dies, each opponent loses 2 life.”$+1: Roll a d20. If you roll a 1, skip your next turn. If you roll a 12 or higher, draw a card.$-6: You get an adventuring party. (Your party is a 3/3 red Fighter with first strike, a 1/1 white Cleric with lifelink, a 2/2 black Rogue with hexproof, and a 1/1 blue Wizard with flying.)|
Nira, Hellkite Duelist|Heroes of the Realm|3|M|{W}{U}{B}{R}{G}|Legendary Creature — Dragon|6|6|Flash$Flying, trample, haste$When Nira, Hellkite Duelist enters the battlefield, the next time you would lose the game this turn, instead draw three cards and your life total becomes 5.| Nira, Hellkite Duelist|Heroes of the Realm|3|M|{W}{U}{B}{R}{G}|Legendary Creature — Dragon|6|6|Flash$Flying, trample, haste$When Nira, Hellkite Duelist enters the battlefield, the next time you would lose the game this turn, instead draw three cards and your life total becomes 5.|
Silvergill Adept|Rivals of Ixalan|53|U|{1}{U}|Creature - Merfolk Wizard|2|1|As an additional cost to cast Silvergill Adept, reveal a Merfolk card from your hand or pay {3}.$When Silvergill Adept enters the battlefield, draw a card.|
Tetzimoc, Primal Death|Rivals of Ixalan|86|R|{4}{B}{B}|Legendary Creature - Elder Dinosaur|6|6|Deathtouch${B}, Reveal Tetzimoc, Primal Death from your hand: Put a prey counter on target creature. Activate this ability only during your turn.$When Tetzimoc, Primal Death enters the battlefield, destroy each creature your opponents control with a prey counter on it.|
Ghalta, Primal Hunger|Rivals of Ixalan|130|R|{10}{G}{G}|Legendary Creature - Elder Dinosaur|12|12|Ghalta, Primal Hunger costs {X} less to cast, where X is the total power of creatures you control.$Trample|
Storm the Vault|Rivals of Ixalan|173|R|{2}{U}{R}|Legendary Enchantment|||Whenever one or more creatures you control deal combat damage to a player, create a colorless Treasure artifact token with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."$At the beginning of your end step, if you control five or more artifacts, transform Storm the Vault.|
Vault of Catlacan|Rivals of Ixalan|173|R||Legendary Land|||<i>(Transforms from Storm the Vault.)</i>${T}: Add one mana of any color to your mana pool.${T}: Add {U} to your mana pool for each artifact you control.|
The Immortal Sun|Rivals of Ixalan|180|M|{6}|Legendary Artifact|||Players can't activate loyalty abilities of planeswalkers.$At the beginning of your draw step, draw an additional card.$Spells you cast cost {1} less to cast.$Creatures you control get +1/+1.|