From c9ab896d2490382ed9c0d768431aa81ce683aecf Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 9 Aug 2021 11:25:51 +0400 Subject: [PATCH] * GUI: added auto-choose for replacement effects (remember answer in dialog + reset answer in popup menu + new option in preferences; #4360, #328, #4219, #6676, #7914); --- .../client/dialog/PickCheckBoxDialog.java | 4 +- .../mage/client/dialog/PickChoiceDialog.form | 9 +- .../mage/client/dialog/PickChoiceDialog.java | 39 ++++++-- .../mage/client/dialog/PreferencesDialog.form | 23 ++++- .../mage/client/dialog/PreferencesDialog.java | 32 ++++-- .../main/java/mage/client/game/GamePanel.java | 21 ++-- .../src/mage/player/human/HumanPlayer.java | 50 ++++++++-- .../abilities/effects/ContinuousEffects.java | 6 +- Mage/src/main/java/mage/choices/Choice.java | 55 +++++++++-- .../main/java/mage/choices/ChoiceImpl.java | 98 +++++++++++++++---- .../main/java/mage/players/net/UserData.java | 51 ++++++++-- 11 files changed, 311 insertions(+), 77 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickCheckBoxDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickCheckBoxDialog.java index 9aa36e19ca..5768bfe42b 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickCheckBoxDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickCheckBoxDialog.java @@ -324,9 +324,9 @@ public class PickCheckBoxDialog extends MageDialog { if (item != null) { if (choice.isKeyChoice()) { - choice.setChoiceByKey(item.getKey()); + choice.setChoiceByKey(item.getKey(), false); } else { - choice.setChoice(item.getKey()); + choice.setChoice(item.getKey(), false); } return true; } else { diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form index a6cffbba21..a4bd9250d2 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form @@ -161,7 +161,8 @@ - + + @@ -177,6 +178,7 @@ + @@ -203,6 +205,11 @@ + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java index 768d9e6220..7e4fd068a6 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java @@ -18,10 +18,11 @@ import mage.client.util.gui.MageDialogState; public class PickChoiceDialog extends MageDialog { Choice choice; + java.util.List allItems = new ArrayList<>(); DefaultListModel dataModel = new DefaultListModel<>(); - final private static String HTML_TEMPLATE = "
%s
"; + final private static String HTML_HEADERS_TEMPLATE = "
%s
"; public void showDialog(Choice choice) { showDialog(choice, null, null, null); @@ -40,9 +41,13 @@ public class PickChoiceDialog extends MageDialog { setLabelText(this.labelMessage, choice.getMessage()); setLabelText(this.labelSubMessage, choice.getSubMessage()); - btCancel.setEnabled(!choice.isRequired()); + // special choice (example: auto-choose answer next time) + cbSpecial.setVisible(choice.isSpecialEnabled()); + cbSpecial.setText(choice.getSpecialText()); + cbSpecial.setToolTipText(choice.getSpecialHint()); + // 2 modes: string or key-values // sore data in allItems for inremental filtering // http://logicbig.com/tutorials/core-java-tutorial/swing/list-filter/ @@ -199,7 +204,7 @@ public class PickChoiceDialog extends MageDialog { private void setLabelText(JLabel label, String text) { if ((text != null) && !text.equals("")) { - label.setText(String.format(HTML_TEMPLATE, text)); + label.setText(String.format(HTML_HEADERS_TEMPLATE, text)); label.setVisible(true); } else { label.setText(""); @@ -247,6 +252,7 @@ public class PickChoiceDialog extends MageDialog { public boolean setChoice() { KeyValueItem item = (KeyValueItem) this.listChoices.getSelectedValue(); + boolean isSpecial = choice.isSpecialEnabled() && cbSpecial.isSelected(); // auto select one item (after incemental filtering) if ((item == null) && (this.listChoices.getModel().getSize() == 1)) { @@ -256,12 +262,23 @@ public class PickChoiceDialog extends MageDialog { if (item != null) { if (choice.isKeyChoice()) { - choice.setChoiceByKey(item.getKey()); + choice.setChoiceByKey(item.getKey(), isSpecial); } else { - choice.setChoice(item.getKey()); + choice.setChoice(item.getKey(), isSpecial); } return true; } else { + // special choice can be empty + if (choice.isSpecialEnabled() && choice.isSpecialCanBeEmpty()) { + if (choice.isKeyChoice()) { + choice.setChoiceByKey(null, isSpecial); + } else { + choice.setChoice(null, isSpecial); + } + return true; + } + + // nothing to choose choice.clearChoice(); return false; } @@ -311,6 +328,7 @@ public class PickChoiceDialog extends MageDialog { panelCommands = new javax.swing.JPanel(); btOK = new javax.swing.JButton(); btCancel = new javax.swing.JButton(); + cbSpecial = new javax.swing.JCheckBox(); setResizable(true); @@ -386,19 +404,22 @@ public class PickChoiceDialog extends MageDialog { } }); + cbSpecial.setText("Remember choose"); + javax.swing.GroupLayout panelCommandsLayout = new javax.swing.GroupLayout(panelCommands); panelCommands.setLayout(panelCommandsLayout); panelCommandsLayout.setHorizontalGroup( panelCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(panelCommandsLayout.createSequentialGroup() - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(cbSpecial) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(btOK) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(btCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); - panelCommandsLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, btCancel, btOK); + panelCommandsLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btCancel, btOK}); panelCommandsLayout.setVerticalGroup( panelCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -406,7 +427,8 @@ public class PickChoiceDialog extends MageDialog { .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(panelCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(btCancel) - .addComponent(btOK)) + .addComponent(btOK) + .addComponent(cbSpecial)) .addContainerGap()) ); @@ -460,6 +482,7 @@ public class PickChoiceDialog extends MageDialog { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton btCancel; private javax.swing.JButton btOK; + private javax.swing.JCheckBox cbSpecial; private javax.swing.JTextField editSearch; private javax.swing.JLabel labelMessage; private javax.swing.JLabel labelSearch; diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form index a2eff5066e..c2f5942521 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -4082,8 +4082,8 @@
- - + +
@@ -4182,7 +4182,7 @@ - + @@ -4281,8 +4281,8 @@ - - + + @@ -4292,6 +4292,19 @@ + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index c175326c4a..6efba3c286 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -134,6 +134,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_PASS_PRIORITY_CAST = "passPriorityCast"; public static final String KEY_PASS_PRIORITY_ACTIVATION = "passPriorityActivation"; public static final String KEY_AUTO_ORDER_TRIGGER = "autoOrderTrigger"; + public static final String KEY_USE_SAME_SETTINGS_FOR_SAME_REPLACEMENT_EFFECTS = "useSameSettingsForSameReplacementEffects"; public static final String KEY_USE_FIRST_MANA_ABILITY = "useFirstManaAbility"; // mana auto payment @@ -501,6 +502,7 @@ public class PreferencesDialog extends javax.swing.JDialog { cbPassPriorityCast = new javax.swing.JCheckBox(); cbPassPriorityActivation = new javax.swing.JCheckBox(); cbAutoOrderTrigger = new javax.swing.JCheckBox(); + cbUseSameSettingsForReplacementEffect = new javax.swing.JCheckBox(); tabImages = new javax.swing.JPanel(); panelCardImages = new javax.swing.JPanel(); cbUseDefaultImageFolder = new javax.swing.JCheckBox(); @@ -1405,7 +1407,7 @@ public class PreferencesDialog extends javax.swing.JDialog { jLabelEndOfTurn.setText("End of turn:"); phases_stopSettings.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "SKIP settings")); - phases_stopSettings.setLayout(new java.awt.GridLayout(9, 1)); + phases_stopSettings.setLayout(new java.awt.GridLayout(10, 1)); cbStopAttack.setSelected(true); cbStopAttack.setText("STOP skips on declare attackers if attackers are available"); @@ -1493,8 +1495,8 @@ public class PreferencesDialog extends javax.swing.JDialog { }); phases_stopSettings.add(cbPassPriorityActivation); - cbAutoOrderTrigger.setText("Set order for your triggers automatically if all have the same text"); - cbAutoOrderTrigger.setToolTipText("If activated the order to put on the stack your triggers that trigger at the same time
\nis set automatically if all have the same text."); + cbAutoOrderTrigger.setText("TRIGGERS: auto-choose triggers order for same rule texts (put same triggers to the stack at default order)"); + cbAutoOrderTrigger.setToolTipText("If you put same triggers with same texts on the stack then auto-choose their order.
\nYou can change that settings anytime at the game."); cbAutoOrderTrigger.setActionCommand(""); cbAutoOrderTrigger.setPreferredSize(new java.awt.Dimension(300, 25)); cbAutoOrderTrigger.addActionListener(new java.awt.event.ActionListener() { @@ -1504,6 +1506,17 @@ public class PreferencesDialog extends javax.swing.JDialog { }); phases_stopSettings.add(cbAutoOrderTrigger); + cbUseSameSettingsForReplacementEffect.setText("REPLACEMENT EFFECTS: use same auto-choose settings for same cards (choose replacement effects order dialog)"); + cbUseSameSettingsForReplacementEffect.setToolTipText("If you setup auto-choose for one object/card then it will be applied for all other objects with same name.
\nYou can change that settings anytime at the game."); + cbUseSameSettingsForReplacementEffect.setActionCommand(""); + cbUseSameSettingsForReplacementEffect.setPreferredSize(new java.awt.Dimension(300, 25)); + cbUseSameSettingsForReplacementEffect.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cbUseSameSettingsForReplacementEffectActionPerformed(evt); + } + }); + phases_stopSettings.add(cbUseSameSettingsForReplacementEffect); + org.jdesktop.layout.GroupLayout tabPhasesLayout = new org.jdesktop.layout.GroupLayout(tabPhases); tabPhases.setLayout(tabPhasesLayout); tabPhasesLayout.setHorizontalGroup( @@ -1614,8 +1627,8 @@ public class PreferencesDialog extends javax.swing.JDialog { .add(jLabelEndOfTurn) .add(checkBoxEndTurnOthers)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED) - .add(phases_stopSettings, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) - .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .add(phases_stopSettings, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 291, Short.MAX_VALUE) + .addContainerGap()) ); tabsPanel.addTab("Phases & Priority", tabPhases); @@ -2925,6 +2938,7 @@ public class PreferencesDialog extends javax.swing.JDialog { save(prefs, dialog.cbPassPriorityCast, KEY_PASS_PRIORITY_CAST, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbPassPriorityActivation, KEY_PASS_PRIORITY_ACTIVATION, "true", "false", UPDATE_CACHE_POLICY); save(prefs, dialog.cbAutoOrderTrigger, KEY_AUTO_ORDER_TRIGGER, "true", "false", UPDATE_CACHE_POLICY); + save(prefs, dialog.cbUseSameSettingsForReplacementEffect, KEY_USE_SAME_SETTINGS_FOR_SAME_REPLACEMENT_EFFECTS, "true", "false", UPDATE_CACHE_POLICY); // images save(prefs, dialog.cbUseDefaultImageFolder, KEY_CARD_IMAGES_USE_DEFAULT, "true", "false", UPDATE_CACHE_POLICY); @@ -3309,6 +3323,10 @@ public class PreferencesDialog extends javax.swing.JDialog { } }//GEN-LAST:event_sliderGUISizeStateChanged + private void cbUseSameSettingsForReplacementEffectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbUseSameSettingsForReplacementEffectActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cbUseSameSettingsForReplacementEffectActionPerformed + private void showProxySettings() { Connection.ProxyType proxyType = (Connection.ProxyType) cbProxyType.getSelectedItem(); switch (proxyType) { @@ -3465,7 +3483,7 @@ public class PreferencesDialog extends javax.swing.JDialog { load(prefs, dialog.cbPassPriorityCast, KEY_PASS_PRIORITY_CAST, "true", "false"); load(prefs, dialog.cbPassPriorityActivation, KEY_PASS_PRIORITY_ACTIVATION, "true", "false"); load(prefs, dialog.cbAutoOrderTrigger, KEY_AUTO_ORDER_TRIGGER, "true", "true"); - + load(prefs, dialog.cbUseSameSettingsForReplacementEffect, KEY_USE_SAME_SETTINGS_FOR_SAME_REPLACEMENT_EFFECTS, "true", "true"); } private static void loadGuiSize(Preferences prefs) { @@ -3996,6 +4014,7 @@ public class PreferencesDialog extends javax.swing.JDialog { PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PASS_PRIORITY_CAST, "true").equals("true"), PreferencesDialog.getCachedValue(PreferencesDialog.KEY_PASS_PRIORITY_ACTIVATION, "true").equals("true"), PreferencesDialog.getCachedValue(PreferencesDialog.KEY_AUTO_ORDER_TRIGGER, "true").equals("true"), + PreferencesDialog.getCachedValue(PreferencesDialog.KEY_USE_SAME_SETTINGS_FOR_SAME_REPLACEMENT_EFFECTS, "true").equals("true"), PreferencesDialog.getCachedValue(PreferencesDialog.KEY_USE_FIRST_MANA_ABILITY, "false").equals("true"), userStrId ); @@ -4069,6 +4088,7 @@ public class PreferencesDialog extends javax.swing.JDialog { private javax.swing.JCheckBox cbUseDefaultBattleImage; private javax.swing.JCheckBox cbUseDefaultImageFolder; private javax.swing.JCheckBox cbUseRandomBattleImage; + private javax.swing.JCheckBox cbUseSameSettingsForReplacementEffect; private javax.swing.JLabel chatFontSizeLabel; private javax.swing.JCheckBox checkBoxBeforeCOthers; private javax.swing.JCheckBox checkBoxBeforeCYou; diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index d1bd6e8eef..0a10031925 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -108,7 +108,7 @@ public final class GamePanel extends javax.swing.JPanel { private boolean initComponents; - private Timer resizeTimer; // can't be final + private final Timer resizeTimer; // can't be final private enum PopUpMenuType { TRIGGER_ORDER @@ -128,6 +128,7 @@ public final class GamePanel extends javax.swing.JPanel { Map options; Set targets; } + private final LastGameData lastGameData = new LastGameData(); @@ -1746,18 +1747,22 @@ public final class GamePanel extends javax.swing.JPanel { // TODO: remember last choices and search incremental for same events? PickChoiceDialog pickChoice = new PickChoiceDialog(); pickChoice.showDialog(choice, objectId, choiceWindowState); + + // special mode adds # to the answer (server side code must process that prefix, see replacementEffectChoice) + String specialPrefix = choice.isChosenSpecial() ? "#" : ""; + + String valueToSend; if (choice.isKeyChoice()) { - SessionHandler.sendPlayerString(gameId, choice.getChoiceKey()); - /* // old code, auto complete was for auto scripting? - if (pickChoice.isAutoSelect()) { - SessionHandler.sendPlayerString(gameId, '#' + choice.getChoiceKey()); - } else { - SessionHandler.sendPlayerString(gameId, choice.getChoiceKey()); - }*/ + valueToSend = choice.getChoiceKey(); } else { + valueToSend = choice.getChoice(); SessionHandler.sendPlayerString(gameId, choice.getChoice()); } + SessionHandler.sendPlayerString(gameId, valueToSend == null ? null : specialPrefix + valueToSend); + + // keep dialog position choiceWindowState = new MageDialogState(pickChoice); + pickChoice.removeDialog(); } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index f264459bda..5eced2ccbb 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -74,7 +74,7 @@ public class HumanPlayer extends PlayerImpl { protected final Choice replacementEffectChoice; private static final Logger logger = Logger.getLogger(HumanPlayer.class); - protected HashSet autoSelectReplacementEffects = new HashSet<>(); + protected HashSet autoSelectReplacementEffects = new LinkedHashSet<>(); // must be sorted protected ManaCost currentlyUnpaidMana; protected Set triggerAutoOrderAbilityFirst = new HashSet<>(); @@ -96,9 +96,16 @@ public class HumanPlayer extends PlayerImpl { public HumanPlayer(String name, RangeOfInfluence range, int skill) { super(name, range); + human = true; + replacementEffectChoice = new ChoiceImpl(true); replacementEffectChoice.setMessage("Choose replacement effect to resolve first"); - human = true; + replacementEffectChoice.setSpecial( + true, + false, + "Remember answer", + "Choose same answer next time (you can reset saved answers by battlefield popup menu)" + ); } public HumanPlayer(final HumanPlayer player) { @@ -289,6 +296,8 @@ public class HumanPlayer extends PlayerImpl { if (falseText != null) { options.put("UI.right.btn.text", falseText); } + + // auto-answer if (source != null) { Boolean answer = requestAutoAnswerId.get(source.getOriginalId() + "#" + message); if (answer != null) { @@ -359,11 +368,20 @@ public class HumanPlayer extends PlayerImpl { return 0; } + // use auto-choice: + // - uses "always first" logic (choose in same order as user answer saves) + // - search same effects by text (object name [id]: rules) + // - autoSelectReplacementEffects is sorted set + // - must get "same settings" option between cycle/response (user can change it by preferences) + + boolean useSameSettings = getControllingPlayersUserData(game).isUseSameSettingsForReplacementEffects(); if (!autoSelectReplacementEffects.isEmpty()) { - for (String autoKey : autoSelectReplacementEffects) { + for (String autoText : autoSelectReplacementEffects) { int count = 0; + // find effect with same saved text for (String effectKey : rEffects.keySet()) { - if (effectKey.equals(autoKey)) { + String currentText = prepareReplacementText(rEffects.get(effectKey), useSameSettings); + if (currentText.equals(autoText)) { return count; } count++; @@ -371,10 +389,11 @@ public class HumanPlayer extends PlayerImpl { } } + replacementEffectChoice.clearChoice(); replacementEffectChoice.getChoices().clear(); replacementEffectChoice.setKeyChoices(rEffects); - // Check if there are different ones + // if same choices then select first int differentChoices = 0; String lastChoice = ""; for (String value : replacementEffectChoice.getKeyChoices().values()) { @@ -397,11 +416,18 @@ public class HumanPlayer extends PlayerImpl { logger.debug("Choose effect: " + response.getString()); if (response.getString() != null) { + // save auto-choice (effect's text) if (response.getString().startsWith("#")) { - autoSelectReplacementEffects.add(response.getString().substring(1)); - replacementEffectChoice.setChoiceByKey(response.getString().substring(1)); + replacementEffectChoice.setChoiceByKey(response.getString().substring(1), true); + if (replacementEffectChoice.isChosen()) { + // put auto-choice to the end + useSameSettings = getControllingPlayersUserData(game).isUseSameSettingsForReplacementEffects(); + String effectText = prepareReplacementText(replacementEffectChoice.getChoiceValue(), useSameSettings); + autoSelectReplacementEffects.remove(effectText); + autoSelectReplacementEffects.add(effectText); + } } else { - replacementEffectChoice.setChoiceByKey(response.getString()); + replacementEffectChoice.setChoiceByKey(response.getString(), false); } if (replacementEffectChoice.getChoiceKey() != null) { @@ -419,6 +445,14 @@ public class HumanPlayer extends PlayerImpl { return 0; } + private String prepareReplacementText(String fullText, boolean useSameSettingsForDifferentObjects) { + // remove object id from the rules text (example: object [abd]: rules -> object : rules) + if (useSameSettingsForDifferentObjects) { + fullText = fullText.replaceAll("\\[\\w+\\]", ""); + } + return fullText; + } + @Override public boolean choose(Outcome outcome, Choice choice, Game game) { if (gameInCheckPlayableState(game)) { diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 434286e542..883807e98e 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -189,7 +189,7 @@ public class ContinuousEffects implements Serializable { } } } else { - logger.error("No abilities for continuous effect: " + effect.toString()); + logger.error("No abilities for continuous effect: " + effect); } break; default: @@ -1282,7 +1282,7 @@ public class ContinuousEffects implements Serializable { logger.error("Effect is null: " + source.toString()); return; } else if (source == null) { - logger.warn("Adding effect without ability : " + effect.toString()); + logger.warn("Adding effect without ability : " + effect); } switch (effect.getEffectType()) { case REPLACEMENT: @@ -1369,6 +1369,8 @@ public class ContinuousEffects implements Serializable { } public Map getReplacementEffectsTexts(Map> rEffects, Game game) { + // warning, autoSelectReplacementEffects uses [object id] in texts as different settings, + // so if you change keys or texts logic then don't forget to change auto-choose too Map texts = new LinkedHashMap<>(); for (Map.Entry> entry : rEffects.entrySet()) { if (entry.getValue() != null) { diff --git a/Mage/src/main/java/mage/choices/Choice.java b/Mage/src/main/java/mage/choices/Choice.java index 3a6a4fa8db..21b1ed6a4f 100644 --- a/Mage/src/main/java/mage/choices/Choice.java +++ b/Mage/src/main/java/mage/choices/Choice.java @@ -1,55 +1,92 @@ - - package mage.choices; +import mage.util.Copyable; + +import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.Set; /** - * * @author BetaSteward_at_googlemail.com, JayDi85 */ -public interface Choice { +public interface Choice extends Serializable, Copyable { String getMessage(); + void setMessage(String message); String getSubMessage(); + void setSubMessage(String subMessage); void clearChoice(); + boolean isChosen(); + + boolean isChosenSpecial(); + boolean isRequired(); - Choice copy(); + // special mode for all choices + void setSpecial(boolean enabled, boolean canBeEmpty, String text, String hint); + + boolean isSpecialEnabled(); + + boolean isSpecialCanBeEmpty(); + + String getSpecialText(); + + String getSpecialHint(); // string choice void setChoices(Set choices); + Set getChoices(); - void setChoice(String choice); + + void setChoice(String choice, boolean isSpecial); + String getChoice(); + default void setChoice(String choice) { + setChoice(choice, false); + } + // key-value choice boolean isKeyChoice(); + void setKeyChoices(Map choices); - Map getKeyChoices(); - void setChoiceByKey(String choiceKey); + + Map getKeyChoices(); + + void setChoiceByKey(String choiceKey, boolean isSpecial); + String getChoiceKey(); + String getChoiceValue(); + default void setChoiceByKey(String choiceKey) { + setChoiceByKey(choiceKey, false); + } + // search boolean isSearchEnabled(); + void setSearchEnabled(boolean isEnabled); + void setSearchText(String searchText); + String getSearchText(); // sorting boolean isSortEnabled(); + void setSortData(Map sortData); + Map getSortData(); - // random choice + // random choice (for AI usage) void setRandomChoice(); + boolean setChoiceByAnswers(List answers, boolean removeSelectAnswerFromList); } diff --git a/Mage/src/main/java/mage/choices/ChoiceImpl.java b/Mage/src/main/java/mage/choices/ChoiceImpl.java index 0809bea419..e45fb02d2e 100644 --- a/Mage/src/main/java/mage/choices/ChoiceImpl.java +++ b/Mage/src/main/java/mage/choices/ChoiceImpl.java @@ -2,15 +2,15 @@ package mage.choices; import mage.util.RandomUtil; -import java.io.Serializable; import java.util.*; /** * @author BetaSteward_at_googlemail.com, JayDi85 */ -public class ChoiceImpl implements Choice, Serializable { +public class ChoiceImpl implements Choice { - protected boolean chosen; + protected boolean chosenNormal; + protected boolean chosenSpecial; protected final boolean required; protected String choice; protected String choiceKey; @@ -22,6 +22,13 @@ public class ChoiceImpl implements Choice, Serializable { protected boolean searchEnabled = true; // enable for all windows by default protected String searchText; + // special button with #-answer + // warning, only for human's GUI, not AI + protected boolean specialEnabled = false; + protected boolean specialCanBeEmpty = false; // enable if you want to select "nothing", but not cancel + protected String specialText = ""; + protected String specialHint = ""; + public ChoiceImpl() { this(false); } @@ -30,9 +37,10 @@ public class ChoiceImpl implements Choice, Serializable { this.required = required; } - public ChoiceImpl(ChoiceImpl choice) { + public ChoiceImpl(final ChoiceImpl choice) { this.choice = choice.choice; - this.chosen = choice.chosen; + this.chosenNormal = choice.chosenNormal; + this.chosenSpecial = choice.chosenSpecial; this.required = choice.required; this.message = choice.message; this.subMessage = choice.subMessage; @@ -42,18 +50,28 @@ public class ChoiceImpl implements Choice, Serializable { this.choiceKey = choice.choiceKey; this.keyChoices = choice.keyChoices; // list should never change for the same object so copy by reference TODO: check errors with that, it that ok? Color list is static this.sortData = choice.sortData; + this.specialEnabled = choice.specialEnabled; + this.specialCanBeEmpty = choice.specialCanBeEmpty; + this.specialText = choice.specialText; + this.specialHint = choice.specialHint; } @Override public boolean isChosen() { - return chosen; + return chosenNormal || chosenSpecial; + } + + @Override + public boolean isChosenSpecial() { + return chosenSpecial; } @Override public void clearChoice() { - choice = null; - choiceKey = null; - chosen = false; + this.choice = null; + this.choiceKey = null; + this.chosenNormal = false; + this.chosenSpecial = false; } @Override @@ -92,10 +110,17 @@ public class ChoiceImpl implements Choice, Serializable { } @Override - public void setChoice(String choice) { + public void setChoice(String choice, boolean isSpecial) { if (choices.contains(choice)) { this.choice = choice; - this.chosen = true; + this.chosenNormal = true; + this.chosenSpecial = isSpecial; + } + + if (isSpecial && this.specialCanBeEmpty && (choice == null || choice.isEmpty())) { + clearChoice(); + this.chosenNormal = false; + this.chosenSpecial = true; } } @@ -134,12 +159,19 @@ public class ChoiceImpl implements Choice, Serializable { } @Override - public void setChoiceByKey(String choiceKey) { + public void setChoiceByKey(String choiceKey, boolean isSpecial) { String choiceToSet = keyChoices.get(choiceKey); if (choiceToSet != null) { this.choice = choiceToSet; this.choiceKey = choiceKey; - this.chosen = true; + this.chosenNormal = true; + this.chosenSpecial = isSpecial; + } + + if (isSpecial && this.specialCanBeEmpty && (choiceKey == null || choiceKey.isEmpty())) { + clearChoice(); + this.chosenNormal = false; + this.chosenSpecial = true; } } @@ -191,14 +223,14 @@ public class ChoiceImpl implements Choice, Serializable { String[] vals = this.getKeyChoices().keySet().toArray(new String[0]); if (vals.length > 0) { int choiceNum = RandomUtil.nextInt(vals.length); - this.setChoiceByKey(vals[choiceNum]); + this.setChoiceByKey(vals[choiceNum], false); } } else { // string mode String[] vals = this.getChoices().toArray(new String[0]); if (vals.length > 0) { int choiceNum = RandomUtil.nextInt(vals.length); - this.setChoice(vals[choiceNum]); + this.setChoice(vals[choiceNum], false); } } } @@ -211,18 +243,18 @@ public class ChoiceImpl implements Choice, Serializable { for (String needChoice : answers) { for (Map.Entry currentChoice : this.getKeyChoices().entrySet()) { if (currentChoice.getKey().equals(needChoice)) { - this.setChoiceByKey(needChoice); + this.setChoiceByKey(needChoice, false); answers.remove(needChoice); return true; } } } - // no key answer found, try to macht by text starting with + // no key answer found, try to match by text starting with for (String needChoice : answers) { for (Map.Entry currentChoice : this.getKeyChoices().entrySet()) { if (currentChoice.getValue().startsWith(needChoice)) { - this.setChoiceByKey(currentChoice.getKey()); + this.setChoiceByKey(currentChoice.getKey(), false); answers.remove(needChoice); return true; } @@ -233,7 +265,7 @@ public class ChoiceImpl implements Choice, Serializable { for (String needChoice : answers) { for (String currentChoice : this.getChoices()) { if (currentChoice.equals(needChoice)) { - this.setChoice(needChoice); + this.setChoice(needChoice, false); answers.remove(needChoice); return true; } @@ -242,4 +274,32 @@ public class ChoiceImpl implements Choice, Serializable { } return false; // can't find answer } + + @Override + public void setSpecial(boolean enabled, boolean canBeEmpty, String text, String hint) { + this.specialEnabled = enabled; + this.specialCanBeEmpty = canBeEmpty; + this.specialText = text; + this.specialHint = hint; + } + + @Override + public boolean isSpecialEnabled() { + return this.specialEnabled; + } + + @Override + public boolean isSpecialCanBeEmpty() { + return this.specialCanBeEmpty; + } + + @Override + public String getSpecialText() { + return this.specialText; + } + + @Override + public String getSpecialHint() { + return this.specialHint; + } } diff --git a/Mage/src/main/java/mage/players/net/UserData.java b/Mage/src/main/java/mage/players/net/UserData.java index 06f2aff922..f136075d44 100644 --- a/Mage/src/main/java/mage/players/net/UserData.java +++ b/Mage/src/main/java/mage/players/net/UserData.java @@ -23,6 +23,7 @@ public class UserData implements Serializable { protected boolean passPriorityCast; protected boolean passPriorityActivation; protected boolean autoOrderTrigger; + protected boolean useSameSettingsForReplacementEffects; protected boolean useFirstManaAbility = false; private String userIdStr; protected Map> requestedHandPlayersList; // game -> players list @@ -36,10 +37,22 @@ public class UserData implements Serializable { private int constructedRating; private int limitedRating; - public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced, - boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps, - String flagName, boolean askMoveToGraveOrder, boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, - boolean passPriorityCast, boolean passPriorityActivation, boolean autoOrderTrigger, boolean useFirstManaAbility, String userIdStr) { + public UserData(UserGroup userGroup, + int avatarId, + boolean showAbilityPickerForced, + boolean allowRequestShowHandCards, + boolean confirmEmptyManaPool, + UserSkipPrioritySteps userSkipPrioritySteps, + String flagName, + boolean askMoveToGraveOrder, + boolean manaPoolAutomatic, + boolean manaPoolAutomaticRestricted, + boolean passPriorityCast, + boolean passPriorityActivation, + boolean autoOrderTrigger, + boolean useSameSettingsForReplacementEffects, + boolean useFirstManaAbility, + String userIdStr) { this.groupId = userGroup.getGroupId(); this.avatarId = avatarId; this.showAbilityPickerForced = showAbilityPickerForced; @@ -53,6 +66,7 @@ public class UserData implements Serializable { this.passPriorityCast = passPriorityCast; this.passPriorityActivation = passPriorityActivation; this.autoOrderTrigger = autoOrderTrigger; + this.useSameSettingsForReplacementEffects = useSameSettingsForReplacementEffects; this.useFirstManaAbility = useFirstManaAbility; this.matchHistory = ""; this.matchQuitRatio = 0; @@ -76,13 +90,31 @@ public class UserData implements Serializable { this.passPriorityCast = userData.passPriorityCast; this.passPriorityActivation = userData.passPriorityActivation; this.autoOrderTrigger = userData.autoOrderTrigger; + this.useSameSettingsForReplacementEffects = userData.useSameSettingsForReplacementEffects; this.useFirstManaAbility = userData.useFirstManaAbility; this.userIdStr = userData.userIdStr; // todo: why we don't update user stats here? => can't be updated from client side } public static UserData getDefaultUserDataView() { - return new UserData(UserGroup.DEFAULT, 0, false, false, true, new UserSkipPrioritySteps(), getDefaultFlagName(), false, true, true, false, false, false, false, ""); + return new UserData( + UserGroup.DEFAULT, + 0, + false, + false, + true, + new UserSkipPrioritySteps(), + getDefaultFlagName(), + false, + true, + true, + false, + false, + true, + true, + false, + "" + ); } public void setGroupId(int groupId) { @@ -115,10 +147,7 @@ public class UserData implements Serializable { public boolean isAllowRequestHandToPlayer(UUID gameId, UUID requesterPlayerId) { // once per game - boolean allowToPlayer = true; - if (requestedHandPlayersList.containsKey(gameId) && requestedHandPlayersList.get(gameId).contains(requesterPlayerId)) { - allowToPlayer = false; - } + boolean allowToPlayer = !requestedHandPlayersList.containsKey(gameId) || !requestedHandPlayersList.get(gameId).contains(requesterPlayerId); return isAllowRequestHandToAll() && allowToPlayer; } @@ -206,6 +235,10 @@ public class UserData implements Serializable { return autoOrderTrigger; } + public boolean isUseSameSettingsForReplacementEffects() { + return useSameSettingsForReplacementEffects; + } + public void setAutoOrderTrigger(boolean autoOrderTrigger) { this.autoOrderTrigger = autoOrderTrigger; }