diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java
index e1b803028c8..638fa4363a6 100644
--- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java
+++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java
@@ -57,6 +57,7 @@ public class HoverButton extends JPanel implements MouseListener {
private Command observer = null;
private Command onHover = null;
private Color textColor = Color.white;
+ private Color topTextColor = null;
private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40);
private Color centerTextColor = new Color(200, 210, 0, 200);
private Color origCenterTextColor = new Color(200, 210, 0, 200);
@@ -152,7 +153,7 @@ public class HoverButton extends JPanel implements MouseListener {
topTextOffsetX = calculateOffsetForTop(g2d, topText);
g2d.setColor(textBGColor);
g2d.drawString(topText, topTextOffsetX + 1, 14);
- g2d.setColor(textColor);
+ g2d.setColor(topTextColor != null ? topTextColor : textColor);
g2d.drawString(topText, topTextOffsetX, 13);
}
if (topTextImage != null) {
@@ -235,6 +236,15 @@ public class HoverButton extends JPanel implements MouseListener {
this.textColor = textColor;
}
+ /**
+ * Overrides textColor for the upper text if non-null.
+ * If null, return back to textColor.
+ * @param textColor
+ */
+ public void setTopTextColor(Color textColor) {
+ this.topTextColor = textColor;
+ }
+
public void setOverlayImage(Image image) {
this.overlayImage = image;
this.overlayImageSize = new Dimension(image.getWidth(null), image.getHeight(null));
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 28d100eb090..4e38b6ea41c 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form
@@ -364,6 +364,14 @@
+
+
+
+
+
+
+
+
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 38153e1bf8c..27157781f65 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java
@@ -8,6 +8,7 @@ import mage.client.table.TablePlayerPanel;
import mage.client.util.Event;
import mage.client.util.IgnoreList;
import mage.client.util.Listener;
+import mage.constants.MatchBufferTime;
import mage.constants.MatchTimeLimit;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
@@ -85,6 +86,8 @@ public class NewTableDialog extends MageDialog {
cbDeckType = new javax.swing.JComboBox();
lbTimeLimit = new javax.swing.JLabel();
cbTimeLimit = new javax.swing.JComboBox();
+ lbBufferTime = new javax.swing.JLabel();
+ cbBufferTime = new javax.swing.JComboBox();
lblGameType = new javax.swing.JLabel();
cbGameType = new javax.swing.JComboBox();
chkRollbackTurnsAllowed = new javax.swing.JCheckBox();
@@ -190,6 +193,9 @@ public class NewTableDialog extends MageDialog {
lbTimeLimit.setText("Time Limit:");
lbTimeLimit.setToolTipText("The active time a player may use to finish the match. If their time runs out, the player looses the current game.");
+ lbBufferTime.setText("Buffer Time:");
+ lbBufferTime.setToolTipText("The extra time a player gets whenever the timer starts before their normal time limit starts going down.");
+
lblGameType.setText("Game Type:");
cbGameType.addActionListener(new java.awt.event.ActionListener() {
@@ -332,7 +338,11 @@ public class NewTableDialog extends MageDialog {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lbTimeLimit)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 102, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lbBufferTime)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cbBufferTime, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblPassword)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
@@ -412,6 +422,8 @@ public class NewTableDialog extends MageDialog {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cbTimeLimit)
.addComponent(lbTimeLimit)
+ .addComponent(cbBufferTime)
+ .addComponent(lbBufferTime)
.addComponent(lblPassword)
.addComponent(txtPassword)
.addComponent(chkSpectatorsAllowed)))
@@ -604,6 +616,7 @@ public class NewTableDialog extends MageDialog {
}
options.setDeckType((String) this.cbDeckType.getSelectedItem());
options.setMatchTimeLimit((MatchTimeLimit) this.cbTimeLimit.getSelectedItem());
+ options.setMatchBufferTime((MatchBufferTime) this.cbBufferTime.getSelectedItem());
options.setAttackOption((MultiplayerAttackOption) this.cbAttackOption.getSelectedItem());
options.setSkillLevel((SkillLevel) this.cbSkillLevel.getSelectedItem());
options.setRange((RangeOfInfluence) this.cbRange.getSelectedItem());
@@ -801,6 +814,7 @@ public class NewTableDialog extends MageDialog {
cbDeckType.setModel(new DefaultComboBoxModel(SessionHandler.getDeckTypes()));
selectLimitedByDefault();
cbTimeLimit.setModel(new DefaultComboBoxModel(MatchTimeLimit.values()));
+ cbBufferTime.setModel(new DefaultComboBoxModel(MatchBufferTime.values()));
cbRange.setModel(new DefaultComboBoxModel(RangeOfInfluence.values()));
cbAttackOption.setModel(new DefaultComboBoxModel(MultiplayerAttackOption.values()));
cbSkillLevel.setModel(new DefaultComboBoxModel(SkillLevel.values()));
@@ -881,6 +895,14 @@ public class NewTableDialog extends MageDialog {
break;
}
}
+ // TODO: Rethink defaults with buffer time?
+ int bufferTime = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_BUFFER_TIME + versionStr, "0"));
+ for (MatchBufferTime mtl : MatchBufferTime.values()) {
+ if (mtl.getBufferTime() == bufferTime) {
+ this.cbBufferTime.setSelectedItem(mtl);
+ break;
+ }
+ }
cbDeckType.setSelectedItem(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_DECK_TYPE + versionStr, "Limited"));
String deckFile = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE + versionStr, null);
if (deckFile != null) {
@@ -985,6 +1007,7 @@ public class NewTableDialog extends MageDialog {
private javax.swing.JComboBox cbRange;
private javax.swing.JComboBox cbSkillLevel;
private javax.swing.JComboBox cbTimeLimit;
+ private javax.swing.JComboBox cbBufferTime;
private javax.swing.JCheckBox chkPlaneChase;
private javax.swing.JCheckBox chkRated;
private javax.swing.JCheckBox chkRollbackTurnsAllowed;
@@ -996,6 +1019,7 @@ public class NewTableDialog extends MageDialog {
private javax.swing.JSeparator jSeparator3;
private javax.swing.JLabel lbDeckType;
private javax.swing.JLabel lbTimeLimit;
+ private javax.swing.JLabel lbBufferTime;
private javax.swing.JLabel lblAttack;
private javax.swing.JLabel lblEdhPowerLevel;
private javax.swing.JLabel lblFreeMulligans;
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 01be5c944cd..91e22008877 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.form
@@ -373,6 +373,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 32d13e79b21..ca8d6509a01 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java
@@ -18,6 +18,7 @@ import mage.client.SessionHandler;
import mage.client.table.TournamentPlayerPanel;
import mage.client.util.IgnoreList;
import mage.client.util.gui.FastSearchUtil;
+import mage.constants.MatchBufferTime;
import mage.constants.MatchTimeLimit;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
@@ -85,6 +86,7 @@ public class NewTournamentDialog extends MageDialog {
cbDeckType.setModel(new DefaultComboBoxModel(SessionHandler.getDeckTypes()));
cbTimeLimit.setModel(new DefaultComboBoxModel(MatchTimeLimit.values()));
+ cbBufferTime.setModel(new DefaultComboBoxModel(MatchBufferTime.values()));
cbSkillLevel.setModel(new DefaultComboBoxModel(SkillLevel.values()));
cbDraftCube.setModel(new DefaultComboBoxModel(SessionHandler.getDraftCubes()));
cbDraftTiming.setModel(new DefaultComboBoxModel(Arrays.stream(TimingOption.values())
@@ -131,6 +133,8 @@ public class NewTournamentDialog extends MageDialog {
txtName = new javax.swing.JTextField();
lbTimeLimit = new javax.swing.JLabel();
cbTimeLimit = new javax.swing.JComboBox();
+ lbBufferTime = new javax.swing.JLabel();
+ cbBufferTime = new javax.swing.JComboBox();
lbSkillLevel = new javax.swing.JLabel();
cbSkillLevel = new javax.swing.JComboBox();
lblPassword = new javax.swing.JLabel();
@@ -246,6 +250,16 @@ public class NewTournamentDialog extends MageDialog {
cbTimeLimit.setToolTipText("The time a player has for the whole match. If a player runs out of time during a game, they lose the complete match. ");
+ lbBufferTime.setText("Buffer Time:");
+ lbBufferTime.setToolTipText(
+ "The time a player gets whenever their timer starts before their match time starts going down. ");
+
+ org.jdesktop.beansbinding.Binding binding2 = org.jdesktop.beansbinding.Bindings.createAutoBinding( org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, cbBufferTime, org.jdesktop.beansbinding.ObjectProperty.create(), lbBufferTime, org.jdesktop.beansbinding.BeanProperty.create("labelFor"));
+ bindingGroup.addBinding(binding2);
+
+ cbBufferTime.setToolTipText(
+ "The time a player gets whenever their timer starts before their match time starts going down. ");
+
lbSkillLevel.setText("Skill Level:");
lbSkillLevel.setToolTipText("The time a player has for the whole match. If a player runs out of time during a game, they lose the complete match. ");
@@ -552,7 +566,11 @@ public class NewTournamentDialog extends MageDialog {
.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)
+ .addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lbBufferTime)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cbBufferTime, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblPassword)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
@@ -573,6 +591,8 @@ public class NewTournamentDialog extends MageDialog {
.addComponent(lblName)
.addComponent(lbTimeLimit)
.addComponent(cbTimeLimit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(lbBufferTime)
+ .addComponent(cbBufferTime, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblPassword)
.addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(cbAllowSpectators, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
@@ -1339,6 +1359,7 @@ public class NewTournamentDialog extends MageDialog {
tOptions.getMatchOptions().setBannedUsers(IgnoreList.getIgnoredUsers(serverAddress));
tOptions.getMatchOptions().setMatchTimeLimit((MatchTimeLimit) this.cbTimeLimit.getSelectedItem());
+ tOptions.getMatchOptions().setMatchBufferTime((MatchBufferTime) this.cbBufferTime.getSelectedItem());
tOptions.getMatchOptions().setSkillLevel((SkillLevel) this.cbSkillLevel.getSelectedItem());
tOptions.getMatchOptions().setWinsNeeded((Integer) this.spnNumWins.getValue());
tOptions.getMatchOptions().setFreeMulligans((Integer) this.spnFreeMulligans.getValue());
@@ -1363,6 +1384,14 @@ public class NewTournamentDialog extends MageDialog {
break;
}
}
+ // TODO: Rethink default match time with buffer time.
+ int bufferTime = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_BUFFER_TIME + versionStr, "0"));
+ for (MatchBufferTime mtl : MatchBufferTime.values()) {
+ if (mtl.getBufferTime() == bufferTime) {
+ this.cbBufferTime.setSelectedItem(mtl);
+ break;
+ }
+ }
String skillLevelDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_SKILL_LEVEL + versionStr, "Casual");
for (SkillLevel skillLevel : SkillLevel.values()) {
if (skillLevel.toString().equals(skillLevelDefault)) {
@@ -1485,6 +1514,7 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JCheckBox cbPlaneChase;
private javax.swing.JComboBox cbSkillLevel;
private javax.swing.JComboBox cbTimeLimit;
+ private javax.swing.JComboBox cbBufferTime;
private javax.swing.JComboBox cbTournamentType;
private javax.swing.JCheckBox chkRated;
private javax.swing.JCheckBox chkRollbackTurnsAllowed;
@@ -1492,6 +1522,7 @@ public class NewTournamentDialog extends MageDialog {
private javax.swing.JLabel lbDeckType;
private javax.swing.JLabel lbSkillLevel;
private javax.swing.JLabel lbTimeLimit;
+ private javax.swing.JLabel lbBufferTime;
private javax.swing.JLabel lblConstructionTime;
private javax.swing.JLabel lblDraftCube;
private javax.swing.JLabel lblFreeMulligans;
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 e500442a2aa..e028de5745c 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java
@@ -196,6 +196,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_NEW_TABLE_PASSWORD_JOIN = "newTablePasswordJoin";
public static final String KEY_NEW_TABLE_DECK_TYPE = "newTableDeckType";
public static final String KEY_NEW_TABLE_TIME_LIMIT = "newTableTimeLimit";
+ public static final String KEY_NEW_TABLE_BUFFER_TIME = "newTableBufferTime";
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";
@@ -218,6 +219,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_NEW_TOURNAMENT_NAME = "newTournamentName";
public static final String KEY_NEW_TOURNAMENT_PASSWORD = "newTournamentPassword";
public static final String KEY_NEW_TOURNAMENT_TIME_LIMIT = "newTournamentTimeLimit";
+ public static final String KEY_NEW_TOURNAMENT_BUFFER_TIME = "newTournamentBufferTime";
public static final String KEY_NEW_TOURNAMENT_CONSTR_TIME = "newTournamentConstructionTime";
public static final String KEY_NEW_TOURNAMENT_TYPE = "newTournamentType";
public static final String KEY_NEW_TOURNAMENT_NUMBER_OF_FREE_MULLIGANS = "newTournamentNumberOfFreeMulligans";
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 8cdb7213a6a..1bb24363081 100644
--- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java
+++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java
@@ -605,7 +605,8 @@ public final class GamePanel extends javax.swing.JPanel {
}
PlayerView player = game.getPlayers().get(playerSeat);
PlayAreaPanel playAreaPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
- new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), game.isPlayer(), game.isRollbackTurnsAllowed(), row == 0));
+ new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), game.isPlayer(),
+ game.isRollbackTurnsAllowed(), row == 0));
players.put(player.getPlayerId(), playAreaPanel);
playersWhoLeft.put(player.getPlayerId(), false);
GridBagConstraints c = new GridBagConstraints();
@@ -649,7 +650,8 @@ public final class GamePanel extends javax.swing.JPanel {
}
player = game.getPlayers().get(playerNum);
PlayAreaPanel playerPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
- new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), false, game.isRollbackTurnsAllowed(), row == 0));
+ new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), false, game.isRollbackTurnsAllowed(),
+ row == 0));
players.put(player.getPlayerId(), playerPanel);
playersWhoLeft.put(player.getPlayerId(), false);
c = new GridBagConstraints();
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 3e4f0df69dd..abf74ca67e5 100644
--- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java
+++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java
@@ -57,7 +57,8 @@ public class PlayAreaPanel extends javax.swing.JPanel {
* @param gamePanel
* @param options
*/
- public PlayAreaPanel(PlayerView player, BigCard bigCard, UUID gameId, int priorityTime, GamePanel gamePanel, PlayAreaPanelOptions options) {
+ public PlayAreaPanel(PlayerView player, BigCard bigCard, UUID gameId, int priorityTime, GamePanel gamePanel,
+ PlayAreaPanelOptions options) {
this.gamePanel = gamePanel;
this.options = options;
initComponents();
diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java
index 87bff89fac3..034d607f6e0 100644
--- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java
+++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java
@@ -103,10 +103,14 @@ public class PlayerPanelExt extends javax.swing.JPanel {
});
final PriorityTimer pt = timer;
timer.setTaskOnTick(() -> {
- int priorityTimeValue = pt.getCount();
+ int priorityTimeValue = pt.getCount() + pt.getBufferCount();
String text = getPriorityTimeLeftString(priorityTimeValue);
+
PlayerPanelExt.this.avatar.setTopText(text);
+ PlayerPanelExt.this.avatar.setTopTextColor(pt.getBufferCount() > 0 ? Color.GREEN : null);
PlayerPanelExt.this.timerLabel.setText(text);
+ PlayerPanelExt.this.timerLabel
+ .setForeground(pt.getBufferCount() > 0 ? Color.GREEN.darker().darker() : Color.BLACK);
PlayerPanelExt.this.avatar.repaint();
});
timer.init(gameId);
@@ -304,8 +308,13 @@ public class PlayerPanelExt extends javax.swing.JPanel {
if (player.getPriorityTimeLeft() != Integer.MAX_VALUE) {
String priorityTimeValue = getPriorityTimeLeftString(player);
this.timer.setCount(player.getPriorityTimeLeft());
+ this.timer.setBufferCount(player.getBufferTimeLeft());
this.avatar.setTopText(priorityTimeValue);
this.timerLabel.setText(priorityTimeValue);
+
+ this.avatar.setTopTextColor(player.getBufferTimeLeft() > 0 ? Color.GREEN : null);
+ this.timerLabel
+ .setForeground(player.getBufferTimeLeft() > 0 ? Color.GREEN.darker().darker() : Color.BLACK);
}
if (player.isTimerActive()) {
this.timer.resume();
@@ -396,7 +405,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
}
private String getPriorityTimeLeftString(PlayerView player) {
- int priorityTimeLeft = player.getPriorityTimeLeft();
+ int priorityTimeLeft = player.getPriorityTimeLeft() + player.getBufferTimeLeft();
return getPriorityTimeLeftString(priorityTimeLeft);
}
diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java
index 8282e14b8e2..c1901322944 100644
--- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java
+++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java
@@ -1708,6 +1708,7 @@ public class TablesPanel extends javax.swing.JPanel {
options.setRange(RangeOfInfluence.ONE);
options.setWinsNeeded(2);
options.setMatchTimeLimit(MatchTimeLimit.NONE);
+ options.setMatchBufferTime(MatchBufferTime.NONE);
options.setFreeMulligans(2);
options.setSkillLevel(SkillLevel.CASUAL);
options.setRollbackTurnsAllowed(true);
diff --git a/Mage.Common/src/main/java/mage/utils/timer/PriorityTimer.java b/Mage.Common/src/main/java/mage/utils/timer/PriorityTimer.java
index e49a820c813..fa628df4a61 100644
--- a/Mage.Common/src/main/java/mage/utils/timer/PriorityTimer.java
+++ b/Mage.Common/src/main/java/mage/utils/timer/PriorityTimer.java
@@ -18,6 +18,7 @@ public class PriorityTimer extends TimerTask {
private final Action taskOnTimeout;
private int count;
+ private int bufferCount = 0;
private Action taskOnTick;
private States state = States.NONE;
@@ -76,6 +77,14 @@ public class PriorityTimer extends TimerTask {
this.count = count;
}
+ public int getBufferCount() {
+ return bufferCount;
+ }
+
+ public void setBufferCount(int count) {
+ this.bufferCount = count;
+ }
+
public void setTaskOnTick(Action taskOnTick) {
this.taskOnTick = taskOnTick;
}
@@ -83,7 +92,13 @@ public class PriorityTimer extends TimerTask {
@Override
public void run() {
if (state == States.RUNNING) {
- count--;
+ // Count down buffer time first
+ if (bufferCount > 0) {
+ bufferCount--;
+ } else {
+ count--;
+ }
+
if (taskOnTick != null) {
try {
taskOnTick.execute();
diff --git a/Mage.Common/src/main/java/mage/view/GameView.java b/Mage.Common/src/main/java/mage/view/GameView.java
index 84a2abbf959..91529f791a5 100644
--- a/Mage.Common/src/main/java/mage/view/GameView.java
+++ b/Mage.Common/src/main/java/mage/view/GameView.java
@@ -42,6 +42,7 @@ public class GameView implements Serializable {
private static final Logger LOGGER = Logger.getLogger(GameView.class);
private final int priorityTime;
+ private final int bufferTime;
private final List players = new ArrayList<>();
private CardsView hand;
private PlayableObjectsList canPlayObjects;
@@ -68,6 +69,7 @@ public class GameView implements Serializable {
Player createdForPlayer = null;
this.isPlayer = createdForPlayerId != null;
this.priorityTime = game.getPriorityTime();
+ this.bufferTime = game.getBufferTime();
for (Player player : state.getPlayers().values()) {
players.add(new PlayerView(player, state, game, createdForPlayerId, watcherUserId));
if (player.getId().equals(createdForPlayerId)) {
@@ -313,6 +315,10 @@ public class GameView implements Serializable {
return priorityTime;
}
+ public int getBufferTime() {
+ return bufferTime;
+ }
+
public UUID getActivePlayerId() {
return activePlayerId;
}
diff --git a/Mage.Common/src/main/java/mage/view/PlayerView.java b/Mage.Common/src/main/java/mage/view/PlayerView.java
index 9703f20509e..0e2b136afec 100644
--- a/Mage.Common/src/main/java/mage/view/PlayerView.java
+++ b/Mage.Common/src/main/java/mage/view/PlayerView.java
@@ -47,6 +47,7 @@ public class PlayerView implements Serializable {
private final List attachments = new ArrayList<>();
private final int statesSavedSize;
private final int priorityTimeLeft;
+ private final int bufferTimeLeft;
private final boolean passedTurn; // F4
private final boolean passedUntilEndOfTurn; // F5
private final boolean passedUntilNextMain; // F6
@@ -74,6 +75,7 @@ public class PlayerView implements Serializable {
this.isActive = (player.getId().equals(state.getActivePlayerId()));
this.hasPriority = player.getId().equals(state.getPriorityPlayerId());
this.priorityTimeLeft = player.getPriorityTimeLeft();
+ this.bufferTimeLeft = player.getBufferTimeLeft();
this.timerActive = (this.hasPriority && player.isGameUnderControl())
|| (player.getPlayersUnderYourControl().contains(state.getPriorityPlayerId()))
|| player.getId().equals(game.getState().getChoosingPlayerId());
@@ -275,6 +277,10 @@ public class PlayerView implements Serializable {
return priorityTimeLeft;
}
+ public int getBufferTimeLeft() {
+ return bufferTimeLeft;
+ }
+
public boolean hasPriority() {
return hasPriority;
}
diff --git a/Mage.Common/src/main/java/mage/view/TableView.java b/Mage.Common/src/main/java/mage/view/TableView.java
index 32d09ee79a2..9bd2403f02a 100644
--- a/Mage.Common/src/main/java/mage/view/TableView.java
+++ b/Mage.Common/src/main/java/mage/view/TableView.java
@@ -98,6 +98,7 @@ public class TableView implements Serializable {
if (table.getMatch().getGames().isEmpty()) {
addInfo.append("Wins:").append(table.getMatch().getWinsNeeded());
addInfo.append(" Time: ").append(table.getMatch().getOptions().getMatchTimeLimit().toString());
+ addInfo.append(" Buffer: ").append(table.getMatch().getOptions().getMatchBufferTime().toString());
if (table.getMatch().getFreeMulligans() > 0) {
addInfo.append(" FM: ").append(table.getMatch().getFreeMulligans());
}
@@ -150,6 +151,7 @@ public class TableView implements Serializable {
stateText.append(" (").append(table.getTournament().getPlayers().size()).append('/').append(table.getNumberOfSeats()).append(')');
}
infoText.append(" Time: ").append(table.getTournament().getOptions().getMatchOptions().getMatchTimeLimit().toString());
+ infoText.append(" Buffer: ").append(table.getTournament().getOptions().getMatchOptions().getMatchBufferTime().toString());
if (table.getTournament().getOptions().getMatchOptions().getFreeMulligans() > 0) {
infoText.append(" FM: ").append(table.getTournament().getOptions().getMatchOptions().getFreeMulligans());
}
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 8c1b23bfcb3..3f00ad33bae 100644
--- a/Mage.Server/src/main/java/mage/server/game/GameController.java
+++ b/Mage.Server/src/main/java/mage/server/game/GameController.java
@@ -150,15 +150,18 @@ public class GameController implements GameCallback {
if (playerId == null) {
throw new MageException("RESUME_TIMER: playerId can't be null");
}
+ Player player = game.getState().getPlayer(playerId);
+ if (player == null) {
+ throw new MageException("RESUME_TIMER: player can't be null");
+ }
+
timer = timers.get(playerId);
if (timer == null) {
- Player player = game.getState().getPlayer(playerId);
- if (player != null) {
- timer = createPlayerTimer(event.getPlayerId(), player.getPriorityTimeLeft());
- } else {
- throw new MageException("RESUME_TIMER: player can't be null");
- }
+ timer = createPlayerTimer(event.getPlayerId(), player.getPriorityTimeLeft());
}
+
+ player.setBufferTimeLeft(game.getBufferTime());
+ timer.setBufferCount(game.getBufferTime());
timer.resume();
break;
case PAUSE_TIMER:
diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
index 472e6c968d2..5a5357206d5 100644
--- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
+++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
@@ -3856,6 +3856,16 @@ public class TestPlayer implements Player {
return computerPlayer.getPriorityTimeLeft();
}
+ @Override
+ public void setBufferTimeLeft(int timeLeft) {
+ computerPlayer.setBufferTimeLeft(timeLeft);
+ }
+
+ @Override
+ public int getBufferTimeLeft() {
+ return computerPlayer.getBufferTimeLeft();
+ }
+
@Override
public boolean hasQuit() {
return computerPlayer.hasQuit();
diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
index 1b5c9b622dc..52b0b6b62ef 100644
--- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
+++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
@@ -1149,6 +1149,16 @@ public class PlayerStub implements Player {
return 0;
}
+ @Override
+ public void setBufferTimeLeft(int timeLeft) {
+
+ }
+
+ @Override
+ public int getBufferTimeLeft() {
+ return 0;
+ }
+
@Override
public void setReachedNextTurnAfterLeaving(boolean reachedNextTurnAfterLeaving) {
diff --git a/Mage/src/main/java/mage/constants/MatchBufferTime.java b/Mage/src/main/java/mage/constants/MatchBufferTime.java
new file mode 100644
index 00000000000..bfb93e2ca5e
--- /dev/null
+++ b/Mage/src/main/java/mage/constants/MatchBufferTime.java
@@ -0,0 +1,41 @@
+package mage.constants;
+
+/**
+ * The time a player receives whenever the timer starts. This ticks down before their normal time,
+ * and refreshes to full every time the timer starts, creating a sort of buffer. Similar to how to
+ * chess clocks work.
+ *
+ * Based off of MatchTimeLimit
+ *
+ * @author alexander-novo
+ */
+public enum MatchBufferTime {
+ NONE(0, "None"),
+ SEC__05(5, "5 Seconds"),
+ SEC__10(10, "10 Seconds"),
+ SEC__15(15, "15 Seconds"),
+ SEC__20(20, "20 Seconds"),
+ SEC__25(25, "25 Seconds"),
+ SEC__30(30, "30 Seconds");
+
+ private final int matchSeconds;
+ private final String name;
+
+ MatchBufferTime(int matchSeconds, String name) {
+ this.matchSeconds = matchSeconds;
+ this.name = name;
+ }
+
+ public int getBufferTime() {
+ return matchSeconds;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java
index 63e85a38dd3..464bc1eaff1 100644
--- a/Mage/src/main/java/mage/game/Game.java
+++ b/Mage/src/main/java/mage/game/Game.java
@@ -529,6 +529,10 @@ public interface Game extends MageItem, Serializable, Copyable {
void setPriorityTime(int priorityTime);
+ int getBufferTime();
+
+ void setBufferTime(int bufferTime);
+
UUID getStartingPlayerId();
void setStartingPlayerId(UUID startingPlayerId);
diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java
index 10c6c77b67d..e10dfb29f68 100644
--- a/Mage/src/main/java/mage/game/GameImpl.java
+++ b/Mage/src/main/java/mage/game/GameImpl.java
@@ -143,7 +143,8 @@ public abstract class GameImpl implements Game {
private boolean scopeRelevant = false; // replacement effects: used to indicate that currently applied replacement effects have to check for scope relevance (614.12 13/01/18)
private boolean saveGame = false; // replay code, not done
- private int priorityTime; // match time limit
+ private int priorityTime; // Match time limit (per player). Set at the start of the match and only goes down.
+ private int bufferTime; // Buffer time before priority time starts going down. Buffer time is refreshed every time the timer starts.
private final int startingLife;
private final int startingHandSize;
private final int minimumDeckSize;
@@ -253,6 +254,7 @@ public abstract class GameImpl implements Game {
this.scopeRelevant = game.scopeRelevant;
this.saveGame = game.saveGame;
this.priorityTime = game.priorityTime;
+ this.bufferTime = game.bufferTime;
this.startingLife = game.startingLife;
this.startingHandSize = game.startingHandSize;
this.minimumDeckSize = game.minimumDeckSize;
@@ -3645,6 +3647,16 @@ public abstract class GameImpl implements Game {
this.priorityTime = priorityTime;
}
+ @Override
+ public int getBufferTime() {
+ return bufferTime;
+ }
+
+ @Override
+ public void setBufferTime(int bufferTime) {
+ this.bufferTime = bufferTime;
+ }
+
@Override
public UUID getStartingPlayerId() {
return startingPlayerId;
diff --git a/Mage/src/main/java/mage/game/match/MatchImpl.java b/Mage/src/main/java/mage/game/match/MatchImpl.java
index 535b768ccdd..1ab1ddbeaf7 100644
--- a/Mage/src/main/java/mage/game/match/MatchImpl.java
+++ b/Mage/src/main/java/mage/game/match/MatchImpl.java
@@ -201,9 +201,11 @@ public abstract class MatchImpl implements Match {
// set the priority time left for the match
if (games.isEmpty()) { // first game full time
matchPlayer.getPlayer().setPriorityTimeLeft(options.getPriorityTime());
+ matchPlayer.getPlayer().setBufferTimeLeft(options.getBufferTime());
} else {
if (matchPlayer.getPriorityTimeLeft() > 0) {
matchPlayer.getPlayer().setPriorityTimeLeft(matchPlayer.getPriorityTimeLeft());
+ matchPlayer.getPlayer().setBufferTimeLeft(options.getBufferTime());
}
}
} else {
@@ -213,6 +215,7 @@ public abstract class MatchImpl implements Match {
}
}
game.setPriorityTime(options.getPriorityTime());
+ game.setBufferTime(options.getBufferTime());
}
protected void shufflePlayers() {
diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java
index 0fd193193e3..dc0dfb0eee6 100644
--- a/Mage/src/main/java/mage/game/match/MatchOptions.java
+++ b/Mage/src/main/java/mage/game/match/MatchOptions.java
@@ -1,6 +1,7 @@
package mage.game.match;
+import mage.constants.MatchBufferTime;
import mage.constants.MatchTimeLimit;
import mage.constants.MultiplayerAttackOption;
import mage.constants.RangeOfInfluence;
@@ -48,6 +49,7 @@ public class MatchOptions implements Serializable {
* Time each player has during the game to play using his\her priority.
*/
protected MatchTimeLimit matchTimeLimit; // 0 = no priorityTime handling
+ protected MatchBufferTime matchBufferTime; // Amount of time each player gets before their normal time limit counts down. Refreshes each time the normal timer is invoked.
protected MulliganType mulliganType;
/*public MatchOptions(String name, String gameType) {
@@ -160,6 +162,21 @@ public class MatchOptions implements Serializable {
this.matchTimeLimit = matchTimeLimit;
}
+ public int getBufferTime() {
+ if (matchBufferTime == null) {
+ return MatchBufferTime.NONE.getBufferTime();
+ }
+ return matchBufferTime.getBufferTime();
+ }
+
+ public MatchBufferTime getMatchBufferTime() {
+ return this.matchBufferTime;
+ }
+
+ public void setMatchBufferTime(MatchBufferTime matchBufferTime) {
+ this.matchBufferTime = matchBufferTime;
+ }
+
public String getPassword() {
return password;
}
diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java
index 26fc0aab0f8..da497976b74 100644
--- a/Mage/src/main/java/mage/players/Player.java
+++ b/Mage/src/main/java/mage/players/Player.java
@@ -854,6 +854,20 @@ public interface Player extends MageItem, Copyable {
*/
int getPriorityTimeLeft();
+ /**
+ * Set seconds left before priority time starts ticking down.
+ *
+ * @param timeLeft
+ */
+ void setBufferTimeLeft(int timeLeft);
+
+ /**
+ * Returns seconds left before priority time starts ticking down.
+ *
+ * @return
+ */
+ int getBufferTimeLeft();
+
void setReachedNextTurnAfterLeaving(boolean reachedNextTurnAfterLeaving);
boolean hasReachedNextTurnAfterLeaving();
diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java
index 321e04fbf87..5454d412472 100644
--- a/Mage/src/main/java/mage/players/PlayerImpl.java
+++ b/Mage/src/main/java/mage/players/PlayerImpl.java
@@ -119,6 +119,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected int turns;
protected int storedBookmark = -1;
protected int priorityTimeLeft = Integer.MAX_VALUE;
+ protected int bufferTimeLeft = 0;
// conceded or connection lost game
protected boolean left;
@@ -275,6 +276,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.justActivatedType = player.justActivatedType;
this.priorityTimeLeft = player.getPriorityTimeLeft();
+ this.bufferTimeLeft = player.getBufferTimeLeft();
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana());
@@ -4454,6 +4456,16 @@ public abstract class PlayerImpl implements Player, Serializable {
return priorityTimeLeft;
}
+ @Override
+ public void setBufferTimeLeft(int timeLeft) {
+ bufferTimeLeft = timeLeft;
+ }
+
+ @Override
+ public int getBufferTimeLeft() {
+ return bufferTimeLeft;
+ }
+
@Override
public boolean hasQuit() {
return quit;