Merge branch 'github-actions' of github.com:fearphage/mage into github-actions

This commit is contained in:
Phred 2021-10-04 09:18:39 -05:00
commit 8557860812
No known key found for this signature in database
GPG key ID: 8103F27168DAA2A0
756 changed files with 22047 additions and 7502 deletions

View file

@ -9,15 +9,4 @@ before_install:
- echo "MAVEN_OPTS='-Xmx2g'" > ~/.mavenrc
cache:
directories:
- $HOME/.m2
install: >
mvn
install
jacoco:prepare-agent
--define jacoco.skip=false
--define maven.javadoc.skip=true
--define skipTests=true
--batch-mode
--show-version
after_success:
- mvn jacoco:report jacoco:report-aggregate verify coveralls:report --define jacoco.skip=false
- $HOME/.m2

View file

@ -14,7 +14,6 @@
<name>Mage Client</name>
<dependencies>
<dependency>
<groupId>org.mage</groupId>
<artifactId>mage</artifactId>
@ -31,102 +30,97 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.jspf</groupId>
<artifactId>jspf-core</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<groupId>${project.groupId}</groupId>
<artifactId>mage-counter-plugin</artifactId>
<version>0.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<!-- needs for server connection by jboss -->
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>net.java.truevfs</groupId>
<artifactId>truevfs-profile-base</artifactId>
</dependency>
<dependency>
<groupId>org.unbescape</groupId>
<artifactId>unbescape</artifactId>
</dependency>
<dependency>
<!-- inner lib for jboss network implementation -->
<groupId>net.sf.trove4j</groupId>
<artifactId>trove4j</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<!-- wtf lib, related to plugins system TODO: unused and can be deleted? -->
<groupId>com.googlecode.jspf</groupId>
<artifactId>jspf-core</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<!-- image scaling for card images -->
<!-- TODO: library is dead, must be replaced -->
<groupId>com.mortennobel</groupId>
<artifactId>java-image-scaling</artifactId>
<version>0.8.6</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<!-- graphic lib to draw GUI with effects -->
<!-- TODO: library is dead, must be replaced -->
<groupId>org.swinglabs</groupId>
<artifactId>swingx</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.jetlang</groupId>
<artifactId>jetlang</artifactId>
<version>0.2.23</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.827</version>
</dependency>
<dependency>
<groupId>com.jgoodies</groupId>
<artifactId>forms</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.intellij</groupId>
<artifactId>forms_rt</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-counter-plugin</artifactId>
<version>0.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jdesktop</groupId>
<artifactId>beansbinding</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<!-- GUI lib, part of SwingX -->
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.2</version>
<!-- multi-threading lib for symbols/images download -->
<groupId>org.jetlang</groupId>
<artifactId>jetlang</artifactId>
<version>0.2.23</version>
</dependency>
<dependency>
<artifactId>truevfs-profile-base</artifactId>
<groupId>net.java.truevfs</groupId>
<type>jar</type>
<version>0.11.1</version>
<exclusions>
<exclusion>
<artifactId>truevfs-access-swing</artifactId>
<groupId>net.java.truevfs</groupId>
</exclusion>
<exclusion>
<artifactId>truecommons-key-swing</artifactId>
<groupId>net.java.truecommons</groupId>
</exclusion>
</exclusions>
<!-- amazon s3 cloud lib to upload game logs from experimental client -->
<!-- TODO: feature must be removed as unused or implemented for all -->
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.78</version>
</dependency>
<dependency>
<!-- GUI lib TODO: unused and can be deleted? -->
<groupId>com.jgoodies</groupId>
<artifactId>forms</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<!-- GUI lib TODO: unused and can be deleted? -->
<groupId>com.intellij</groupId>
<artifactId>forms_rt</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<!-- GUI lib -->
<groupId>org.jdesktop</groupId>
<artifactId>beansbinding</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<!-- scraping lib to download and parse symbols/images/svg -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
<!-- music player START -->
@ -148,6 +142,7 @@
<!-- music player END -->
<dependency>
<!-- GUI lib for balloon popup TODO: unused and can be deleted? -->
<groupId>net.java.balloontip</groupId>
<artifactId>balloontip</artifactId>
<version>1.2.4.1</version>
@ -166,15 +161,11 @@
<!-- svg support END -->
<dependency>
<!-- time lib for GUI time info -->
<groupId>org.ocpsoft.prettytime</groupId>
<artifactId>prettytime</artifactId>
<version>4.0.6.Final</version>
</dependency>
<dependency>
<groupId>org.unbescape</groupId>
<artifactId>unbescape</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
</dependencies>
<!-- to get the reference to local repository with com\googlecode\jspf\jspf-core\0.9.1\ -->

View file

@ -186,14 +186,22 @@ public class CardArea extends JPanel implements CardEventProducer {
card = tmp;
}
CardIconRenderSettings customIconsRender = new CardIconRenderSettings()
.withDebugMode(true)
.withCustomPosition(customCardIconPosition)
.withCustomOrder(customCardIconOrder)
.withCustomColor(customCardIconColor)
.withCustomMaxVisibleCount(customCardIconsMaxVisibleCount)
.withCustomIconSizePercent(30);
MageCard cardPanel = Plugins.instance.getMageCard(card, bigCard, customIconsRender, cardDimension, gameId, true, true,
CardIconRenderSettings currentIconsRender;
if (this.customRenderMode >= 0) {
// debug
currentIconsRender = new CardIconRenderSettings()
.withDebugMode(true)
.withCustomPosition(customCardIconPosition)
.withCustomOrder(customCardIconOrder)
.withCustomColor(customCardIconColor)
.withCustomMaxVisibleCount(customCardIconsMaxVisibleCount)
.withCustomIconSizePercent(30);
} else {
// default
currentIconsRender = new CardIconRenderSettings();
}
MageCard cardPanel = Plugins.instance.getMageCard(card, bigCard, currentIconsRender, cardDimension, gameId, true, true,
customRenderMode != -1 ? customRenderMode : PreferencesDialog.getRenderMode(), customNeedFullPermanentRender);
cardPanel.setCardContainerRef(this);
cardPanel.update(card);

View file

@ -22,7 +22,7 @@ import java.util.List;
import java.util.*;
/**
* Dialog for choosing abilities.
* GUI: Dialog for choosing abilities (list)
*
* @author nantuko, JayDi85
*/

View file

@ -4378,11 +4378,11 @@
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="labelNumberOfDownloadThreads" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="labelPreferedImageLanguage" alignment="0" max="-2" attributes="0"/>
<Component id="labelPreferredImageLanguage" alignment="0" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cbPreferedImageLanguage" min="-2" pref="153" max="-2" attributes="0"/>
<Component id="cbPreferredImageLanguage" min="-2" pref="153" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="cbNumberOfDownloadThreads" min="-2" pref="153" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
@ -4416,8 +4416,8 @@
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="labelPreferedImageLanguage" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbPreferedImageLanguage" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="labelPreferredImageLanguage" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbPreferredImageLanguage" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
@ -4453,7 +4453,7 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbSaveToZipFilesActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JComboBox" name="cbPreferedImageLanguage">
<Component class="javax.swing.JComboBox" name="cbPreferredImageLanguage">
<Properties>
<Property name="maximumRowCount" type="int" value="20"/>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
@ -4469,7 +4469,7 @@
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="labelPreferedImageLanguage">
<Component class="javax.swing.JLabel" name="labelPreferredImageLanguage">
<Properties>
<Property name="text" type="java.lang.String" value="Default images language:"/>
<Property name="focusable" type="boolean" value="false"/>

View file

@ -81,7 +81,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_CARD_IMAGES_THREADS = "cardImagesThreads";
public static final String KEY_CARD_IMAGES_THREADS_DEFAULT = "3";
public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip";
public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferedImageLaguage";
public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage";
public static final String KEY_CARD_RENDERING_FALLBACK = "cardRenderingFallback";
public static final String KEY_CARD_RENDERING_ICONS_FOR_ABILITIES = "cardRenderingIconsForAbilities";
@ -400,7 +400,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbTheme.setModel(new DefaultComboBoxModel<>(ThemeType.values()));
addAvatars();
cbPreferedImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList()));
cbPreferredImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList()));
cbNumberOfDownloadThreads.setModel(new DefaultComboBoxModel<>(new String[]{"10", "9", "8", "7", "6", "5", "4", "3", "2", "1"}));
}
@ -509,8 +509,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
txtImageFolderPath = new javax.swing.JTextField();
btnBrowseImageLocation = new javax.swing.JButton();
cbSaveToZipFiles = new javax.swing.JCheckBox();
cbPreferedImageLanguage = new javax.swing.JComboBox<>();
labelPreferedImageLanguage = new javax.swing.JLabel();
cbPreferredImageLanguage = new javax.swing.JComboBox<>();
labelPreferredImageLanguage = new javax.swing.JLabel();
labelNumberOfDownloadThreads = new javax.swing.JLabel();
cbNumberOfDownloadThreads = new javax.swing.JComboBox();
labelHint1 = new javax.swing.JLabel();
@ -1658,11 +1658,11 @@ public class PreferencesDialog extends javax.swing.JDialog {
}
});
cbPreferedImageLanguage.setMaximumRowCount(20);
cbPreferedImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
cbPreferredImageLanguage.setMaximumRowCount(20);
cbPreferredImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
labelPreferedImageLanguage.setText("Default images language:");
labelPreferedImageLanguage.setFocusable(false);
labelPreferredImageLanguage.setText("Default images language:");
labelPreferredImageLanguage.setFocusable(false);
labelNumberOfDownloadThreads.setText("Default download threads:");
@ -1689,10 +1689,10 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(panelCardImagesLayout.createSequentialGroup()
.add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(labelNumberOfDownloadThreads)
.add(labelPreferedImageLanguage))
.add(labelPreferredImageLanguage))
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(panelCardImagesLayout.createSequentialGroup()
.add(cbNumberOfDownloadThreads, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
@ -1716,8 +1716,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(labelHint1))
.addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
.add(panelCardImagesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(labelPreferedImageLanguage)
.add(cbPreferedImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
.add(labelPreferredImageLanguage)
.add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
);
panelCardStyles.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)"));
@ -2945,7 +2945,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
saveImagesPath(prefs);
save(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true", "false", UPDATE_CACHE_POLICY);
save(prefs, dialog.cbNumberOfDownloadThreads, KEY_CARD_IMAGES_THREADS);
save(prefs, dialog.cbPreferedImageLanguage, KEY_CARD_IMAGES_PREF_LANGUAGE);
save(prefs, dialog.cbPreferredImageLanguage, KEY_CARD_IMAGES_PREF_LANGUAGE);
save(prefs, dialog.cbUseDefaultBackground, KEY_BACKGROUND_IMAGE_DEFAULT, "true", "false", UPDATE_CACHE_POLICY);
save(prefs, dialog.cbUseDefaultBattleImage, KEY_BATTLEFIELD_IMAGE_DEFAULT, "true", "false", UPDATE_CACHE_POLICY);
@ -3518,7 +3518,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
}
load(prefs, dialog.cbSaveToZipFiles, KEY_CARD_IMAGES_SAVE_TO_ZIP, "true");
dialog.cbNumberOfDownloadThreads.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_THREADS, KEY_CARD_IMAGES_THREADS_DEFAULT));
dialog.cbPreferedImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode()));
dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode()));
// rendering settings
load(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_FALLBACK, "true", "false");
@ -4073,7 +4073,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JComboBox cbNumberOfDownloadThreads;
private javax.swing.JCheckBox cbPassPriorityActivation;
private javax.swing.JCheckBox cbPassPriorityCast;
private javax.swing.JComboBox<String> cbPreferedImageLanguage;
private javax.swing.JComboBox<String> cbPreferredImageLanguage;
private javax.swing.JComboBox<ProxyType> cbProxyType;
private javax.swing.JCheckBox cbSaveToZipFiles;
private javax.swing.JCheckBox cbShowStormCounter;
@ -4176,7 +4176,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JLabel labelMainStep;
private javax.swing.JLabel labelNextTurn;
private javax.swing.JLabel labelNumberOfDownloadThreads;
private javax.swing.JLabel labelPreferedImageLanguage;
private javax.swing.JLabel labelPreferredImageLanguage;
private javax.swing.JLabel labelPriorEnd;
private javax.swing.JLabel labelSkipStep;
private javax.swing.JLabel labelStackWidth;

View file

