* Added optional rollback current turn and up to 3 previous turns to the battlefield menu. All other players have to agree to the rollback to let it happen.

This commit is contained in:
LevelX2 2015-06-07 00:53:08 +02:00
parent 5736efa103
commit 8acf28eed1
38 changed files with 661 additions and 252 deletions

View file

@ -32,20 +32,22 @@
<Component id="lblGameType" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="cbGameType" min="-2" pref="398" max="-2" attributes="1"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Group type="102" attributes="0">
<Component id="cbGameType" min="-2" pref="270" max="-2" attributes="1"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="chkRollbackTurnsAllowed" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="13" max="32767" attributes="0"/>
<Component id="lblFreeMulligans" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnFreeMulligans" min="-2" pref="50" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Component id="txtName" alignment="0" max="32767" attributes="0"/>
<Component id="cbDeckType" alignment="0" pref="338" max="32767" attributes="1"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="txtName" max="32767" attributes="0"/>
<Component id="cbDeckType" pref="332" max="32767" attributes="1"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="lbTimeLimit" alignment="1" min="-2" max="-2" attributes="0"/>
<Component id="lblPassword" alignment="1" min="-2" max="-2" attributes="0"/>
@ -89,10 +91,10 @@
<Component id="cbSkillLevel" min="-2" pref="148" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="spnNumWins" min="-2" pref="50" max="-2" attributes="0"/>
<Component id="lblNumWins" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblNumWins" min="-2" max="-2" attributes="0"/>
<Component id="spnNumWins" alignment="0" min="-2" pref="50" max="-2" attributes="0"/>
</Group>
</Group>
<Component id="jSeparator2" alignment="1" max="32767" attributes="0"/>
@ -105,7 +107,7 @@
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="jSeparator3" pref="606" max="32767" attributes="0"/>
<Component id="jSeparator3" pref="586" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
@ -133,6 +135,7 @@
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
<Component id="spnFreeMulligans" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblFreeMulligans" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="chkRollbackTurnsAllowed" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="cbGameType" alignment="3" min="-2" max="-2" attributes="0"/>
@ -147,11 +150,13 @@
<Component id="spnNumPlayers" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblRange" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblAttack" alignment="3" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="lblSkillLevel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblNumWins" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblRange" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblAttack" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
@ -171,8 +176,8 @@
<EmptySpace min="-2" pref="16" max="-2" attributes="0"/>
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pnlOtherPlayers" pref="113" max="32767" attributes="0"/>
<EmptySpace pref="13" max="32767" attributes="0"/>
<Component id="pnlOtherPlayers" pref="105" max="32767" attributes="0"/>
<EmptySpace pref="7" max="32767" attributes="0"/>
<Component id="jSeparator1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
@ -185,7 +190,7 @@
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="201" max="-2" attributes="0"/>
<Component id="jSeparator3" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="178" max="32767" attributes="0"/>
<EmptySpace pref="167" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
@ -237,6 +242,12 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbGameTypeActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="chkRollbackTurnsAllowed">
<Properties>
<Property name="text" type="java.lang.String" value="Allow rollbacks"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Allow to rollback to the start of previous turns&lt;br&gt;&#xa;if all players agree.&#xa;"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="lblFreeMulligans">
<Properties>
<Property name="text" type="java.lang.String" value="Free Mulligans:"/>

View file

