GUI: Autochoose targets if choice can be made (#9206)

This commit is contained in:
Alex Vasile 2022-07-08 21:58:42 -04:00 committed by GitHub
parent 1e01efd49d
commit 96f6fbefc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 492 additions and 213 deletions

View file

@ -93,6 +93,10 @@ public final class Constants {
public static final int BATTLEFIELD_FEEDBACK_COLORIZING_MODE_ENABLE_BY_ONE_COLOR = 1;
public static final int BATTLEFIELD_FEEDBACK_COLORIZING_MODE_ENABLE_BY_MULTICOLOR = 2;
public static final int AUTO_TARGET_DISABLE = 0;
public static final int AUTO_TARGET_NON_FEEL_BAD = 1;
public static final int AUTO_TARGET_ALL = 2;
public interface IO {
String DEFAULT_IMAGES_DIR = "plugins" + File.separator + "images" + File.separator;
}

View file

@ -42,8 +42,8 @@
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tabsPanel" min="-2" pref="554" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="tabsPanel" pref="554" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="saveButton" alignment="3" min="-2" pref="30" max="-2" attributes="0"/>
<Component id="exitButton" alignment="3" min="-2" pref="30" max="-2" attributes="0"/>
@ -98,7 +98,7 @@
<Component id="main_gamelog" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="main_battlefield" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace pref="22" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -171,18 +171,19 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Component id="tooltipDelayLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="383" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" alignment="0" groupAlignment="1" max="-2" attributes="0">
<Component id="tooltipDelayLabel" max="32767" attributes="0"/>
<Component id="tooltipDelay" alignment="1" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="showCardName" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="showFullImagePath" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="tooltipDelay" alignment="0" min="-2" pref="522" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -268,22 +269,25 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="showPlayerNamesPermanently" alignment="0" max="32767" attributes="0"/>
<Component id="nonLandPermanentsInOnePile" alignment="0" max="32767" attributes="0"/>
<Component id="cbConfirmEmptyManaPool" alignment="0" max="32767" attributes="0"/>
<Component id="cbAllowRequestToShowHandCards" alignment="0" max="32767" attributes="0"/>
<Component id="cbShowStormCounter" alignment="0" max="32767" attributes="0"/>
<Component id="cbAskMoveToGraveOrder" alignment="0" max="32767" attributes="0"/>
<Component id="showAbilityPickerForced" alignment="0" max="32767" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Component id="displayLifeOnAvatar" alignment="0" max="32767" attributes="0"/>
</Group>
<Component id="lblTargetAutoChoose" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbTargetAutoChooseLevel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
<Component id="displayLifeOnAvatar" alignment="0" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cbAskMoveToGraveOrder" alignment="0" min="-2" pref="596" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="0" max="-2" attributes="0">
<Component id="showPlayerNamesPermanently" alignment="0" max="32767" attributes="0"/>
<Component id="nonLandPermanentsInOnePile" alignment="0" max="32767" attributes="0"/>
<Component id="cbConfirmEmptyManaPool" alignment="0" max="32767" attributes="0"/>
<Component id="cbAllowRequestToShowHandCards" alignment="0" max="32767" attributes="0"/>
<Component id="cbShowStormCounter" alignment="0" max="32767" attributes="0"/>
<Component id="showAbilityPickerForced" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="315" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -305,6 +309,11 @@
<Component id="cbConfirmEmptyManaPool" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbAskMoveToGraveOrder" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblTargetAutoChoose" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbTargetAutoChooseLevel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
@ -328,7 +337,6 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Show player names on avatar permanently"/>
<Property name="toolTipText" type="java.lang.String" value="Instead showing the names only if you hover over the avatar with the mouse, the name is shown all the time."/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showPlayerNamesPermanentlyActionPerformed"/>
@ -339,7 +347,6 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Display life on avatar image"/>
<Property name="toolTipText" type="java.lang.String" value="Display the player&apos;s life over its avatar image."/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="displayLifeOnAvatarActionPerformed"/>
@ -350,7 +357,6 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Show ability picker for 1 available option (spells without costs, mdf/split side, adventure)"/>
<Property name="toolTipText" type="java.lang.String" value="This prevents you from accidently activating abilities what you don&apos;t want (example: if you haven&apos;t mana to cast main side, but clicks on mdf card and play land instead)"/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showAbilityPickerForcedActionPerformed"/>
@ -361,7 +367,6 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Allow requests from players and spectators to show your hand cards"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;html&gt;This is the default setting used for your matches. If activated other players or spectators&lt;br&gt;&#xa;of your match can send a request so you can allow them to see your hand cards."/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbAllowRequestToShowHandCardsActionPerformed"/>
@ -372,7 +377,6 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Show the number of spell casts during the current turn"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;html&gt;Adds a little box left to the short keys line with the number&lt;br&gt;&#xa;of spells already cast during the current turn (storm counter)."/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbShowStormCounterActionPerformed"/>
@ -383,7 +387,6 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Confirm if you want to pass a phase/step but there is still mana in your mana pool"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;html&gt;If activated you get a confirm message if you pass priority while stack is empty&lt;br&gt;&#xa; and you still have mana in your mana pool."/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbConfirmEmptyManaPoolActionPerformed"/>
@ -394,12 +397,41 @@
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" value="Ask player for setting order cards go to graveyard"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;html&gt;If activated and multiple cards go to the graveyard at the same time&lt;br&gt;&#xa;the player is asked to set the order of the cards."/>
<Property name="horizontalAlignment" type="int" value="2"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbAskMoveToGraveOrderActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="lblTargetAutoChoose">
<Properties>
<Property name="text" type="java.lang.String" value="Auto-choose targets for player:"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;html&gt;&#xa;When there is only one possible outcome for targeting, the targets can be chosen for you.&#xa;&lt;br&gt;&#xa;&lt;b&gt;None:&lt;/b&gt; All targeting must be done by the player.&#xa;&lt;br&gt;&#xa;&lt;b&gt;Most:&lt;/b&gt; All targeting other than feel-bad effects (discarding, destroy, sacrifice, exile) that target you, a card you own, or a permanent/spell you control.&#xa;&lt;br&gt;&#xa;&lt;b&gt;All:&lt;/b&gt; All targeting that can be automated will be."/>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="cbTargetAutoChooseLevel">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="3">
<StringItem index="0" value="Off"/>
<StringItem index="1" value="Most"/>
<StringItem index="2" value="All"/>
</StringArray>
</Property>
<Property name="selectedIndex" type="int" value="1"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection component="lblTargetAutoChoose" name="toolTipText" type="property"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value="Auto-choose targets for player combo box"/>
</AccessibilityProperties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbTargetAutoChooseLevelActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="main_battlefield">
@ -4082,7 +4114,7 @@
<Component id="checkBoxEndTurnOthers" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="phases_stopSettings" pref="291" max="32767" attributes="0"/>
<Component id="phases_stopSettings" pref="354" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
@ -4341,7 +4373,7 @@
<Component id="panelCardImages" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="panelBackgroundImages" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="98" max="32767" attributes="0"/>
<EmptySpace pref="142" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -6297,12 +6329,12 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="526" max="32767" attributes="0"/>
<EmptySpace min="0" pref="623" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
<Component id="themesCategory" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="430" max="32767" attributes="0"/>
<EmptySpace pref="523" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
@ -6395,7 +6427,6 @@
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 30]"/>
</Property>
<Property name="verticalAlignment" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="saveButtonActionPerformed"/>
@ -6413,7 +6444,6 @@
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 30]"/>
</Property>
<Property name="verticalAlignment" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exitButtonActionPerformed"/>

