diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form
index 20d3cfa718b..01912da458f 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form
@@ -32,20 +32,22 @@
-
-
-
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
@@ -89,10 +91,10 @@
-
+
-
-
+
+
@@ -105,7 +107,7 @@
-
+
@@ -133,6 +135,7 @@
+
@@ -147,11 +150,13 @@
-
-
-
+
+
+
+
+
@@ -171,8 +176,8 @@
-
-
+
+
@@ -185,7 +190,7 @@
-
+
@@ -237,6 +242,12 @@
+
+
+
+
+
+
diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java
index a3f45cffdee..9f0dd4a6494 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java
@@ -101,6 +101,7 @@ public class NewTableDialog extends MageDialog {
cbTimeLimit = new javax.swing.JComboBox();
lblGameType = new javax.swing.JLabel();
cbGameType = new javax.swing.JComboBox();
+ chkRollbackTurnsAllowed = new javax.swing.JCheckBox();
lblFreeMulligans = new javax.swing.JLabel();
spnFreeMulligans = new javax.swing.JSpinner();
lblNumPlayers = new javax.swing.JLabel();
@@ -144,6 +145,9 @@ public class NewTableDialog extends MageDialog {
}
});
+ chkRollbackTurnsAllowed.setText("Allow rollbacks");
+ chkRollbackTurnsAllowed.setToolTipText("Allow to rollback to the start of previous turns
\nif all players agree.\n");
+
lblFreeMulligans.setText("Free Mulligans:");
lblFreeMulligans.setToolTipText("The number of mulligans a player can use without decreasing the number of drawn cards.");
@@ -216,18 +220,20 @@ public class NewTableDialog extends MageDialog {
.addComponent(lbDeckType)
.addComponent(lblGameType))
.addGap(6, 6, 6)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
- .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, 398, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, 270, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
+ .addComponent(chkRollbackTurnsAllowed)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 13, Short.MAX_VALUE)
.addComponent(lblFreeMulligans)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
- .addComponent(txtName, javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(cbDeckType, javax.swing.GroupLayout.Alignment.LEADING, 0, 338, Short.MAX_VALUE))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(txtName)
+ .addComponent(cbDeckType, 0, 332, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lbTimeLimit, javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(lblPassword, javax.swing.GroupLayout.Alignment.TRAILING))
@@ -260,10 +266,10 @@ public class NewTableDialog extends MageDialog {
.addComponent(cbAttackOption, javax.swing.GroupLayout.PREFERRED_SIZE, 177, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 148, javax.swing.GroupLayout.PREFERRED_SIZE)))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(lblNumWins)))
+ .addComponent(lblNumWins)
+ .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(jSeparator2)
.addComponent(player1Panel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
@@ -272,7 +278,7 @@ public class NewTableDialog extends MageDialog {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
- .addComponent(jSeparator3, javax.swing.GroupLayout.DEFAULT_SIZE, 606, Short.MAX_VALUE)
+ .addComponent(jSeparator3, javax.swing.GroupLayout.DEFAULT_SIZE, 586, Short.MAX_VALUE)
.addContainerGap()))
);
layout.setVerticalGroup(
@@ -294,7 +300,8 @@ public class NewTableDialog extends MageDialog {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(lblFreeMulligans))
+ .addComponent(lblFreeMulligans)
+ .addComponent(chkRollbackTurnsAllowed))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblGameType)))
@@ -305,11 +312,12 @@ public class NewTableDialog extends MageDialog {
.addGap(0, 0, 0)
.addComponent(spnNumPlayers, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(lblRange)
- .addComponent(lblAttack)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblSkillLevel)
- .addComponent(lblNumWins))
+ .addComponent(lblNumWins)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(lblRange)
+ .addComponent(lblAttack)))
.addGap(0, 0, 0)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cbRange, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
@@ -325,8 +333,8 @@ public class NewTableDialog extends MageDialog {
.addGap(16, 16, 16)
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 113, Short.MAX_VALUE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 13, Short.MAX_VALUE)
+ .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 105, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 7, Short.MAX_VALUE)
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
@@ -337,7 +345,7 @@ public class NewTableDialog extends MageDialog {
.addGroup(layout.createSequentialGroup()
.addGap(201, 201, 201)
.addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(178, Short.MAX_VALUE)))
+ .addContainerGap(167, Short.MAX_VALUE)))
);
pack();
@@ -363,6 +371,7 @@ public class NewTableDialog extends MageDialog {
options.setSkillLevel((SkillLevel) this.cbSkillLevel.getSelectedItem());
options.setRange((RangeOfInfluence) this.cbRange.getSelectedItem());
options.setWinsNeeded((Integer)this.spnNumWins.getValue());
+ options.setRollbackTurnsAllowed(chkRollbackTurnsAllowed.isSelected());
options.setFreeMulligans((Integer)this.spnFreeMulligans.getValue());
options.setPassword(this.txtPassword.getText());
if (!checkMatchOptions(options)) {
@@ -597,6 +606,9 @@ public class NewTableDialog extends MageDialog {
this.player1Panel.setDeckFile(deckFile);
}
this.spnNumWins.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_WINS, "2")));
+ this.chkRollbackTurnsAllowed.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_ROLLBACK_TURNS_ALLOWED, "Yes").equals("Yes"));
+
+
int range = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_RANGE, "1"));
for (RangeOfInfluence roi :RangeOfInfluence.values()) {
if (roi.getRange() == range) {
@@ -633,6 +645,8 @@ public class NewTableDialog extends MageDialog {
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_TIME_LIMIT, Integer.toString(options.getPriorityTime()));
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_GAME_TYPE, options.getGameType());
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_WINS, Integer.toString(options.getWinsNeeded()));
+ PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_ROLLBACK_TURNS_ALLOWED, options.isRollbackTurnsAllowed() ? "Yes": "No");
+ PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_FREE_MULLIGANS, Integer.toString(options.getFreeMulligans()));
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE, deckFile);
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_PLAYERS, spnNumPlayers.getValue().toString());
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_RANGE, Integer.toString(options.getRange().getRange()));
@@ -659,6 +673,7 @@ public class NewTableDialog extends MageDialog {
private javax.swing.JComboBox cbRange;
private javax.swing.JComboBox cbSkillLevel;
private javax.swing.JComboBox cbTimeLimit;
+ private javax.swing.JCheckBox chkRollbackTurnsAllowed;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JSeparator jSeparator1;
diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form
index 5736b534446..9e201ee6a22 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form
@@ -51,7 +51,11 @@
-
+
+
+
+
+
@@ -67,52 +71,49 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -183,10 +184,11 @@
+
-
+
@@ -440,6 +442,12 @@
+
+
+
+
+
+
@@ -462,7 +470,7 @@
-
+
diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java
index ca6a30b6f66..168442a87c8 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java
@@ -164,6 +164,7 @@ public class NewTournamentDialog extends MageDialog {
cbAllowSpectators = new javax.swing.JCheckBox();
lblPlayer1 = new javax.swing.JLabel();
lblConstructionTime = new javax.swing.JLabel();
+ chkRollbackTurnsAllowed = new javax.swing.JCheckBox();
spnConstructTime = new javax.swing.JSpinner();
player1Panel = new mage.client.table.NewPlayerPanel();
pnlPlayers = new javax.swing.JPanel();
@@ -297,6 +298,9 @@ public class NewTournamentDialog extends MageDialog {
lblConstructionTime.setText("Construction Time (Minutes):");
+ chkRollbackTurnsAllowed.setText("Allow rollbacks");
+ chkRollbackTurnsAllowed.setToolTipText("Allow to rollback to the start of previous turns
if all players agree. ");
+
spnConstructTime.setToolTipText("The time players have to build their deck.");
player1Panel.setPreferredSize(new java.awt.Dimension(400, 44));
@@ -312,7 +316,7 @@ public class NewTournamentDialog extends MageDialog {
);
pnlPlayersLayout.setVerticalGroup(
pnlPlayersLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 5, Short.MAX_VALUE)
+ .addComponent(pnlOtherPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, 7, Short.MAX_VALUE)
);
btnOk.setText("OK");
@@ -357,7 +361,10 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(lblConstructionTime)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(chkRollbackTurnsAllowed))
.addGroup(layout.createSequentialGroup()
.addComponent(spnNumRounds, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
@@ -369,46 +376,44 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(btnOk)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(btnCancel))
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addComponent(lblDraftCube)
- .addComponent(lblTournamentType)
- .addComponent(lbDeckType)
- .addComponent(lblGameType))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(cbDraftCube, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGroup(layout.createSequentialGroup()
- .addComponent(cbTournamentType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(lblFreeMulligans)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, 41, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(lblNumWins)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE))))
- .addGroup(layout.createSequentialGroup()
- .addComponent(lblName)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(txtName, javax.swing.GroupLayout.PREFERRED_SIZE, 124, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(lbTimeLimit)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 89, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(lbSkillLevel)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 112, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(lblPassword)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE)))
- .addGap(0, 0, 0))))
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(lblDraftCube)
+ .addComponent(lblTournamentType)
+ .addComponent(lbDeckType)
+ .addComponent(lblGameType))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(cbDraftCube, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(cbGameType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(cbTournamentType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lblFreeMulligans)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(spnFreeMulligans, javax.swing.GroupLayout.PREFERRED_SIZE, 41, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lblNumWins)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE))))
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(lblName)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(txtName, javax.swing.GroupLayout.PREFERRED_SIZE, 124, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lbTimeLimit)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(lbSkillLevel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(cbSkillLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 88, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lblPassword)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE)))))
.addComponent(player1Panel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
@@ -464,9 +469,10 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(lblPlayer1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(spnConstructTime, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addComponent(lblConstructionTime)))
+ .addComponent(lblConstructionTime)
+ .addComponent(chkRollbackTurnsAllowed)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(player1Panel, javax.swing.GroupLayout.DEFAULT_SIZE, 62, Short.MAX_VALUE)
+ .addComponent(player1Panel, javax.swing.GroupLayout.DEFAULT_SIZE, 64, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pnlPlayers, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
@@ -535,6 +541,7 @@ public class NewTournamentDialog extends MageDialog {
tOptions.getMatchOptions().setFreeMulligans((Integer)this.spnFreeMulligans.getValue());
tOptions.getMatchOptions().setAttackOption(MultiplayerAttackOption.LEFT);
tOptions.getMatchOptions().setRange(RangeOfInfluence.ALL);
+ tOptions.getMatchOptions().setRollbackTurnsAllowed(this.chkRollbackTurnsAllowed.isSelected());
saveTournamentSettingsToPrefs(tOptions);
table = session.createTournamentTable(roomId, tOptions);
@@ -834,6 +841,7 @@ public class NewTournamentDialog extends MageDialog {
}
}
this.cbAllowSpectators.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS, "Yes").equals("Yes"));
+ this.chkRollbackTurnsAllowed.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS, "Yes").equals("Yes"));
}
private void loadBoosterPacks(String packString) {
@@ -896,6 +904,7 @@ public class NewTournamentDialog extends MageDialog {
}
}
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS, (tOptions.isWatchingAllowed()?"Yes":"No"));
+ PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS, (tOptions.getMatchOptions().isRollbackTurnsAllowed()?"Yes":"No"));
}
@@ -915,6 +924,7 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JComboBox cbSkillLevel;
private javax.swing.JComboBox cbTimeLimit;
private javax.swing.JComboBox cbTournamentType;
+ private javax.swing.JCheckBox chkRollbackTurnsAllowed;
private javax.swing.JLabel jLabel6;
private javax.swing.JLabel lbDeckType;
private javax.swing.JLabel lbSkillLevel;
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 18c0d7f78c6..af889cad681 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java
@@ -162,6 +162,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_NEW_TABLE_TIME_LIMIT = "newTableTimeLimit";
public static final String KEY_NEW_TABLE_GAME_TYPE = "newTableGameType";
public static final String KEY_NEW_TABLE_NUMBER_OF_WINS = "newTableNumberOfWins";
+ public static final String KEY_NEW_TABLE_ROLLBACK_TURNS_ALLOWED = "newTableRollbackTurnsAllowed";
public static final String KEY_NEW_TABLE_NUMBER_OF_FREE_MULLIGANS = "newTableNumberOfFreeMulligans";
public static final String KEY_NEW_TABLE_DECK_FILE = "newTableDeckFile";
public static final String KEY_NEW_TABLE_RANGE = "newTableRange";
@@ -184,6 +185,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_NEW_TOURNAMENT_PLAYERS_DRAFT = "newTournamentPlayersDraft";
public static final String KEY_NEW_TOURNAMENT_DRAFT_TIMING = "newTournamentDraftTiming";
public static final String KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS = "newTournamentAllowSpectators";
+ public static final String KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS = "newTournamentAllowRollbacks";
public static final String KEY_NEW_TOURNAMENT_DECK_FILE = "newTournamentDeckFile";
// pref setting for deck generator
diff --git a/Mage.Client/src/main/java/mage/client/dialog/UserRequestDialog.java b/Mage.Client/src/main/java/mage/client/dialog/UserRequestDialog.java
index 2b859be4df2..3ecb0f35252 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/UserRequestDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/UserRequestDialog.java
@@ -177,12 +177,8 @@ public class UserRequestDialog extends MageDialog {
private void sendUserReplay(PlayerAction playerAction) {
Session session = MageFrame.getSession();
- switch(playerAction) {
- case ADD_PERMISSION_TO_SEE_HAND_CARDS:
- session.sendPlayerAction(playerAction, userRequestMessage.getGameId(), userRequestMessage.getRelatedUserId());
- break;
- default:
- // not supported action
+ if (session != null && playerAction != null) {
+ session.sendPlayerAction(playerAction, userRequestMessage.getGameId(), userRequestMessage.getRelatedUserId());
}
}
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 6c153c8a625..7f08d273407 100644
--- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java
+++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java
@@ -483,7 +483,8 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
PlayerView player = game.getPlayers().get(playerSeat);
- PlayAreaPanel sessionPlayer = new PlayAreaPanel(player, bigCard, gameId, true, game.getPriorityTime(), game.isPlayer(), this);
+ PlayAreaPanel sessionPlayer = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
+ new PlayAreaPanelOptions(game.isPlayer(), true, game.isRollbackTurnsAllowed()));
players.put(player.getPlayerId(), sessionPlayer);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
@@ -515,7 +516,8 @@ public final class GamePanel extends javax.swing.JPanel {
col = numColumns - 1;
}
player = game.getPlayers().get(playerNum);
- PlayAreaPanel playerPanel = new PlayAreaPanel(player, bigCard, gameId, false, game.getPriorityTime(), game.isPlayer(), this);
+ PlayAreaPanel playerPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
+ new PlayAreaPanelOptions(game.isPlayer(), false, game.isRollbackTurnsAllowed()));
players.put(player.getPlayerId(), playerPanel);
c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java
index 1fc7bb38b9a..eef809c720e 100644
--- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java
+++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java
@@ -42,6 +42,7 @@ import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
@@ -69,8 +70,8 @@ public class PlayAreaPanel extends javax.swing.JPanel {
private boolean smallMode = false;
private boolean playingMode = true;
private final GamePanel gamePanel;
- private final boolean playerItself;
-
+ private final PlayAreaPanelOptions options;
+
private JCheckBoxMenuItem manaPoolMenuItem;
private JCheckBoxMenuItem allowViewHandCardsMenuItem;
@@ -81,19 +82,18 @@ public class PlayAreaPanel extends javax.swing.JPanel {
* @param player
* @param bigCard
* @param gameId
- * @param isPlayer true if the client is a player / false if the client is a watcher
- * @param playerItself true if it's the area of the player itself
* @param priorityTime
- * @param gamePanel */
- public PlayAreaPanel(PlayerView player, BigCard bigCard, UUID gameId, boolean playerItself, int priorityTime, boolean isPlayer, GamePanel gamePanel) {
- //this(isPlayer);
- this.playerItself = playerItself;
+ * @param gamePanel
+ * @param options
+ */
+ public PlayAreaPanel(PlayerView player, BigCard bigCard, UUID gameId, int priorityTime, GamePanel gamePanel, PlayAreaPanelOptions options) {
+ this.options = options;
initComponents();
setOpaque(false);
battlefieldPanel.setOpaque(false);
popupMenu = new JPopupMenu();
- if (isPlayer) {
+ if (options.isPlayer) {
addPopupMenuPlayer(player.getUserData().allowRequestShowHandCards());
} else {
addPopupMenuWatcher();
@@ -242,7 +242,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
popupMenu.addSeparator();
- if (!playerItself) {
+ if (!options.playerItself) {
menuItem = new JMenuItem("Request permission to see hand cards");
popupMenu.add(menuItem);
@@ -283,6 +283,63 @@ public class PlayAreaPanel extends javax.swing.JPanel {
});
}
popupMenu.addSeparator();
+
+ if (options.rollbackTurnsAllowed) {
+ ActionListener rollBackActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int turnsToRollBack = Integer.parseInt(e.getActionCommand());
+ gamePanel.getSession().sendPlayerAction(PlayerAction.ROLLBACK_TURNS, gameId, turnsToRollBack);
+ }
+ };
+
+ JMenu rollbackMainItem = new JMenu("Roll back");
+ rollbackMainItem.setMnemonic(KeyEvent.VK_R);
+ rollbackMainItem.setToolTipText("The game will be rolled back to the start of the requested turn if all players agree.");
+ popupMenu.add(rollbackMainItem);
+
+ menuItem = new JMenuItem("to the start of the current turn");
+ menuItem.setMnemonic(KeyEvent.VK_C);
+ menuItem.setActionCommand("0");
+ menuItem.addActionListener(rollBackActionListener);
+ rollbackMainItem.add(menuItem);
+
+ menuItem = new JMenuItem("to the start of the previous turn");
+ menuItem.setMnemonic(KeyEvent.VK_P);
+ menuItem.setActionCommand("1");
+ menuItem.addActionListener(rollBackActionListener);
+ rollbackMainItem.add(menuItem);
+
+ menuItem = new JMenuItem("the current turn and the 2 turns before");
+ menuItem.setMnemonic(KeyEvent.VK_2);
+ menuItem.setActionCommand("2");
+ menuItem.addActionListener(rollBackActionListener);
+ rollbackMainItem.add(menuItem);
+
+ menuItem = new JMenuItem("the current turn and the 3 turns before");
+ menuItem.setMnemonic(KeyEvent.VK_3);
+ menuItem.setActionCommand("3");
+ menuItem.addActionListener(rollBackActionListener);
+ rollbackMainItem.add(menuItem);
+
+ popupMenu.addSeparator();
+
+ }
+
+ menuItem = new JMenuItem("Revoke all permission(s) to see your hand cards");
+ menuItem.setMnemonic(KeyEvent.VK_P);
+ menuItem.setToolTipText("Revoke already granted permission for all spectators to see your hand cards.");
+ popupMenu.add(menuItem);
+
+ // revoke permissions to see hand cards
+ menuItem.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ gamePanel.getSession().sendPlayerAction(PlayerAction.REVOKE_PERMISSIONS_TO_SEE_HAND_CARDS, gameId, null);
+ }
+ });
+
+ popupMenu.addSeparator();
menuItem = new JMenuItem("Concede game");
popupMenu.add(menuItem);
diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java
new file mode 100644
index 00000000000..377b7591c4b
--- /dev/null
+++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanelOptions.java
@@ -0,0 +1,58 @@
+/*
+* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification, are
+* permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice, this list of
+* conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright notice, this list
+* of conditions and the following disclaimer in the documentation and/or other materials
+* provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
+* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+* The views and conclusions contained in the software and documentation are those of the
+* authors and should not be interpreted as representing official policies, either expressed
+* or implied, of BetaSteward_at_googlemail.com.
+*/
+package mage.client.game;
+
+/**
+ * Defines some options for the PlayAreaPanel
+ *
+ * @author LevelX2
+ */
+public class PlayAreaPanelOptions {
+
+ public PlayAreaPanelOptions(boolean isPlayer, boolean playerItself, boolean rollbackTurnsAllowed) {
+ this.isPlayer = isPlayer;
+ this.playerItself = playerItself;
+ this.rollbackTurnsAllowed = rollbackTurnsAllowed;
+ }
+
+ /**
+ * true if the client is a player / false if the client is a watcher
+ */
+ public boolean isPlayer = false;
+
+ /**
+ * true if the player is the client player itself, false if the player is another player playing with the clinet player
+ */
+ public boolean playerItself = false;
+
+ /**
+ * true if the player can roll back turns if all players agree
+ */
+ public boolean rollbackTurnsAllowed = false;
+
+}
diff --git a/Mage.Common/src/mage/view/GameView.java b/Mage.Common/src/mage/view/GameView.java
index 32b8be1bcd9..b75d8045131 100644
--- a/Mage.Common/src/mage/view/GameView.java
+++ b/Mage.Common/src/mage/view/GameView.java
@@ -81,6 +81,7 @@ public class GameView implements Serializable {
private boolean special = false;
private final boolean isPlayer;
private final int spellsCastCurrentTurn;
+ private final boolean rollbackTurnsAllowed;
public GameView(GameState state, Game game, UUID createdForPlayerId, UUID watcherUserId) {
@@ -179,7 +180,8 @@ public class GameView implements Serializable {
spellsCastCurrentTurn = watcher.getAmountOfSpellsAllPlayersCastOnCurrentTurn();
} else {
spellsCastCurrentTurn = 0;
- }
+ }
+ rollbackTurnsAllowed = game.getOptions().rollbackTurnsAllowed;
}
private void checkPaid(UUID uuid, StackAbility stackAbility) {
@@ -322,5 +324,9 @@ public class GameView implements Serializable {
public int getSpellsCastCurrentTurn() {
return spellsCastCurrentTurn;
}
+
+ public boolean isRollbackTurnsAllowed() {
+ return rollbackTurnsAllowed;
+ }
}
diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java
index 1dc151f90b3..81371831294 100644
--- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java
+++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/src/mage/game/CommanderFreeForAll.java
@@ -51,9 +51,9 @@ public class CommanderFreeForAll extends GameCommanderImpl {
}
@Override
- protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
+ protected void init(UUID choosingPlayerId) {
startingPlayerSkipsDraw = false;
- super.init(choosingPlayerId, gameOptions);
+ super.init(choosingPlayerId);
}
@Override
diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java
index 41f2c60d263..12b68a296ea 100644
--- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java
+++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java
@@ -59,8 +59,8 @@ public class TwoPlayerDuel extends GameImpl {
}
@Override
- protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
- super.init(choosingPlayerId, gameOptions);
+ protected void init(UUID choosingPlayerId) {
+ super.init(choosingPlayerId);
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
}
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 767819d066b..537b568f6ca 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
@@ -554,6 +554,9 @@ public class HumanPlayer extends PlayerImpl {
updateGameStatePriority("priority", game);
game.firePriorityEvent(playerId);
waitForResponse(game);
+ if(game.executingRollback()) {
+ return true;
+ }
if (response.getBoolean() != null) {
pass(game);
return false;
diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java
index d9231e36a16..4e5eceec45e 100644
--- a/Mage.Server/src/main/java/mage/server/TableController.java
+++ b/Mage.Server/src/main/java/mage/server/TableController.java
@@ -43,6 +43,7 @@ import mage.constants.RangeOfInfluence;
import mage.constants.TableState;
import mage.game.Game;
import mage.game.GameException;
+import mage.game.GameOptions;
import mage.game.Seat;
import mage.game.Table;
import mage.game.draft.Draft;
@@ -551,7 +552,10 @@ public class TableController {
try {
match.startGame();
table.initGame();
- GameManager.getInstance().createGameSession(match.getGame(), userPlayerMap, table.getId(), choosingPlayerId);
+ GameOptions gameOptions = new GameOptions();
+ gameOptions.rollbackTurnsAllowed = match.getOptions().isRollbackTurnsAllowed();
+ match.getGame().setGameOptions(gameOptions);
+ GameManager.getInstance().createGameSession(match.getGame(), userPlayerMap, table.getId(), choosingPlayerId, gameOptions);
String creator = null;
StringBuilder opponent = new StringBuilder();
for (Entry entry: userPlayerMap.entrySet()) { // no AI players
diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java
index 39c87163cce..da30ce7fde6 100644
--- a/Mage.Server/src/main/java/mage/server/game/GameController.java
+++ b/Mage.Server/src/main/java/mage/server/game/GameController.java
@@ -64,6 +64,7 @@ import mage.constants.PlayerAction;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.GameException;
+import mage.game.GameOptions;
import mage.game.Table;
import mage.game.events.Listener;
import mage.game.events.PlayerQueryEvent;
@@ -116,15 +117,23 @@ public class GameController implements GameCallback {
private UUID choosingPlayerId;
private Future> gameFuture;
private boolean useTimeout = true;
+ private GameOptions gameOptions;
+
+ private UUID userReqestingRollback;
+ private int turnsToRollback;
+ private int requestsOpen;
+
- public GameController(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId) {
+ public GameController(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) {
gameSessionId = UUID.randomUUID();
this.userPlayerMap = userPlayerMap;
chatId = ChatManager.getInstance().createChatSession("Game " + game.getId());
+ this.userReqestingRollback = null;
this.game = game;
this.game.setSaveGame(ConfigSettings.getInstance().isSaveGameActivated());
this.tableId = tableId;
this.choosingPlayerId = choosingPlayerId;
+ this.gameOptions = gameOptions;
for (Player player: game.getPlayers().values()) {
if (!player.isHuman()) {
useTimeout = false; // no timeout for AI players because of beeing idle
@@ -474,6 +483,56 @@ public class GameController implements GameCallback {
case UNDO:
game.undo(getPlayerId(userId));
break;
+ case ROLLBACK_TURNS: // basic request of a player to rollback
+ if (data instanceof Integer) {
+ turnsToRollback = (Integer) data;
+ if (game.canRollbackTurns(turnsToRollback)) {
+ requestsOpen = requestPermissionToRollback(userId, turnsToRollback);
+ if (requestsOpen == 0) {
+ game.rollbackTurns(turnsToRollback);
+ turnsToRollback = -1;
+ requestsOpen = -1;
+ } else {
+ userReqestingRollback = userId;
+ }
+ } else {
+ UUID playerId = getPlayerId(userId);
+ if (playerId != null) {
+ Player player = game.getPlayer(playerId);
+ if (player != null) {
+ game.informPlayer(player, "That turn is not available for rollback.");
+ }
+ }
+ }
+ }
+ break;
+ case ADD_PERMISSION_TO_ROLLBACK_TURN:
+ if (userReqestingRollback != null && requestsOpen > 0 && !userId.equals(userReqestingRollback)) {
+ requestsOpen--;
+ if (requestsOpen == 0) {
+ game.rollbackTurns(turnsToRollback);
+ turnsToRollback = -1;
+ userReqestingRollback = null;
+ requestsOpen = -1;
+ }
+ }
+ break;
+ case DENY_PERMISSON_TO_ROLLBACK_TURN: // one player has denied - so cancel the request
+ {
+ UUID playerId = getPlayerId(userId);
+ if (playerId != null) {
+ Player player = game.getPlayer(playerId);
+ if (player != null) {
+ if (userReqestingRollback != null && requestsOpen > 0 && !userId.equals(userReqestingRollback)) {
+ turnsToRollback = -1;
+ userReqestingRollback = null;
+ requestsOpen = -1;
+ game.informPlayers("Rollback request denied by " + player.getLogName());
+ }
+ }
+ }
+ }
+ break;
case CONCEDE:
game.concede(getPlayerId(userId));
break;
@@ -513,6 +572,23 @@ public class GameController implements GameCallback {
}
}
+ private int requestPermissionToRollback(UUID userIdRequester, int numberTurns) {
+ int requests = 0;
+ for (Player player: game.getState().getPlayers().values()) {
+ User requestedUser = getUserByPlayerId(player.getId());
+ if (player.isInGame() && player.isHuman() &&
+ requestedUser != null &&
+ !requestedUser.getId().equals(userIdRequester)) {
+ requests++;
+ GameSessionPlayer gameSession = gameSessions.get(player.getId());
+ if (gameSession != null) {
+ gameSession.requestPermissionToRollbackTurn(userIdRequester, numberTurns);
+ }
+ }
+ }
+ return requests;
+ }
+
private void requestPermissionToSeeHandCards(UUID userIdRequester, UUID userIdGranter) {
Player grantingPlayer = game.getPlayer(userIdGranter);
if (grantingPlayer != null) {
diff --git a/Mage.Server/src/main/java/mage/server/game/GameManager.java b/Mage.Server/src/main/java/mage/server/game/GameManager.java
index 7075ea8e078..0b4b2e9c37e 100644
--- a/Mage.Server/src/main/java/mage/server/game/GameManager.java
+++ b/Mage.Server/src/main/java/mage/server/game/GameManager.java
@@ -34,6 +34,7 @@ import mage.cards.decks.DeckCardLists;
import mage.constants.ManaType;
import mage.constants.PlayerAction;
import mage.game.Game;
+import mage.game.GameOptions;
import mage.view.GameView;
/**
@@ -51,8 +52,8 @@ public class GameManager {
private final ConcurrentHashMap gameControllers = new ConcurrentHashMap<>();
- public UUID createGameSession(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId) {
- GameController gameController = new GameController(game, userPlayerMap, tableId, choosingPlayerId);
+ public UUID createGameSession(Game game, ConcurrentHashMap userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) {
+ GameController gameController = new GameController(game, userPlayerMap, tableId, choosingPlayerId, gameOptions);
gameControllers.put(game.getId(), gameController);
return gameController.getSessionId();
}
diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java
index 2cfff99a561..0b43412b383 100644
--- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java
+++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java
@@ -170,6 +170,34 @@ public class GameSessionPlayer extends GameSessionWatcher {
}
}
+ public void requestPermissionToRollbackTurn(UUID requestingUserId, int numberTurns) {
+ if (!killed) {
+ User requestingUser = UserManager.getInstance().getUser(requestingUserId);
+ User requestedUser = UserManager.getInstance().getUser(userId);
+ if (requestedUser != null && requestingUser != null) {
+ String message;
+ switch(numberTurns) {
+ case 0:
+ message = "Allow rollback to the start of the current turn?";
+ break;
+ case 1:
+ message = "Allow rollback to the start of the previous turn?";
+ break;
+ default:
+ message = "Allow to rollback "+numberTurns+ " turns?";
+ }
+ UserRequestMessage userRequestMessage = new UserRequestMessage(
+ "Request by " + requestedUser.getName(), message
+ , PlayerAction.REQUEST_PERMISSION_TO_ROLLBACK_TURN);
+ userRequestMessage.setRelatedUser(requestingUserId, requestingUser.getName());
+ userRequestMessage.setGameId(game.getId());
+ userRequestMessage.setButton1("Accept", PlayerAction.ADD_PERMISSION_TO_ROLLBACK_TURN);
+ userRequestMessage.setButton2("Deny", PlayerAction.DENY_PERMISSON_TO_ROLLBACK_TURN);
+ requestedUser.fireCallback(new ClientCallback("userRequestDialog", game.getId(), userRequestMessage));
+ }
+ }
+ }
+
public void requestPermissionToSeeHandCards(UUID watcherId) {
if (!killed) {
User watcher = UserManager.getInstance().getUser(watcherId);
diff --git a/Mage.Server/src/main/java/mage/server/game/GameWorker.java b/Mage.Server/src/main/java/mage/server/game/GameWorker.java
index 7a1ae9753c3..50ec2f75b95 100644
--- a/Mage.Server/src/main/java/mage/server/game/GameWorker.java
+++ b/Mage.Server/src/main/java/mage/server/game/GameWorker.java
@@ -37,6 +37,7 @@ import org.apache.log4j.Logger;
/**
*
* @author BetaSteward_at_googlemail.com
+ * @param
*/
public class GameWorker implements Callable {
diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java
index b3f37610ae9..a52650da1c4 100644
--- a/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java
+++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/SunspearShikari.java
@@ -32,6 +32,7 @@ import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.MageInt;
+import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.EquippedCondition;
import mage.abilities.decorator.ConditionalContinuousEffect;
@@ -47,23 +48,23 @@ import mage.constants.Zone;
*/
public class SunspearShikari extends CardImpl {
- private static final String rule1 = "As long as {this} is equipped, it has first strike";
- private static final String rule2 = "As long as {this} is equipped, it has lifelink";
-
public SunspearShikari(UUID ownerId) {
super(ownerId, 23, "Sunspear Shikari", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{1}{W}");
this.expansionSetCode = "SOM";
this.subtype.add("Cat");
this.subtype.add("Soldier");
- this.color.setWhite(true);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
- ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), EquippedCondition.getInstance(), rule1);
- this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect1));
- ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(LifelinkAbility.getInstance()), EquippedCondition.getInstance(), rule2);
- this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect2));
+ // As long as Sunspear Shikari is equipped, it has first strike and lifelink.
+ ConditionalContinuousEffect effect1 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()),
+ EquippedCondition.getInstance(), "As long as {this} is equipped, it has first strike");
+ Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect1);
+ ConditionalContinuousEffect effect2 = new ConditionalContinuousEffect(new GainAbilitySourceEffect(LifelinkAbility.getInstance()),
+ EquippedCondition.getInstance(), "and lifelink");
+ ability.addEffect(effect2);
+ this.addAbility(ability);
}
public SunspearShikari(final SunspearShikari card) {
diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java
index 0e6390f9f05..230159d4d6d 100644
--- a/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/serverside/PlayGameTest.java
@@ -27,7 +27,7 @@ import java.util.Random;
*/
public class PlayGameTest extends MageTestBase {
- private static List colorChoices = Arrays.asList("bu", "bg", "br", "bw", "ug", "ur", "uw", "gr", "gw", "rw", "bur", "buw", "bug", "brg", "brw", "bgw", "wur", "wug", "wrg", "rgu");
+ private final static List colorChoices = Arrays.asList("bu", "bg", "br", "bw", "ug", "ur", "uw", "gr", "gw", "rw", "bur", "buw", "bug", "brg", "brw", "bgw", "wur", "wug", "wrg", "rgu");
@Ignore
@Test
@@ -67,7 +67,8 @@ public class PlayGameTest extends MageTestBase {
long t1 = System.nanoTime();
GameOptions options = new GameOptions();
options.testMode = true;
- game.start(computerA.getId(), options);
+ game.setGameOptions(options);
+ game.start(computerA.getId());
long t2 = System.nanoTime();
logger.info("Winner: " + game.getWinner());
diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java b/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java
index 029eff40c7b..e409b738285 100644
--- a/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java
+++ b/Mage.Tests/src/test/java/org/mage/test/serverside/TestPlayRandomGame.java
@@ -27,7 +27,7 @@ import java.util.Random;
*/
public class TestPlayRandomGame extends MageTestBase {
- private static List colorChoices = Arrays.asList("bu", "bg", "br", "bw", "ug", "ur", "uw", "gr", "gw", "rw", "bur", "buw", "bug", "brg", "brw", "bgw", "wur", "wug", "wrg", "rgu");
+ private final static List colorChoices = Arrays.asList("bu", "bg", "br", "bw", "ug", "ur", "uw", "gr", "gw", "rw", "bur", "buw", "bug", "brg", "brw", "bgw", "wur", "wug", "wrg", "rgu");
@Test
@Ignore
@@ -58,12 +58,11 @@ public class TestPlayRandomGame extends MageTestBase {
game.addPlayer(computerB, deck2);
game.loadCards(deck2.getCards(), computerB.getId());
- boolean testMode = true;
-
long t1 = System.nanoTime();
GameOptions options = new GameOptions();
options.testMode = true;
- game.start(computerA.getId(), options);
+ game.setGameOptions(options);
+ game.start(computerA.getId());
long t2 = System.nanoTime();
logger.info("Winner: " + game.getWinner());
diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java
index b1eb8560ff8..c46ae3fa20a 100644
--- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java
+++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java
@@ -172,7 +172,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
gameOptions.testMode = true;
gameOptions.stopOnTurn = stopOnTurn;
gameOptions.stopAtStep = stopAtStep;
- currentGame.start(activePlayer.getId(), gameOptions);
+ currentGame.setGameOptions(gameOptions);
+ currentGame.start(activePlayer.getId());
long t2 = System.nanoTime();
logger.debug("Winner: " + currentGame.getWinner());
logger.info("Test has been executed. Execution time: " + (t2 - t1) / 1000000 + " ms");
diff --git a/Mage/src/mage/constants/PlayerAction.java b/Mage/src/mage/constants/PlayerAction.java
index ccf54950b4b..5194b113033 100644
--- a/Mage/src/mage/constants/PlayerAction.java
+++ b/Mage/src/mage/constants/PlayerAction.java
@@ -39,6 +39,7 @@ public enum PlayerAction {
PASS_PRIORITY_UNTIL_NEXT_TURN,
PASS_PRIORITY_UNTIL_STACK_RESOLVED,
PASS_PRIORITY_CANCEL_ALL_ACTIONS,
+ ROLLBACK_TURNS,
UNDO,
CONCEDE,
MANA_AUTO_PAYMENT_ON,
@@ -46,7 +47,10 @@ public enum PlayerAction {
RESET_AUTO_SELECT_REPLACEMENT_EFFECTS,
REVOKE_PERMISSIONS_TO_SEE_HAND_CARDS,
REQUEST_PERMISSION_TO_SEE_HAND_CARDS,
+ REQUEST_PERMISSION_TO_ROLLBACK_TURN,
ADD_PERMISSION_TO_SEE_HAND_CARDS,
+ ADD_PERMISSION_TO_ROLLBACK_TURN,
+ DENY_PERMISSON_TO_ROLLBACK_TURN,
PERMISSION_REQUESTS_ALLOWED_ON,
PERMISSION_REQUESTS_ALLOWED_OFF
}
\ No newline at end of file
diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java
index 817c65571c4..6429c34a0ab 100644
--- a/Mage/src/mage/game/Game.java
+++ b/Mage/src/mage/game/Game.java
@@ -208,9 +208,7 @@ public interface Game extends MageItem, Serializable {
*/
PreventionEffectData preventDamage(GameEvent event, Ability source, Game game, boolean preventAllDamage);
- //game play methods
void start(UUID choosingPlayerId);
- void start(UUID choosingPlayerId, GameOptions options);
void resume();
void pause();
boolean isPaused();
@@ -293,5 +291,10 @@ public interface Game extends MageItem, Serializable {
int getPriorityTime();
void setPriorityTime(int priorityTime);
UUID getStartingPlayerId();
+
+ void saveRollBackGameState();
+ boolean canRollbackTurns(int turnsToRollback);
+ void rollbackTurns(int turnsToRollback);
+ boolean executingRollback();
+}
-}
diff --git a/Mage/src/mage/game/GameCommanderImpl.java b/Mage/src/mage/game/GameCommanderImpl.java
index 42d3ac77ef8..4a12851b2e2 100644
--- a/Mage/src/mage/game/GameCommanderImpl.java
+++ b/Mage/src/mage/game/GameCommanderImpl.java
@@ -75,7 +75,7 @@ public abstract class GameCommanderImpl extends GameImpl {
}
@Override
- protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
+ protected void init(UUID choosingPlayerId) {
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
//Move commander to command zone
for (UUID playerId: state.getPlayerList(startingPlayerId)) {
@@ -101,7 +101,7 @@ public abstract class GameCommanderImpl extends GameImpl {
}
this.getState().addAbility(ability, null);
- super.init(choosingPlayerId, gameOptions);
+ super.init(choosingPlayerId);
if (startingPlayerSkipsDraw) {
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
}
diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java
index bdd68d29771..991c93727c5 100644
--- a/Mage/src/mage/game/GameImpl.java
+++ b/Mage/src/mage/game/GameImpl.java
@@ -116,6 +116,7 @@ import mage.players.Players;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.TargetPlayer;
+import mage.util.GameLog;
import mage.util.functions.ApplyToPermanent;
import mage.watchers.Watchers;
import mage.watchers.common.BlockedAttackerWatcher;
@@ -129,6 +130,8 @@ import org.apache.log4j.Logger;
public abstract class GameImpl implements Game, Serializable {
+ private static final int ROLLBACK_TURNS_MAX = 4;
+
private static final transient Logger logger = Logger.getLogger(GameImpl.class);
private static final FilterPermanent filterAura = new FilterPermanent();
@@ -155,7 +158,8 @@ public abstract class GameImpl implements Game, Serializable {
private transient Object customData;
protected boolean simulation = false;
- protected final UUID id;
+ protected final UUID id;
+
protected boolean ready;
protected transient TableEventSource tableEventSource = new TableEventSource();
protected transient PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource();
@@ -170,6 +174,9 @@ public abstract class GameImpl implements Game, Serializable {
protected GameState state;
private transient Stack savedStates = new Stack<>();
protected transient GameStates gameStates = new GameStates();
+ // game states to allow player roll back
+ protected transient Map gameStatesRollBack = new HashMap<>();
+ protected boolean executingRollback;
protected Date startTime;
protected Date endTime;
@@ -206,7 +213,7 @@ public abstract class GameImpl implements Game, Serializable {
this.attackOption = attackOption;
this.state = new GameState();
this.startLife = startLife;
- // this.actions = new LinkedList();
+ this.executingRollback = false;
}
public GameImpl(final GameImpl game) {
@@ -232,7 +239,6 @@ public abstract class GameImpl implements Game, Serializable {
copyCount++;
copyTime += (System.currentTimeMillis() - t1);
}
-// this.actions = new LinkedList();
this.stateCheckRequired = game.stateCheckRequired;
this.scorePlayer = game.scorePlayer;
this.scopeRelevant = game.scopeRelevant;
@@ -268,7 +274,10 @@ public abstract class GameImpl implements Game, Serializable {
@Override
public GameOptions getOptions() {
- return gameOptions;
+ if (gameOptions != null) {
+ return gameOptions;
+ }
+ return new GameOptions(); // happens during the first game updates
}
@Override
@@ -610,23 +619,17 @@ public abstract class GameImpl implements Game, Serializable {
return 0;
}
- @Override
- public void start(UUID choosingPlayerId) {
- start(choosingPlayerId, this.gameOptions != null ? gameOptions : GameOptions.getDefault());
- }
-
@Override
public void cleanUp() {
gameCards.clear();
}
@Override
- public void start(UUID choosingPlayerId, GameOptions options) {
+ public void start(UUID choosingPlayerId) {
startTime = new Date();
- this.gameOptions = options;
if (state.getPlayers().values().iterator().hasNext()) {
scorePlayer = state.getPlayers().values().iterator().next();
- init(choosingPlayerId, options);
+ init(choosingPlayerId);
play(startingPlayerId);
}
}
@@ -731,13 +734,26 @@ public abstract class GameImpl implements Game, Serializable {
}
private boolean playTurn(Player player) {
- this.logStartOfTurn(player);
- if (checkStopOnTurnOption()) {
- return false;
- }
- state.setActivePlayerId(player.getId());
- player.becomesActivePlayer();
- state.getTurn().play(this, player.getId());
+ do {
+ if (executingRollback) {
+ executingRollback = false;
+ player = getPlayer(state.getActivePlayerId());
+ for (Player playerObject: getPlayers().values()) {
+ if (playerObject.isInGame()) {
+ playerObject.abortReset();
+ }
+ }
+ } else {
+ state.setActivePlayerId(player.getId());
+ saveRollBackGameState();
+ }
+ this.logStartOfTurn(player);
+ if (checkStopOnTurnOption()) {
+ return false;
+ }
+ state.getTurn().play(this, player);
+ } while (executingRollback);
+
if (isPaused() || gameOver(null)) {
return false;
}
@@ -778,7 +794,7 @@ public abstract class GameImpl implements Game, Serializable {
return false;
}
- protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
+ protected void init(UUID choosingPlayerId) {
for (Player player: state.getPlayers().values()) {
player.beginTurn(this);
// init only if match is with timer (>0) and time left was not set yet (== MAX_VALUE).
@@ -1151,6 +1167,9 @@ public abstract class GameImpl implements Game, Serializable {
}
// resetPassed should be called if player performs any action
if (player.priority(this)) {
+ if(executingRollback()) {
+ return;
+ }
applyEffects();
}
if (isPaused()) {
@@ -2566,6 +2585,51 @@ public abstract class GameImpl implements Game, Serializable {
}
}
+ @Override
+ public void saveRollBackGameState() {
+ if (gameOptions.rollbackTurnsAllowed) {
+ int toDelete = getTurnNum()- ROLLBACK_TURNS_MAX;
+ if (toDelete > 0 && gameStatesRollBack.containsKey(toDelete)) {
+ gameStatesRollBack.remove(toDelete);
+ }
+ gameStatesRollBack.put(getTurnNum(), state.copy());
+ }
+ }
+ @Override
+ public boolean canRollbackTurns(int turnsToRollback) {
+ int turnToGoTo = getTurnNum() - turnsToRollback;
+ return turnToGoTo > 0 && gameStatesRollBack.containsKey(turnToGoTo);
+ }
+
+ @Override
+ public synchronized void rollbackTurns(int turnsToRollback) {
+ if (gameOptions.rollbackTurnsAllowed) {
+ int turnToGoTo = getTurnNum() - turnsToRollback;
+ if (turnToGoTo < 1 || !gameStatesRollBack.containsKey(turnToGoTo)) {
+ informPlayers(GameLog.getPlayerRequestColoredText("Player request: It's not possible to rollback " + turnsToRollback +" turn(s)"));
+ } else {
+ GameState restore = gameStatesRollBack.get(turnToGoTo);
+ if (restore != null) {
+ informPlayers(GameLog.getPlayerRequestColoredText("Player request: Rolling back to start of turn " + restore.getTurnNum()));
+ for (Player playerObject: getPlayers().values()) {
+ if (playerObject.isHuman() && playerObject.isInGame()) {
+ playerObject.abort();
+ }
+ }
+ state.restore(restore);
+ // because restore uses the objects without copy each copy the state again
+ gameStatesRollBack.put(getTurnNum(), state.copy());
+ executingRollback = true;
+ fireUpdatePlayersEvent();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean executingRollback() {
+ return executingRollback;
+ }
}
diff --git a/Mage/src/mage/game/GameOptions.java b/Mage/src/mage/game/GameOptions.java
index 63ba1888368..7767880a589 100644
--- a/Mage/src/mage/game/GameOptions.java
+++ b/Mage/src/mage/game/GameOptions.java
@@ -37,4 +37,9 @@ public class GameOptions implements Serializable {
* If true, library won't be shuffled at the beginning of the game
*/
public boolean skipInitShuffling = false;
+
+ /**
+ * If true, players can roll back turn if all players agree
+ */
+ public boolean rollbackTurnsAllowed = true;
}
diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java
index 8e82a48f4ff..a29a353ee67 100644
--- a/Mage/src/mage/game/GameState.java
+++ b/Mage/src/mage/game/GameState.java
@@ -77,19 +77,19 @@ public class GameState implements Serializable, Copyable {
private final Players players;
private final PlayerList playerList;
- private final Turn turn;
+ private UUID choosingPlayerId; // player that makes a choice at game start
+
// revealed cards >, will be reset if all players pass priority
private final Revealed revealed;
private final Map lookedAt = new HashMap<>();
- private final DelayedTriggeredAbilities delayed;
- private final SpecialActions specialActions;
- private final TurnMods turnMods;
- private final Watchers watchers;
-
+ private DelayedTriggeredAbilities delayed;
+ private SpecialActions specialActions;
+ private Watchers watchers;
+ private Turn turn;
+ private TurnMods turnMods;
private UUID activePlayerId; // playerId which turn it is
private UUID priorityPlayerId; // player that has currently priority
- private UUID choosingPlayerId; // player that makes a choice at game start
private SpellStack stack;
private Command command;
private Exile exile;
@@ -134,21 +134,24 @@ public class GameState implements Serializable, Copyable {
public GameState(final GameState state) {
this.players = state.players.copy();
this.playerList = state.playerList.copy();
+ this.choosingPlayerId = state.choosingPlayerId;
+ this.revealed = state.revealed.copy();
+ this.lookedAt.putAll(state.lookedAt);
+ this.gameOver = state.gameOver;
+ this.paused = state.paused;
+
this.activePlayerId = state.activePlayerId;
this.priorityPlayerId = state.priorityPlayerId;
- this.choosingPlayerId = state.choosingPlayerId;
this.turn = state.turn.copy();
+
this.stack = state.stack.copy();
this.command = state.command.copy();
this.exile = state.exile.copy();
- this.revealed = state.revealed.copy();
- this.lookedAt.putAll(state.lookedAt);
this.battlefield = state.battlefield.copy();
this.turnNum = state.turnNum;
this.stepNum = state.stepNum;
this.extraTurn = state.extraTurn;
this.legendaryRuleActive = state.legendaryRuleActive;
- this.gameOver = state.gameOver;
this.effects = state.effects.copy();
for (TriggeredAbility trigger: state.triggered) {
this.triggered.add(trigger.copy());
@@ -163,7 +166,6 @@ public class GameState implements Serializable, Copyable {
this.values.put(entry.getKey(), entry.getValue());
}
this.zones.putAll(state.zones);
- this.paused = state.paused;
this.simultaneousEvents.addAll(state.simultaneousEvents);
for (Map.Entry entry: state.cardState.entrySet()) {
cardState.put(entry.getKey(), entry.getValue().copy());
@@ -172,6 +174,40 @@ public class GameState implements Serializable, Copyable {
this.copiedCards.putAll(state.copiedCards);
this.permanentOrderNumber = state.permanentOrderNumber;
}
+
+ public void restore(GameState state) {
+ this.activePlayerId = state.activePlayerId;
+ this.priorityPlayerId = state.priorityPlayerId;
+ this.turn = state.turn;
+
+ this.stack = state.stack;
+ this.command = state.command;
+ this.exile = state.exile;
+ this.battlefield = state.battlefield;
+ this.turnNum = state.turnNum;
+ this.stepNum = state.stepNum;
+ this.extraTurn = state.extraTurn;
+ this.legendaryRuleActive = state.legendaryRuleActive;
+ this.effects = state.effects;
+ this.triggered = state.triggered;
+ this.triggers = state.triggers;
+ this.delayed = state.delayed;
+ this.specialActions = state.specialActions;
+ this.combat = state.combat;
+ this.turnMods = state.turnMods;
+ this.watchers = state.watchers;
+ this.values = state.values;
+ for (Player copyPlayer: state.players.values()) {
+ Player origPlayer = players.get(copyPlayer.getId());
+ origPlayer.restore(copyPlayer);
+ }
+ this.zones = state.zones;
+ this.simultaneousEvents = state.simultaneousEvents;
+ this.cardState = state.cardState;
+ this.zoneChangeCounter = state.zoneChangeCounter;
+ this.copiedCards = state.copiedCards;
+ this.permanentOrderNumber = state.permanentOrderNumber;
+ }
@Override
public GameState copy() {
@@ -558,28 +594,6 @@ public class GameState implements Serializable, Copyable {
zones.put(id, zone);
}
- public void restore(GameState state) {
- this.stack = state.stack;
- this.command = state.command;
- this.effects = state.effects;
- this.triggers = state.triggers;
- this.triggered = state.triggered;
- this.combat = state.combat;
- this.exile = state.exile;
- this.battlefield = state.battlefield;
- this.zones = state.zones;
- this.values = state.values;
- for (Player copyPlayer: state.players.values()) {
- Player origPlayer = players.get(copyPlayer.getId());
- origPlayer.restore(copyPlayer);
- }
- this.simultaneousEvents = state.simultaneousEvents;
- this.cardState = state.cardState;
- this.zoneChangeCounter = state.zoneChangeCounter;
- this.copiedCards = state.copiedCards;
- this.permanentOrderNumber = state.permanentOrderNumber;
- }
-
public void addSimultaneousEvent(GameEvent event, Game game) {
simultaneousEvents.add(event);
}
diff --git a/Mage/src/mage/game/GameStates.java b/Mage/src/mage/game/GameStates.java
index ab624bb9978..e88e45c27d7 100644
--- a/Mage/src/mage/game/GameStates.java
+++ b/Mage/src/mage/game/GameStates.java
@@ -42,8 +42,12 @@ public class GameStates implements Serializable {
private static final transient Logger logger = Logger.getLogger(GameStates.class);
-// private List states = new LinkedList();
- private final List states = new LinkedList<>();
+// private final List states;
+ private final List states;
+
+ public GameStates() {
+ this.states = new LinkedList<>();
+ }
public void save(GameState gameState) {
// states.add(new Copier().copyCompressed(gameState));
@@ -60,8 +64,8 @@ public class GameStates implements Serializable {
while (states.size() > index + 1) {
states.remove(states.size() - 1);
}
-// return new Copier().uncompressCopy(states.get(index));
logger.trace("Rolling back state: " + index);
+// return new Copier().uncompressCopy(states.get(index));
return states.get(index);
}
return null;
@@ -78,7 +82,7 @@ public class GameStates implements Serializable {
public GameState get(int index) {
if (index < states.size()) {
- // return new Copier().uncompressCopy(states.get(index));
+// return new Copier().uncompressCopy(states.get(index));
return states.get(index);
}
return null;
diff --git a/Mage/src/mage/game/GameTinyLeadersImpl.java b/Mage/src/mage/game/GameTinyLeadersImpl.java
index d25f23cea01..6addf9f0c9e 100644
--- a/Mage/src/mage/game/GameTinyLeadersImpl.java
+++ b/Mage/src/mage/game/GameTinyLeadersImpl.java
@@ -74,7 +74,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl{
}
@Override
- protected void init(UUID choosingPlayerId, GameOptions gameOptions) {
+ protected void init(UUID choosingPlayerId) {
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
//Move tiny leader to command zone
for (UUID playerId: state.getPlayerList(startingPlayerId)) {
@@ -101,7 +101,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl{
}
this.getState().addAbility(ability, null);
- super.init(choosingPlayerId, gameOptions);
+ super.init(choosingPlayerId);
if (startingPlayerSkipsDraw) {
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
}
diff --git a/Mage/src/mage/game/match/MatchOptions.java b/Mage/src/mage/game/match/MatchOptions.java
index 71ff1cd2501..7fce8625475 100644
--- a/Mage/src/mage/game/match/MatchOptions.java
+++ b/Mage/src/mage/game/match/MatchOptions.java
@@ -53,6 +53,7 @@ public class MatchOptions implements Serializable {
protected List playerTypes = new ArrayList<>();
protected String password;
protected SkillLevel skillLevel;
+ protected boolean rollbackTurnsAllowed;
/**
* Time each player has during the game to play using his\her priority.
@@ -159,4 +160,12 @@ public class MatchOptions implements Serializable {
public void setSkillLevel(SkillLevel skillLevel) {
this.skillLevel = skillLevel;
}
+
+ public boolean isRollbackTurnsAllowed() {
+ return rollbackTurnsAllowed;
+ }
+
+ public void setRollbackTurnsAllowed(boolean rollbackTurnsAllowed) {
+ this.rollbackTurnsAllowed = rollbackTurnsAllowed;
+ }
}
diff --git a/Mage/src/mage/game/match/MatchPlayer.java b/Mage/src/mage/game/match/MatchPlayer.java
index af956dcc747..6f77ebfe8a5 100644
--- a/Mage/src/mage/game/match/MatchPlayer.java
+++ b/Mage/src/mage/game/match/MatchPlayer.java
@@ -28,6 +28,7 @@
package mage.game.match;
+import java.io.Serializable;
import mage.cards.Card;
import mage.cards.decks.Deck;
import mage.players.Player;
@@ -36,7 +37,10 @@ import mage.players.Player;
*
* @author BetaSteward_at_googlemail.com
*/
-public class MatchPlayer {
+public class MatchPlayer implements Serializable {
+
+ private static final long serialVersionUID = 42L;
+
private int wins;
private boolean matchWinner;
@@ -45,7 +49,7 @@ public class MatchPlayer {
private final String name;
private boolean quit;
- private final boolean timerTimeout;
+ //private final boolean timerTimeout;
private boolean doneSideboarding;
private int priorityTimeLeft;
@@ -56,7 +60,7 @@ public class MatchPlayer {
this.wins = 0;
this.doneSideboarding = true;
this.quit = false;
- this.timerTimeout = false;
+ //this.timerTimeout = false;
this.name = player.getName();
this.matchWinner = false;
}
diff --git a/Mage/src/mage/game/turn/Phase.java b/Mage/src/mage/game/turn/Phase.java
index 8881f21c1c1..0fc741fcc4f 100644
--- a/Mage/src/mage/game/turn/Phase.java
+++ b/Mage/src/mage/game/turn/Phase.java
@@ -113,6 +113,9 @@ public abstract class Phase implements Serializable {
currentStep = step;
if (!game.getState().getTurnMods().skipStep(activePlayerId, getStep().getType())) {
playStep(game);
+ if (game.executingRollback()) {
+ return true;
+ }
}
if (!game.isSimulation() && checkStopOnStepOption(game)) {
return false;
@@ -201,6 +204,9 @@ public abstract class Phase implements Serializable {
prePriority(game, activePlayerId);
if (!game.isPaused() && !game.gameOver(null)) {
currentStep.priority(game, activePlayerId, false);
+ if(game.executingRollback()) {
+ return;
+ }
}
if (!game.isPaused() && !game.gameOver(null)) {
postPriority(game, activePlayerId);
diff --git a/Mage/src/mage/game/turn/Turn.java b/Mage/src/mage/game/turn/Turn.java
index da1b3b830f8..c781a711d2d 100644
--- a/Mage/src/mage/game/turn/Turn.java
+++ b/Mage/src/mage/game/turn/Turn.java
@@ -117,30 +117,34 @@ public class Turn implements Serializable {
return null;
}
- public void play(Game game, UUID activePlayerId) {
+ public void play(Game game, Player activePlayer) {
+ activePlayer.becomesActivePlayer();
this.setDeclareAttackersStepStarted(false);
if (game.isPaused() || game.gameOver(null)) {
return;
}
- if (game.getState().getTurnMods().skipTurn(activePlayerId)) {
+ if (game.getState().getTurnMods().skipTurn(activePlayer.getId())) {
return;
}
- checkTurnIsControlledByOtherPlayer(game, activePlayerId);
+ checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
- this.activePlayerId = activePlayerId;
+ this.activePlayerId = activePlayer.getId();
resetCounts();
- game.getPlayer(activePlayerId).beginTurn(game);
+ game.getPlayer(activePlayer.getId()).beginTurn(game);
for (Phase phase: phases) {
if (game.isPaused() || game.gameOver(null)) {
return;
}
if (!isEndTurnRequested() || phase.getType().equals(TurnPhase.END)) {
currentPhase = phase;
- game.fireEvent(new GameEvent(GameEvent.EventType.PHASE_CHANGED, activePlayerId, null, activePlayerId));
- if (!game.getState().getTurnMods().skipPhase(activePlayerId, currentPhase.getType())) {
- if (phase.play(game, activePlayerId)) {
+ game.fireEvent(new GameEvent(GameEvent.EventType.PHASE_CHANGED, activePlayer.getId(), null, activePlayer.getId()));
+ if (!game.getState().getTurnMods().skipPhase(activePlayer.getId(), currentPhase.getType())) {
+ if (phase.play(game, activePlayer.getId())) {
+ if(game.executingRollback()) {
+ return;
+ }
//20091005 - 500.4/703.4n
game.emptyManaPools();
game.saveState(false);
diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java
index e6c4338ab32..603744d0655 100644
--- a/Mage/src/mage/players/Player.java
+++ b/Mage/src/mage/players/Player.java
@@ -280,6 +280,7 @@ public interface Player extends MageItem, Copyable {
void leave();
void concede(Game game);
void abort();
+ void abortReset();
void skip();
// priority, undo, ...
diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java
index cc5236331e0..da437f09435 100644
--- a/Mage/src/mage/players/PlayerImpl.java
+++ b/Mage/src/mage/players/PlayerImpl.java
@@ -369,8 +369,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.canPaySacrificeCost = player.canPaySacrificeCost();
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard();
- this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
- this.storedBookmark = player.getStoredBookmark();
+ this.alternativeSourceCosts.addAll(player.getAlternativeSourceCosts());
this.topCardRevealed = player.isTopCardRevealed();
this.playersUnderYourControl.clear();
@@ -385,7 +384,9 @@ public abstract class PlayerImpl implements Player, Serializable {
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts();
- this.usersAllowedToSeeHandCards.addAll(player.getUsersAllowedToSeeHandCards());
+ // Don't restore!
+ // this.storedBookmark
+ // this.usersAllowedToSeeHandCards
}
@Override
@@ -1855,7 +1856,7 @@ public abstract class PlayerImpl implements Player, Serializable {
passedAllTurns = false;
passedUntilEndOfTurn = true;
passedUntilStackResolved = false;
- skippedAtLeastOnce = !game.getTurn().getStepType().equals(PhaseStep.END_TURN);
+ skippedAtLeastOnce = !PhaseStep.END_TURN.equals(game.getTurn().getStepType());
this.skip();
break;
case PASS_PRIORITY_UNTIL_NEXT_TURN: // F4
@@ -3126,4 +3127,9 @@ public abstract class PlayerImpl implements Player, Serializable {
return matchPlayer;
}
+ @Override
+ public void abortReset() {
+ abort = false;
+ }
+
}
diff --git a/Mage/src/mage/util/Copier.java b/Mage/src/mage/util/Copier.java
index 613723ce17d..4830d61bf2c 100644
--- a/Mage/src/mage/util/Copier.java
+++ b/Mage/src/mage/util/Copier.java
@@ -38,6 +38,7 @@ import java.util.zip.GZIPOutputStream;
/**
*
* @author BetaSteward_at_googlemail.com
+ * @param
*/
public class Copier {
@@ -95,8 +96,7 @@ public class Copier {
public T uncompressCopy(byte[] buffer) {
T copy = null;
- try {
- ObjectInputStream in = new CopierObjectInputStream(loader, new GZIPInputStream(new ByteArrayInputStream(buffer)));
+ try (ObjectInputStream in = new CopierObjectInputStream(loader, new GZIPInputStream(new ByteArrayInputStream(buffer)))) {
copy = (T) in.readObject();
}
catch(IOException e) {
diff --git a/Mage/src/mage/util/GameLog.java b/Mage/src/mage/util/GameLog.java
index 27887668cd3..c71081c96dc 100644
--- a/Mage/src/mage/util/GameLog.java
+++ b/Mage/src/mage/util/GameLog.java
@@ -38,6 +38,7 @@ import mage.ObjectColor;
public class GameLog {
static final String LOG_COLOR_PLAYER = "#20B2AA"; // LightSeaGreen
+ static final String LOG_COLOR_PLAYER_REQUEST = "#D2691E"; // Chocolate
static final String LOG_COLOR_GREEN = "#90EE90"; // LightGreen
static final String LOG_COLOR_RED = "#FF6347"; // Tomato
static final String LOG_COLOR_BLUE = "#87CEFA"; // LightSkyBlue
@@ -64,6 +65,10 @@ public class GameLog {
public static String getColoredPlayerName(String name) {
return "" + name + "";
}
+
+ public static String getPlayerRequestColoredText(String name) {
+ return "" + name + "";
+ }
private static String getColorName(ObjectColor objectColor) {
if (objectColor.isMulticolored()) {