@ -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("<HTML>Allow to rollback to the start of previous turns<br>\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;

View file

@ -51,7 +51,11 @@
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="spnConstructTime" min="-2" pref="50" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Component id="spnConstructTime" min="-2" pref="50" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="chkRollbackTurnsAllowed" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="spnNumRounds" min="-2" pref="50" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
@ -67,52 +71,49 @@
<EmptySpace max="-2" attributes="0"/>
<Component id="btnCancel" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Component id="lblDraftCube" max="-2" attributes="0"/>
<Component id="lblTournamentType" min="-2" max="-2" attributes="0"/>
<Component id="lbDeckType" max="-2" attributes="0"/>
<Component id="lblGameType" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cbDraftCube" min="-2" pref="290" max="-2" attributes="0"/>
<Component id="cbDeckType" alignment="0" min="-2" pref="290" max="-2" attributes="1"/>
<Component id="cbGameType" min="-2" pref="290" max="-2" attributes="1"/>
<Group type="102" alignment="0" attributes="0">
<Component id="cbTournamentType" min="-2" pref="290" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblFreeMulligans" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnFreeMulligans" min="-2" pref="41" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblNumWins" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnNumWins" min="-2" pref="50" max="-2" attributes="0"/>
</Group>
</Group>
<Group type="103" alignment="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Component id="lblDraftCube" max="-2" attributes="0"/>
<Component id="lblTournamentType" min="-2" max="-2" attributes="0"/>
<Component id="lbDeckType" max="-2" attributes="0"/>
<Component id="lblGameType" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="lblName" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtName" min="-2" pref="124" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lbTimeLimit" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbTimeLimit" min="-2" pref="89" max="-2" attributes="1"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lbSkillLevel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbSkillLevel" min="-2" pref="112" max="-2" attributes="1"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblPassword" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtPassword" min="-2" pref="56" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cbDraftCube" min="-2" pref="290" max="-2" attributes="0"/>
<Component id="cbDeckType" alignment="0" min="-2" pref="290" max="-2" attributes="1"/>
<Component id="cbGameType" min="-2" pref="290" max="-2" attributes="1"/>
<Group type="102" alignment="0" attributes="0">
<Component id="cbTournamentType" min="-2" pref="290" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblFreeMulligans" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnFreeMulligans" min="-2" pref="41" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblNumWins" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="spnNumWins" min="-2" pref="50" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="lblName" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtName" min="-2" pref="124" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lbTimeLimit" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbTimeLimit" min="-2" pref="101" max="-2" attributes="1"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="lbSkillLevel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="cbSkillLevel" min="-2" pref="88" max="-2" attributes="1"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblPassword" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtPassword" min="-2" pref="56" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</Group>
@ -183,10 +184,11 @@
<Group type="103" groupAlignment="3" attributes="0">
<Component id="spnConstructTime" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblConstructionTime" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="chkRollbackTurnsAllowed" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="player1Panel" pref="62" max="32767" attributes="0"/>
<Component id="player1Panel" pref="64" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pnlPlayers" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
@ -440,6 +442,12 @@
<Property name="text" type="java.lang.String" value="Construction Time (Minutes):"/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="chkRollbackTurnsAllowed">
<Properties>
<Property name="text" type="java.lang.String" value="Allow rollbacks"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Allow to rollback to the start of previous turns&lt;br&gt; if all players agree. "/>
</Properties>
</Component>
<Component class="javax.swing.JSpinner" name="spnConstructTime">
<Properties>
<Property name="toolTipText" type="java.lang.String" value="The time players have to build their deck."/>
@ -462,7 +470,7 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="pnlOtherPlayers" alignment="0" pref="5" max="32767" attributes="0"/>
<Component id="pnlOtherPlayers" alignment="0" pref="7" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>

View file

@ -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("<HTML>Allow to rollback to the start of previous turns<br> 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;

View file

@ -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

View file

@ -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());
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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));
}

View file

@ -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;

View file

