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()) {