game timer: Add chess-style buffer time option (#10598)

* UI Changes

* Add new buffer time options

* Main functionality

* Final implementation

Also added player UI for when they are using their buffer time (timer turns green)
This commit is contained in:
Alexander Novotny 2023-07-28 22:05:21 -04:00 committed by GitHub
parent b7543af939
commit 519b3988be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 273 additions and 16 deletions

View file

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

View file

@ -364,6 +364,14 @@
</Component>
<Component class="javax.swing.JComboBox" name="cbTimeLimit">
</Component>
<Component class="javax.swing.JLabel" name="lbBufferTime">
<Properties>
<Property name="text" type="java.lang.String" value="Buffer Time:"/>
<Property name="toolTipText" type="java.lang.String" value="The extra time a player gets whenever the timer starts before their normal time limit starts going down."/>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="cbBufferTime">
</Component>
<Component class="javax.swing.JLabel" name="lblGameType">
<Properties>
<Property name="text" type="java.lang.String" value="Game Type:"/>

View file

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

View file

@ -373,6 +373,20 @@
<Property name="toolTipText" type="java.lang.String" value="The time a player has for the whole match. If a player runs out of time during a game, they lose the complete match. "/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="lbBufferTime">
<Properties>
<Property name="text" type="java.lang.String" value="Buffer Time:"/>
<Property name="toolTipText" type="java.lang.String" value="The time a player gets whenever their timer starts before their match time starts going down. "/>
</Properties>
<BindingProperties>
<BindingProperty name="labelFor" source="cbBufferTime" target="lbBufferTime" targetPath="labelFor" updateStrategy="0" immediately="false"/>
</BindingProperties>
</Component>
<Component class="javax.swing.JComboBox" name="cbBufferTime">
<Properties>
<Property name="toolTipText" type="java.lang.String" value="The time a player gets whenever their timer starts before their match time starts going down. "/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="lbSkillLevel">
<Properties>
<Property name="text" type="java.lang.String" value="Skill Level:"/>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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