@ -14,6 +14,7 @@ import org.apache.log4j.Logger;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
@ -38,6 +39,7 @@ public class FeedbackPanel extends javax.swing.JPanel {
private MageDialog connectedDialog;
private ChatPanelBasic connectedChatPanel;
private int lastMessageId;
private Map<String, Serializable> lastOptions = new HashMap<>();
private static final ScheduledExecutorService WORKER = Executors.newSingleThreadScheduledExecutor();
@ -63,8 +65,8 @@ public class FeedbackPanel extends javax.swing.JPanel {
private void setGUISize() {
}
public void getFeedback(FeedbackMode mode, String message, boolean special, Map<String, Serializable> options,
int messageId, boolean gameNeedUserFeedback, TurnPhase gameTurnPhase) {
public void prepareFeedback(FeedbackMode mode, String message, boolean special, Map<String, Serializable> options,
int messageId, boolean gameNeedUserFeedback, TurnPhase gameTurnPhase) {
synchronized (this) {
if (messageId < this.lastMessageId) {
// if too many warning messages here then look at GAME_REDRAW_GUI event logic
@ -72,13 +74,16 @@ public class FeedbackPanel extends javax.swing.JPanel {
return;
}
this.lastMessageId = messageId;
this.lastOptions = options;
this.mode = mode;
}
this.helper.setBasicMessage(message);
this.helper.setOriginalId(null); // reference to the feedback causing ability
String lblText = addAdditionalText(message, options);
this.helper.setTextArea(lblText);
this.mode = mode;
switch (this.mode) {
case INFORM:
setButtonState("", "", mode);
@ -113,7 +118,7 @@ public class FeedbackPanel extends javax.swing.JPanel {
}
requestFocusIfPossible();
handleOptions(options);
updateOptions(options);
this.revalidate();
this.repaint();
@ -167,29 +172,35 @@ public class FeedbackPanel extends javax.swing.JPanel {
WORKER.schedule(task, 8, TimeUnit.SECONDS);
}
private void handleOptions(Map<String, Serializable> options) {
// clear already opened dialog (second request)
if (connectedDialog != null) {
connectedDialog.removeDialog();
connectedDialog = null;
}
public void updateOptions(Map<String, Serializable> options) {
this.lastOptions = options;
if (options != null) {
if (options.containsKey("UI.left.btn.text")) {
String text = (String) options.get("UI.left.btn.text");
if (this.lastOptions != null) {
if (this.lastOptions.containsKey("UI.left.btn.text")) {
String text = (String) this.lastOptions.get("UI.left.btn.text");
this.btnLeft.setText(text);
this.helper.setLeft(text, !text.isEmpty());
}
if (options.containsKey("UI.right.btn.text")) {
String text = (String) options.get("UI.right.btn.text");
if (this.lastOptions.containsKey("UI.right.btn.text")) {
String text = (String) this.lastOptions.get("UI.right.btn.text");
this.btnRight.setText(text);
this.helper.setRight(text, !text.isEmpty());
}
if (options.containsKey("dialog")) {
connectedDialog = (MageDialog) options.get("dialog");
}
updateConnectedDialog((MageDialog) this.lastOptions.getOrDefault("dialog", null));
this.helper.autoSizeButtonsAndFeedbackState();
} else {
updateConnectedDialog(null);
}
}
private void updateConnectedDialog(MageDialog newDialog) {
if (this.connectedDialog != null && this.connectedDialog != newDialog) {
// remove old
this.connectedDialog.removeDialog();
}
this.connectedDialog = newDialog;
if (this.connectedDialog != null) {
this.connectedDialog.setVisible(true);
}
}
@ -244,10 +255,7 @@ public class FeedbackPanel extends javax.swing.JPanel {
}
private void btnRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRightActionPerformed
if (connectedDialog != null) {
connectedDialog.removeDialog();
connectedDialog = null;
}
updateConnectedDialog(null);
if (mode == FeedbackMode.SELECT && (evt.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
SessionHandler.sendPlayerInteger(gameId, 0);
} else if (mode == FeedbackMode.END) {

View file

@ -274,8 +274,7 @@ public final class GamePanel extends javax.swing.JPanel {
windowDialog.removeDialog();
}
clearPickTargetDialogs();
clearPickPileDialogs();
clearPickDialogs();
Plugins.instance.getActionCallback().hideOpenComponents();
try {
@ -288,18 +287,34 @@ public final class GamePanel extends javax.swing.JPanel {
this.bigCard = null;
}
private void hidePickDialogs() {
// temporary hide opened dialog on redraw/update
for (ShowCardsDialog dialog : this.pickTarget) {
dialog.setVisible(false);
}
for (PickPileDialog dialog : this.pickPile) {
dialog.setVisible(false);
}
}
private void clearPickDialogs() {
// remove dialogs forever on clean or full update
clearPickTargetDialogs();
clearPickPileDialogs();
}
private void clearPickTargetDialogs() {
for (ShowCardsDialog pickTargetDialog : this.pickTarget) {
pickTargetDialog.cleanUp();
pickTargetDialog.removeDialog();
for (ShowCardsDialog dialog : this.pickTarget) {
dialog.cleanUp();
dialog.removeDialog();
}
this.pickTarget.clear();
}
private void clearPickPileDialogs() {
for (PickPileDialog pickPileDialog : this.pickPile) {
pickPileDialog.cleanUp();
pickPileDialog.removeDialog();
for (PickPileDialog dialog : this.pickPile) {
dialog.cleanUp();
dialog.removeDialog();
}
this.pickPile.clear();
}
@ -929,6 +944,7 @@ public final class GamePanel extends javax.swing.JPanel {
}
feedbackPanel.disableUndo();
feedbackPanel.updateOptions(lastGameData.options);
this.revalidate();
this.repaint();
@ -1344,7 +1360,7 @@ public final class GamePanel extends javax.swing.JPanel {
public void ask(String question, GameView gameView, int messageId, Map<String, Serializable> options) {
updateGame(gameView, false, options, null);
this.feedbackPanel.getFeedback(FeedbackMode.QUESTION, question, false, options, messageId, true, gameView.getPhase());
this.feedbackPanel.prepareFeedback(FeedbackMode.QUESTION, question, false, options, messageId, true, gameView.getPhase());
}
private void keepLastGameData(GameView game, boolean showPlayable, Map<String, Serializable> options, Set<UUID> targets) {
@ -1604,11 +1620,16 @@ public final class GamePanel extends javax.swing.JPanel {
* @param options
* @param messageId
*/
public void pickTarget(String message, CardsView cardsView, GameView gameView, Set<UUID> targets, boolean required, Map<String, Serializable> options, int messageId) {
public void pickTarget(GameView gameView, Map<String, Serializable> options, String message, CardsView cardsView, Set<UUID> targets, boolean required, int messageId) {
updateGame(gameView, false, options, targets);
hideAll();
DialogManager.getManager(gameId).fadeOut();
clearPickTargetDialogs();
PopUpMenuType popupMenuType = null;
if (options != null) {
if (lastGameData.options != null) {
if (options.containsKey("queryType")) {
PlayerQueryEvent.QueryType needType = (PlayerQueryEvent.QueryType) options.get("queryType");
PlayerQueryEvent.QueryType needType = (PlayerQueryEvent.QueryType) lastGameData.options.get("queryType");
switch (needType) {
case PICK_ABILITY:
popupMenuType = PopUpMenuType.TRIGGER_ORDER;
@ -1622,17 +1643,13 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
updateGame(gameView, false, options, targets);
Map<String, Serializable> options0 = options == null ? new HashMap<>() : options;
Map<String, Serializable> options0 = lastGameData.options == null ? new HashMap<>() : lastGameData.options;
ShowCardsDialog dialog = null;
if (cardsView != null && !cardsView.isEmpty()) {
// clear old dialogs before the new
clearPickTargetDialogs();
dialog = showCards(message, cardsView, required, options0, popupMenuType);
dialog = prepareCardsDialog(message, cardsView, required, options0, popupMenuType);
options0.put("dialog", dialog);
}
this.feedbackPanel.getFeedback(required ? FeedbackMode.INFORM : FeedbackMode.CANCEL, message, gameView.getSpecial(), options0, messageId, true, gameView.getPhase());
this.feedbackPanel.prepareFeedback(required ? FeedbackMode.INFORM : FeedbackMode.CANCEL, message, gameView.getSpecial(), options0, messageId, true, gameView.getPhase());
if (dialog != null) {
this.pickTarget.add(dialog);
}
@ -1640,15 +1657,23 @@ public final class GamePanel extends javax.swing.JPanel {
public void inform(String information, GameView gameView, int messageId) {
updateGame(gameView);
this.feedbackPanel.getFeedback(FeedbackMode.INFORM, information, gameView.getSpecial(), null, messageId, false, gameView.getPhase());
this.feedbackPanel.prepareFeedback(FeedbackMode.INFORM, information, gameView.getSpecial(), null, messageId, false, gameView.getPhase());
}
public void endMessage(String message, int messageId) {
this.feedbackPanel.getFeedback(FeedbackMode.END, message, false, null, messageId, true, null);
public void endMessage(GameView gameView, Map<String, Serializable> options, String message, int messageId) {
updateGame(gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.feedbackPanel.prepareFeedback(FeedbackMode.END, message, false, null, messageId, true, null);
ArrowBuilder.getBuilder().removeAllArrows(gameId);
}
public void select(String message, GameView gameView, int messageId, Map<String, Serializable> options) {
public void select(GameView gameView, Map<String, Serializable> options, String message, int messageId) {
updateGame(gameView, true, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.abilityPicker.setVisible(false);
holdingPriority = false;
@ -1659,8 +1684,6 @@ public final class GamePanel extends javax.swing.JPanel {
PreferencesDialog.getCachedValue(KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"),
false);
updateGame(gameView, true, options, null);
boolean controllingPlayer = false;
for (PlayerView playerView : gameView.getPlayers()) {
if (playerView.getPlayerId().equals(playerId)) {
@ -1675,8 +1698,8 @@ public final class GamePanel extends javax.swing.JPanel {
}
Map<String, Serializable> panelOptions = new HashMap<>();
if (options != null) {
panelOptions.putAll(options);
if (lastGameData.options != null) {
panelOptions.putAll(lastGameData.options);
}
panelOptions.put("your_turn", true);
String activePlayerText;
@ -1690,39 +1713,45 @@ public final class GamePanel extends javax.swing.JPanel {
priorityPlayerText = " / priority " + gameView.getPriorityPlayerName();
}
String messageToDisplay = message + FeedbackPanel.getSmallText(activePlayerText + " / " + gameView.getStep().toString() + priorityPlayerText);
this.feedbackPanel.getFeedback(FeedbackMode.SELECT, messageToDisplay, gameView.getSpecial(), panelOptions, messageId, true, gameView.getPhase());
this.feedbackPanel.prepareFeedback(FeedbackMode.SELECT, messageToDisplay, gameView.getSpecial(), panelOptions, messageId, true, gameView.getPhase());
}
public void playMana(String message, GameView gameView, Map<String, Serializable> options, int messageId) {
public void playMana(GameView gameView, Map<String, Serializable> options, String message, int messageId) {
updateGame(gameView, true, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.feedbackPanel.getFeedback(FeedbackMode.CANCEL, message, gameView.getSpecial(), options, messageId, true, gameView.getPhase());
this.feedbackPanel.prepareFeedback(FeedbackMode.CANCEL, message, gameView.getSpecial(), options, messageId, true, gameView.getPhase());
}
public void playXMana(String message, GameView gameView, int messageId) {
updateGame(gameView, true, null, null);
public void playXMana(GameView gameView, Map<String, Serializable> options, String message, int messageId) {
updateGame(gameView, true, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.feedbackPanel.getFeedback(FeedbackMode.CONFIRM, message, gameView.getSpecial(), null, messageId, true, gameView.getPhase());
this.feedbackPanel.prepareFeedback(FeedbackMode.CONFIRM, message, gameView.getSpecial(), null, messageId, true, gameView.getPhase());
}
public void replayMessage(String message) {
//TODO: implement this
}
public void pickAbility(AbilityPickerView choices) {
public void pickAbility(GameView gameView, Map<String, Serializable> options, AbilityPickerView choices) {
updateGame(gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
this.abilityPicker.show(choices, MageFrame.getDesktop().getMousePosition());
}
private void hideAll() {
hidePickDialogs();
this.abilityPicker.setVisible(false);
ActionCallback callback = Plugins.instance.getActionCallback();
((MageActionCallback) callback).hideGameUpdate(gameId);
}
private ShowCardsDialog showCards(String title, CardsView cards, boolean required, Map<String, Serializable> options, PopUpMenuType popupMenuType) {
hideAll();
private ShowCardsDialog prepareCardsDialog(String title, CardsView cards, boolean required, Map<String, Serializable> options, PopUpMenuType popupMenuType) {
ShowCardsDialog showCards = new ShowCardsDialog();
JPopupMenu popupMenu = null;
if (PopUpMenuType.TRIGGER_ORDER == popupMenuType) {
@ -1732,7 +1761,11 @@ public final class GamePanel extends javax.swing.JPanel {
return showCards;
}
public void getAmount(int min, int max, String message) {
public void getAmount(GameView gameView, Map<String, Serializable> options, int min, int max, String message) {
updateGame(gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
pickNumber.showDialog(min, max, message);
if (pickNumber.isCancel()) {
SessionHandler.sendPlayerBoolean(gameId, false);
@ -1741,13 +1774,20 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
public void getMultiAmount(List<String> messages, int min, int max, Map<String, Serializable> options) {
pickMultiNumber.showDialog(messages, min, max, options);
public void getMultiAmount(List<String> messages, GameView gameView, Map<String, Serializable> options, int min, int max) {
updateGame(gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
pickMultiNumber.showDialog(messages, min, max, lastGameData.options);
SessionHandler.sendPlayerString(gameId, pickMultiNumber.getMultiAmount());
}
public void getChoice(Choice choice, UUID objectId) {
public void getChoice(GameView gameView, Map<String, Serializable> options, Choice choice, UUID objectId) {
updateGame(gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
// TODO: remember last choices and search incremental for same events?
PickChoiceDialog pickChoice = new PickChoiceDialog();
pickChoice.showDialog(choice, null, objectId, choiceWindowState, bigCard);
@ -1769,8 +1809,10 @@ public final class GamePanel extends javax.swing.JPanel {
pickChoice.removeDialog();
}
public void pickPile(String message, CardsView pile1, CardsView pile2) {
public void pickPile(GameView gameView, Map<String, Serializable> options, String message, CardsView pile1, CardsView pile2) {
updateGame(gameView, false, options, null);
hideAll();
DialogManager.getManager(gameId).fadeOut();
// remove old dialogs before the new
clearPickPileDialogs();

View file

@ -157,7 +157,7 @@ public class CallbackClientImpl implements CallbackClient {
case REPLAY_DONE: {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
panel.endMessage((String) callback.getData(), callback.getMessageId());
panel.endMessage(null, null, (String) callback.getData(), callback.getMessageId());
}
break;
}
@ -180,16 +180,17 @@ public class CallbackClientImpl implements CallbackClient {
}
case GAME_OVER: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
Session session = SessionHandler.getSession();
if (session.isJsonLogActive()) {
appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData());
ActionData actionData = appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData());
String logFileName = "game-" + actionData.gameId + ".json";
S3Uploader.upload(logFileName, actionData.gameId.toString());
UUID gameId = callback.getObjectId();
appendJsonEvent("GAME_OVER", callback.getObjectId(), message);
String logFileName = "game-" + gameId + ".json";
S3Uploader.upload(logFileName, gameId.toString());
}
panel.endMessage((String) callback.getData(), callback.getMessageId());
panel.endMessage(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId());
}
break;
}
@ -209,35 +210,34 @@ public class CallbackClientImpl implements CallbackClient {
break;
}
case GAME_TARGET: // e.g. Pick triggered ability
{
case GAME_TARGET: {
// e.g. Pick triggered ability
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_TARGET", callback.getObjectId(), message);
panel.pickTarget(message.getMessage(), message.getCardsView(), message.getGameView(),
message.getTargets(), message.isFlag(), message.getOptions(), callback.getMessageId());
panel.pickTarget(message.getGameView(), message.getOptions(), message.getMessage(),
message.getCardsView1(), message.getTargets(), message.isFlag(), callback.getMessageId());
}
break;
}
case GAME_SELECT: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_SELECT", callback.getObjectId(), message);
panel.select(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions());
panel.select(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId());
}
break;
}
case GAME_CHOOSE_ABILITY: {
AbilityPickerView abilityPickerView = (AbilityPickerView) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_CHOOSE_ABILITY", callback.getObjectId(), callback.getData());
panel.pickAbility((AbilityPickerView) callback.getData());
panel.pickAbility(abilityPickerView.getGameView(), null, abilityPickerView);
}
break;
}
@ -247,19 +247,17 @@ public class CallbackClientImpl implements CallbackClient {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_CHOOSE_PILE", callback.getObjectId(), message);
panel.pickPile(message.getMessage(), message.getPile1(), message.getPile2());
panel.pickPile(message.getGameView(), message.getOptions(), message.getMessage(), message.getCardsView1(), message.getCardsView2());
}
break;
}
case GAME_CHOOSE_CHOICE: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_CHOOSE_CHOICE", callback.getObjectId(), message);
panel.getChoice(message.getChoice(), callback.getObjectId());
panel.getChoice(message.getGameView(), message.getOptions(), message.getChoice(), callback.getObjectId());
}
break;
}
@ -269,53 +267,48 @@ public class CallbackClientImpl implements CallbackClient {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_PLAY_MANA", callback.getObjectId(), message);
panel.playMana(message.getMessage(), message.getGameView(), message.getOptions(), callback.getMessageId());
panel.playMana(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId());
}
break;
}
case GAME_PLAY_XMANA: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_PLAY_XMANA", callback.getObjectId(), message);
panel.playXMana(message.getMessage(), message.getGameView(), callback.getMessageId());
panel.playXMana(message.getGameView(), message.getOptions(), message.getMessage(), callback.getMessageId());
}
break;
}
case GAME_GET_AMOUNT: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_GET_AMOUNT", callback.getObjectId(), message);
panel.getAmount(message.getMin(), message.getMax(), message.getMessage());
panel.getAmount(message.getGameView(), message.getOptions(), message.getMin(), message.getMax(), message.getMessage());
}
break;
}
case GAME_GET_MULTI_AMOUNT: {
GameClientMessage message = (GameClientMessage) callback.getData();
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_GET_MULTI_AMOUNT", callback.getObjectId(), message);
panel.getMultiAmount(message.getMessages(), message.getMin(), message.getMax(), message.getOptions());
panel.getMultiAmount(message.getMessages(), message.getGameView(), message.getOptions(), message.getMin(), message.getMax());
}
break;
}
case GAME_UPDATE: {
GamePanel panel = MageFrame.getGame(callback.getObjectId());
if (panel != null) {
appendJsonEvent("GAME_UPDATE", callback.getObjectId(), callback.getData());
panel.updateGame((GameView) callback.getData(), true, null, null); // update after undo
panel.updateGame((GameView) callback.getData(), true, null, null); // update after undo wtf?!
}
break;
}

View file

@ -16,14 +16,13 @@ import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.log4j.Logger;
import mage.utils.ThreadUtils;
public class LinePool {
private final Logger log = LoggerFactory.getLogger(getClass());
private final org.apache.log4j.Logger logger = Logger.getLogger(LinePool.class);
private static final int LINE_CLEANUP_INTERVAL = 30000;
private final Queue<SourceDataLine> freeLines = new ArrayDeque<>();
@ -55,7 +54,7 @@ public class LinePool {
SourceDataLine line = (SourceDataLine) mixer.getLine(lineInfo);
freeLines.add(line);
} catch (LineUnavailableException e) {
log.warn("Failed to get line from mixer", e);
logger.warn("Failed to get line from mixer", e);
}
}
new Timer("Line cleanup", true).scheduleAtFixedRate(new TimerTask() {
@ -65,7 +64,7 @@ public class LinePool {
for (SourceDataLine sourceDataLine : freeLines) {
if (sourceDataLine.isOpen()) {
sourceDataLine.close();
log.debug("Closed line {}", sourceDataLine);
logger.debug("Closed line " + sourceDataLine);
}
}
}
@ -96,13 +95,13 @@ public class LinePool {
public void playSound(final MageClip mageClip) {
final SourceDataLine line;
synchronized (LinePool.this) {
log.debug("Playing {}", mageClip.getFilename());
logger.debug("Playing: " + mageClip.getFilename());
logLineStats();
line = borrowLine();
if (line == null) {
// no lines available, queue sound to play it when a line is available
queue.add(mageClip);
log.debug("Sound {} queued.", mageClip.getFilename());
logger.debug("Sound queued: " + mageClip.getFilename());
return;
}
logLineStats();
@ -113,19 +112,19 @@ public class LinePool {
if (!line.isOpen()) {
line.open();
line.addLineListener(event -> {
log.debug("Event: {}", event);
logger.debug("Event: " + event);
if (event.getType() != Type.STOP) {
return;
}
synchronized (LinePool.this) {
log.debug("Before stop on line {}", line);
logger.debug("Before stop on line " + line);
logLineStats();
returnLine(line);
log.debug("After stop on line {}", line);
logger.debug("After stop on line " + line);
logLineStats();
MageClip queuedSound = queue.poll();
if (queuedSound != null) {
log.debug("Playing queued sound {}", queuedSound);
logger.debug("Playing queued sound " + queuedSound);
playSound(queuedSound);
}
}
@ -133,19 +132,21 @@ public class LinePool {
}
line.start();
} catch (LineUnavailableException e) {
log.warn("Failed to open line", e);
logger.warn("Failed to open line", e);
}
}
byte[] buffer = mageClip.getBuffer();
log.debug("Before write to line {}", line);
logger.debug("Before write to line " + line);
line.write(buffer, 0, buffer.length);
line.drain();
line.stop();
log.debug("Line completed: {}", line);
logger.debug("Line completed: " + line);
});
}
private void logLineStats() {
log.debug("Free lines: {} Active: {} Busy: {}", freeLines.size(), activeLines.size(), busyLines.size());
logger.debug(String.format("Free lines: %d; Active: %d; Busy: %d",
freeLines.size(), activeLines.size(), busyLines.size()
));
}
}

View file

@ -315,203 +315,280 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
}
if (card.isPlanesWalker()) {
if (card.getName().toLowerCase(Locale.ENGLISH).equals("jace, the mind sculptor")) {
thisMaxPower = Math.max(thisMaxPower, 6);
}
if (card.getName().toLowerCase(Locale.ENGLISH).equals("ugin, the spirit dragon")) {
thisMaxPower = Math.max(thisMaxPower, 5);
}
thisMaxPower = Math.max(thisMaxPower, 4);
thisMaxPower = Math.max(thisMaxPower, 6);
}
String cn = card.getName().toLowerCase(Locale.ENGLISH);
if (cn.equals("ancient tomb")
if (cn.equals("acid rain")
|| cn.equals("agent of treachery")
|| cn.equals("anafenza, the foremost")
|| cn.equals("ancient tomb")
|| cn.equals("animar, soul of element")
|| cn.equals("animate artifact")
|| cn.equals("apocalypse")
|| cn.equals("archaeomancer")
|| cn.equals("arcum dagsson")
|| cn.equals("armageddon")
|| cn.equals("ashnod's altar")
|| cn.equals("atraxa, praetors' voice")
|| cn.equals("aura flux")
|| cn.equals("aura shards")
|| cn.equals("avacyn, angel of hope")
|| cn.equals("azami, lady of scrolls")
|| cn.equals("azusa, lost but seeking")
|| cn.equals("back to basics")
|| cn.equals("bane of progress")
|| cn.equals("basalt monolith")
|| cn.equals("bend or break")
|| cn.equals("blightsteel collossus")
|| cn.equals("blightsteel colossus")
|| cn.equals("blood moon")
|| cn.equals("boil")
|| cn.equals("boiling seas")
|| cn.equals("brago, king eternal")
|| cn.equals("braids, cabal minion")
|| cn.equals("bribery")
|| cn.equals("burning sands")
|| cn.equals("cabal coffers")
|| cn.equals("candelabra of tawnos")
|| cn.equals("captain sisay")
|| cn.equals("card view")
|| cn.equals("cataclysm")
|| cn.equals("catastrophe")
|| cn.equals("celestial dawn")
|| cn.equals("cephalid aristocrat")
|| cn.equals("cephalid illusionist")
|| cn.equals("changeling berserker")
|| cn.equals("child of alara")
|| cn.equals("chulane, teller of tales")
|| cn.equals("cinderhaze wretch")
|| cn.equals("coalition relic")
|| cn.equals("confusion in the ranks")
|| cn.equals("consecrated sphinx")
|| cn.equals("contamination")
|| cn.equals("craterhoof behemoth")
|| cn.equals("cryptic gateway")
|| cn.equals("cyclonic rift")
|| cn.equals("deadeye navigator")
|| cn.equals("death cloud")
|| cn.equals("decree of annihilation")
|| cn.equals("decree of silence")
|| cn.equals("deepglow skate")
|| cn.equals("demonic consultation")
|| cn.equals("derevi, empyrial tactician")
|| cn.equals("devastation")
|| cn.equals("dig through time")
|| cn.equals("divine intervention")
|| cn.equals("dockside extortionist")
|| cn.equals("doomsday")
|| cn.equals("doubling season")
|| cn.equals("drannith magistrate")
|| cn.equals("dross scorpion")
|| cn.equals("earthcraft")
|| cn.equals("edric, spymaster of trest")
|| cn.equals("elesh norn, grand cenobite")
|| cn.equals("embargo")
|| cn.equals("emrakul, the promised end")
|| cn.equals("enter the infinite")
|| cn.equals("entomb")
|| cn.equals("force of will")
|| cn.equals("epicenter")
|| cn.equals("erratic portal")
|| cn.equals("expropriate")
|| cn.equals("exquisite blood")
|| cn.equals("fall of the thran")
|| cn.equals("fierce guardianship")
|| cn.equals("food chain")
|| cn.equals("force of negation")
|| cn.equals("force of will")
|| cn.equals("future sight")
|| cn.equals("gaddock teeg")
|| cn.equals("gaea's cradle")
|| cn.equals("genesis chamber")
|| cn.equals("ghave, guru of spores")
|| cn.equals("gilded drake")
|| cn.equals("glenn, the voice of calm")
|| cn.equals("global ruin")
|| cn.equals("golos, tireless pilgrim")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("grave pact")
|| cn.equals("grave titan")
|| cn.equals("great whale")
|| cn.equals("grim monolith")
|| cn.equals("grip of chaos")
|| cn.equals("gush")
|| cn.equals("hellkite charger")
|| cn.equals("hermit druid")
|| cn.equals("hokori, dust drinker")
|| cn.equals("humility")
|| cn.equals("impending disaster")
|| cn.equals("imperial seal")
|| cn.equals("intruder alarm")
|| cn.equals("invoke prejudice")
|| cn.equals("iona, shield of emeria")
|| cn.equals("jin-gitaxias, core augur")
|| cn.equals("jokulhaups")
|| cn.equals("kaalia of the vast")
|| cn.equals("karador, ghost chieftain")
|| cn.equals("karakas")
|| cn.equals("karn, silver golem")
|| cn.equals("kataki, war's wage")
|| cn.equals("keldon firebombers")
|| cn.equals("kiki-jiki, mirror breaker")
|| cn.equals("kinnan, bonder prodigy")
|| cn.equals("knowledge pool")
|| cn.equals("kozilek, butcher of truth")
|| cn.equals("krark-clan ironworks")
|| cn.equals("krenko, mob boss")
|| cn.equals("krosan restorer")
|| cn.equals("laboratory maniac")
|| cn.equals("land equilibrium")
|| cn.equals("leonin relic-warder")
|| cn.equals("leovold, emissary of trest")
|| cn.equals("leyline of the void")
|| cn.equals("linvala, keeper of silence")
|| cn.equals("living death")
|| cn.equals("llawan, cephalid empress")
|| cn.equals("loyal retainers")
|| cn.equals("maelstrom wanderer")
|| cn.equals("magister sphinx")
|| cn.equals("malfegor")
|| cn.equals("master of cruelties")
|| cn.equals("mana breach")
|| cn.equals("mana crypt")
|| cn.equals("mana drain")
|| cn.equals("mana vault")
|| cn.equals("mana vortex")
|| cn.equals("master of cruelties")
|| cn.equals("memnarch")
|| cn.equals("meren of clan nel toth")
|| cn.equals("michiko konda, truth seeker")
|| cn.equals("mikaeus the unhallowed")
|| cn.equals("mikaeus, the unhallowed")
|| cn.equals("mindcrank")
|| cn.equals("mindslaver")
|| cn.equals("minion reflector")
|| cn.equals("mycosynth lattice")
|| cn.equals("myr turbine")
|| cn.equals("narset, enlightened master")
|| cn.equals("narset, parter of veils")
|| cn.equals("nath of the gilt-leaf")
|| cn.equals("natural order")
|| cn.equals("necrotic ooze")
|| cn.equals("negan, the cold-blooded")
|| cn.equals("nekusar, the mindrazer")
|| cn.equals("nether void")
|| cn.equals("nexus of fate")
|| cn.equals("nicol bolas")
|| cn.equals("norin the wary")
|| cn.equals("notion thief")
|| cn.equals("numot, the devastator")
|| cn.equals("oath of druids")
|| cn.equals("obliterate")
|| cn.equals("oko, thief of crowns")
|| cn.equals("oloro, ageless ascetic")
|| cn.equals("omniscience")
|| cn.equals("opalescence")
|| cn.equals("opposition agent")
|| cn.equals("oppression")
|| cn.equals("ornithopter")
|| cn.equals("overwhelming splendor")
|| cn.equals("palinchron")
|| cn.equals("paradox engine")
|| cn.equals("pattern of rebirth")
|| cn.equals("peregrine drake")
|| cn.equals("planar portal")
|| cn.equals("possessed portal")
|| cn.equals("power artifact")
|| cn.equals("price of glory")
|| cn.equals("prossh, skyraider of kher")
|| cn.equals("protean hulk")
|| cn.equals("purphoros, god of the forge")
|| cn.equals("ravages of war")
|| cn.equals("reclamation sage")
|| cn.equals("rhystic study")
|| cn.equals("rick, steadfast leader")
|| cn.equals("rings of brighthearth")
|| cn.equals("rising waters")
|| cn.equals("rite of replication")
|| cn.equals("ruination")
|| cn.equals("sanguine bond")
|| cn.equals("scrambleverse")
|| cn.equals("seedborn muse")
|| cn.equals("sen triplets")
|| cn.equals("sensei's divining top")
|| cn.equals("serra's sanctum")
|| cn.equals("sheoldred, whispering one")
|| cn.equals("sire of insanity")
|| cn.equals("skithiryx, the blight dragon")
|| cn.equals("smokestack")
|| cn.equals("smothering tithe")
|| cn.equals("sol ring")
|| cn.equals("sorin markov")
|| cn.equals("splinter twin")
|| cn.equals("spore frog")
|| cn.equals("stasis")
|| cn.equals("static orb")
|| cn.equals("stony silence")
|| cn.equals("storage matrix")
|| cn.equals("storm cauldron")
|| cn.equals("strip mine")
|| cn.equals("the tabernacle at pendrell vale")
|| cn.equals("tinker")
|| cn.equals("treasure cruise")
|| cn.equals("urabrask the hidden")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("winter orb")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 5);
}
// Parts of infinite combos
if (cn.equals("animate artifact") || cn.equals("animar, soul of element")
|| cn.equals("archaeomancer")
|| cn.equals("ashnod's altar") || cn.equals("azami, lady of scrolls")
|| cn.equals("aura flux")
|| cn.equals("basalt monolith") || cn.equals("brago, king eternal")
|| cn.equals("candelabra of tawnos") || cn.equals("cephalid aristocrat")
|| cn.equals("cephalid illusionist") || cn.equals("changeling berserker")
|| cn.equals("consecrated sphinx")
|| cn.equals("cyclonic rift")
|| cn.equals("the chain veil")
|| cn.equals("cinderhaze wretch") || cn.equals("cryptic gateway")
|| cn.equals("deadeye navigator") || cn.equals("derevi, empyrial tactician")
|| cn.equals("doubling season") || cn.equals("dross scorpion")
|| cn.equals("earthcraft") || cn.equals("erratic portal")
|| cn.equals("enter the infinite") || cn.equals("omniscience")
|| cn.equals("exquisite blood") || cn.equals("future sight")
|| cn.equals("genesis chamber")
|| cn.equals("ghave, guru of spores")
|| cn.equals("grave pact")
|| cn.equals("grave titan") || cn.equals("great whale")
|| cn.equals("grim monolith") || cn.equals("gush")
|| cn.equals("hellkite charger") || cn.equals("intruder alarm")
|| cn.equals("hermit druid")
|| cn.equals("humility")
|| cn.equals("iona, shield of emeria")
|| cn.equals("karn, silver golem") || cn.equals("kiki-jiki, mirror breaker")
|| cn.equals("krark-clan ironworks") || cn.equals("krenko, mob boss")
|| cn.equals("krosan restorer") || cn.equals("laboratory maniac")
|| cn.equals("leovold, emissary of trest")
|| cn.equals("leonin relic-warder") || cn.equals("leyline of the void")
|| cn.equals("memnarch")
|| cn.equals("meren of clan nel toth") || cn.equals("mikaeus, the unhallowed")
|| cn.equals("mindcrank") || cn.equals("mindslaver")
|| cn.equals("minion reflector") || cn.equals("mycosynth lattice")
|| cn.equals("myr turbine") || cn.equals("narset, enlightened master")
|| cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary")
|| cn.equals("notion thief")
|| cn.equals("opalescence") || cn.equals("ornithopter")
|| cn.equals("paradox engine")
|| cn.equals("purphoros, god of the forge")
|| cn.equals("peregrine drake") || cn.equals("palinchron")
|| cn.equals("planar portal") || cn.equals("power artifact")
|| cn.equals("rings of brighthearth") || cn.equals("rite of replication")
|| cn.equals("sanguine bond") || cn.equals("sensei's divining top")
|| cn.equals("splinter twin") || cn.equals("stony silence")
|| cn.equals("sunder")
|| cn.equals("storm cauldron") || cn.equals("teferi's puzzle box")
|| cn.equals("survival of the fittest")
|| cn.equals("table view")
|| cn.equals("tainted aether")
|| cn.equals("tangle wire")
|| cn.equals("tectonic break")
|| cn.equals("teferi's protection")
|| cn.equals("teferi's puzzle box")
|| cn.equals("teferi, mage of zhalfir")
|| cn.equals("tezzeret the seeker") || cn.equals("time stretch")
|| cn.equals("time warp") || cn.equals("training grounds")
|| cn.equals("triskelavus") || cn.equals("triskelion")
|| cn.equals("turnabout") || cn.equals("umbral mantle")
|| cn.equals("uyo, silent prophet") || cn.equals("voltaic key")
|| cn.equals("workhorse") || cn.equals("worldgorger dragon")
|| cn.equals("worthy cause") || cn.equals("yawgmoth's will")
|| cn.equals("zealous conscripts")) {
thisMaxPower = Math.max(thisMaxPower, 12);
}
if (cn.equals("animar, soul of element")
|| cn.equals("azami, lady of scrolls")
|| cn.equals("braids, cabal minion")
|| cn.equals("child of alara")
|| cn.equals("derevi, empyrial tactician")
|| cn.equals("edric, spymaster of trest")
|| cn.equals("gaddock teeg")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("hokori, dust drinker")
|| cn.equals("iona, shield of emeria")
|| cn.equals("jin-gitaxias, core augur")
|| cn.equals("kaalia of the vast")
|| cn.equals("karador, ghost chieftain")
|| cn.equals("leovold, emissary of trest")
|| cn.equals("linvala, keeper of silence")
|| cn.equals("llawan, cephalid empress")
|| cn.equals("memnarch")
|| cn.equals("meren of clan nel toth")
|| cn.equals("michiko konda, truth seeker")
|| cn.equals("narset, enlightened master")
|| cn.equals("nekusar, the mindrazer")
|| cn.equals("norin the wary")
|| cn.equals("numot, the devastator")
|| cn.equals("sheoldred, whispering one")
|| cn.equals("teferi, mage of zhalfir")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 12);
}
if (cn.equals("anafenza, the foremost")
|| cn.equals("arcum dagsson")
|| cn.equals("azusa, lost but seeking")
|| cn.equals("brago, king eternal")
|| cn.equals("captain sisay")
|| cn.equals("elesh norn, grand cenobite")
|| cn.equals("malfegor")
|| cn.equals("maelstrom wanderer")
|| cn.equals("mikaeus the unhallowed")
|| cn.equals("nath of the gilt-leaf")
|| cn.equals("prossh, skyraider of kher")
|| cn.equals("purphoros, god of the forge")
|| cn.equals("sen triplets")
|| cn.equals("teferi, master of time")
|| cn.equals("teferi, time raveler")
|| cn.equals("temporal manipulation")
|| cn.equals("tergrid, god of fright")
|| cn.equals("text view")
|| cn.equals("tezzeret the seeker")
|| cn.equals("thassa's oracle")
|| cn.equals("the chain veil")
|| cn.equals("the tabernacle at pendrell vale")
|| cn.equals("thieves' auction")
|| cn.equals("thoughts of ruin")
|| cn.equals("thrasios, triton hero")
|| cn.equals("time stretch")
|| cn.equals("time warp")
|| cn.equals("tinker")
|| cn.equals("tooth and nail")
|| cn.equals("torment of hailfire")
|| cn.equals("torpor orb")
|| cn.equals("training grounds")
|| cn.equals("treasure cruise")
|| cn.equals("triskelavus")
|| cn.equals("triskelion")
|| cn.equals("triumph of the hordes")
|| cn.equals("turnabout")
|| cn.equals("ugin, the spirit dragon")
|| cn.equals("ulamog, the ceaseless hunger")
|| cn.equals("ulamog, the infinite gyre")
|| cn.equals("umbral mantle")
|| cn.equals("urabrask the hidden")
|| cn.equals("vorinclex, voice of hunger")) {
thisMaxPower = Math.max(thisMaxPower, 10);
}
|| cn.equals("urza, lord high artificer")
|| cn.equals("uyo, silent prophet")
|| cn.equals("void winnower")
|| cn.equals("voltaic key")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("wake of destruction")
|| cn.equals("warp world")
|| cn.equals("winter orb")
|| cn.equals("workhorse")
|| cn.equals("worldgorger dragon")
|| cn.equals("worthy cause")
|| cn.equals("xanathar, guild kingpin")
|| cn.equals("yawgmoth's will")
|| cn.equals("zealous conscripts")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 12);
}
return thisMaxPower;
}
}

View file

@ -225,8 +225,9 @@ public abstract class CardRenderer {
// Call the template methods
drawBorder(g);
drawBackground(g);
lessOpaqueRulesTextBox = false;
drawArt(g);
drawFrame(g, attribs, image);
drawFrame(g, attribs, image, lessOpaqueRulesTextBox);
if (!cardView.isAbility()) {
drawOverlays(g);
drawCounters(g);
@ -241,7 +242,7 @@ public abstract class CardRenderer {
protected abstract void drawArt(Graphics2D g);
protected abstract void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image);
protected abstract void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox);
// Template methods that are possible to override, but unlikely to be
// overridden.
@ -318,7 +319,8 @@ public abstract class CardRenderer {
}
}
protected void drawFaceArtIntoRect(Graphics2D g, int x, int y, int w, int h, Rectangle2D artRect, boolean shouldPreserveAspect) {
private boolean lessOpaqueRulesTextBox = false;
protected void drawFaceArtIntoRect(Graphics2D g, int x, int y, int w, int h, int alternate_h, Rectangle2D artRect, boolean shouldPreserveAspect) {
// Perform a process to make sure that the art is scaled uniformly to fill the frame, cutting
// off the minimum amount necessary to make it completely fill the frame without "squashing" it.
double fullCardImgWidth = faceArtImage.getWidth();
@ -346,10 +348,18 @@ public abstract class CardRenderer {
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHints(rh);
g.drawImage(faceArtImage,
x, y,
(int) targetWidth, (int) targetHeight,
null);
if (fullCardImgWidth > fullCardImgHeight) {
g.drawImage(faceArtImage,
x, y,
(int) targetWidth, (int) targetHeight,
null);
} else {
g.drawImage(faceArtImage,
x, y,
(int) targetWidth, alternate_h, // alernate_h is roughly (targetWidth / 0.74)
null);
lessOpaqueRulesTextBox = true;
}
} catch (RasterFormatException e) {
// At very small card sizes we may encounter a problem with rounding error making the rect not fit
System.out.println(e);

View file

@ -450,9 +450,11 @@ public class ModernCardRenderer extends CardRenderer {
// Normal drawing of art from a source part of the card frame into the rect
if (useFaceArt) {
int alternate_height = cardHeight - boxHeight * 2 - totalContentInset;
drawFaceArtIntoRect(g,
totalContentInset + 1, totalContentInset + boxHeight,
contentWidth - 2, typeLineY - totalContentInset - boxHeight,
alternate_height,
sourceRect, shouldPreserveAspect);
} else if (!isZendikarFullArtLand()) {
drawArtIntoRect(g,
@ -464,14 +466,14 @@ public class ModernCardRenderer extends CardRenderer {
}
@Override
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) {
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) {
// Get the card colors to base the frame on
ObjectColor frameColors = getFrameObjectColor();
// Get the border paint
Color boxColor = getBoxColor(frameColors, cardView.getCardTypes(), attribs.isTransformed);
Color additionalBoxColor = getAdditionalBoxColor(frameColors, cardView.getCardTypes(), attribs.isTransformed);
Paint textboxPaint = getTextboxPaint(frameColors, cardView.getCardTypes(), cardWidth);
Paint textboxPaint = getTextboxPaint(frameColors, cardView.getCardTypes(), cardWidth, lessOpaqueRulesTextBox);
Paint borderPaint = getBorderPaint(frameColors, cardView.getCardTypes(), cardWidth);
// Special colors
@ -1765,21 +1767,29 @@ public class ModernCardRenderer extends CardRenderer {
}
}
private static Color getLessOpaqueColor(Color color, boolean lessOpaqueRulesTextBox) {
if (lessOpaqueRulesTextBox) {
Color lessOpaque = new Color (color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() - 50);
return lessOpaque;
}
return color;
}
// Determine the border paint to use, based on an ObjectColors
protected static Paint getTextboxPaint(ObjectColor colors, Collection<CardType> types, int width) {
protected static Paint getTextboxPaint(ObjectColor colors, Collection<CardType> types, int width, boolean lessOpaqueRulesTextBox) {
if (colors.isMulticolored()) {
if (colors.getColorCount() == 2) {
List<ObjectColor> twoColors = colors.getColors();
Color[] translatedColors;
if (types.contains(CardType.LAND)) {
translatedColors = new Color[]{
getLandTextboxColor(twoColors.get(0)),
getLandTextboxColor(twoColors.get(1))
getLessOpaqueColor(getLandTextboxColor(twoColors.get(0)), lessOpaqueRulesTextBox),
getLessOpaqueColor(getLandTextboxColor(twoColors.get(1)), lessOpaqueRulesTextBox)
};
} else {
translatedColors = new Color[]{
getTextboxColor(twoColors.get(0)),
getTextboxColor(twoColors.get(1))
getLessOpaqueColor(getTextboxColor(twoColors.get(0)), lessOpaqueRulesTextBox),
getLessOpaqueColor(getTextboxColor(twoColors.get(1)), lessOpaqueRulesTextBox)
};
}
@ -1789,20 +1799,20 @@ public class ModernCardRenderer extends CardRenderer {
new float[]{0.4f, 0.6f},
translatedColors);
} else if (types.contains(CardType.LAND)) {
return LAND_TEXTBOX_GOLD;
return getLessOpaqueColor(LAND_TEXTBOX_GOLD, lessOpaqueRulesTextBox);
} else {
return TEXTBOX_GOLD;
return getLessOpaqueColor(TEXTBOX_GOLD, lessOpaqueRulesTextBox);
}
} else if (colors.isColorless()) {
if (types.contains(CardType.LAND)) {
return TEXTBOX_LAND;
return getLessOpaqueColor(TEXTBOX_LAND, lessOpaqueRulesTextBox);
} else {
return TEXTBOX_COLORLESS;
return getLessOpaqueColor(TEXTBOX_COLORLESS, lessOpaqueRulesTextBox);
}
} else if (types.contains(CardType.LAND)) {
return getLandTextboxColor(colors);
return getLessOpaqueColor(getLandTextboxColor(colors), lessOpaqueRulesTextBox);
} else {
return getTextboxColor(colors);
return getLessOpaqueColor(getTextboxColor(colors), lessOpaqueRulesTextBox);
}
}
}

View file

@ -223,7 +223,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
protected void drawSplitHalfFrame(Graphics2D g, CardPanelAttributes attribs, HalfCardProps half, int typeLineY) {
// Get the border paint
Color boxColor = getBoxColor(half.color, cardView.getCardTypes(), attribs.isTransformed);
Paint textboxPaint = getTextboxPaint(half.color, cardView.getCardTypes(), cardWidth);
Paint textboxPaint = getTextboxPaint(half.color, cardView.getCardTypes(), cardWidth, false);
Paint borderPaint = getBorderPaint(half.color, cardView.getCardTypes(), cardWidth);
// Draw main frame
@ -299,7 +299,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
}
@Override
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image) {
protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) {
if (isAftermath()) {
drawSplitHalfFrame(getUnmodifiedHalfContext(g), attribs, leftHalf, (int) (leftHalf.ch * TYPE_LINE_Y_FRAC));
drawSplitHalfFrame(getAftermathHalfContext(g), attribs, rightHalf, (rightHalf.ch - boxHeight) / 2);
@ -309,7 +309,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer {
if (isFuse()) {
Graphics2D g2 = getRightHalfContext(g);
int totalFuseBoxWidth = rightHalf.cw * 2 + 2 * borderWidth + dividerSize;
Paint boxColor = getTextboxPaint(cardView.getColor(), ONLY_LAND_TYPE, totalFuseBoxWidth);
Paint boxColor = getTextboxPaint(cardView.getColor(), ONLY_LAND_TYPE, totalFuseBoxWidth, false);
Paint borderPaint = getBorderPaint(cardView.getColor(), ONLY_LAND_TYPE, totalFuseBoxWidth);
CardRendererUtils.drawRoundedBox(g2,
-borderWidth, rightHalf.ch,

View file

@ -1,11 +1,11 @@
package org.mage.plugins.card.dl.sources;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import mage.MageException;
import mage.client.util.CardLanguage;
import mage.util.JsonUtil;
import org.apache.log4j.Logger;
import org.mage.plugins.card.dl.DownloadServiceInfo;
import org.mage.plugins.card.images.CardDownloadData;
@ -173,17 +173,19 @@ public enum ScryfallImageSource implements CardImageSource {
}
// OK, found card data, parse it
JsonParser jp = new JsonParser();
JsonElement root = jp.parse(new InputStreamReader(jsonStream));
JsonObject jsonCard = root.getAsJsonObject();
if (!jsonCard.has("card_faces")) {
JsonObject jsonCard = JsonParser.parseReader(new InputStreamReader(jsonStream)).getAsJsonObject();
JsonArray jsonFaces = JsonUtil.getAsArray(jsonCard, "card_faces");
if (jsonFaces == null) {
throw new MageException("Couldn't find card_faces in card's JSON data: " + jsonUrl);
}
JsonArray jsonCardFaces = jsonCard.getAsJsonArray("card_faces");
JsonObject jsonCardFace = jsonCardFaces.get(card.isSecondSide() ? 1 : 0).getAsJsonObject();
JsonObject jsonImageUris = jsonCardFace.getAsJsonObject("image_uris");
return jsonImageUris.get("large").getAsString();
JsonObject jsonFace = jsonFaces.get(card.isSecondSide() ? 1 : 0).getAsJsonObject();
JsonObject jsonImages = JsonUtil.getAsObject(jsonFace, "image_uris");
if (jsonImages == null) {
throw new MageException("Couldn't find image_uris in card's JSON data: " + jsonUrl);
}
return JsonUtil.getAsString(jsonImages, "large");
}
@Override

View file

@ -20,6 +20,7 @@
<artifactId>mage</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.jspf</groupId>
<artifactId>jspf-core</artifactId>
@ -41,37 +42,26 @@
<artifactId>jboss-serialization</artifactId>
<version>4.2.2.GA</version>
</dependency>
<dependency>
<groupId>concurrent</groupId>
<artifactId>concurrent</artifactId>
<version>1.3.4</version>
</dependency>
<dependency>
<groupId>trove</groupId>
<artifactId>trove</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- to get the reference to local repository with com\googlecode\jspf\jspf-core\0.9.1\ -->
<repositories>
<repository>

View file

@ -122,11 +122,23 @@ public class SessionImpl implements Session {
client.showMessage("Remote task error. " + message);
}
private boolean doRemoteWorkAndHandleErrors(RemotingTask remoting) {
private boolean doRemoteWorkAndHandleErrors(boolean closeConnectionOnFinish, boolean mustWaitServerMessageOnFail,
RemotingTask remoting) {
// execute remote task and wait result, can be canceled
lastRemotingTask = remoting;
try {
return remoting.doWork();
boolean res = remoting.doWork();
if (!res && mustWaitServerMessageOnFail) {
// server send detail error as separate message by existing connection,
// so you need wait some time before disconnect
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
logger.fatal("waiting of error message had failed", e);
Thread.currentThread().interrupt();
}
}
return res;
} catch (InterruptedException | CancellationException t) {
// was canceled by user, nothing to show
} catch (MalformedURLException ex) {
@ -180,13 +192,16 @@ public class SessionImpl implements Session {
}
} finally {
lastRemotingTask = null;
if (closeConnectionOnFinish) {
disconnect(false); // it's ok on mutiple calls
}
}
return false;
}
@Override
public synchronized boolean register(final Connection connection) {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(true, true, new RemotingTask() {
@Override
public boolean work() throws Throwable {
logger.info("Registration: username " + getUserName() + " for email " + getEmail());
@ -199,7 +214,7 @@ public class SessionImpl implements Session {
@Override
public synchronized boolean emailAuthToken(final Connection connection) {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(true, true, new RemotingTask() {
@Override
public boolean work() throws Throwable {
logger.info("Auth request: requesting auth token for username " + getUserName() + " to email " + getEmail());
@ -212,12 +227,12 @@ public class SessionImpl implements Session {
@Override
public synchronized boolean resetPassword(final Connection connection) {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(true, true, new RemotingTask() {
@Override
public boolean work() throws Throwable {
logger.info("Password reset: reseting password for username " + getUserName());
boolean result = server.resetPassword(sessionId, connection.getEmail(), connection.getAuthToken(), connection.getPassword());
logger.info("Password reset: " + (result ? "DONE, check your email for new password" : "FAIL"));
logger.info("Password reset: " + (result ? "DONE, now you can login with new password" : "FAIL"));
return result;
}
});
@ -225,7 +240,7 @@ public class SessionImpl implements Session {
@Override
public synchronized boolean connect(final Connection connection) {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(new RemotingTask() {
return doRemoteConnection(connection) && doRemoteWorkAndHandleErrors(false, true, new RemotingTask() {
@Override
public boolean work() throws Throwable {
setLastError("");
@ -258,7 +273,6 @@ public class SessionImpl implements Session {
}
logger.info("Logging: FAIL");
disconnect(false);
return false;
}
});
@ -437,7 +451,7 @@ public class SessionImpl implements Session {
boolean result;
try {
result = doRemoteWorkAndHandleErrors(lastRemotingTask);
result = doRemoteWorkAndHandleErrors(false, false, lastRemotingTask);
} finally {
lastRemotingTask = null;
}
@ -529,6 +543,7 @@ public class SessionImpl implements Session {
if (sessionState == SessionState.DISCONNECTING || sessionState == SessionState.CONNECTING) {
sessionState = SessionState.DISCONNECTED;
serverState = null;
logger.info("Disconnecting DONE");
if (askForReconnect) {
client.showError("Network error. You have been disconnected from " + connection.getHost());
@ -1654,7 +1669,10 @@ public class SessionImpl implements Session {
@Override
public boolean ping() {
try {
if (isConnected() && sessionId != null) {
// ping must work after login only, all other actions are single call (example: register new user)
// sessionId fills on connection
// serverState fills on good login
if (isConnected() && sessionId != null && serverState != null) {
long startTime = System.nanoTime();
if (!server.ping(sessionId, pingInfo)) {
logger.error("Ping failed: " + this.getUserName() + " Session: " + sessionId + " to MAGE server at " + connection.getHost() + ':' + connection.getPort());

View file

@ -17,9 +17,11 @@ public class AbilityPickerView implements Serializable {
private static final long serialVersionUID = 1L;
private Map<UUID, String> choices = new LinkedHashMap<>();
private String message = null;
private String message;
private GameView gameView;
public AbilityPickerView(String objectName, List<? extends Ability> abilities, String message) {
public AbilityPickerView(GameView gameView, String objectName, List<? extends Ability> abilities, String message) {
this.gameView = gameView;
this.message = message;
int num = 0;
@ -44,6 +46,12 @@ public class AbilityPickerView implements Serializable {
}
}
public AbilityPickerView(GameView gameView, Map<UUID, String> modes, String message) {
this.gameView = gameView;
this.choices = modes;
this.message = message;
}
private String getAbilityRules(Ability ability, String objectName) {
String rule = ability.getRule(objectName);
if (rule.isEmpty()) {
@ -55,11 +63,6 @@ public class AbilityPickerView implements Serializable {
return rule;
}
public AbilityPickerView(Map<UUID, String> modes, String message) {
this.choices = modes;
this.message = message;
}
public Map<UUID, String> getChoices() {
return choices;
}
@ -67,4 +70,8 @@ public class AbilityPickerView implements Serializable {
public String getMessage() {
return message;
}
public GameView getGameView() {
return gameView;
}
}

View file

@ -24,7 +24,7 @@ public class GameClientMessage implements Serializable {
@Expose
private GameView gameView;
@Expose
private CardsView cardsView;
private CardsView cardsView1;
@Expose
private CardsView cardsView2;
@Expose
@ -32,8 +32,6 @@ public class GameClientMessage implements Serializable {
@Expose
private boolean flag;
@Expose
private String[] strings;
@Expose
private Set<UUID> targets;
@Expose
private int min;
@ -46,64 +44,53 @@ public class GameClientMessage implements Serializable {
@Expose
private List<String> messages;
public GameClientMessage(GameView gameView) {
public GameClientMessage(GameView gameView, Map<String, Serializable> options) {
this.gameView = gameView;
}
public GameClientMessage(GameView gameView, String message) {
this.gameView = gameView;
this.message = message;
}
public GameClientMessage(GameView gameView, String message, Map<String, Serializable> options) {
this.gameView = gameView;
this.message = message;
this.options = options;
}
private GameClientMessage(GameView gameView, String question, CardsView cardView, Set<UUID> targets, boolean required) {
public GameClientMessage(GameView gameView, Map<String, Serializable> options, String message) {
this.gameView = gameView;
this.message = question;
this.cardsView = cardView;
this.options = options;
this.message = message;
}
public GameClientMessage(GameView gameView, Map<String, Serializable> options, String message, CardsView cardsView1, Set<UUID> targets, boolean required) {
this.gameView = gameView;
this.options = options;
this.message = message;
this.cardsView1 = cardsView1;
this.targets = targets;
this.flag = required;
}
public GameClientMessage(GameView gameView, String question, CardsView cardView, Set<UUID> targets, boolean required, Map<String, Serializable> options) {
this(gameView, question, cardView, targets, required);
public GameClientMessage(GameView gameView, Map<String, Serializable> options, String message, int min, int max) {
this.gameView = gameView;
this.options = options;
}
public GameClientMessage(String[] choices, String message) {
this.strings = choices;
this.message = message;
}
public GameClientMessage(String message, int min, int max) {
this.message = message;
this.min = min;
this.max = max;
}
public GameClientMessage(String message, CardsView pile1, CardsView pile2) {
public GameClientMessage(GameView gameView, Map<String, Serializable> options, String message, CardsView pile1, CardsView pile2) {
this.gameView = gameView;
this.options = options;
this.message = message;
this.cardsView = pile1;
this.cardsView1 = pile1;
this.cardsView2 = pile2;
}
public GameClientMessage(CardsView cardView, String name) {
this.cardsView = cardView;
this.message = name;
}
public GameClientMessage(List<String> messages, int min, int max, Map<String, Serializable> options) {
public GameClientMessage(GameView gameView, Map<String, Serializable> options, List<String> messages, int min, int max) {
this.gameView = gameView;
this.options = options;
this.messages = messages;
this.min = min;
this.max = max;
this.options = options;
}
public GameClientMessage(Choice choice) {
public GameClientMessage(GameView gameView, Map<String, Serializable> options, Choice choice) {
this.gameView = gameView;
this.options = options;
this.choice = choice;
}
@ -111,8 +98,12 @@ public class GameClientMessage implements Serializable {
return gameView;
}
public CardsView getCardsView() {
return cardsView;
public CardsView getCardsView1() {
return cardsView1;
}
public CardsView getCardsView2() {
return cardsView2;
}
public String getMessage() {
@ -123,22 +114,10 @@ public class GameClientMessage implements Serializable {
return flag;
}
public String[] getStrings() {
return strings;
}
public Set<UUID> getTargets() {
return targets;
}
public CardsView getPile1() {
return cardsView;
}
public CardsView getPile2() {
return cardsView2;
}
public int getMin() {
return min;
}

View file

@ -22,11 +22,6 @@
<artifactId>mage-common</artifactId>
<version>${mage-version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View file

@ -24,11 +24,6 @@
<artifactId>swingx</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View file

@ -22,7 +22,7 @@ public class AusHighlander extends Constructed {
pointMap.put("Black Lotus", 4);
pointMap.put("Time Vault", 4);
pointMap.put("Demonic Tutor", 3);
pointMap.put("Imperial Seal", 3);
pointMap.put("Mana Crypt", 3);
pointMap.put("Mox Emerald", 3);
pointMap.put("Mox Jet", 3);
pointMap.put("Mox Pearl", 3);
@ -30,11 +30,12 @@ public class AusHighlander extends Constructed {
pointMap.put("Mox Sapphire", 3);
pointMap.put("Sol Ring", 3);
pointMap.put("Thassa's Oracle", 3);
pointMap.put("Underworld Breach", 3);
pointMap.put("Vampiric Tutor", 3);
pointMap.put("Channel", 2);
pointMap.put("Dig Through Time", 2);
pointMap.put("Flash", 2);
pointMap.put("Mana Crypt", 2);
pointMap.put("Imperial Seal", 2);
pointMap.put("Mind Twist", 2);
pointMap.put("Mystical Tutor", 2);
pointMap.put("Oko, Thief of Crowns", 2);
@ -46,11 +47,12 @@ public class AusHighlander extends Constructed {
pointMap.put("Balance", 1);
pointMap.put("Birthing Pod", 1);
pointMap.put("Crop Rotation", 1);
pointMap.put("Dark Petition", 1);
pointMap.put("Deathrite Shaman", 1);
pointMap.put("Doomsday", 1);
pointMap.put("Enlightened Tutor", 1);
pointMap.put("Fastbond", 1);
pointMap.put("Force of Will", 1);
pointMap.put("Gifts Ungiven", 1);
pointMap.put("Green Sun's Zenith", 1);
pointMap.put("Hermit Druid", 1);
pointMap.put("Intuition", 1);
@ -77,7 +79,7 @@ public class AusHighlander extends Constructed {
pointMap.put("Timetwister", 1);
pointMap.put("Tolarian Academy", 1);
pointMap.put("Umezawa's Jitte", 1);
pointMap.put("Underworld Breach", 1);
pointMap.put("Uro, Titan of Nature's Wrath", 1);
pointMap.put("Wasteland", 1);
pointMap.put("Wishclaw Talisman", 1);
pointMap.put("Wrenn and Six", 1);

View file

@ -40,15 +40,16 @@ public class CanadianHighlander extends Constructed {
pointMap.put("Mox Ruby", 3);
pointMap.put("Mox Sapphire", 3);
pointMap.put("Mystical Tutor", 2);
pointMap.put("Natural Order", 4);
pointMap.put("Natural Order", 3);
pointMap.put("Price of Progress", 1);
pointMap.put("Protean Hulk", 3);
pointMap.put("Protean Hulk", 2);
pointMap.put("Sol Ring", 4);
pointMap.put("Spellseeker", 2);
pointMap.put("Strip Mine", 3);
pointMap.put("Summoner's Pact", 1);
pointMap.put("Survival of the Fittest", 2);
pointMap.put("Tainted Pact", 1);
pointMap.put("Thassa's Oracle", 2);
pointMap.put("Time Vault", 7);
pointMap.put("Time Walk", 7);
pointMap.put("Tinker", 3);
@ -56,8 +57,8 @@ public class CanadianHighlander extends Constructed {
pointMap.put("Transmute Artifact", 1);
pointMap.put("Treasure Cruise", 1);
pointMap.put("True-Name Nemesis", 1);
pointMap.put("Umezawa's Jitte", 2);
pointMap.put("Underworld Breach", 1);
pointMap.put("Umezawa's Jitte", 1);
pointMap.put("Underworld Breach", 2);
pointMap.put("Vampiric Tutor", 2);
pointMap.put("Wishclaw Talisman", 1);
pointMap.put("Yawgmoth's Will", 2);

View file

@ -46,6 +46,7 @@ public class Commander extends Constructed {
banned.add("Fastbond");
banned.add("Flash");
banned.add("Gifts Ungiven");
banned.add("Golos, Tireless Pilgrim");
banned.add("Griselbrand");
banned.add("Hullbreacher");
banned.add("Iona, Shield of Emeria");
@ -74,7 +75,6 @@ public class Commander extends Constructed {
banned.add("Tolarian Academy");
banned.add("Trade Secrets");
banned.add("Upheaval");
banned.add("Worldfire");
banned.add("Yawgmoth's Bargain");
}
@ -586,13 +586,7 @@ public class Commander extends Constructed {
}
if (card.isPlaneswalker()) {
if (card.getName().toLowerCase(Locale.ENGLISH).equals("jace, the mind sculptor")) {
thisMaxPower = Math.max(thisMaxPower, 6);
}
if (card.getName().toLowerCase(Locale.ENGLISH).equals("ugin, the spirit dragon")) {
thisMaxPower = Math.max(thisMaxPower, 5);
}
thisMaxPower = Math.max(thisMaxPower, 4);
thisMaxPower = Math.max(thisMaxPower, 6);
}
String cn = card.getName().toLowerCase(Locale.ENGLISH);
@ -673,7 +667,7 @@ public class Commander extends Constructed {
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("winter orb")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 5);
thisMaxPower = Math.max(thisMaxPower, 12);
}
// Parts of infinite combos
@ -734,9 +728,151 @@ public class Commander extends Constructed {
|| cn.equals("workhorse") || cn.equals("worldgorger dragon")
|| cn.equals("worthy cause") || cn.equals("yawgmoth's will")
|| cn.equals("zealous conscripts")) {
thisMaxPower = Math.max(thisMaxPower, 12);
thisMaxPower = Math.max(thisMaxPower, 15);
numberInfinitePieces++;
}
// Saltiest cards (edhrec)
if (cn.equals("acid rain")
|| cn.equals("agent of treachery")
|| cn.equals("apocalypse")
|| cn.equals("armageddon")
|| cn.equals("atraxa, praetors' voice")
|| cn.equals("aura shards")
|| cn.equals("avacyn, angel of hope")
|| cn.equals("back to basics")
|| cn.equals("bend or break")
|| cn.equals("blightsteel colossus")
|| cn.equals("blood moon")
|| cn.equals("boil")
|| cn.equals("boiling seas")
|| cn.equals("bribery")
|| cn.equals("burning sands")
|| cn.equals("card view")
|| cn.equals("cataclysm")
|| cn.equals("catastrophe")
|| cn.equals("chulane, teller of tales")
|| cn.equals("confusion in the ranks")
|| cn.equals("consecrated sphinx")
|| cn.equals("contamination")
|| cn.equals("craterhoof behemoth")
|| cn.equals("cyclonic rift")
|| cn.equals("death cloud")
|| cn.equals("decree of annihilation")
|| cn.equals("decree of silence")
|| cn.equals("demonic consultation")
|| cn.equals("derevi, empyrial tactician")
|| cn.equals("devastation")
|| cn.equals("divine intervention")
|| cn.equals("dockside extortionist")
|| cn.equals("doomsday")
|| cn.equals("doubling season")
|| cn.equals("drannith magistrate")
|| cn.equals("elesh norn, grand cenobite")
|| cn.equals("embargo")
|| cn.equals("emrakul, the promised end")
|| cn.equals("epicenter")
|| cn.equals("expropriate")
|| cn.equals("fall of the thran")
|| cn.equals("fierce guardianship")
|| cn.equals("food chain")
|| cn.equals("force of negation")
|| cn.equals("force of will")
|| cn.equals("gaddock teeg")
|| cn.equals("gaea's cradle")
|| cn.equals("gilded drake")
|| cn.equals("glenn, the voice of calm")
|| cn.equals("global ruin")
|| cn.equals("golos, tireless pilgrim")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("grip of chaos")
|| cn.equals("hokori, dust drinker")
|| cn.equals("humility")
|| cn.equals("impending disaster")
|| cn.equals("invoke prejudice")
|| cn.equals("iona, shield of emeria")
|| cn.equals("jin-gitaxias, core augur")
|| cn.equals("jokulhaups")
|| cn.equals("keldon firebombers")
|| cn.equals("kinnan, bonder prodigy")
|| cn.equals("kozilek, butcher of truth")
|| cn.equals("land equilibrium")
|| cn.equals("linvala, keeper of silence")
|| cn.equals("magister sphinx")
|| cn.equals("mana breach")
|| cn.equals("mana crypt")
|| cn.equals("mana drain")
|| cn.equals("mana vortex")
|| cn.equals("mindslaver")
|| cn.equals("narset, enlightened master")
|| cn.equals("narset, parter of veils")
|| cn.equals("negan, the cold-blooded")
|| cn.equals("nether void")
|| cn.equals("nexus of fate")
|| cn.equals("notion thief")
|| cn.equals("obliterate")
|| cn.equals("oko, thief of crowns")
|| cn.equals("oloro, ageless ascetic")
|| cn.equals("omniscience")
|| cn.equals("opposition agent")
|| cn.equals("oppression")
|| cn.equals("overwhelming splendor")
|| cn.equals("palinchron")
|| cn.equals("paradox engine")
|| cn.equals("possessed portal")
|| cn.equals("price of glory")
|| cn.equals("protean hulk")
|| cn.equals("ravages of war")
|| cn.equals("rhystic study")
|| cn.equals("rick, steadfast leader")
|| cn.equals("rising waters")
|| cn.equals("ruination")
|| cn.equals("scrambleverse")
|| cn.equals("seedborn muse")
|| cn.equals("sen triplets")
|| cn.equals("sire of insanity")
|| cn.equals("skithiryx, the blight dragon")
|| cn.equals("smokestack")
|| cn.equals("smothering tithe")
|| cn.equals("sorin markov")
|| cn.equals("stasis")
|| cn.equals("static orb")
|| cn.equals("storage matrix")
|| cn.equals("sunder")
|| cn.equals("survival of the fittest")
|| cn.equals("table view")
|| cn.equals("tainted aether")
|| cn.equals("tectonic break")
|| cn.equals("teferi's protection")
|| cn.equals("teferi, master of time")
|| cn.equals("teferi, time raveler")
|| cn.equals("temporal manipulation")
|| cn.equals("tergrid, god of fright")
|| cn.equals("text view")
|| cn.equals("thassa's oracle")
|| cn.equals("the tabernacle at pendrell vale")
|| cn.equals("thieves' auction")
|| cn.equals("thoughts of ruin")
|| cn.equals("thrasios, triton hero")
|| cn.equals("time stretch")
|| cn.equals("time warp")
|| cn.equals("tooth and nail")
|| cn.equals("torment of hailfire")
|| cn.equals("torpor orb")
|| cn.equals("triumph of the hordes")
|| cn.equals("ugin, the spirit dragon")
|| cn.equals("ulamog, the ceaseless hunger")
|| cn.equals("ulamog, the infinite gyre")
|| cn.equals("urza, lord high artificer")
|| cn.equals("void winnower")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("wake of destruction")
|| cn.equals("warp world")
|| cn.equals("winter orb")
|| cn.equals("xanathar, guild kingpin")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 15);
}
edhPowerLevel += thisMaxPower;
}
@ -769,11 +905,17 @@ public class Commander extends Constructed {
// Least fun commanders
if (cn.equals("animar, soul of element")
|| cn.equals("anafenza, the foremost")
|| cn.equals("arcum dagsson")
|| cn.equals("azami, lady of scrolls")
|| cn.equals("azusa, lost but seeking")
|| cn.equals("brago, king eternal")
|| cn.equals("braids, cabal minion")
|| cn.equals("captain sisay")
|| cn.equals("child of alara")
|| cn.equals("derevi, empyrial tactician")
|| cn.equals("edric, spymaster of trest")
|| cn.equals("elesh norn, grand cenobite")
|| cn.equals("gaddock teeg")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("hokori, dust drinker")
@ -784,41 +926,67 @@ public class Commander extends Constructed {
|| cn.equals("leovold, emissary of trest")
|| cn.equals("linvala, keeper of silence")
|| cn.equals("llawan, cephalid empress")
|| cn.equals("maelstrom wanderer")
|| cn.equals("malfegor")
|| cn.equals("memnarch")
|| cn.equals("meren of clan nel toth")
|| cn.equals("michiko konda, truth seeker")
|| cn.equals("mikaeus the unhallowed")
|| cn.equals("narset, enlightened master")
|| cn.equals("nath of the gilt-leaf")
|| cn.equals("nekusar, the mindrazer")
|| cn.equals("norin the wary")
|| cn.equals("numot, the devastator")
|| cn.equals("prossh, skyraider of kher")
|| cn.equals("purphoros, god of the forge")
|| cn.equals("sen triplets")
|| cn.equals("sheoldred, whispering one")
|| cn.equals("teferi, mage of zhalfir")
|| cn.equals("urabrask the hidden")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 25);
}
// Next least fun commanders
if (cn.equals("anafenza, the foremost")
|| cn.equals("arcum dagsson")
|| cn.equals("azusa, lost but seeking")
|| cn.equals("brago, king eternal")
|| cn.equals("captain sisay")
// Saltiest commanders
if (cn.equals("atraxa, praetors' voice")
|| cn.equals("avacyn, angel of hope")
|| cn.equals("chulane, teller of tales")
|| cn.equals("derevi, empyrial tactician")
|| cn.equals("elesh norn, grand cenobite")
|| cn.equals("malfegor")
|| cn.equals("maelstrom wanderer")
|| cn.equals("mikaeus the unhallowed")
|| cn.equals("nath of the gilt-leaf")
|| cn.equals("prossh, skyraider of kher")
|| cn.equals("purphoros, god of the forge")
|| cn.equals("emrakul, the promised end")
|| cn.equals("gaddock teeg")
|| cn.equals("glenn, the voice of calm")
|| cn.equals("golos, tireless pilgrim")
|| cn.equals("grand arbiter augustin iv")
|| cn.equals("hokori, dust drinker")
|| cn.equals("iona, shield of emeria")
|| cn.equals("jin-gitaxias, core augur")
|| cn.equals("kinnan, bonder prodigy")
|| cn.equals("kozilek, butcher of truth")
|| cn.equals("linvala, keeper of silence")
|| cn.equals("narset, enlightened master")
|| cn.equals("negan, the cold-blooded")
|| cn.equals("oko, thief of crowns")
|| cn.equals("oloro, ageless ascetic")
|| cn.equals("rick, steadfast leader")
|| cn.equals("sen triplets")
|| cn.equals("urabrask the hidden")
|| cn.equals("vorinclex, voice of hunger")) {
thisMaxPower = Math.max(thisMaxPower, 15);
|| cn.equals("skithiryx, the blight dragon")
|| cn.equals("teferi, master of time")
|| cn.equals("teferi, time raveler")
|| cn.equals("thrasios, triton hero")
|| cn.equals("ulamog, the ceaseless hunger")
|| cn.equals("ulamog, the infinite gyre")
|| cn.equals("urza, lord high artificer")
|| cn.equals("vorinclex, voice of hunger")
|| cn.equals("xanathar, guild kingpin")
|| cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 20);
}
edhPowerLevel += thisMaxPower;
}
edhPowerLevel += numberInfinitePieces * 12;
edhPowerLevel += numberInfinitePieces * 18;
edhPowerLevel = Math.round(edhPowerLevel / 10);
if (edhPowerLevel >= 100) {
edhPowerLevel = 99;

View file

@ -36,8 +36,9 @@ public class DuelCommander extends Commander {
banned.add("Karakas");
banned.add("Library of Alexandria");
banned.add("Lion's Eye Diamond");
banned.add("Lutri, The Spellchaser");
banned.add("Loyal Retainers");
banned.add("Lutri, the Spellchaser");
banned.add("Maddening Hex");
banned.add("Mana Crypt");
banned.add("Mana Drain");
banned.add("Mana Vault");
@ -77,6 +78,7 @@ public class DuelCommander extends Commander {
bannedCommander.add("Akiri, Line-Slinger");
bannedCommander.add("Arahbo, Roar of the World");
bannedCommander.add("Ardenn, Intrepid Archaeologist");
bannedCommander.add("Asmoranomardicadaistinaculdacar");
bannedCommander.add("Baral, Chief of Compliance");
bannedCommander.add("Breya, Etherium Shaper");
bannedCommander.add("Bruse Tarl, Boorish Herder");
@ -106,6 +108,7 @@ public class DuelCommander extends Commander {
bannedCommander.add("Tymna, the Weaver");
bannedCommander.add("Urza, Lord High Artificer");
bannedCommander.add("Vial Smasher the Fierce");
bannedCommander.add("Winota, Joiner of Forces");
bannedCommander.add("Yuriko, the Tiger's Shadow");
bannedCommander.add("Zurgo Bellstriker");
}

View file

@ -25,7 +25,6 @@ public class Oathbreaker extends Vintage {
setName("Oathbreaker");
// banned = vintage + oathbreaker's list: https://oathbreakermtg.org/banned-list/
// last updated 4/24/20 - Dark Ritual banned
banned.add("Ad Nauseam");
banned.add("Ancestral Recall");
banned.add("Balance");
@ -77,7 +76,6 @@ public class Oathbreaker extends Vintage {
banned.add("Tooth and Nail");
banned.add("Trade Secrets");
banned.add("Upheaval");
banned.add("Worldfire");
banned.add("Yawgmoth's Bargain");
}

View file

@ -5,7 +5,11 @@ import mage.cards.Sets;
import mage.cards.decks.Constructed;
import mage.constants.SetType;
import java.util.*;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author BetaSteward_at_googlemail.com
@ -17,61 +21,39 @@ public class Standard extends Constructed {
setCodes.addAll(makeLegalSets());
banned.add("Agent of Treachery");
banned.add("Cauldron Familiar");
banned.add("Escape to the Wilds");
banned.add("Field of the Dead");
banned.add("Fires of Invention");
banned.add("Growth Spiral");
banned.add("Lucky Clover");
banned.add("Oko, Thief of Crowns");
banned.add("Omnath, Locus of Creation");
banned.add("Once Upon a Time");
banned.add("Teferi, Time Raveler");
banned.add("Uro, Titan of Nature's Wrath");
banned.add("Wilderness Reclamation");
banned.add("Veil of Summer");
}
private static boolean isFallSet(ExpansionSet set) {
Calendar cal = Calendar.getInstance();
cal.setTime(set.getReleaseDate());
// Fall sets are normally released during or after September
return set.getSetType() == SetType.EXPANSION && (cal.get(Calendar.MONTH) > 7);
// Fall sets are normally released during or after September and before November
return set.getSetType() == SetType.EXPANSION
&& Calendar.SEPTEMBER <= cal.get(Calendar.MONTH)
&& cal.get(Calendar.MONTH) < Calendar.NOVEMBER;
}
static List<String> makeLegalSets() {
List<String> codes = new ArrayList<>();
GregorianCalendar current = new GregorianCalendar();
List<ExpansionSet> sets = new ArrayList(Sets.getInstance().values());
Collections.sort(sets, new Comparator<ExpansionSet>() {
@Override
public int compare(final ExpansionSet lhs, ExpansionSet rhs) {
return lhs.getReleaseDate().after(rhs.getReleaseDate()) ? -1 : 1;
}
});
int fallSetsAdded = 0;
Date earliestDate = null;
// Get the second most recent fall set that's been released.
for (ExpansionSet set : sets) {
if (set.getReleaseDate().after(current.getTime())) {
continue;
}
if (isFallSet(set)) {
fallSetsAdded++;
if (fallSetsAdded == 2) {
earliestDate = set.getReleaseDate();
break;
}
}
}
for (ExpansionSet set : sets) {
boolean isDateCompatible = earliestDate != null && !set.getReleaseDate().before(earliestDate) /*!set.getReleaseDate().after(current.getTime())*/; // no after date restrict for early tests and beta
if (set.getSetType().isStandardLegal() && isDateCompatible) {
codes.add(set.getCode());
}
}
return codes;
Date earliestDate = Sets
.getInstance()
.values()
.stream()
.filter(set -> !set.getReleaseDate().after(current.getTime()))
.filter(Standard::isFallSet)
.sorted(ExpansionSet.getComparator())
.skip(1)
.findFirst()
.get()
.getReleaseDate();
return Sets.getInstance()
.values()
.stream()
.filter(set -> set.getSetType().isStandardLegal())
.filter(set -> !set.getReleaseDate().before(earliestDate))
// .filter(set -> !set.getReleaseDate().after(current.getTime())) // no after date restrict for early tests and beta
.map(ExpansionSet::getCode)
.collect(Collectors.toList());
}
}

View file

@ -28,12 +28,12 @@ public class TinyLeaders extends Constructed {
}
}
//Banned list from tinyleaders.blodspot.ca/p/ban-list.html
//Ban list updated as of 11/08/14
banned.add("Ancestral Recall");
banned.add("Balance");
banned.add("Black Lotus");
banned.add("Black Vise");
banned.add("Channel");
banned.add("Codie, Vociferous Codex");
banned.add("Counterbalance");
banned.add("Demonic Tutor");
banned.add("Earthcraft");
@ -57,14 +57,16 @@ public class TinyLeaders extends Constructed {
banned.add("Mox Pearl");
banned.add("Mox Ruby");
banned.add("Mox Sapphire");
banned.add("Najeela, the Blade Blossom");
banned.add("Najeela, the Blade-Blossom");
banned.add("Necropotence");
banned.add("Shahrazad");
banned.add("Sisay, Weatherlight Captain");
banned.add("Skullclamp");
banned.add("Sol Ring");
banned.add("Strip Mine");
banned.add("Survival of the Fittest");
banned.add("Sword of Body and Mind");
banned.add("Thassa's Oracle");
banned.add("The Tabernacle at Pendrell Vale");
banned.add("Time Vault");
banned.add("Time Walk");

View file

@ -162,13 +162,12 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
boolean usedStack = false;
while (actions.peek() != null) {
Ability ability = actions.poll();
// log example: ===> Act [PlayerA] Action: Cast Blessings of Nature (target 1; target 2)
logger.info(new StringBuilder("===> Act [")
.append(game.getPlayer(playerId).getName())
.append("] Action: ")
.append(ability.toString())
.append(listTargets(game, ability.getTargets(), " (targeting %s)", ""))
.toString());
// example: ===> SELECTED ACTION for PlayerA: Play Swamp
logger.info(String.format("===> SELECTED ACTION for %s: %s",
getName(),
ability.toString()
+ listTargets(game, ability.getTargets(), " (targeting %s)", "")
));
if (!ability.getTargets().isEmpty()) {
for (Target target : ability.getTargets()) {
for (UUID id : target.getTargets()) {
@ -391,6 +390,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
protected void resolve(SimulationNode2 node, int depth, Game game) {
StackObject stackObject = game.getStack().getFirst();
if (stackObject instanceof StackAbility) {
// AI hint for search effects (calc all possible cards for best score)
SearchEffect effect = getSearchEffect((StackAbility) stackObject);
if (effect != null
&& stackObject.getControllerId().equals(playerId)) {
@ -477,15 +477,24 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
SimulationNode2 bestNode = null;
List<Ability> allActions = currentPlayer.simulatePriority(game);
optimize(game, allActions);
int startedScore = GameStateEvaluator2.evaluate(this.getId(), node.getGame()).getTotalScore();
if (logger.isInfoEnabled()
&& !allActions.isEmpty()
&& depth == maxDepth) {
logger.info("ADDED ACTIONS (" + allActions.size() + ") " + ' ' + allActions);
logger.info(String.format("POSSIBLE ACTIONS for %s (%d, started score: %d)%s",
getName(),
allActions.size(),
startedScore,
(actions.isEmpty() ? "" : ":")
));
for (int i = 0; i < allActions.size(); i++) {
logger.info(String.format("-> #%d (%s)", i + 1, allActions.get(i)));
}
}
int counter = 0;
int actionNumber = 0;
int bestValSubNodes = Integer.MIN_VALUE;
for (Ability action : allActions) {
counter++;
actionNumber++;
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
&& Thread.interrupted()) {
Thread.currentThread().interrupt();
@ -503,7 +512,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
}
if (!sim.checkIfGameIsOver()
&& (action.isUsesStack() || action instanceof PassAbility)) {
// only pass if the last action uses the stack
// skip priority for opponents before stack resolve
UUID nextPlayerId = sim.getPlayerList().get();
do {
sim.getPlayer(nextPlayerId).pass(game);
@ -512,48 +521,73 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
}
SimulationNode2 newNode = new SimulationNode2(node, sim, action, depth, currentPlayer.getId());
sim.checkStateAndTriggered();
int val;
int actionScore;
if (action instanceof PassAbility && sim.getStack().isEmpty()) {
// Stop to simulate deeper if PassAbility and stack is empty
val = GameStateEvaluator2.evaluate(this.getId(), sim).getTotalScore();
// no more next actions, it's a final score
actionScore = GameStateEvaluator2.evaluate(this.getId(), sim).getTotalScore();
} else {
val = addActions(newNode, depth - 1, alpha, beta);
// resolve current action and calc all next actions to find best score (return max possible score)
actionScore = addActions(newNode, depth - 1, alpha, beta);
}
logger.debug("Sim Prio " + BLANKS.substring(0, 2 + (maxDepth - depth) * 3) + '[' + depth + "]#" + counter + " <" + val + "> - (" + action + ") ");
logger.debug("Sim Prio " + BLANKS.substring(0, 2 + (maxDepth - depth) * 3) + '[' + depth + "]#" + actionNumber + " <" + actionScore + "> - (" + action + ") ");
// Hints on data:
// * node - started game with executed command (pay and put on stack)
// * newNode - resolved game with resolved command (resolve stack)
// * node.children - rewrites to store only best tree (e.g. contains only final data)
// * node.score - rewrites to store max score (e.g. contains only final data)
if (logger.isInfoEnabled()
&& depth >= maxDepth) {
StringBuilder sb = new StringBuilder("Sim Prio [").append(depth).append("] #").append(counter)
.append(" <").append(val).append("> (").append(action)
.append(action.isModal() ? " Mode = " + action.getModes().getMode().toString() : "")
.append(listTargets(game, action.getTargets(), " (targeting %s)", "")).append(')')
.append(logger.isTraceEnabled() ? " #" + newNode.hashCode() : "");
// show calculated actions and score
// example: Sim Prio [6] #1 <605> (Play Swamp)
int currentActionScore = GameStateEvaluator2.evaluate(this.getId(), newNode.getGame()).getTotalScore();
int diffCurrentAction = currentActionScore - startedScore;
int diffNextActions = actionScore - startedScore - diffCurrentAction;
logger.info(String.format("Sim Prio [%d] #%d <diff %s, %s> (%s)",
depth,
actionNumber,
printDiffScore(diffCurrentAction),
printDiffScore(diffNextActions),
action
+ (action.isModal() ? " Mode = " + action.getModes().getMode().toString() : "")
+ listTargets(game, action.getTargets(), " (targeting %s)", "")
+ (logger.isTraceEnabled() ? " #" + newNode.hashCode() : "")
));
// collect childs info (next actions chain)
SimulationNode2 logNode = newNode;
while (logNode.getChildren() != null
&& !logNode.getChildren().isEmpty()) {
logNode = logNode.getChildren().get(0);
if (logNode.getAbilities() != null
&& !logNode.getAbilities().isEmpty()) {
sb.append(" -> [").append(logNode.getDepth()).append(']').append(logNode.getAbilities().toString()).append('<').append(logNode.getScore()).append('>');
int logCurrentScore = GameStateEvaluator2.evaluate(this.getId(), logNode.getGame()).getTotalScore();
int logPrevScore = GameStateEvaluator2.evaluate(this.getId(), logNode.getParent().getGame()).getTotalScore();
logger.info(String.format("Sim Prio [%d] -> next action: [%d]%s <diff %s, %s>",
depth,
logNode.getDepth(),
logNode.getAbilities().toString(),
printDiffScore(logCurrentScore - logPrevScore),
printDiffScore(actionScore - logCurrentScore)
));
}
}
logger.info(sb);
}
if (currentPlayer.getId().equals(playerId)) {
if (val > bestValSubNodes) {
bestValSubNodes = val;
if (actionScore > bestValSubNodes) {
bestValSubNodes = actionScore;
}
if (depth == maxDepth
&& action instanceof PassAbility) {
val = val - PASSIVITY_PENALTY; // passivity penalty
actionScore = actionScore - PASSIVITY_PENALTY; // passivity penalty
}
if (val > alpha
if (actionScore > alpha
|| (depth == maxDepth
&& val == alpha
&& actionScore == alpha
&& RandomUtil.nextBoolean())) { // Adding random for equal value to get change sometimes
alpha = val;
alpha = actionScore;
bestNode = newNode;
bestNode.setScore(val);
bestNode.setScore(actionScore);
if (!newNode.getChildren().isEmpty()) {
bestNode.setCombat(newNode.getChildren().get(0).getCombat());
}
@ -564,7 +598,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
.stream()
.map(a -> a.toString() + listTargets(game, a.getTargets(), " (targeting %s)", ""))
.collect(Collectors.joining("; "));
logger.info("Sim Prio [" + depth + "] -- Saved best node yet <" + bestNode.getScore() + scoreInfo + "> " + abilitiesInfo);
logger.info("Sim Prio [" + depth + "] >> BEST action chain found <" + bestNode.getScore() + scoreInfo + "> " + abilitiesInfo);
node.children.clear();
node.children.add(bestNode);
node.setScore(bestNode.getScore());
@ -572,22 +606,22 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
}
// no need to check other actions
if (val == GameStateEvaluator2.WIN_GAME_SCORE) {
if (actionScore == GameStateEvaluator2.WIN_GAME_SCORE) {
logger.debug("Sim Prio -- win - break");
break;
}
} else {
if (val < beta) {
beta = val;
if (actionScore < beta) {
beta = actionScore;
bestNode = newNode;
bestNode.setScore(val);
bestNode.setScore(actionScore);
if (!newNode.getChildren().isEmpty()) {
bestNode.setCombat(newNode.getChildren().get(0).getCombat());
}
}
// no need to check other actions
if (val == GameStateEvaluator2.LOSE_GAME_SCORE) {
if (actionScore == GameStateEvaluator2.LOSE_GAME_SCORE) {
logger.debug("Sim Prio -- lose - break");
break;
}
@ -622,6 +656,14 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
}
}
private String printDiffScore(int score) {
if (score >= 0) {
return "+" + score;
} else {
return "" + score;
}
}
/**
* Various AI optimizations for actions.
*

View file

@ -15,11 +15,6 @@
<name>Mage Player AI</name>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<type>jar</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage</artifactId>

View file

@ -540,6 +540,58 @@ public class ComputerPlayer extends PlayerImpl implements Player {
return setTargetPlayer(outcome, target, source, sourceId, abilityControllerId, randomOpponentId, game, required);
}
// Angel of Serenity trigger
if (target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) {
Cards cards = new CardsImpl(possibleTargets);
List<Card> possibleCards = new ArrayList<>(cards.getCards(game));
for (Card card : possibleCards) {
// check permanents first; they have more intrinsic worth
if (card instanceof Permanent) {
Permanent p = ((Permanent) card);
if (outcome.isGood()
&& p.isControlledBy(abilityControllerId)) {
if (target.canTarget(abilityControllerId, p.getId(), source, game)) {
if (target.getTargets().size() >= target.getMaxNumberOfTargets()) {
break;
}
target.addTarget(p.getId(), source, game);
}
}
if (!outcome.isGood()
&& !p.isControlledBy(abilityControllerId)) {
if (target.canTarget(abilityControllerId, p.getId(), source, game)) {
if (target.getTargets().size() >= target.getMaxNumberOfTargets()) {
break;
}
target.addTarget(p.getId(), source, game);
}
}
}
// check the graveyards last
if (game.getState().getZone(card.getId()) == Zone.GRAVEYARD) {
if (outcome.isGood()
&& card.isOwnedBy(abilityControllerId)) {
if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
if (target.getTargets().size() >= target.getMaxNumberOfTargets()) {
break;
}
target.addTarget(card.getId(), source, game);
}
}
if (!outcome.isGood()
&& !card.isOwnedBy(abilityControllerId)) {
if (target.canTarget(abilityControllerId, card.getId(), source, game)) {
if (target.getTargets().size() >= target.getMaxNumberOfTargets()) {
break;
}
target.addTarget(card.getId(), source, game);
}
}
}
}
return target.isChosen();
}
if (target.getOriginalTarget() instanceof TargetDiscard
|| target.getOriginalTarget() instanceof TargetCardInHand) {
if (outcome.isGood()) {
@ -2841,27 +2893,38 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
/**
* Sets a possible target player
* Sets a possible target player. Depends on bad/good outcome
*
* @param source null on choose and non-null on chooseTarget
*/
private boolean setTargetPlayer(Outcome outcome, Target target, Ability source, UUID sourceId, UUID abilityControllerId, UUID randomOpponentId, Game game, boolean required) {
Outcome affectedOutcome;
if (abilityControllerId == this.playerId) {
// selects for itself
affectedOutcome = outcome;
} else {
// selects for another player
affectedOutcome = Outcome.inverse(outcome);
}
if (target.getOriginalTarget() instanceof TargetOpponent) {
if (source == null) {
if (target.canTarget(randomOpponentId, game)) {
target.add(randomOpponentId, game);
return true;
}
} else if (target.canTarget(randomOpponentId, source, game)) {
target.add(randomOpponentId, game);
} else if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.addTarget(randomOpponentId, source, game);
return true;
}
for (UUID currentId : game.getOpponents(abilityControllerId)) {
for (UUID possibleOpponentId : game.getOpponents(abilityControllerId)) {
if (source == null) {
if (target.canTarget(currentId, game)) {
target.add(currentId, game);
if (target.canTarget(possibleOpponentId, game)) {
target.add(possibleOpponentId, game);
return true;
}
} else if (target.canTarget(currentId, source, game)) {
target.add(currentId, game);
} else if (target.canTarget(abilityControllerId, possibleOpponentId, source, game)) {
target.addTarget(possibleOpponentId, source, game);
return true;
}
}
@ -2869,8 +2932,9 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
if (target.getOriginalTarget() instanceof TargetPlayer) {
if (outcome.isGood()) {
if (affectedOutcome.isGood()) {
if (source == null) {
// good
if (target.canTarget(abilityControllerId, game)) {
target.add(abilityControllerId, game);
return true;
@ -2882,19 +2946,20 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
} else {
// good
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) {
target.addTarget(playerId, source, game);
target.addTarget(abilityControllerId, source, game);
return true;
}
if (target.isRequired(sourceId, game)) {
if (target.canTarget(randomOpponentId, game)) {
target.add(randomOpponentId, game);
if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.addTarget(randomOpponentId, source, game);
return true;
}
}
}
} else if (source == null) {
// bad
if (target.canTarget(randomOpponentId, game)) {
target.add(randomOpponentId, game);
return true;
@ -2906,13 +2971,14 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
}
} else {
if (target.canTarget(randomOpponentId, game)) {
target.add(randomOpponentId, game);
// bad
if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.addTarget(randomOpponentId, source, game);
return true;
}
if (required) {
if (target.canTarget(abilityControllerId, game)) {
target.add(abilityControllerId, game);
if (target.canTarget(abilityControllerId, abilityControllerId, source, game)) {
target.addTarget(abilityControllerId, source, game);
return true;
}
}
@ -2950,7 +3016,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
Map<UUID, ActivatedAbility> useable = PlayerImpl.getSpellAbilities(this.getId(), card, game.getState().getZone(card.getId()), game);
Map<UUID, ActivatedAbility> useable = PlayerImpl.getCastableSpellAbilities(game, this.getId(), card, game.getState().getZone(card.getId()), noMana);
return (SpellAbility) useable.values().stream().findFirst().orElse(null);
}

View file

@ -15,11 +15,6 @@
<name>Mage Player AI MCTS</name>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<type>jar</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage</artifactId>

View file

@ -15,11 +15,6 @@
<name>Mage Player AI Minimax</name>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<type>jar</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage</artifactId>

View file

@ -351,6 +351,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
}
protected int simulatePriority(SimulationNode node, Game game, int alpha, int beta) {
// NOT USED in real AI, see ComputerPlayer6
if (Thread.interrupted()) {
Thread.currentThread().interrupt();
logger.debug(indent(node.depth) + "interrupted");

View file

@ -25,11 +25,6 @@
<artifactId>mage-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View file

@ -2174,7 +2174,7 @@ public class HumanPlayer extends PlayerImpl {
}
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean nonMana) {
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
if (gameInCheckPlayableState(game)) {
return null;
}
@ -2186,8 +2186,8 @@ public class HumanPlayer extends PlayerImpl {
MageObject object = game.getObject(card.getId()); // must be object to find real abilities (example: commander)
if (object != null) {
String message = "Choose ability to cast" + (nonMana ? " for FREE" : "") + "<br>" + object.getLogName();
LinkedHashMap<UUID, ActivatedAbility> useableAbilities = getSpellAbilities(playerId, object, game.getState().getZone(object.getId()), game);
String message = "Choose ability to cast" + (noMana ? " for FREE" : "") + "<br>" + object.getLogName();
LinkedHashMap<UUID, ActivatedAbility> useableAbilities = PlayerImpl.getCastableSpellAbilities(game, playerId, object, game.getState().getZone(object.getId()), noMana);
if (useableAbilities != null
&& useableAbilities.size() == 1) {
return (SpellAbility) useableAbilities.values().iterator().next();

View file

@ -19,11 +19,6 @@
<artifactId>mage</artifactId>
<version>${mage-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-common</artifactId>
@ -34,30 +29,6 @@
<artifactId>mage-sets</artifactId>
<version>${mage-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<!-- server xml config needs optional dependency for jaxb-impl -->
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-player-ai</artifactId>
@ -88,12 +59,6 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>[1.19,)</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-game-commanderfreeforall</artifactId>
@ -184,7 +149,6 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-game-freeformcommanderfreeforall</artifactId>
@ -203,7 +167,6 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-game-oathbreakerduel</artifactId>
@ -216,7 +179,6 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-game-momirduel</artifactId>
@ -229,41 +191,57 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<!-- server xml config needs optional dependency for jaxb-impl -->
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>[1.19,)</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.8.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.31.1</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-gmail</artifactId>
<version>v1-rev20210614-1.32.1</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-java6</artifactId>
<version>1.31.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-jetty</artifactId>
<version>1.31.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
@ -281,21 +259,15 @@
<version>1.19.4</version>
</dependency>
<dependency>
<!-- database support - sqlite db engine (additional db for game records and stats) -->
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.32.3.2</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
<groupId>org.unbescape</groupId>
<artifactId>unbescape</artifactId>
</dependency>
</dependencies>

View file

@ -22,9 +22,7 @@ import java.io.File;
import java.sql.SQLException;
import java.util.List;
public enum AuthorizedUserRepository {
instance;
public class AuthorizedUserRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/authorized_user.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "authorized_user";
@ -32,15 +30,20 @@ public enum AuthorizedUserRepository {
private static final long DB_VERSION = 2;
private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator();
private static final AuthorizedUserRepository instance;
static {
instance = new AuthorizedUserRepository(JDBC_URL);
}
private Dao<AuthorizedUser, Object> dao;
AuthorizedUserRepository() {
public AuthorizedUserRepository(String connectionString) {
File file = new File("db");
if (!file.exists()) {
file.mkdirs();
}
try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
ConnectionSource connectionSource = new JdbcConnectionSource(connectionString);
TableUtils.createTableIfNotExists(connectionSource, AuthorizedUser.class);
dao = DaoManager.createDao(connectionSource, AuthorizedUser.class);
} catch (SQLException ex) {
@ -48,6 +51,10 @@ public enum AuthorizedUserRepository {
}
}
public static AuthorizedUserRepository getInstance() {
return instance;
}
public void add(final String userName, final String password, final String email) {
try {
Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024);

View file

@ -128,7 +128,7 @@ public class ChatManagerImpl implements ChatManager {
Matcher matchPattern = cardNamePattern.matcher(message);
while (matchPattern.find()) {
String cardName = matchPattern.group(1);
CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardName, true);
CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardName, true);
if (cardInfo != null) {
String colour = "silver";
if (cardInfo.getCard().getColor(null).isMulticolored()) {
@ -270,7 +270,7 @@ public class ChatManagerImpl implements ChatManager {
Matcher matchPattern = getCardTextPattern.matcher(message.toLowerCase(Locale.ENGLISH));
if (matchPattern.find()) {
String cardName = matchPattern.group(1);
CardInfo cardInfo = CardRepository.instance.findPreferedCoreExpansionCard(cardName, true);
CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardName, true);
if (cardInfo != null) {
cardInfo.getRules();
message = "<font color=orange>" + cardInfo.getName() + "</font>: Cost:" + cardInfo.getManaCosts(CardInfo.ManaCostSide.ALL).toString() + ", Types:" + cardInfo.getTypes().toString() + ", ";

View file

@ -34,8 +34,8 @@ import mage.server.util.SystemUtil;
import mage.utils.*;
import mage.view.*;
import mage.view.ChatMessage.MessageColor;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.unbescape.html.HtmlEscape;
import javax.management.timer.Timer;
import java.security.SecureRandom;
@ -83,15 +83,17 @@ public class MageServerImpl implements MageServer {
@Override
public boolean emailAuthToken(String sessionId, String email) throws MageException {
if (!managerFactory.configSettings().isAuthenticationActivated()) {
sendErrorMessageToClient(sessionId, "Registration is disabled by the server config");
sendErrorMessageToClient(sessionId, Session.REGISTRATION_DISABLED_MESSAGE);
return false;
}
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email);
AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByEmail(email);
if (authorizedUser == null) {
sendErrorMessageToClient(sessionId, "No user was found with the email address " + email);
logger.info("Auth token is requested for " + email + " but there's no such user in DB");
return false;
}
String authToken = generateAuthToken();
activeAuthTokens.put(email, authToken);
String subject = "XMage Password Reset Auth Token";
@ -113,23 +115,31 @@ public class MageServerImpl implements MageServer {
@Override
public boolean resetPassword(String sessionId, String email, String authToken, String password) throws MageException {
if (!managerFactory.configSettings().isAuthenticationActivated()) {
sendErrorMessageToClient(sessionId, "Registration is disabled by the server config");
sendErrorMessageToClient(sessionId, Session.REGISTRATION_DISABLED_MESSAGE);
return false;
}
// multi-step reset:
// - send auth token
// - check auth token to confirm reset
String storedAuthToken = activeAuthTokens.get(email);
if (storedAuthToken == null || !storedAuthToken.equals(authToken)) {
sendErrorMessageToClient(sessionId, "Invalid auth token");
logger.info("Invalid auth token " + authToken + " is sent for " + email);
return false;
}
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email);
AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByEmail(email);
if (authorizedUser == null) {
sendErrorMessageToClient(sessionId, "The user is no longer in the DB");
sendErrorMessageToClient(sessionId, "User with that email doesn't exists");
logger.info("Auth token is valid, but the user with email address " + email + " is no longer in the DB");
return false;
}
AuthorizedUserRepository.instance.remove(authorizedUser.getName());
AuthorizedUserRepository.instance.add(authorizedUser.getName(), password, email);
// recreate user with new password
AuthorizedUserRepository.getInstance().remove(authorizedUser.getName());
AuthorizedUserRepository.getInstance().add(authorizedUser.getName(), password, email);
activeAuthTokens.remove(email);
return true;
}
@ -479,7 +489,7 @@ public class MageServerImpl implements MageServer {
public void sendChatMessage(final UUID chatId, final String userName, final String message) throws MageException {
try {
callExecutor.execute(
() -> managerFactory.chatManager().broadcast(chatId, userName, StringEscapeUtils.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null)
() -> managerFactory.chatManager().broadcast(chatId, userName, HtmlEscape.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null)
);
} catch (Exception ex) {
handleException(ex);
@ -1042,7 +1052,7 @@ public class MageServerImpl implements MageServer {
@Override
public void setActivation(final String sessionId, final String userName, boolean active) throws MageException {
execute("setActivation", sessionId, () -> {
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName);
AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName);
Optional<User> u = managerFactory.userManager().getUserByName(userName);
if (u.isPresent()) {
User user = u.get();

View file

@ -66,7 +66,15 @@ public final class Main {
public static final PluginClassLoader classLoader = new PluginClassLoader();
private static TransporterServer server;
// special test mode:
// - fast game buttons;
// - cheat commands;
// - no deck validation;
// - simplified registration and login (no password check);
// - debug main menu for GUI and rendering testing;
private static boolean testMode;
private static boolean fastDbMode;
/**
@ -98,7 +106,7 @@ public final class Main {
if (config.isAuthenticationActivated()) {
logger.info("Check authorized user DB version ...");
if (!AuthorizedUserRepository.instance.checkAlterAndMigrateAuthorizedUser()) {
if (!AuthorizedUserRepository.getInstance().checkAlterAndMigrateAuthorizedUser()) {
logger.fatal("Failed to start server.");
return;
}

View file

@ -35,6 +35,8 @@ public class Session {
private static final Pattern alphabetsPattern = Pattern.compile("[a-zA-Z]");
private static final Pattern digitsPattern = Pattern.compile("[0-9]");
public static final String REGISTRATION_DISABLED_MESSAGE = "Registration has been disabled on the server. You can use any name and empty password to login.";
private final ManagerFactory managerFactory;
private final String sessionId;
private UUID userId;
@ -60,30 +62,36 @@ public class Session {
public String registerUser(String userName, String password, String email) throws MageException {
if (!managerFactory.configSettings().isAuthenticationActivated()) {
String returnMessage = "Registration is disabled by the server config";
String returnMessage = REGISTRATION_DISABLED_MESSAGE;
sendErrorMessageToClient(returnMessage);
return returnMessage;
}
synchronized (AuthorizedUserRepository.instance) {
synchronized (AuthorizedUserRepository.getInstance()) {
// name
String returnMessage = validateUserName(userName);
if (returnMessage != null) {
sendErrorMessageToClient(returnMessage);
return returnMessage;
}
// auto-generated password
RandomString randomString = new RandomString(10);
password = randomString.nextString();
returnMessage = validatePassword(password, userName);
if (returnMessage != null) {
sendErrorMessageToClient(returnMessage);
sendErrorMessageToClient("Auto-generated password fail, try again: " + returnMessage);
return returnMessage;
}
// email
returnMessage = validateEmail(email);
if (returnMessage != null) {
sendErrorMessageToClient(returnMessage);
return returnMessage;
}
AuthorizedUserRepository.instance.add(userName, password, email);
// create
AuthorizedUserRepository.getInstance().add(userName, password, email);
String text = "You are successfully registered as " + userName + '.';
text += " Your initial, generated password is: " + password;
@ -95,15 +103,15 @@ public class Session {
success = managerFactory.mailgunClient().sendMessage(email, subject, text);
}
if (success) {
String ok = "Sent a registration confirmation / initial password email to " + email + " for " + userName;
String ok = "Email with initial password sent to " + email + " for a user " + userName;
logger.info(ok);
sendInfoMessageToClient(ok);
} else if (Main.isTestMode()) {
String ok = "Server is in test mode. Your account is registered with a password of " + password + " for " + userName;
String ok = "Email sending failed. Server is in test mode. Your account registered with a password " + password + " for a user " + userName;
logger.info(ok);
sendInfoMessageToClient(ok);
} else {
String err = "Failed sending a registration confirmation / initial password email to " + email + " for " + userName;
String err = "Email sending failed. Try use another email address or service. Or reset password by email " + email + " for a user " + userName;
logger.error(err);
sendErrorMessageToClient(err);
return err;
@ -113,9 +121,13 @@ public class Session {
}
private String validateUserName(String userName) {
// return error message or null on good name
if (userName.equals("Admin")) {
// virtual user for admin console
return "User name Admin already in use";
}
ConfigSettings config = managerFactory.configSettings();
if (userName.length() < config.getMinUserNameLength()) {
return "User name may not be shorter than " + config.getMinUserNameLength() + " characters";
@ -123,15 +135,19 @@ public class Session {
if (userName.length() > config.getMaxUserNameLength()) {
return "User name may not be longer than " + config.getMaxUserNameLength() + " characters";
}
Pattern invalidUserNamePattern = Pattern.compile(managerFactory.configSettings().getInvalidUserNamePattern(), Pattern.CASE_INSENSITIVE);
Matcher m = invalidUserNamePattern.matcher(userName);
if (m.find()) {
return "User name '" + userName + "' includes not allowed characters: use a-z, A-Z and 0-9";
}
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName);
AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName);
if (authorizedUser != null) {
return "User name '" + userName + "' already in use";
}
// all fine
return null;
}
@ -159,7 +175,7 @@ public class Session {
if (email == null || email.isEmpty()) {
return "Email address cannot be blank";
}
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email);
AuthorizedUser authorizedUser = AuthorizedUserRepository.getInstance().getByEmail(email);
if (authorizedUser != null) {
return "Email address '" + email + "' is associated with another user";
}
@ -182,8 +198,8 @@ public class Session {
this.isAdmin = false;
AuthorizedUser authorizedUser = null;
if (managerFactory.configSettings().isAuthenticationActivated()) {
authorizedUser = AuthorizedUserRepository.instance.getByName(userName);
String errorMsg = "Wrong username or password. In case you haven't, please register your account first.";
authorizedUser = AuthorizedUserRepository.getInstance().getByName(userName);
String errorMsg = "Wrong username or password. You must register your account first.";
if (authorizedUser == null) {
return errorMsg;
}
@ -193,16 +209,16 @@ public class Session {
}
if (!authorizedUser.active) {
return "Your profile is deactivated, you can't sign on.";
return "Your profile has been deactivated by admin.";
}
if (authorizedUser.lockedUntil != null) {
if (authorizedUser.lockedUntil.compareTo(Calendar.getInstance().getTime()) > 0) {
return "Your profile is deactivated until " + SystemUtil.dateFormat.format(authorizedUser.lockedUntil);
return "Your profile has need deactivated by admin until " + SystemUtil.dateFormat.format(authorizedUser.lockedUntil);
} else {
// unlock on timeout end
managerFactory.userManager().createUser(userName, host, authorizedUser).ifPresent(user
-> user.setLockedUntil(null)
);
}
}
}

View file

@ -817,7 +817,7 @@ public class User {
authorizedUser.chatLockedUntil = this.chatLockedUntil;
authorizedUser.lockedUntil = this.lockedUntil;
authorizedUser.active = this.active;
AuthorizedUserRepository.instance.update(authorizedUser);
AuthorizedUserRepository.getInstance().update(authorizedUser);
}
}
}

View file

@ -810,7 +810,7 @@ public class GameController implements GameCallback {
}
private synchronized void chooseAbility(UUID playerId, final String objectName, final List<? extends Ability> choices, String message) throws MageException {
perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(objectName, choices, message)));
perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(getGameView(playerId), objectName, choices, message)));
}
private synchronized void choosePile(UUID playerId, final String message, final List<? extends Card> pile1, final List<? extends Card> pile2) throws MageException {
@ -818,7 +818,7 @@ public class GameController implements GameCallback {
}
private synchronized void chooseMode(UUID playerId, final Map<UUID, String> modes, final String message) throws MageException {
perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(modes, message)));
perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(getGameView(playerId), modes, message)));
}
private synchronized void chooseChoice(UUID playerId, final Choice choice) throws MageException {

View file

@ -47,7 +47,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void ask(final String question, final Map<String, Serializable> options) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ASK, game.getId(), new GameClientMessage(getGameView(), question, options)))
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ASK, game.getId(), new GameClientMessage(getGameView(), options, question)))
);
}
}
@ -55,7 +55,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void target(final String question, final CardsView cardView, final Set<UUID> targets, final boolean required, final Map<String, Serializable> options) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> {
user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_TARGET, game.getId(), new GameClientMessage(getGameView(), question, cardView, targets, required, options)));
user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_TARGET, game.getId(), new GameClientMessage(getGameView(), options, question, cardView, targets, required)));
});
}
@ -63,7 +63,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void select(final String message, final Map<String, Serializable> options) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_SELECT, game.getId(), new GameClientMessage(getGameView(), message, options))));
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_SELECT, game.getId(), new GameClientMessage(getGameView(), options, message))));
}
}
@ -78,7 +78,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void choosePile(final String message, final CardsView pile1, final CardsView pile2) {
if (!killed) {
userManager.getUser(userId).ifPresent(user
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_PILE, game.getId(), new GameClientMessage(message, pile1, pile2))));
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_PILE, game.getId(), new GameClientMessage(getGameView(), null, message, pile1, pile2))));
}
}
@ -86,7 +86,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void chooseChoice(final Choice choice) {
if (!killed) {
userManager.getUser(userId).ifPresent(user
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_CHOICE, game.getId(), new GameClientMessage(choice))));
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_CHOOSE_CHOICE, game.getId(), new GameClientMessage(getGameView(), null, choice))));
}
}
@ -94,14 +94,14 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void playMana(final String message, final Map<String, Serializable> options) {
if (!killed) {
userManager.getUser(userId).ifPresent(user
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_MANA, game.getId(), new GameClientMessage(getGameView(), message, options))));
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_MANA, game.getId(), new GameClientMessage(getGameView(), options, message))));
}
}
public void playXMana(final String message) {
if (!killed) {
userManager.getUser(userId).ifPresent(user
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_XMANA, game.getId(), new GameClientMessage(getGameView(), message))));
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_PLAY_XMANA, game.getId(), new GameClientMessage(getGameView(), null, message))));
}
}
@ -109,7 +109,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void getAmount(final String message, final int min, final int max) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> {
user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_AMOUNT, game.getId(), new GameClientMessage(message, min, max)));
user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_AMOUNT, game.getId(), new GameClientMessage(getGameView(), null, message, min, max)));
});
}
}
@ -117,7 +117,7 @@ public class GameSessionPlayer extends GameSessionWatcher {
public void getMultiAmount(final List<String> messages, final int min, final int max, final Map<String, Serializable> options) {
if (!killed) {
userManager.getUser(userId).ifPresent(user
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_MULTI_AMOUNT, game.getId(), new GameClientMessage(messages, min, max, options))));
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_MULTI_AMOUNT, game.getId(), new GameClientMessage(getGameView(), options, messages, min, max))));
}
}

View file

@ -58,14 +58,14 @@ public class GameSessionWatcher {
public void inform(final String message) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM, game.getId(), new GameClientMessage(getGameView(), message))));
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM, game.getId(), new GameClientMessage(getGameView(), null, message))));
}
}
public void informPersonal(final String message) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM_PERSONAL, game.getId(), new GameClientMessage(getGameView(), message))));
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INFORM_PERSONAL, game.getId(), new GameClientMessage(getGameView(), null, message))));
}
}
@ -74,7 +74,7 @@ public class GameSessionWatcher {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> {
user.removeGameWatchInfo(game.getId());
user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_OVER, game.getId(), message));
user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_OVER, game.getId(), new GameClientMessage(getGameView(), null, message)));
});
}
}
@ -89,7 +89,6 @@ public class GameSessionWatcher {
public void gameError(final String message) {
if (!killed) {
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_ERROR, game.getId(), message)));
}
}

View file

@ -24,7 +24,7 @@ public class ConfigFactoryTest {
@DisplayName("should fail if config is malformed")
void failOnMalformed() {
assertThatExceptionOfType(ConfigurationException.class)
.isThrownBy(() -> ConfigFactory.loadFromFile(Paths.get("src", "test", "resources", "config_error.xml").toString()));
.isThrownBy(() -> ConfigFactory.loadFromFile(Paths.get("src", "test", "data", "config_error.xml").toString()));
}
@Test

View file

@ -21,17 +21,6 @@
<artifactId>mage</artifactId>
<version>${mage-version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<type>jar</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View file

@ -25,7 +25,7 @@ public final class AbandonThePost extends CardImpl {
this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2));
// Flashback {3}{R}
this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{R}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}")));
}
private AbandonThePost(final AbandonThePost card) {

View file

@ -59,7 +59,7 @@ class AccursedWitchReturnTransformedEffect extends OneShotEffect {
AccursedWitchReturnTransformedEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "Put {this} from your graveyard onto the battlefield transformed under your control attached to target opponent";
this.staticText = "return it to the battlefield transformed under your control attached to target opponent";
}
private AccursedWitchReturnTransformedEffect(final AccursedWitchReturnTransformedEffect effect) {
@ -78,15 +78,17 @@ class AccursedWitchReturnTransformedEffect extends OneShotEffect {
if (controller == null || !(game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) || attachTo == null) {
return false;
}
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE);
UUID secondFaceId = game.getCard(source.getSourceId()).getSecondCardFace().getId();
game.getState().setValue("attachTo:" + secondFaceId, attachTo.getId());
//note: should check for null after game.getCard
Card card = game.getCard(source.getSourceId());
if (card != null) {
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
attachTo.addAttachment(card.getId(), source, game);
}
if (card == null) {
return false;
}
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE);
UUID secondFaceId = card.getSecondCardFace().getId();
game.getState().setValue("attachTo:" + secondFaceId, attachTo.getId());
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
attachTo.addAttachment(card.getId(), source, game);
}
return true;
}

View file

@ -26,7 +26,7 @@ public final class AcornHarvest extends CardImpl {
this.getSpellAbility().addEffect(new CreateTokenEffect(new SquirrelToken(), 2));
// Flashback-{1}{G} - Pay 3 life.
FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{G}"), TimingRule.SORCERY);
FlashbackAbility ability = new FlashbackAbility(this, new ManaCostsImpl("{1}{G}"));
ability.addCost(new PayLifeCost(3));
this.addAbility(ability);

View file

@ -0,0 +1,79 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.SetPowerSourceEffect;
import mage.abilities.hint.common.CreaturesYouControlHint;
import mage.constants.*;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.game.Game;
import mage.game.permanent.token.HumanToken;
/**
*
* @author weirddan455
*/
public final class AdelineResplendentCathar extends CardImpl {
public AdelineResplendentCathar(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{W}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.KNIGHT);
this.power = new MageInt(0);
this.toughness = new MageInt(4);
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Adeline, Resplendent Cathar's power is equal to the number of creatures you control.
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerSourceEffect(
CreaturesYouControlCount.instance, Duration.EndOfGame)).addHint(CreaturesYouControlHint.instance)
);
// Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
this.addAbility(new AttacksWithCreaturesTriggeredAbility(new AdelineResplendentCatharEffect(), 1));
}
private AdelineResplendentCathar(final AdelineResplendentCathar card) {
super(card);
}
@Override
public AdelineResplendentCathar copy() {
return new AdelineResplendentCathar(this);
}
}
class AdelineResplendentCatharEffect extends OneShotEffect {
public AdelineResplendentCatharEffect() {
super(Outcome.Benefit);
staticText = "for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control";
}
private AdelineResplendentCatharEffect(final AdelineResplendentCatharEffect effect) {
super(effect);
}
@Override
public AdelineResplendentCatharEffect copy() {
return new AdelineResplendentCatharEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID opponentId : game.getOpponents(source.getControllerId())) {
new HumanToken().putOntoBattlefield(1, game, source, source.getControllerId(), true, true, opponentId);
}
return true;
}
}

View file

@ -1,4 +1,3 @@
package mage.cards.a;
import java.util.UUID;

View file

@ -14,8 +14,8 @@ import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterEquipmentPermanent;
import mage.filter.predicate.ObjectPlayer;
import mage.filter.predicate.ObjectPlayerPredicate;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent;
@ -103,11 +103,11 @@ class AkiriFearlessVoyagerTriggeredAbility extends TriggeredAbilityImpl {
class AkiriFearlessVoyagerEffect extends OneShotEffect {
private static enum AkiriFearlessVoyagerPredicate implements ObjectPlayerPredicate<ObjectPlayer<Permanent>> {
private static enum AkiriFearlessVoyagerPredicate implements ObjectSourcePlayerPredicate<Permanent> {
instance;
@Override
public boolean apply(ObjectPlayer<Permanent> input, Game game) {
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return game.getPermanent(input.getObject().getAttachedTo()) != null
&& game.getControllerId(input.getObject().getAttachedTo()).equals(input.getPlayerId());
}

View file

@ -1,6 +1,5 @@
package mage.cards.a;
import com.google.common.collect.Sets;
import mage.MageInt;
import mage.abilities.Abilities;
import mage.abilities.Ability;
@ -17,6 +16,8 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@ -66,7 +67,7 @@ public final class AkromaVisionOfIxidor extends CardImpl {
class AkromaVisionOfIxidorEffect extends OneShotEffect {
private static final Set<Class<? extends Ability>> classes = Sets.newHashSet(
private static final Set<Class<? extends Ability>> classes = new HashSet<>(Arrays.asList(
FlyingAbility.class,
FirstStrikeAbility.class,
DoubleStrikeAbility.class,
@ -81,7 +82,7 @@ class AkromaVisionOfIxidorEffect extends OneShotEffect {
TrampleAbility.class,
VigilanceAbility.class,
PartnerAbility.class
);
));
AkromaVisionOfIxidorEffect() {
super(Outcome.Benefit);

View file

@ -1,38 +1,39 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class AleshaWhoSmilesAtDeath extends CardImpl {
private static final FilterCreatureCard filter = new FilterCreatureCard("creature card with power 2 or less");
private static final FilterCard filter
= new FilterCreatureCard("creature card with power 2 or less from your graveyard");
static {
filter.add(new PowerPredicate(ComparisonType.FEWER_THAN, 3));
}
public AleshaWhoSmilesAtDeath(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}");
addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WARRIOR);
@ -43,7 +44,10 @@ public final class AleshaWhoSmilesAtDeath extends CardImpl {
this.addAbility(FirstStrikeAbility.getInstance());
// Whenever Alesha, Who Smiles at Death attacks, you may pay {W/B}{W/B}. If you do, return target creature card with power 2 or less from your graveyard to the battlefield tapped and attacking.
Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid(new AleshaWhoSmilesAtDeathEffect(), new ManaCostsImpl("{W/B}{W/B}")), false);
Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid(
new ReturnFromGraveyardToBattlefieldTargetEffect(true, true),
new ManaCostsImpl<>("{W/B}{W/B}")
), false);
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);
}
@ -57,38 +61,3 @@ public final class AleshaWhoSmilesAtDeath extends CardImpl {
return new AleshaWhoSmilesAtDeath(this);
}
}
class AleshaWhoSmilesAtDeathEffect extends OneShotEffect {
public AleshaWhoSmilesAtDeathEffect() {
super(Outcome.PutCreatureInPlay);
this.staticText = "return target creature card with power 2 or less from your graveyard to the battlefield tapped and attacking";
}
public AleshaWhoSmilesAtDeathEffect(final AleshaWhoSmilesAtDeathEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card != null) {
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null)) {
game.getCombat().addAttackingCreature(card.getId(), game);
}
}
return true;
}
return false;
}
@Override
public AleshaWhoSmilesAtDeathEffect copy() {
return new AleshaWhoSmilesAtDeathEffect(this);
}
}

View file

@ -0,0 +1,63 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.CovenCondition;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.abilities.hint.common.CovenHint;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AmbitiousFarmhand extends CardImpl {
private static final FilterCard filter = new FilterCard("basic Plains card");
static {
filter.add(SuperType.BASIC.getPredicate());
filter.add(SubType.PLAINS.getPredicate());
}
public AmbitiousFarmhand(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.PEASANT);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
this.transformable = true;
this.secondSideCardClazz = mage.cards.s.SeasonedCathar.class;
// When Ambitious Farmhand enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle.
this.addAbility(new EntersBattlefieldTriggeredAbility(
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter)), true
));
// Coven{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers.
this.addAbility(new TransformAbility());
this.addAbility(new ActivateIfConditionActivatedAbility(
Zone.BATTLEFIELD, new TransformSourceEffect(true),
new ManaCostsImpl<>("{1}{W}{W}"), CovenCondition.instance
).setAbilityWord(AbilityWord.COVEN).addHint(CovenHint.instance));
}
private AmbitiousFarmhand(final AmbitiousFarmhand card) {
super(card);
}
@Override
public AmbitiousFarmhand copy() {
return new AmbitiousFarmhand(this);
}
}

View file

@ -27,7 +27,7 @@ public final class AncestralTribute extends CardImpl {
this.getSpellAbility().addEffect(new GainLifeEffect((new CardsInControllerGraveyardCount(new FilterCard(), 2))));
// Flashback {9}{W}{W}{W}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{9}{W}{W}{W}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{9}{W}{W}{W}")));
}

View file

@ -23,7 +23,7 @@ public final class AncientGrudge extends CardImpl {
this.getSpellAbility().addTarget(new TargetArtifactPermanent());
// Flashback {G}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}")));
}
private AncientGrudge(final AncientGrudge card) {

View file

@ -12,13 +12,12 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.target.common.TargetCardInGraveyardOrBattlefield;
import java.util.UUID;
import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
/**
* @author LevelX2
@ -27,10 +26,13 @@ public final class AngelOfSerenity extends CardImpl {
private static final String rule = "you may exile up to three other target creatures " +
"from the battlefield and/or creature cards from graveyards.";
private static final FilterPermanent filter = new FilterCreaturePermanent("other target creatures");
private static final FilterCreatureCard filterCreatureCard = new FilterCreatureCard("creature card in a graveyard");
private static final FilterCreaturePermanent filterCreaturePermanent = new FilterCreaturePermanent("other target creature");
static {
filter.add(AnotherPredicate.instance);
filterCreaturePermanent.add(AnotherPredicate.instance);
}
public AngelOfSerenity(UUID ownerId, CardSetInfo setInfo) {
@ -48,7 +50,7 @@ public final class AngelOfSerenity extends CardImpl {
new ExileTargetForSourceEffect().setText(rule), true
);
ability.addTarget(new TargetCardInGraveyardOrBattlefield(
0, 3, StaticFilters.FILTER_CARD_CREATURE, filter
0, 3, filterCreatureCard, filterCreaturePermanent
));
this.addAbility(ability);

View file

@ -42,7 +42,7 @@ public final class AngelfireIgnition extends CardImpl {
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
// Flashback {2}{R}{W}
this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{2}{R}{W}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{2}{R}{W}")));
}
private AngelfireIgnition(final AngelfireIgnition card) {

View file

@ -32,7 +32,7 @@ public final class ArcaneInfusion extends CardImpl {
"Put the rest on the bottom of your library in a random order."));
// Flashback {3}{U}{R}
this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{U}{R}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{U}{R}")));
}
private ArcaneInfusion(final ArcaneInfusion card) {

View file

@ -44,7 +44,7 @@ public final class ArchmagesCharm extends CardImpl {
this.getSpellAbility().addMode(mode);
// Gain control of target nonland permanent with converted mana cost 1 or less.
mode = new Mode(new GainControlTargetEffect(Duration.Custom, true));
mode = new Mode(new GainControlTargetEffect(Duration.EndOfGame, true));
mode.addTarget(new TargetPermanent(filter));
this.getSpellAbility().addMode(mode);
}

View file

@ -0,0 +1,119 @@
package mage.cards.a;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.mana.BasicManaEffect;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.IndestructibleAbility;
import mage.abilities.keyword.NightboundAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ArlinnTheMoonsFury extends CardImpl {
public ArlinnTheMoonsFury(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.ARLINN);
this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4));
this.color.setRed(true);
this.color.setGreen(true);
this.transformable = true;
this.nightCard = true;
// Nightbound
this.addAbility(new NightboundAbility());
// +2: Add {R}{G}.
this.addAbility(new LoyaltyAbility(new BasicManaEffect(new Mana(
0, 0, 0, 1, 1, 0, 0, 0
)), 2));
// 0: Until end of turn, Arlinn, the Moon's Fury becomes a 5/5 Werewolf creature with trample, indestructible, and haste.
this.addAbility(new LoyaltyAbility(new ArlinnTheMoonsFuryEffect(), 0));
}
private ArlinnTheMoonsFury(final ArlinnTheMoonsFury card) {
super(card);
}
@Override
public ArlinnTheMoonsFury copy() {
return new ArlinnTheMoonsFury(this);
}
}
class ArlinnTheMoonsFuryEffect extends ContinuousEffectImpl {
ArlinnTheMoonsFuryEffect() {
super(Duration.EndOfTurn, Outcome.Benefit);
staticText = "until end of turn, {this} becomes a 5/5 Werewolf creature with trample, indestructible, and haste";
}
private ArlinnTheMoonsFuryEffect(final ArlinnTheMoonsFuryEffect effect) {
super(effect);
}
@Override
public ArlinnTheMoonsFuryEffect copy() {
return new ArlinnTheMoonsFuryEffect(this);
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null) {
discard();
return false;
}
switch (layer) {
case TypeChangingEffects_4:
permanent.removeAllCardTypes(game);
permanent.addCardType(game, CardType.CREATURE);
permanent.removeAllCreatureTypes(game);
permanent.addSubType(game, SubType.WEREWOLF);
return true;
case AbilityAddingRemovingEffects_6:
permanent.addAbility(TrampleAbility.getInstance(), source.getSourceId(), game);
permanent.addAbility(IndestructibleAbility.getInstance(), source.getSourceId(), game);
permanent.addAbility(HasteAbility.getInstance(), source.getSourceId(), game);
return true;
case PTChangingEffects_7:
if (sublayer == SubLayer.SetPT_7b) {
permanent.getPower().setValue(5);
permanent.getToughness().setValue(5);
return true;
}
}
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean hasLayer(Layer layer) {
switch (layer) {
case TypeChangingEffects_4:
case AbilityAddingRemovingEffects_6:
case PTChangingEffects_7:
return true;
}
return false;
}
}

View file

@ -0,0 +1,106 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect;
import mage.abilities.keyword.DayboundAbility;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.WolfToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ArlinnThePacksHope extends CardImpl {
private static final FilterCard filter = new FilterCreatureCard("creature spells");
public ArlinnThePacksHope(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{G}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.ARLINN);
this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4));
this.transformable = true;
this.secondSideCardClazz = mage.cards.a.ArlinnTheMoonsFury.class;
// Daybound
this.addAbility(new TransformAbility());
this.addAbility(new DayboundAbility());
// +1: Until your next turn, you may cast creature spells as though they had flash, and each creature you control enters the battlefield with an additional +1/+1 counter on it.
Ability ability = new LoyaltyAbility(new CastAsThoughItHadFlashAllEffect(
Duration.UntilYourNextTurn, filter
).setText("until your next turn, you may cast creature spells as though they had flash"), 1);
ability.addEffect(new ArlinnThePacksHopeEffect());
this.addAbility(ability);
// 3: Create two 2/2 green Wolf creature tokens.
this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new WolfToken(), 2), -3));
}
private ArlinnThePacksHope(final ArlinnThePacksHope card) {
super(card);
}
@Override
public ArlinnThePacksHope copy() {
return new ArlinnThePacksHope(this);
}
}
class ArlinnThePacksHopeEffect extends ReplacementEffectImpl {
ArlinnThePacksHopeEffect() {
super(Duration.UntilYourNextTurn, Outcome.BoostCreature);
this.staticText = ", and each creature you control enters the battlefield with an additional +1/+1 counter on it";
}
private ArlinnThePacksHopeEffect(ArlinnThePacksHopeEffect effect) {
super(effect);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
return permanent != null && permanent.isControlledBy(source.getControllerId()) && permanent.isCreature(game);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent target = ((EntersTheBattlefieldEvent) event).getTarget();
if (target != null) {
target.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game, event.getAppliedEffects());
}
return false;
}
@Override
public ArlinnThePacksHopeEffect copy() {
return new ArlinnThePacksHopeEffect(this);
}
}

View file

@ -23,7 +23,7 @@ public final class ArmyOfTheDamned extends CardImpl {
this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken(), 13, true, false));
// Flashback {7}{B}{B}{B}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{7}{B}{B}{B}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{7}{B}{B}{B}")));
}
private ArmyOfTheDamned(final ArmyOfTheDamned card) {

View file

@ -25,7 +25,7 @@ public final class ArtfulDodge extends CardImpl {
this.getSpellAbility().addEffect(new CantBeBlockedTargetEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
// Flashback {U}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{U}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{U}")));
}
private ArtfulDodge(final ArtfulDodge card) {

View file

@ -0,0 +1,51 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AshmouthDragon extends CardImpl {
public AshmouthDragon(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
this.subtype.add(SubType.DRAGON);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
this.color.setRed(true);
this.transformable = true;
this.nightCard = true;
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever you cast an instant or sorcery spell, Ashmouth Dragon deals 2 damage to any target.
Ability ability = new SpellCastControllerTriggeredAbility(
new DamageTargetEffect(2), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false
);
ability.addTarget(new TargetAnyTarget());
this.addAbility(ability);
}
private AshmouthDragon(final AshmouthDragon card) {
super(card);
}
@Override
public AshmouthDragon copy() {
return new AshmouthDragon(this);
}
}

View file

@ -14,8 +14,8 @@ import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.Filter;
import mage.filter.FilterPermanent;
import mage.filter.predicate.ObjectPlayer;
import mage.filter.predicate.ObjectPlayerPredicate;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -54,19 +54,19 @@ public final class AuraGraft extends CardImpl {
}
}
class AttachedToPermanentPredicate implements ObjectPlayerPredicate<ObjectPlayer<Permanent>> {
class AttachedToPermanentPredicate implements ObjectSourcePlayerPredicate<Permanent> {
public AttachedToPermanentPredicate() {
super();
}
public boolean apply(ObjectPlayer<Permanent> input, Game game) {
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
Permanent attached = input.getObject();
return attached != null && game.getPermanent(attached.getAttachedTo()) != null;
}
}
class PermanentCanBeAttachedToPredicate implements ObjectPlayerPredicate<ObjectPlayer<Permanent>> {
class PermanentCanBeAttachedToPredicate implements ObjectSourcePlayerPredicate<Permanent> {
protected Permanent aura;
@ -76,7 +76,7 @@ class PermanentCanBeAttachedToPredicate implements ObjectPlayerPredicate<ObjectP
}
@Override
public boolean apply(ObjectPlayer<Permanent> input, Game game) {
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
Permanent potentialAttachment = input.getObject();
for (TargetAddress addr : TargetAddress.walk(aura)) {
Target target = addr.getTarget(aura);

View file

@ -0,0 +1,35 @@
package mage.cards.a;
import mage.MageInt;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AwokenDemon extends CardImpl {
public AwokenDemon(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
this.subtype.add(SubType.DEMON);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
this.color.setBlack(true);
this.transformable = true;
this.nightCard = true;
}
private AwokenDemon(final AwokenDemon card) {
super(card);
}
@Override
public AwokenDemon copy() {
return new AwokenDemon(this);
}
}

View file

@ -93,15 +93,7 @@ class BackdraftHellkiteEffect extends ContinuousEffectImpl {
if (card == null) {
return;
}
FlashbackAbility ability = null;
if (card.isInstant(game)) {
ability = new FlashbackAbility(card.getManaCost(), TimingRule.INSTANT);
} else if (card.isSorcery(game)) {
ability = new FlashbackAbility(card.getManaCost(), TimingRule.SORCERY);
}
if (ability == null) {
return;
}
FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost());
ability.setSourceId(cardId);
ability.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card, ability);

View file

@ -3,6 +3,7 @@ package mage.cards.b;
import mage.MageInt;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.keyword.DisturbAbility;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -26,6 +27,7 @@ public final class BaithookAngler extends CardImpl {
this.secondSideCardClazz = mage.cards.h.HookHauntDrifter.class;
// Disturb {1}{U}
this.addAbility(new TransformAbility());
this.addAbility(new DisturbAbility(new ManaCostsImpl<>("{1}{U}")));
}

View file

@ -56,7 +56,7 @@ public final class BarteredCow extends CardImpl {
}
}
enum BarteredCowPredicate implements ObjectSourcePlayerPredicate<ObjectSourcePlayer<MageObject>> {
enum BarteredCowPredicate implements ObjectSourcePlayerPredicate<MageObject> {
instance;
@Override

View file

@ -27,7 +27,7 @@ public final class BashToBits extends CardImpl {
Target target = new TargetArtifactPermanent();
this.getSpellAbility().addTarget(target);
// Flashback {4}{R}{R}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{4}{R}{R}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{4}{R}{R}")));
}
private BashToBits(final BashToBits card) {

View file

@ -37,7 +37,7 @@ public final class BattleScreech extends CardImpl {
this.getSpellAbility().addEffect(new CreateTokenEffect(new BirdToken(), 2));
// Flashback-Tap three untapped white creatures you control.
this.addAbility(new FlashbackAbility(new TapTargetCost(new TargetControlledCreaturePermanent(3,3, filter, true)), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new TapTargetCost(new TargetControlledCreaturePermanent(3,3, filter, true))));
}
private BattleScreech(final BattleScreech card) {

View file

@ -24,7 +24,7 @@ public final class BeastAttack extends CardImpl {
this.getSpellAbility().addEffect(new CreateTokenEffect(new BeastToken2()));
// Flashback {2}{G}{G}{G}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{2}{G}{G}{G}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{2}{G}{G}{G}")));
}
private BeastAttack(final BeastAttack card) {

View file

@ -0,0 +1,54 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.PutIntoGraveFromAnywhereSourceAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.CantBeCounteredControlledEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BenevolentGeist extends CardImpl {
public BenevolentGeist(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
this.color.setBlue(true);
this.transformable = true;
this.nightCard = true;
// Flying
this.addAbility(FlyingAbility.getInstance());
// Noncreature spells you control can't be countered.
this.addAbility(new SimpleStaticAbility(new CantBeCounteredControlledEffect(
StaticFilters.FILTER_SPELLS_NON_CREATURE, null, Duration.WhileOnBattlefield
)));
// If Benevolent Geist would be put into a graveyard from anywhere, exile it instead.
this.addAbility(new PutIntoGraveFromAnywhereSourceAbility(new ExileSourceEffect().setText("exile it instead")));
}
private BenevolentGeist(final BenevolentGeist card) {
super(card);
}
@Override
public BenevolentGeist copy() {
return new BenevolentGeist(this);
}
}

View file

@ -0,0 +1,46 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BereavedSurvivor extends CardImpl {
public BereavedSurvivor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.PEASANT);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
this.transformable = true;
this.secondSideCardClazz = mage.cards.d.DauntlessAvenger.class;
// When another creature you control dies, transform Bereaved Survivor.
this.addAbility(new TransformAbility());
this.addAbility(new DiesCreatureTriggeredAbility(
new TransformSourceEffect(true), false,
StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE
));
}
private BereavedSurvivor(final BereavedSurvivor card) {
super(card);
}
@Override
public BereavedSurvivor copy() {
return new BereavedSurvivor(this);
}
}

View file

@ -100,7 +100,7 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect {
}
}
class SameControllerPredicate implements ObjectSourcePlayerPredicate<ObjectSourcePlayer<MageItem>> {
class SameControllerPredicate implements ObjectSourcePlayerPredicate<MageItem> {
@Override
public boolean apply(ObjectSourcePlayer<MageItem> input, Game game) {

View file

@ -32,7 +32,7 @@ public final class BirdAdmirer extends CardImpl {
// Daybound
this.addAbility(new TransformAbility());
this.addAbility(DayboundAbility.getInstance());
this.addAbility(new DayboundAbility());
}
private BirdAdmirer(final BirdAdmirer card) {

View file

@ -26,7 +26,7 @@ public final class Bladebrand extends CardImpl {
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
// Draw a card.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1));
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("<br>"));
}
private Bladebrand(final Bladebrand card) {

View file

@ -35,7 +35,7 @@ public final class BlastFromThePast extends CardImpl {
// Kicker {2}{R}
this.addAbility(new KickerAbility("{2}{R}"));
// Flashback {3}{R}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{R}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{R}")));
// Buyback {4}{R}
this.addAbility(new BuybackAbility("{4}{R}"));

View file

@ -1,12 +1,9 @@
package mage.cards.b;
import java.util.UUID;
import mage.abilities.condition.LockedInCondition;
import mage.abilities.Ability;
import mage.abilities.condition.common.KickedCondition;
import mage.abilities.decorator.ConditionalContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.KickerAbility;
@ -14,7 +11,11 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.game.Game;
import java.util.UUID;
/**
* @author nantuko, Loki
@ -28,12 +29,7 @@ public final class BoldDefense extends CardImpl {
this.addAbility(new KickerAbility("{3}{W}"));
// Creatures you control get +1/+1 until end of turn. If Bold Defense was kicked, instead creatures you control get +2/+2 and gain first strike until end of turn.
this.getSpellAbility().addEffect(new ConditionalContinuousEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn),
new BoostTargetEffect(1, 1, Duration.EndOfTurn), new LockedInCondition(KickedCondition.instance),
"Creatures you control get +1/+1 until end of turn. If this spell was kicked, instead creatures you control get +2/+2"));
this.getSpellAbility().addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE, false),
null, new LockedInCondition(KickedCondition.instance),
"and gain first strike until end of turn"));
this.getSpellAbility().addEffect(new BoldDefenseEffect());
}
private BoldDefense(final BoldDefense card) {
@ -45,3 +41,35 @@ public final class BoldDefense extends CardImpl {
return new BoldDefense(this);
}
}
class BoldDefenseEffect extends OneShotEffect {
BoldDefenseEffect() {
super(Outcome.Benefit);
staticText = "Creatures you control get +1/+1 until end of turn. If this spell was kicked, " +
"instead creatures you control get +2/+2 and gain first strike until end of turn.";
}
private BoldDefenseEffect(final BoldDefenseEffect effect) {
super(effect);
}
@Override
public BoldDefenseEffect copy() {
return new BoldDefenseEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
if (KickedCondition.instance.apply(game, source)) {
game.addEffect(new BoostControlledEffect(2, 2, Duration.EndOfTurn), source);
game.addEffect(new GainAbilityControlledEffect(
FirstStrikeAbility.getInstance(), Duration.EndOfTurn,
StaticFilters.FILTER_PERMANENT_CREATURE
), source);
} else {
game.addEffect(new BoostControlledEffect(1, 1, Duration.EndOfTurn), source);
}
return true;
}
}

View file

@ -138,7 +138,7 @@ class BoreasChargerEffect extends OneShotEffect {
}
}
class BoreasChargerPredicate implements ObjectSourcePlayerPredicate<ObjectSourcePlayer<Player>> {
class BoreasChargerPredicate implements ObjectSourcePlayerPredicate<Player> {
@Override
public boolean apply(ObjectSourcePlayer<Player> input, Game game) {

View file

@ -110,8 +110,7 @@ class BrilliantUltimatumEffect extends OneShotEffect {
TargetCard targetExiledCard = new TargetCard(Zone.EXILED, new FilterCard());
if (controller.chooseTarget(Outcome.PlayForFree, selectedPile, targetExiledCard, source, game)) {
Card card = selectedPile.get(targetExiledCard.getFirstTarget(), game);
controller.canPlayLand();
if (controller.playCard(card, game, true, true, new ApprovingObject(source, game))) {
if (controller.playCard(card, game, true, new ApprovingObject(source, game))) {
selectedPileCards.remove(card);
selectedPile.remove(card);
}

View file

@ -0,0 +1,99 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.delayed.OnLeaveReturnExiledToBattlefieldAbility;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.ExileUntilSourceLeavesEffect;
import mage.abilities.keyword.DayboundAbility;
import mage.abilities.keyword.TransformAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.common.TargetOpponentsCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BrutalCathar extends CardImpl {
public BrutalCathar(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SOLDIER);
this.subtype.add(SubType.WEREWOLF);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
this.transformable = true;
this.secondSideCardClazz = mage.cards.m.MoonrageBrute.class;
// When this creature enters the battlefield or transforms into Brutal Cathar, exile target creature an opponent controls until this creature leaves the battlefield.
this.addAbility(new BrutalCatharTriggeredAbility());
// Daybound
this.addAbility(new TransformAbility());
this.addAbility(new DayboundAbility());
}
private BrutalCathar(final BrutalCathar card) {
super(card);
}
@Override
public BrutalCathar copy() {
return new BrutalCathar(this);
}
}
class BrutalCatharTriggeredAbility extends TriggeredAbilityImpl {
public BrutalCatharTriggeredAbility() {
super(Zone.BATTLEFIELD, new ExileUntilSourceLeavesEffect("creature an opponent controls"), false);
this.addTarget(new TargetOpponentsCreaturePermanent());
this.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility()));
}
public BrutalCatharTriggeredAbility(final BrutalCatharTriggeredAbility ability) {
super(ability);
}
@Override
public BrutalCatharTriggeredAbility copy() {
return new BrutalCatharTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TRANSFORMED
|| event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getTargetId().equals(this.getSourceId())) {
return false;
}
switch (event.getType()) {
case TRANSFORMED:
Permanent permanent = getSourcePermanentIfItStillExists(game);
return permanent != null && !permanent.isTransformed();
case ENTERS_THE_BATTLEFIELD:
return true;
}
return false;
}
@Override
public String getRule() {
return "When this creature enters the battlefield or transforms into {this}, " +
"exile target creature an opponent controls until this creature leaves the battlefield.";
}
}

View file

@ -24,7 +24,7 @@ public final class BumpInTheNight extends CardImpl {
this.getSpellAbility().addTarget(new TargetOpponent());
// Flashback {5}{R}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{R}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{R}")));
}
private BumpInTheNight(final BumpInTheNight card) {

View file

@ -32,7 +32,7 @@ public final class BurlyBreaker extends CardImpl {
// Daybound
this.addAbility(new TransformAbility());
this.addAbility(DayboundAbility.getInstance());
this.addAbility(new DayboundAbility());
}
private BurlyBreaker(final BurlyBreaker card) {

View file

@ -0,0 +1,70 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileTargetIfDiesEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BurnTheAccursed extends CardImpl {
public BurnTheAccursed(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}");
// Burn the Accused deals 5 damage to target creature and 2 damage to that creature's controller. If that creature would die this turn, exile it instead.
this.getSpellAbility().addEffect(new BurnTheAccursedEffect());
this.getSpellAbility().addEffect(new ExileTargetIfDiesEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
}
private BurnTheAccursed(final BurnTheAccursed card) {
super(card);
}
@Override
public BurnTheAccursed copy() {
return new BurnTheAccursed(this);
}
}
class BurnTheAccursedEffect extends OneShotEffect {
BurnTheAccursedEffect() {
super(Outcome.Benefit);
staticText = "{this} deals 5 damage to target creature and 2 damage to that creature's controller.";
}
private BurnTheAccursedEffect(final BurnTheAccursedEffect effect) {
super(effect);
}
@Override
public BurnTheAccursedEffect copy() {
return new BurnTheAccursedEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getFirstTarget());
if (permanent == null) {
return false;
}
permanent.damage(5, source.getSourceId(), source, game);
Player player = game.getPlayer(permanent.getControllerId());
if (player != null) {
player.damage(2, source.getSourceId(), source, game);
}
return true;
}
}

View file

@ -25,7 +25,7 @@ public final class BurningOil extends CardImpl {
this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature());
this.getSpellAbility().addEffect(new DamageTargetEffect(3));
// Flashback {3}{W}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{W}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{W}")));
}
private BurningOil(final BurningOil card) {

View file

@ -12,7 +12,6 @@ import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.TimingRule;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
@ -36,9 +35,9 @@ public final class CabalTherapy extends CardImpl {
this.getSpellAbility().addEffect(new CabalTherapyEffect());
// Flashback-Sacrifice a creature.
this.addAbility(new FlashbackAbility(
new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT, true)),
TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new SacrificeTargetCost(
new TargetControlledCreaturePermanent(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT)
)));
}
private CabalTherapy(final CabalTherapy card) {

View file

@ -24,7 +24,7 @@ public final class CacklingCounterpart extends CardImpl {
this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
// Flashback {5}{U}{U}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{5}{U}{U}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{5}{U}{U}")));
}
private CacklingCounterpart(final CacklingCounterpart card) {

View file

@ -28,7 +28,7 @@ public final class CalibratedBlast extends CardImpl {
this.getSpellAbility().addEffect(new CalibratedBlastEffect());
// Flashback {3}{R}{R}
this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{R}{R}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{R}{R}")));
}
private CalibratedBlast(final CalibratedBlast card) {

View file

@ -29,6 +29,7 @@ import java.util.UUID;
import java.util.stream.Collectors;
import static mage.constants.Outcome.Benefit;
import mage.util.CardUtil;
/**
* @author TheElk801
@ -105,21 +106,24 @@ class CalixDestinysHandExileEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Player controller = game.getPlayer(source.getControllerId());
if (source.getTargets().size() > 2) {
return false;
}
source.getTargets();
Permanent theirPerm = game.getPermanent(source.getTargets().get(0).getFirstTarget());
Permanent myPerm = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (player == null || theirPerm == null || myPerm == null) {
if (controller == null
|| theirPerm == null
|| myPerm == null) {
return false;
}
MageObjectReference theirMor = new MageObjectReference(
theirPerm.getId(), theirPerm.getZoneChangeCounter(game) + 1, game
);
MageObjectReference myMor = new MageObjectReference(myPerm, game);
player.moveCards(theirPerm, Zone.EXILED, source, game);
UUID exileId = CardUtil.getExileZoneId(game, source);
controller.moveCardsToExile(theirPerm, source, game, true, exileId, myPerm.getLogName());
game.addDelayedTriggeredAbility(new CalixDestinysHandDelayedTriggeredAbility(theirMor, myMor), source);
return true;
}

View file

@ -24,7 +24,7 @@ public final class CallOfTheHerd extends CardImpl {
// Create a 3/3 green Elephant creature token.
this.getSpellAbility().addEffect(new CreateTokenEffect(new ElephantToken()));
// Flashback {3}{G}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{3}{G}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{3}{G}")));
}
private CallOfTheHerd(final CallOfTheHerd card) {

View file

@ -26,7 +26,7 @@ public final class CanopyClaws extends CardImpl {
this.getSpellAbility().addEffect(new LoseAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn));
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
// Flashback {G}
this.addAbility(new FlashbackAbility(new ManaCostsImpl("{G}"), TimingRule.INSTANT));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl("{G}")));
}
private CanopyClaws(final CanopyClaws card) {

View file

@ -44,7 +44,7 @@ public final class CantStayAway extends CardImpl {
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(filter));
// Flashback {3}{W}{B}
this.addAbility(new FlashbackAbility(new ManaCostsImpl<>("{3}{W}{B}"), TimingRule.SORCERY));
this.addAbility(new FlashbackAbility(this, new ManaCostsImpl<>("{3}{W}{B}")));
}
private CantStayAway(final CantStayAway card) {

View file

@ -30,7 +30,7 @@ public final class CatharCommando extends CardImpl {
this.addAbility(FlashAbility.getInstance());
// {1}, Sacrifice Cathar Commando: Destroy target artifact or enchantment.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{1}{G}"));
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{1}"));
ability.addCost(new SacrificeSourceCost());
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ARTIFACT_OR_ENCHANTMENT));
this.addAbility(ability);

View file

@ -0,0 +1,56 @@
package mage.cards.c;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.permanent.token.HumanToken;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class CatharsCall extends CardImpl {
public CatharsCall(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
this.subtype.add(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability);
// Enchanted creature has vigilance and "At the beginning of your end step, create a 1/1 white Human creature token."
ability = new SimpleStaticAbility(new GainAbilityAttachedEffect(VigilanceAbility.getInstance(), AttachmentType.AURA));
ability.addEffect(new GainAbilityAttachedEffect(
new BeginningOfEndStepTriggeredAbility(
new CreateTokenEffect(new HumanToken()),
TargetController.YOU, false
), AttachmentType.AURA
).setText("and \"At the beginning of your end step, create a 1/1 white Human creature token.\""));
this.addAbility(ability);
}
private CatharsCall(final CatharsCall card) {
super(card);
}
@Override
public CatharsCall copy() {
return new CatharsCall(this);
}
}

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