@ -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<UUID, UUID> entry: userPlayerMap.entrySet()) { // no AI players

View file

@ -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<UUID, UUID> userPlayerMap, UUID tableId, UUID choosingPlayerId) {
public GameController(Game game, ConcurrentHashMap<UUID, UUID> 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) {

View file

@ -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<UUID, GameController> gameControllers = new ConcurrentHashMap<>();
public UUID createGameSession(Game game, ConcurrentHashMap<UUID, UUID> userPlayerMap, UUID tableId, UUID choosingPlayerId) {
GameController gameController = new GameController(game, userPlayerMap, tableId, choosingPlayerId);
public UUID createGameSession(Game game, ConcurrentHashMap<UUID, UUID> userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) {
GameController gameController = new GameController(game, userPlayerMap, tableId, choosingPlayerId, gameOptions);
gameControllers.put(game.getId(), gameController);
return gameController.getSessionId();
}

View file

@ -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);

View file

@ -37,6 +37,7 @@ import org.apache.log4j.Logger;
/**
*
* @author BetaSteward_at_googlemail.com
* @param <T>
*/
public class GameWorker<T> implements Callable {

View file

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

View file

@ -27,7 +27,7 @@ import java.util.Random;
*/
public class PlayGameTest extends MageTestBase {
private static List<String> 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<String> 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());

View file

@ -27,7 +27,7 @@ import java.util.Random;
*/
public class TestPlayRandomGame extends MageTestBase {
private static List<String> 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<String> 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());

View file

@ -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");

View file

@ -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
}

View file

@ -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();
}
}

View file

@ -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));
}

View file

@ -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<Integer> savedStates = new Stack<>();
protected transient GameStates gameStates = new GameStates();
// game states to allow player roll back
protected transient Map<Integer, GameState> 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<MageAction>();
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<MageAction>();
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;
}
}

View file

@ -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;
}

View file

@ -77,19 +77,19 @@ public class GameState implements Serializable, Copyable<GameState> {
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 <Name, <Cards>>, will be reset if all players pass priority
private final Revealed revealed;
private final Map<UUID, LookedAt> 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<GameState> {
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<GameState> {
this.values.put(entry.getKey(), entry.getValue());
}
this.zones.putAll(state.zones);
this.paused = state.paused;
this.simultaneousEvents.addAll(state.simultaneousEvents);
for (Map.Entry<UUID, CardState> entry: state.cardState.entrySet()) {
cardState.put(entry.getKey(), entry.getValue().copy());
@ -172,6 +174,40 @@ public class GameState implements Serializable, Copyable<GameState> {
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<GameState> {
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);
}

View file

@ -42,8 +42,12 @@ public class GameStates implements Serializable {
private static final transient Logger logger = Logger.getLogger(GameStates.class);
// private List<byte[]> states = new LinkedList<byte[]>();
private final List<GameState> states = new LinkedList<>();
// private final List<byte[]> states;
private final List<GameState> states;
public GameStates() {
this.states = new LinkedList<>();
}
public void save(GameState gameState) {
// states.add(new Copier<GameState>().copyCompressed(gameState));
@ -60,8 +64,8 @@ public class GameStates implements Serializable {
while (states.size() > index + 1) {
states.remove(states.size() - 1);
}
// return new Copier<GameState>().uncompressCopy(states.get(index));
logger.trace("Rolling back state: " + index);
// return new Copier<GameState>().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<GameState>().uncompressCopy(states.get(index));
// return new Copier<GameState>().uncompressCopy(states.get(index));
return states.get(index);
}
return null;

View file

@ -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));
}

View file

@ -53,6 +53,7 @@ public class MatchOptions implements Serializable {
protected List<String> 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;
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -280,6 +280,7 @@ public interface Player extends MageItem, Copyable<Player> {
void leave();
void concede(Game game);
void abort();
void abortReset();
void skip();
// priority, undo, ...

View file

@ -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;
}
}

View file

@ -38,6 +38,7 @@ import java.util.zip.GZIPOutputStream;
/**
*
* @author BetaSteward_at_googlemail.com
* @param <T>
*/
public class Copier<T> {
@ -95,8 +96,7 @@ public class Copier<T> {
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) {

View file

@ -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 "<font color=\'" + LOG_COLOR_PLAYER + "\'>" + name + "</font>";
}
public static String getPlayerRequestColoredText(String name) {
return "<font color=\'" + LOG_COLOR_PLAYER_REQUEST + "\'>" + name + "</font>";
}
private static String getColorName(ObjectColor objectColor) {
if (objectColor.isMulticolored()) {