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 415573b89ff..e3254b3a02f 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
@@ -710,9 +710,9 @@ public class HumanPlayer extends PlayerImpl {
if (!isExecutingMacro()) {
String selectedNames = target.getTargetedName(game);
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()
- + "
Amount remaining: " + target.getAmountRemaining()
- + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames),
- getRelatedObjectName(source, game)),
+ + "
Amount remaining: " + target.getAmountRemaining()
+ + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames),
+ getRelatedObjectName(source, game)),
target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game),
target.isRequired(source),
getOptions(target, null));
@@ -725,7 +725,7 @@ public class HumanPlayer extends PlayerImpl {
boolean removeMode = target.getTargets().contains(targetId)
&& chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : "target") + "?", "",
- "Remove from selected", "Add extra amount", source, game);
+ "Remove from selected", "Add extra amount", source, game);
if (removeMode) {
target.remove(targetId);
@@ -862,9 +862,9 @@ public class HumanPlayer extends PlayerImpl {
if (!skippedAtLeastOnce
|| (playerId.equals(game.getActivePlayerId())
&& !controllingPlayer
- .getUserData()
- .getUserSkipPrioritySteps()
- .isStopOnAllEndPhases())) {
+ .getUserData()
+ .getUserSkipPrioritySteps()
+ .isStopOnAllEndPhases())) {
skippedAtLeastOnce = true;
if (passWithManaPoolCheck(game)) {
return false;
@@ -896,9 +896,9 @@ public class HumanPlayer extends PlayerImpl {
if (haveNewObjectsOnStack
&& (playerId.equals(game.getActivePlayerId())
&& controllingPlayer
- .getUserData()
- .getUserSkipPrioritySteps()
- .isStopOnStackNewObjects())) {
+ .getUserData()
+ .getUserSkipPrioritySteps()
+ .isStopOnStackNewObjects())) {
// new objects on stack -- disable "pass until stack resolved"
passedUntilStackResolved = false;
} else {
@@ -1235,8 +1235,8 @@ public class HumanPlayer extends PlayerImpl {
if (passedAllTurns
|| passedUntilEndStepBeforeMyTurn
|| (!getControllingPlayersUserData(game)
- .getUserSkipPrioritySteps()
- .isStopOnDeclareAttackers()
+ .getUserSkipPrioritySteps()
+ .isStopOnDeclareAttackers()
&& (passedTurn
|| passedTurnSkipStack
|| passedUntilEndOfTurn
@@ -1419,7 +1419,7 @@ public class HumanPlayer extends PlayerImpl {
/**
* Selects a defender for an attacker and adds the attacker to combat
*
- * @param defenders - list of possible defender
+ * @param defenders - list of possible defender
* @param attackerId - UUID of attacker
* @param game
* @return
@@ -1817,17 +1817,20 @@ public class HumanPlayer extends PlayerImpl {
Map modeMap = new LinkedHashMap<>();
AvailableModes:
for (Mode mode : modes.getAvailableModes(source, game)) {
- int timesSelected = 0;
+ int timesSelected = modes.getSelectedStats(mode.getId());
for (UUID selectedModeId : modes.getSelectedModes()) {
Mode selectedMode = modes.get(selectedModeId);
if (mode.getId().equals(selectedMode.getId())) {
+ // mode selected
if (modes.isEachModeMoreThanOnce()) {
- timesSelected++;
+ // can select again
} else {
- continue AvailableModes;
+ // hide mode from dialog
+ continue AvailableModes; // TODO: test 2x cheat here
}
}
}
+
if (mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { // and needed targets have to be available
String modeText = mode.getEffects().getText(mode);
if (obj != null) {
@@ -1852,11 +1855,12 @@ public class HumanPlayer extends PlayerImpl {
if (response.getUUID() != null) {
for (Mode mode : modes.getAvailableModes(source, game)) {
if (mode.getId().equals(response.getUUID())) {
+ // TODO: add checks on 2x selects (cheaters can rewrite client side code and select same mode multiple times)
+ // reason: wrong setup eachModeMoreThanOnce and eachModeOnlyOnce in many cards
return mode;
}
}
- }
- else if (modes.getSelectedModes().size() >= modes.getMinModes()) {
+ } else if (modes.getSelectedModes().size() >= modes.getMinModes()) {
/* let the player cancel mode selection if they do not need to select any further modes */
done = true;
}
diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java
index 77b8bb3afe0..2417b494e32 100644
--- a/Mage/src/main/java/mage/abilities/Modes.java
+++ b/Mage/src/main/java/mage/abilities/Modes.java
@@ -19,13 +19,15 @@ import java.util.*;
public class Modes extends LinkedHashMap {
private Mode currentMode; // the current mode of the selected modes
- private final List selectedModes = new ArrayList<>();
+ private final List selectedModes = new ArrayList<>(); // all selected modes (this + duplicate)
+ private final Map duplicateModes = new LinkedHashMap<>(); // for 2x selects: copy mode and put it to duplicate list
+ private final Map duplicateToOriginalModeRefs = new LinkedHashMap<>(); // for 2x selects: stores ref from duplicate to original mode
+
private int minModes;
private int maxModes;
private TargetController modeChooser;
private boolean eachModeMoreThanOnce; // each mode can be selected multiple times during one choice
private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists
- private final Map duplicateModes = new LinkedHashMap<>();
private OptionalAdditionalModeSourceCosts optionalAdditionalModeSourceCosts = null; // only set if costs have to be paid
private Filter maxModesFilter = null; // calculates the max number of available modes
private boolean isRandom = false;
@@ -49,6 +51,8 @@ public class Modes extends LinkedHashMap {
for (Map.Entry entry : modes.duplicateModes.entrySet()) {
duplicateModes.put(entry.getKey(), entry.getValue().copy());
}
+ duplicateToOriginalModeRefs.putAll(modes.duplicateToOriginalModeRefs);
+
this.minModes = modes.minModes;
this.maxModes = modes.maxModes;
this.selectedModes.addAll(modes.getSelectedModes());
@@ -116,6 +120,32 @@ public class Modes extends LinkedHashMap {
return selectedModes;
}
+ public int getSelectedStats(UUID modeId) {
+ int count = 0;
+ if (this.selectedModes.contains(modeId)) {
+
+ // single select
+ count++;
+
+ // multiple select (all 2x select generate new duplicate mode)
+ UUID originalId;
+ if (this.duplicateModes.containsKey(modeId)) {
+ // modeId is duplicate
+ originalId = this.duplicateToOriginalModeRefs.get(modeId);
+ } else {
+ // modeId is original
+ originalId = modeId;
+ }
+ for (UUID id : this.duplicateToOriginalModeRefs.values()) {
+ if (id.equals(originalId)) {
+ count++;
+ }
+ }
+ }
+
+ return count;
+ }
+
public void setMinModes(int minModes) {
this.minModes = minModes;
}
@@ -168,6 +198,7 @@ public class Modes extends LinkedHashMap {
if (this.size() > 1) {
this.selectedModes.clear();
this.duplicateModes.clear();
+ this.duplicateToOriginalModeRefs.clear();
if (this.isRandom) {
List modes = getAvailableModes(source, game);
this.addSelectedMode(modes.get(RandomUtil.nextInt(modes.size())).getId());
@@ -286,9 +317,11 @@ public class Modes extends LinkedHashMap {
private void addSelectedMode(UUID modeId) {
if (selectedModes.contains(modeId) && eachModeMoreThanOnce) {
Mode duplicateMode = get(modeId).copy();
+ UUID originalId = modeId;
duplicateMode.setRandomId();
modeId = duplicateMode.getId();
duplicateModes.put(modeId, duplicateMode);
+ duplicateToOriginalModeRefs.put(duplicateMode.getId(), originalId);
}
this.selectedModes.add(modeId);
@@ -329,7 +362,7 @@ public class Modes extends LinkedHashMap {
nonAvailableModes = getAlreadySelectedModes(source, game);
}
for (Mode mode : this.values()) {
- if (isEachModeOnlyOnce() && nonAvailableModes != null && nonAvailableModes.contains(mode.getId())) {
+ if (isEachModeOnlyOnce() && nonAvailableModes.contains(mode.getId())) {
continue;
}
availableModes.add(mode);