View file

@ -31,6 +31,7 @@ import java.util.*;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD;
import static mage.client.constants.Constants.BATTLEFIELD_FEEDBACK_COLORIZING_MODE_ENABLE_BY_MULTICOLOR;
import static mage.constants.Constants.*;
@ -236,6 +237,9 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_NEW_TOURNAMENT_MINIMUM_RATING = "newTournamentMinimumRating";
public static final String KEY_NEW_TOURNAMENT_RATED = "newTournamentRated";
// Settings for auto-choosing targets
public static final String KEY_AUTO_TARGET_LEVEL = "autoTargetLevel";
// pref setting for deck generator
public static final String KEY_NEW_DECK_GENERATOR_DECK_SIZE = "newDeckGeneratorDeckSize";
public static final String KEY_NEW_DECK_GENERATOR_SET = "newDeckGeneratorSet";
@ -435,6 +439,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbShowStormCounter = new javax.swing.JCheckBox();
cbConfirmEmptyManaPool = new javax.swing.JCheckBox();
cbAskMoveToGraveOrder = new javax.swing.JCheckBox();
lblTargetAutoChoose = new javax.swing.JLabel();
cbTargetAutoChooseLevel = new javax.swing.JComboBox<>();
main_battlefield = new javax.swing.JPanel();
cbBattlefieldFeedbackColorizingMode = new javax.swing.JComboBox();
lblBattlefieldFeedbackColorizingMode = new javax.swing.JLabel();
@ -689,15 +695,16 @@ public class PreferencesDialog extends javax.swing.JDialog {
main_cardLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(main_cardLayout.createSequentialGroup()
.add(6, 6, 6)
.add(tooltipDelayLabel)
.addContainerGap(383, Short.MAX_VALUE))
.add(main_cardLayout.createSequentialGroup()
.add(main_cardLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(main_cardLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING, false)
.add(tooltipDelayLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(tooltipDelay, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.add(main_cardLayout.createSequentialGroup()
.add(showCardName)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
.add(showFullImagePath)))
.addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.add(showFullImagePath))
.add(tooltipDelay, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 522, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
.add(0, 0, Short.MAX_VALUE))
);
main_cardLayout.setVerticalGroup(
main_cardLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
@ -725,7 +732,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
showPlayerNamesPermanently.setSelected(true);
showPlayerNamesPermanently.setText("Show player names on avatar permanently");
showPlayerNamesPermanently.setToolTipText("Instead showing the names only if you hover over the avatar with the mouse, the name is shown all the time.");
showPlayerNamesPermanently.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
showPlayerNamesPermanently.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
showPlayerNamesPermanentlyActionPerformed(evt);
@ -735,7 +741,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
displayLifeOnAvatar.setSelected(true);
displayLifeOnAvatar.setText("Display life on avatar image");
displayLifeOnAvatar.setToolTipText("Display the player's life over its avatar image.");
displayLifeOnAvatar.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
displayLifeOnAvatar.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
displayLifeOnAvatarActionPerformed(evt);
@ -745,7 +750,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
showAbilityPickerForced.setSelected(true);
showAbilityPickerForced.setText("Show ability picker for 1 available option (spells without costs, mdf/split side, adventure)");
showAbilityPickerForced.setToolTipText("This prevents you from accidently activating abilities what you don't want (example: if you haven't mana to cast main side, but clicks on mdf card and play land instead)");
showAbilityPickerForced.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
showAbilityPickerForced.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
showAbilityPickerForcedActionPerformed(evt);
@ -755,7 +759,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbAllowRequestToShowHandCards.setSelected(true);
cbAllowRequestToShowHandCards.setText("Allow requests from players and spectators to show your hand cards");
cbAllowRequestToShowHandCards.setToolTipText("<html>This is the default setting used for your matches. If activated other players or spectators<br>\nof your match can send a request so you can allow them to see your hand cards.");
cbAllowRequestToShowHandCards.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
cbAllowRequestToShowHandCards.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cbAllowRequestToShowHandCardsActionPerformed(evt);
@ -765,7 +768,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbShowStormCounter.setSelected(true);
cbShowStormCounter.setText("Show the number of spell casts during the current turn");
cbShowStormCounter.setToolTipText("<html>Adds a little box left to the short keys line with the number<br>\nof spells already cast during the current turn (storm counter).");
cbShowStormCounter.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
cbShowStormCounter.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cbShowStormCounterActionPerformed(evt);
@ -775,7 +777,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbConfirmEmptyManaPool.setSelected(true);
cbConfirmEmptyManaPool.setText("Confirm if you want to pass a phase/step but there is still mana in your mana pool");
cbConfirmEmptyManaPool.setToolTipText("<html>If activated you get a confirm message if you pass priority while stack is empty<br>\n and you still have mana in your mana pool.");
cbConfirmEmptyManaPool.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
cbConfirmEmptyManaPool.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cbConfirmEmptyManaPoolActionPerformed(evt);
@ -785,32 +786,46 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbAskMoveToGraveOrder.setSelected(true);
cbAskMoveToGraveOrder.setText("Ask player for setting order cards go to graveyard");
cbAskMoveToGraveOrder.setToolTipText("<html>If activated and multiple cards go to the graveyard at the same time<br>\nthe player is asked to set the order of the cards.");
cbAskMoveToGraveOrder.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
cbAskMoveToGraveOrder.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cbAskMoveToGraveOrderActionPerformed(evt);
}
});
lblTargetAutoChoose.setText("Auto-choose targets for player:");
lblTargetAutoChoose.setToolTipText("<html>\nWhen there is only one possible outcome for targeting, the targets can be chosen for you.\n<br>\n<b>None:</b> All targeting must be done by the player.\n<br>\n<b>Most:</b> All targeting other than feel-bad effects (discarding, destroy, sacrifice, exile) that target you, a card you own, or a permanent/spell you control.\n<br>\n<b>All:</b> All targeting that can be automated will be.");
cbTargetAutoChooseLevel.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Off", "Most", "All" }));
cbTargetAutoChooseLevel.setSelectedIndex(1);
cbTargetAutoChooseLevel.setToolTipText(lblTargetAutoChoose.getToolTipText());
cbTargetAutoChooseLevel.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cbTargetAutoChooseLevelActionPerformed(evt);
}
});
org.jdesktop.layout.GroupLayout main_gameLayout = new org.jdesktop.layout.GroupLayout(main_game);
main_game.setLayout(main_gameLayout);
main_gameLayout.setHorizontalGroup(
main_gameLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(main_gameLayout.createSequentialGroup()
.addContainerGap()
.add(lblTargetAutoChoose)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbTargetAutoChooseLevel, 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(displayLifeOnAvatar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(main_gameLayout.createSequentialGroup()
.add(main_gameLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(main_gameLayout.createSequentialGroup()
.add(main_gameLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
.add(showPlayerNamesPermanently, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(nonLandPermanentsInOnePile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbConfirmEmptyManaPool, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbAllowRequestToShowHandCards, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbShowStormCounter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbAskMoveToGraveOrder, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(showAbilityPickerForced, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.add(0, 0, Short.MAX_VALUE))
.add(displayLifeOnAvatar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
.add(cbAskMoveToGraveOrder, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 596, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(main_gameLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
.add(showPlayerNamesPermanently, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(nonLandPermanentsInOnePile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbConfirmEmptyManaPool, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbAllowRequestToShowHandCards, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(cbShowStormCounter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(showAbilityPickerForced)))
.add(0, 315, Short.MAX_VALUE))
);
main_gameLayout.setVerticalGroup(
main_gameLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
@ -829,10 +844,15 @@ public class PreferencesDialog extends javax.swing.JDialog {
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbConfirmEmptyManaPool)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbAskMoveToGraveOrder))
.add(cbAskMoveToGraveOrder)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(main_gameLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(lblTargetAutoChoose)
.add(cbTargetAutoChooseLevel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
);
nonLandPermanentsInOnePile.getAccessibleContext().setAccessibleName("nonLandPermanentsInOnePile");
cbTargetAutoChooseLevel.getAccessibleContext().setAccessibleName("Auto-choose targets for player combo box");
main_battlefield.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Battlefield"));
@ -889,7 +909,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(main_gamelog, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(main_battlefield, 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))
.addContainerGap(22, Short.MAX_VALUE))
);
main_card.getAccessibleContext().setAccessibleName("Game panel");
@ -1627,7 +1647,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(jLabelEndOfTurn)
.add(checkBoxEndTurnOthers))
.addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
.add(phases_stopSettings, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 291, Short.MAX_VALUE)
.add(phases_stopSettings, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 354, Short.MAX_VALUE)
.addContainerGap())
);
@ -1852,7 +1872,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(panelCardImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(panelBackgroundImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addContainerGap(98, Short.MAX_VALUE))
.addContainerGap(142, Short.MAX_VALUE))
);
tabsPanel.addTab("Images", tabImages);
@ -2819,12 +2839,12 @@ public class PreferencesDialog extends javax.swing.JDialog {
);
tabThemesLayout.setVerticalGroup(
tabThemesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(0, 526, Short.MAX_VALUE)
.add(0, 623, Short.MAX_VALUE)
.add(tabThemesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(tabThemesLayout.createSequentialGroup()
.add(21, 21, 21)
.add(themesCategory, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addContainerGap(430, Short.MAX_VALUE)))
.addContainerGap(523, Short.MAX_VALUE)))
);
tabsPanel.addTab("Themes", tabThemes);
@ -2833,7 +2853,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
saveButton.setMaximumSize(new java.awt.Dimension(100, 30));
saveButton.setMinimumSize(new java.awt.Dimension(100, 30));
saveButton.setPreferredSize(new java.awt.Dimension(100, 30));
saveButton.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM);
saveButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
saveButtonActionPerformed(evt);
@ -2844,7 +2863,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
exitButton.setMaximumSize(new java.awt.Dimension(100, 30));
exitButton.setMinimumSize(new java.awt.Dimension(100, 30));
exitButton.setPreferredSize(new java.awt.Dimension(100, 30));
exitButton.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM);
exitButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
exitButtonActionPerformed(evt);
@ -2869,8 +2887,8 @@ public class PreferencesDialog extends javax.swing.JDialog {
layout.setVerticalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(layout.createSequentialGroup()
.add(tabsPanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 554, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(tabsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 554, Short.MAX_VALUE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(saveButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 30, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(exitButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 30, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
@ -2910,6 +2928,16 @@ public class PreferencesDialog extends javax.swing.JDialog {
}
}
String paramNameAutoTarget = KEY_AUTO_TARGET_LEVEL;
int paramValueAutoTarger = dialog.cbTargetAutoChooseLevel.getSelectedIndex();
int paramDefaultAutoTarget = AUTO_TARGET_NON_FEEL_BAD;
if (getCachedValue(paramNameAutoTarget, paramDefault) != paramValueAutoTarger) {
prefs.putInt(paramNameAutoTarget, paramValueAutoTarger);
if (UPDATE_CACHE_POLICY) {
updateCache(paramNameAutoTarget, Integer.toString(paramValueAutoTarger));
}
}
saveGUISize();
// Phases & Priority
@ -3327,6 +3355,10 @@ public class PreferencesDialog extends javax.swing.JDialog {
// TODO add your handling code here:
}//GEN-LAST:event_cbUseSameSettingsForReplacementEffectActionPerformed
private void cbTargetAutoChooseLevelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTargetAutoChooseLevelActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_cbTargetAutoChooseLevelActionPerformed
private void showProxySettings() {
Connection.ProxyType proxyType = (Connection.ProxyType) cbProxyType.getSelectedItem();
switch (proxyType) {
@ -3458,6 +3490,17 @@ public class PreferencesDialog extends javax.swing.JDialog {
dialog.cbBattlefieldFeedbackColorizingMode.setSelectedIndex(BATTLEFIELD_FEEDBACK_COLORIZING_MODE_ENABLE_BY_MULTICOLOR);
}
String autoTargetParam;
try {
autoTargetParam = MageFrame.getPreferences().get(KEY_AUTO_TARGET_LEVEL, "1");
int autoTargetMode = Integer.parseInt(autoTargetParam);
dialog.cbTargetAutoChooseLevel.setSelectedIndex(autoTargetMode);
} catch (Throwable e) {
autoTargetParam = "";
dialog.cbTargetAutoChooseLevel.setSelectedIndex(AUTO_TARGET_NON_FEEL_BAD);
logger.error("Can't Parse and setup param " + KEY_AUTO_TARGET_LEVEL + " = " + autoTargetParam, e);
}
load(prefs, dialog.checkBoxUpkeepYou, UPKEEP_YOU, "on", "on");
load(prefs, dialog.checkBoxDrawYou, DRAW_YOU, "on", "on");
load(prefs, dialog.checkBoxMainYou, MAIN_YOU, "on", "on");
@ -4014,6 +4057,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_AUTO_TARGET_LEVEL, 1),
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
@ -4083,6 +4127,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JCheckBox cbStopOnAllEnd;
private javax.swing.JCheckBox cbStopOnAllMain;
private javax.swing.JCheckBox cbStopOnNewStackObjects;
private javax.swing.JComboBox<String> cbTargetAutoChooseLevel;
private javax.swing.JComboBox<ThemeType> cbTheme;
private javax.swing.JCheckBox cbUseDefaultBackground;
private javax.swing.JCheckBox cbUseDefaultBattleImage;
@ -4192,6 +4237,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JLabel lblProxyServer;
private javax.swing.JLabel lblProxyType;
private javax.swing.JLabel lblProxyUserName;
private javax.swing.JLabel lblTargetAutoChoose;
private javax.swing.JLabel lblURLServerList;
private javax.swing.JLabel lebelSkip;
private javax.swing.JPanel main_battlefield;

View file

@ -69,12 +69,12 @@
cardArea.clearCardEventListeners();
cardArea.loadCards(showCards, bigCard, gameId);
if (options != null) {
if (options.containsKey("chosen")) {
java.util.List<UUID> chosenCards = (java.util.List<UUID>) options.get("chosen");
if (options.containsKey("chosenTargets")) {
java.util.List<UUID> chosenCards = (java.util.List<UUID>) options.get("chosenTargets");
cardArea.selectCards(chosenCards);
}
if (options.containsKey("choosable")) {
java.util.List<UUID> choosableCards = (java.util.List<UUID>) options.get("choosable");
if (options.containsKey("possibleTargets")) {
java.util.List<UUID> choosableCards = (java.util.List<UUID>) options.get("possibleTargets");
cardArea.markCards(choosableCards);
}
if (options.containsKey("queryType") && options.get("queryType") == QueryType.PICK_ABILITY) {

View file

@ -1382,8 +1382,8 @@ public final class GamePanel extends javax.swing.JPanel {
}
List<UUID> needChosen;
if (lastGameData.options != null && lastGameData.options.containsKey("chosen")) {
needChosen = (List<UUID>) lastGameData.options.get("chosen");
if (lastGameData.options != null && lastGameData.options.containsKey("chosenTargets")) {
needChosen = (List<UUID>) lastGameData.options.get("chosenTargets");
} else {
needChosen = new ArrayList<>();
}

View file

@ -519,8 +519,8 @@ public class HumanPlayer extends PlayerImpl {
}
while (canRespond()) {
Set<UUID> targetIds = target.possibleTargets(abilityControllerId, source, game);
if (targetIds == null || targetIds.isEmpty()) {
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game);
if (possibleTargetIds == null || possibleTargetIds.isEmpty()) {
return target.getTargets().size() >= target.getNumberOfTargets();
}
@ -529,17 +529,22 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
java.util.List<UUID> chosen = target.getTargets();
options.put("chosen", (Serializable) chosen);
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
updateGameStatePriority("choose(5)", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), targetIds, required, getOptions(target, options));
// responseId is null if a choice couldn't be automatically made
if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
options.put("chosenTargets", (Serializable) chosenTargets);
updateGameStatePriority("choose(5)", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargetIds, required, getOptions(target, options));
}
waitForResponse(game);
responseId = getFixedResponseUUID(game);
}
waitForResponse(game);
UUID responseId = getFixedResponseUUID(game);
if (responseId != null) {
// selected some target
@ -549,7 +554,7 @@ public class HumanPlayer extends PlayerImpl {
continue;
}
if (!targetIds.contains(responseId)) {
if (!possibleTargetIds.contains(responseId)) {
continue;
}
@ -617,25 +622,32 @@ public class HumanPlayer extends PlayerImpl {
Map<String, Serializable> options = new HashMap<>();
while (canRespond()) {
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game);
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
if (possibleTargets.isEmpty()
if (possibleTargetIds.isEmpty()
|| target.getTargets().size() >= target.getNumberOfTargets()) {
required = false;
}
java.util.List<UUID> chosen = target.getTargets();
options.put("chosen", (Serializable) chosen);
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
updateGameStatePriority("chooseTarget", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)),
possibleTargets, required, getOptions(target, options));
// responseId is null if a choice couldn't be automatically made
if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
options.put("chosenTargets", (Serializable) chosenTargets);
updateGameStatePriority("chooseTarget", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)),
possibleTargetIds, required, getOptions(target, options));
}
waitForResponse(game);
responseId = getFixedResponseUUID(game);
}
waitForResponse(game);
UUID responseId = getFixedResponseUUID(game);
if (responseId != null) {
// remove selected
if (target.getTargets().contains(responseId)) {
@ -643,7 +655,7 @@ public class HumanPlayer extends PlayerImpl {
continue;
}
if (possibleTargets.contains(responseId)) {
if (possibleTargetIds.contains(responseId)) {
if (target.canTarget(abilityControllerId, responseId, source, game)) {
target.addTarget(responseId, source, game);
if (target.doneChoosing()) {
@ -687,10 +699,12 @@ public class HumanPlayer extends PlayerImpl {
return false;
}
UUID abilityControllerId = playerId;
UUID abilityControllerId;
if (target.getTargetController() != null
&& target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
} else {
abilityControllerId = playerId;
}
while (canRespond()) {
@ -701,32 +715,37 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
Map<String, Serializable> options = getOptions(target, null);
java.util.List<UUID> chosen = target.getTargets();
options.put("chosen", (Serializable) chosen);
java.util.List<UUID> choosable = new ArrayList<>();
List<UUID> chosenTargets = target.getTargets();
List<UUID> possibleTargets = new ArrayList<>();
for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, null, cards, game)) {
choosable.add(cardId);
possibleTargets.add(cardId);
}
}
if (!choosable.isEmpty()) {
options.put("choosable", (Serializable) choosable);
}
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
if (required && choosable.isEmpty()) {
if (required && possibleTargets.isEmpty()) {
required = false;
}
updateGameStatePriority("choose(4)", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options);
}
waitForResponse(game);
UUID responseId = target.tryToAutoChoose(abilityControllerId, null, game, possibleTargets);
if (responseId == null) {
Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) chosenTargets);
if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets);
}
updateGameStatePriority("choose(4)", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options);
}
waitForResponse(game);
responseId = getFixedResponseUUID(game);
}
UUID responseId = getFixedResponseUUID(game);
if (responseId != null) {
if (target.getTargets().contains(responseId)) { // if already included remove it with
target.remove(responseId);
@ -762,10 +781,12 @@ public class HumanPlayer extends PlayerImpl {
return false;
}
UUID abilityControllerId = playerId;
UUID abilityControllerId;
if (target.getTargetController() != null
&& target.getAbilityController() != null) {
abilityControllerId = target.getAbilityController();
} else {
abilityControllerId = playerId;
}
while (canRespond()) {
@ -776,32 +797,38 @@ public class HumanPlayer extends PlayerImpl {
required = false;
}
Map<String, Serializable> options = getOptions(target, null);
java.util.List<UUID> chosen = target.getTargets();
options.put("chosen", (Serializable) chosen);
java.util.List<UUID> choosable = new ArrayList<>();
List<UUID> possibleTargets = new ArrayList<>();
for (UUID cardId : cards) {
if (target.canTarget(abilityControllerId, cardId, source, cards, game)) {
choosable.add(cardId);
possibleTargets.add(cardId);
}
}
if (!choosable.isEmpty()) {
options.put("choosable", (Serializable) choosable);
}
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
if (required && choosable.isEmpty()) {
if (required && possibleTargets.isEmpty()) {
required = false;
}
updateGameStatePriority("chooseTarget(5)", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options);
}
waitForResponse(game);
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game, possibleTargets);
if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) chosenTargets);
if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets);
}
updateGameStatePriority("chooseTarget(5)", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options);
}
waitForResponse(game);
responseId = getFixedResponseUUID(game);
}
UUID responseId = getFixedResponseUUID(game);
if (responseId != null) {
if (target.getTargets().contains(responseId)) { // if already included remove it
target.remove(responseId);
@ -849,47 +876,53 @@ public class HumanPlayer extends PlayerImpl {
// 1. Select targets
while (canRespond()) {
Set<UUID> possibleTargets = target.possibleTargets(abilityControllerId, source, game);
Set<UUID> possibleTargetIds = target.possibleTargets(abilityControllerId, source, game);
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
if (possibleTargets.isEmpty()
if (possibleTargetIds.isEmpty()
|| target.getSize() >= target.getNumberOfTargets()) {
required = false;
}
// selected
Map<String, Serializable> options = getOptions(target, null);
java.util.List<UUID> chosen = target.getTargets();
options.put("chosen", (Serializable) chosen);
// selectable
java.util.List<UUID> choosable = new ArrayList<>();
for (UUID targetId : possibleTargets) {
if (target.canTarget(abilityControllerId, targetId, source, game)) {
choosable.add(targetId);
UUID responseId = target.tryToAutoChoose(abilityControllerId, source, game);
// responseId is null if a choice couldn't be automatically made
if (responseId == null) {
List<UUID> chosenTargets = target.getTargets();
List<UUID> possibleTargets = new ArrayList<>();
for (UUID targetId : possibleTargetIds) {
if (target.canTarget(abilityControllerId, targetId, source, game)) {
possibleTargets.add(targetId);
}
}
}
if (!choosable.isEmpty()) {
options.put("choosable", (Serializable) choosable);
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
if (required && possibleTargets.isEmpty()) {
required = false;
}
// selected
Map<String, Serializable> options = getOptions(target, null);
options.put("chosenTargets", (Serializable) chosenTargets);
if (!possibleTargets.isEmpty()) {
options.put("possibleTargets", (Serializable) possibleTargets);
}
updateGameStatePriority("chooseTargetAmount", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
// target amount uses for damage only, if you see another use case then message must be changed here and on getMultiAmount call
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargetIds, required, options);
}
waitForResponse(game);
responseId = getFixedResponseUUID(game);
}
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
if (required && choosable.isEmpty()) {
required = false;
}
updateGameStatePriority("chooseTargetAmount", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
// target amount uses for damage only, if you see another use case then message must be changed here and on getMultiAmount call
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargets, required, options);
}
waitForResponse(game);
UUID responseId = getFixedResponseUUID(game);
if (responseId != null) {
if (target.contains(responseId)) {
// unselect
target.remove(responseId);
} else if (possibleTargets.contains(responseId) && target.canTarget(abilityControllerId, responseId, source, game)) {
} else if (possibleTargetIds.contains(responseId) && target.canTarget(abilityControllerId, responseId, source, game)) {
// select
target.addTarget(responseId, source, game);
}
@ -1892,6 +1925,8 @@ public class HumanPlayer extends PlayerImpl {
return;
}
UUID responseId = null;
updateGameStatePriority("selectCombatGroup", game);
prepareForResponse(game);
if (!isExecutingMacro()) {
@ -1905,12 +1940,19 @@ public class HumanPlayer extends PlayerImpl {
possibleTargets.add(attackerId);
}
}
game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)),
possibleTargets, false, getOptions(target, null));
if (possibleTargets.size() == 1) {
responseId = possibleTargets.stream().iterator().next();
} else {
game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)),
possibleTargets, false, getOptions(target, null));
}
}
waitForResponse(game);
UUID responseId = getFixedResponseUUID(game);
if (responseId == null) {
responseId = getFixedResponseUUID(game);
}
if (response.getBoolean() != null) {
// do nothing
} else if (responseId != null) {

View file

@ -41,7 +41,7 @@ public class EvokeTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shriekmaw");
setChoice(playerA, true);
addTarget(playerA, "Silvercoat Lion");
// addTarget(playerA, "Silvercoat Lion"); Autochosen, only target
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Exhume");
setStopAt(1, PhaseStep.END_TURN);
execute();

View file

@ -59,11 +59,11 @@ public class FiendHunterTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fiend Hunter");
addTarget(playerA, "Primeval Titan");
// addTarget(playerA, "Primeval Titan"); Autochosen, only option
// When Restoration Angel enters the battlefield, you may exile target non-Angel creature you control, then return that card to the battlefield under your control
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Restoration Angel");
addTarget(playerA, "Fiend Hunter");
// addTarget(playerA, "Fiend Hunter"); Autochosen, only option
setStopAt(4, PhaseStep.PRECOMBAT_MAIN);
execute();

View file

@ -32,7 +32,7 @@ public class MerfolkTricksterTest extends CardTestPlayerBase {
attack(1, playerA, "Flying Men");
castSpell(1, PhaseStep.DECLARE_BLOCKERS, playerB, mTrickster);
addTarget(playerB, "Flying Men");
// addTarget(playerB, "Flying Men"); Autochosen, only option
setStopAt(1, PhaseStep.END_COMBAT);
execute();
@ -60,7 +60,7 @@ public class MerfolkTricksterTest extends CardTestPlayerBase {
attack(1, playerA, "Flying Men");
castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, mTrickster);
addTarget(playerB, "Flying Men");
// addTarget(playerB, "Flying Men"); Autochosen, only target
block(1, playerB, mTrickster, "Flying Men");
setStopAt(1, PhaseStep.END_COMBAT);

View file

@ -105,7 +105,7 @@ public class CleverImpersonatorTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Liliana, Defiant Necromancer", 1);
attack(1, playerA, "Alesha, Who Smiles at Death");
addTarget(playerA, "Clever Impersonator");
// addTarget(playerA, "Clever Impersonator"); (Autochosen, only target)
setChoice(playerA, "Liliana, Defiant Necromancer");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "+2: Each player discards a card");

View file

@ -59,25 +59,28 @@ public class SharuumTheHegemonTest extends CardTestPlayerBase {
setChoice(playerA, "Whenever {this} or another creature dies"); // blood first
addTarget(playerA, playerB); // damage by blood
setChoice(playerA, true); // return
addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum
// addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum (Autochosen, only target)
addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep
setChoice(playerA, "Whenever {this} or another creature dies"); // blood first
addTarget(playerA, playerB); // damage by blood
setChoice(playerA, true); // return
addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum
// addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum (Autochosen, only target
addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep
setChoice(playerA, "Whenever {this} or another creature dies"); // blood first
addTarget(playerA, playerB); // damage by blood
setChoice(playerA, true); // return
addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum
// addTarget(playerA, "Sharuum the Hegemon"); // return real sharuum (Autochosen, only target)
addTarget(playerA, "Sharuum the Hegemon[only copy]"); // which legend to keep
setChoice(playerA, "Whenever {this} or another creature dies"); // blood first
addTarget(playerA, playerB); // damage by blood
setChoice(playerA, false); // Don't use it anymore
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 24);
assertLife(playerB, 16);

View file

@ -26,8 +26,8 @@ public class SpelltwineTest extends CardTestPlayerBase {
addCard(Zone.GRAVEYARD, playerB, "Shock");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spelltwine");
addTarget(playerA, "Lightning Bolt");
addTarget(playerA, "Shock");
// addTarget(playerA, "Lightning Bolt"); Autochosen, only target
// addTarget(playerA, "Shock"); Autochosen, only target
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();

View file

@ -80,7 +80,7 @@ public class SweepTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Plow Through Reito");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Plow Through Reito");
addTarget(playerA, "Raging Goblin"); // target to boost
// addTarget(playerA, "Raging Goblin"); // Autochosen (target to boost)
addTarget(playerA, TestPlayer.TARGET_SKIP); // targets to sweep (zero)
setStopAt(1, PhaseStep.BEGIN_COMBAT);

View file

@ -24,7 +24,7 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// turn 1 - A
// oath A triggers for A and activates
addTarget(playerA, playerB); // who control more lands
// addTarget(playerA, playerB); // who control more lands (Autochosen, only target)
setChoice(playerA, true); // search library
addTarget(playerA, "Plains"); // card from library
@ -56,7 +56,7 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// turn 2 - B
// oath A triggers for B and activates
addTarget(playerB, playerA); // who control more lands
// addTarget(playerB, playerA); // who control more lands (Autochosen, only target)
setChoice(playerB, true); // search library
addTarget(playerB, "Plains"); // card from library
@ -80,7 +80,7 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// turn 1 - A
// oath B triggers for A and activates
addTarget(playerA, playerB); // who control more lands
// addTarget(playerA, playerB); // who control more lands (Autochosen, only target)
setChoice(playerA, true); // search library
addTarget(playerA, "Plains"); // card from library
@ -105,11 +105,11 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// oath A triggers for A and activates
// oath B triggers for A and activates
// 1
addTarget(playerA, playerB); // who control more lands
// addTarget(playerA, playerB); // who control more lands (Autochosen, only target)
setChoice(playerA, true); // search library
addTarget(playerA, "Plains"); // card from library
// 2
addTarget(playerA, playerB); // who control more lands
// addTarget(playerA, playerB); // who control more lands (Autochosen, only target)
setChoice(playerA, true); // search library
addTarget(playerA, "Plains"); // card from library
@ -212,11 +212,11 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// oath A triggers for A and activates
// copy oath B triggers for A and activates
// 1
addTarget(playerA, playerB); // who control more lands
// addTarget(playerA, playerB); // who control more lands (Autochosen, only target)
setChoice(playerA, true); // search library
addTarget(playerA, "Plains"); // card from library
// 2
addTarget(playerA, playerB); // who control more lands
// addTarget(playerA, playerB); // who control more lands (Autochosen, only target)
setChoice(playerA, true); // search library
addTarget(playerA, "Plains"); // card from library

View file

@ -45,7 +45,7 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Starfield of Nyx");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudform");
addTarget(playerA, "Cloudform");
// addTarget(playerA, "Cloudform"); Autochosen, only target
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();

View file

@ -55,7 +55,9 @@ public class LilianaTest extends CardTestPlayerBase {
addTarget(playerA, yOx); // tap the ox
setStopAt(1, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, bMummy, 1);
assertPermanentCount(playerA, liliannaDM, 1);

View file

@ -27,7 +27,7 @@ public class EonFrolickerTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eon Frolicker");
addTarget(playerA, playerB);
// addTarget(playerA, playerB); Autochosen, only target
// AI can targets only Eon Frolicker (cause A protected from B)
checkPlayableAbility("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Cast Chandra's Fury", true);

View file

@ -71,20 +71,13 @@ public class SimpleDominariaCards extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Avatar of Woe");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{T}");
addTarget(playerB, "Knight of Grace");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
try {
execute();
Assert.fail("must throw exception on execute");
} catch (Throwable e) {
if (!e.getMessage().contains("setup good targets")) {
Assert.fail("must throw error about bad targets, but got:\n" + e.getMessage());
}
}
execute();
assertAllCommandsUsed();
assertTapped("Avatar of Woe", false);
assertGraveyardCount(playerA, "Knight of Grace", 0);
assertGraveyardCount(playerB, "Avatar of Woe", 1); // Autokills itself since its only valid target
}
@Test

View file

@ -23,7 +23,7 @@ public class BrainMaggotTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Bloodflow Connoisseur", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brain Maggot");
addTarget(playerA, playerB);
// addTarget(playerA, playerB); Autochosen, only target
setChoice(playerA, "Bloodflow Connoisseur");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
@ -46,7 +46,7 @@ public class BrainMaggotTest extends CardTestPlayerBase {
// exile
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brain Maggot");
addTarget(playerA, playerB);
// addTarget(playerA, playerB); Autochosen, only target
setChoice(playerA, "Bloodflow Connoisseur");
// showExile("exile", 1, PhaseStep.BEGIN_COMBAT, playerB);
checkExileCount("blood must be in exile", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 1);
@ -77,7 +77,7 @@ public class BrainMaggotTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mesmeric Fiend");
addTarget(playerA, playerB);
// addTarget(playerA, playerB); Autochosen, only target
setChoice(playerA, "Bloodflow Connoisseur");
//
castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt", "Mesmeric Fiend");

View file

@ -31,7 +31,7 @@ public class TidehollowScullerTest extends CardTestPlayerBase {
// cast and exile from hand
checkHandCardCount("B hand must have blood", 1, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tidehollow Sculler");
addTarget(playerA, playerB); // choose opponent
// addTarget(playerA, playerB); // choose opponent (Autochosen, only target)
setChoice(playerA, "Bloodflow Connoisseur"); // card to exile
checkHandCardCount("B hand must lost blood", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 0);
@ -68,13 +68,13 @@ public class TidehollowScullerTest extends CardTestPlayerBase {
// cast 1 and exile from hand
checkHandCardCount("B hand must have blood", 1, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tidehollow Sculler");
addTarget(playerA, playerB); // choose opponent
// addTarget(playerA, playerB); // choose opponent (Autochosen, only target)
setChoice(playerA, "Bloodflow Connoisseur"); // card to exile
checkHandCardCount("B hand must lost blood", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 0);
// cast 2 and exile from hand
checkHandCardCount("B hand must have lion", 1, PhaseStep.END_COMBAT, playerB, "Silvercoat Lion", 1);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tidehollow Sculler");
addTarget(playerA, playerB); // choose opponent
// addTarget(playerA, playerB); // choose opponent (Autochosen, only target)
setChoice(playerA, "Silvercoat Lion"); // card to exile
checkHandCardCount("B hand must lost lion", 1, PhaseStep.END_TURN, playerB, "Silvercoat Lion", 0);

View file

@ -75,8 +75,8 @@ public class BlatantThieveryTest extends CardTestMultiPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blatant Thievery");
addTarget(playerA, "Silvercoat Lion");
addTarget(playerA, "Walking Corpse");
addTarget(playerA, "Pillarfield Ox");
// addTarget(playerA, "Walking Corpse"); Autochosen, only target
// addTarget(playerA, "Pillarfield Ox"); Autochosen, only target
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Act of Aggression", "Pillarfield Ox", "Blatant Thievery");
setStopAt(1, PhaseStep.BEGIN_COMBAT);

View file

@ -4346,6 +4346,16 @@ public class TestPlayer implements Player {
this.strictChooseMode = enable;
}
@Override
public boolean getStrictChooseMode() {
return this.strictChooseMode;
}
@Override
public UserData getControllingPlayersUserData(Game game) {
return null;
}
@Override
public void addPhyrexianToColors(FilterMana colors) {
computerPlayer.addPhyrexianToColors(colors);

View file

@ -1430,6 +1430,11 @@ public class PlayerStub implements Player {
return (new FilterMana());
}
@Override
public UserData getControllingPlayersUserData(Game game) {
return null;
}
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
return card.getSpellAbility();

View file

@ -788,7 +788,7 @@ public abstract class AbilityImpl implements Ability {
@Override
public String getRule(boolean all) {
StringBuilder sbRule = threadLocalBuilder.get();
if (all || this.abilityType != AbilityType.SPELL) {
if (all || this.abilityType != AbilityType.SPELL) { // TODO: Why the override for non-spells?
if (!manaCosts.isEmpty()) {
sbRule.append(manaCosts.getText());
}

View file

@ -1054,4 +1054,16 @@ public interface Player extends MageItem, Copyable<Player> {
* @return
*/
FilterMana getPhyrexianColors();
/**
* Function to query if the player has strictChooseMode enabled. Only the test player can have it.
* Function is added here so that the test suite project does not have to be imported into the client/server project.
*
* @return whether the player has strictChooseMode enabled
*/
public default boolean getStrictChooseMode() {
return false;
}
public UserData getControllingPlayersUserData(Game game);
}

View file

@ -4268,6 +4268,7 @@ public abstract class PlayerImpl implements Player, Serializable {
return this.userData;
}
@Override
public UserData getControllingPlayersUserData(Game game) {
if (!isGameUnderControl()) {
Player player = game.getPlayer(getTurnControlledBy());

View file

@ -23,6 +23,7 @@ public class UserData implements Serializable {
protected boolean passPriorityCast;
protected boolean passPriorityActivation;
protected boolean autoOrderTrigger;
protected int autoTargetLevel;
protected boolean useSameSettingsForReplacementEffects;
protected boolean useFirstManaAbility = false;
private String userIdStr;
@ -50,6 +51,7 @@ public class UserData implements Serializable {
boolean passPriorityCast,
boolean passPriorityActivation,
boolean autoOrderTrigger,
int autoTargetLevel,
boolean useSameSettingsForReplacementEffects,
boolean useFirstManaAbility,
String userIdStr) {
@ -66,6 +68,7 @@ public class UserData implements Serializable {
this.passPriorityCast = passPriorityCast;
this.passPriorityActivation = passPriorityActivation;
this.autoOrderTrigger = autoOrderTrigger;
this.autoTargetLevel = autoTargetLevel;
this.useSameSettingsForReplacementEffects = useSameSettingsForReplacementEffects;
this.useFirstManaAbility = useFirstManaAbility;
this.matchHistory = "";
@ -90,6 +93,7 @@ public class UserData implements Serializable {
this.passPriorityCast = userData.passPriorityCast;
this.passPriorityActivation = userData.passPriorityActivation;
this.autoOrderTrigger = userData.autoOrderTrigger;
this.autoTargetLevel = userData.autoTargetLevel;
this.useSameSettingsForReplacementEffects = userData.useSameSettingsForReplacementEffects;
this.useFirstManaAbility = userData.useFirstManaAbility;
this.userIdStr = userData.userIdStr;
@ -111,6 +115,7 @@ public class UserData implements Serializable {
false,
false,
true,
1,
true,
false,
""
@ -235,14 +240,22 @@ public class UserData implements Serializable {
return autoOrderTrigger;
}
public boolean isUseSameSettingsForReplacementEffects() {
return useSameSettingsForReplacementEffects;
}
public void setAutoOrderTrigger(boolean autoOrderTrigger) {
this.autoOrderTrigger = autoOrderTrigger;
}
public int getAutoTargetLevel() {
return autoTargetLevel;
}
public void setAutoTargetLevel(int autoTargetLevel) {
this.autoTargetLevel = autoTargetLevel;
}
public boolean isUseSameSettingsForReplacementEffects() {
return useSameSettingsForReplacementEffects;
}
public boolean isUseFirstManaAbility() {
return useFirstManaAbility;
}

View file

@ -1,6 +1,7 @@
package mage.target;
import mage.abilities.Ability;
import mage.cards.Cards;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.Filter;
@ -8,6 +9,7 @@ import mage.game.Game;
import mage.players.Player;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@ -159,4 +161,35 @@ public interface Target extends Serializable {
int getSize();
boolean contains(UUID targetId);
/**
* This function tries to auto-choose the next target.
* <p>
* It will NOT add it to the list of targets, it will ony choose the next target
* <p>
* Use this version when the targets is selected from targets.getTargets.
* <p>
* It will auto-choosen if all of the following criteria are met:
* - The minimum and maximum number of targets is the same (i.e. effect does not have "up to" in its name)
* - The number of valid targets is equal to the number of targets still left to be specified
*
*
* @param abilityControllerId
* @param source
* @param game
* @return The UUID of the chosen option, or null if one could not be chosen
*/
UUID tryToAutoChoose(UUID abilityControllerId, Ability source, Game game);
/**
* Use this version when the target is chosen from a specified collection.
* E.g. {@link Player#chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game)}
*
* @param abilityControllerId
* @param source
* @param game
* @param possibleTargets
* @return
*/
UUID tryToAutoChoose(UUID abilityControllerId, Ability source, Game game, Collection<UUID> possibleTargets);
}

View file

@ -10,6 +10,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.TargetEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.RandomUtil;
@ -317,8 +318,14 @@ public abstract class TargetImpl implements Target {
possibleTargets.remove(index);
}
}
} else if (!targetController.chooseTarget(outcome, this, source, game)) {
return chosen;
} else {
// Try to autochoosen
UUID autoChosenId = tryToAutoChoose(playerId, source, game);
if (autoChosenId != null) {
addTarget(autoChosenId, source, game);
} else if (!targetController.chooseTarget(outcome, this, source, game)) { // If couldn't autochoose ask player
return chosen;
}
}
chosen = targets.size() >= getNumberOfTargets();
} while (!isChosen() && !doneChoosing());
@ -595,4 +602,82 @@ public abstract class TargetImpl implements Target {
public boolean contains(UUID targetId) {
return targets.containsKey(targetId);
}
@Override
public UUID tryToAutoChoose(UUID abilityControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = possibleTargets(abilityControllerId, source, game);
possibleTargets.removeAll(this.targets.keySet());
return tryToAutoChoose(abilityControllerId, source, game, possibleTargets);
}
@Override
public UUID tryToAutoChoose(UUID abilityControllerId, Ability source, Game game, Collection<UUID> possibleTargets) {
Player player = game.getPlayer(abilityControllerId);
if (player == null) {
return null;
}
int playerAutoTargetLevel;
if (player.isHuman() && player.getControllingPlayersUserData(game) != null) { // Ensure that non-strictChooseMode ComputerPlayer will still use this ability
playerAutoTargetLevel = player.getControllingPlayersUserData(game).getAutoTargetLevel();
} else {
playerAutoTargetLevel = 2;
}
boolean strictModeEnabled = player.getStrictChooseMode();
boolean canAutoChoose = this.getMinNumberOfTargets() == this.getMaxNumberOfTargets() && // Targets must be picked
possibleTargets.size() == this.getNumberOfTargets() - this.getSize() && // Available targets are equal to the number that must be picked
!strictModeEnabled && // Test AI is not set to strictChooseMode(true)
playerAutoTargetLevel > 0; // Human player has enabled auto-choose in settings
if (canAutoChoose) {
boolean autoTargetAll = playerAutoTargetLevel == 2;
for (UUID possibleChooseId : possibleTargets) {
// Don't pick a target that's already been chosen, this will lead to an infinite loop of
// choosen and unchoosing the same target.
if (this.targets.containsKey(possibleChooseId)) {
continue;
}
if (autoTargetAll) { // No need for further checks since all targeting is to be automated
return possibleChooseId;
}
// Check if you control the target (or own the card)
boolean targetingOwnThing;
if (possibleChooseId == abilityControllerId) {
targetingOwnThing = true;
} else {
Permanent targetPermanent = game.getPermanent(possibleChooseId);
Card targetCard = game.getCard(possibleChooseId);
Spell targetSpell = game.getSpell(possibleChooseId);
if (targetPermanent != null) {
targetingOwnThing = abilityControllerId == targetPermanent.getControllerId();
} else if (targetCard != null) {
targetingOwnThing = abilityControllerId == targetCard.getOwnerId();
} else if (targetSpell != null) {
targetingOwnThing = abilityControllerId == targetSpell.getControllerId();
} else {
// No point further checking
continue;
}
}
// If you control (or own the card) the target, check if it's one of the feel-bad effects.
if (targetingOwnThing) {
String abilityText = source.getRule(true).toLowerCase();
if (abilityText.contains("discard")
|| abilityText.contains("sacrifice")
|| abilityText.contains("destroy")
|| abilityText.contains("exile")) {
continue;
}
// Otherwise return the target with the return statement below.
}
// If we get here then it means that the target UUID passes the checks.
return possibleChooseId;
}
}
return null;
}
}