mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-23 10:05:10 -07:00
[Client] Add tournament UI for creating and following lobby tournaments
Wires the tournament protocol into the client: - DlgCreateGame gains a tournament checkbox with a games-per-match settings sub-dialog; both values are sent with Command_CreateGame and mirrored in the join-info variant of the dialog - TournamentWidget renders live standings (with deck-submission state) and pairings for the local player's perspective, and asks to open the current match game via openMatchGameRequested() - TournamentTabGameExtension attaches an overview page to TabGame's stacked views, mirrors phase and submission progress onto a deck-view status strip, and provides round-trip navigation between game view and standings; hosts get an in-game settings dialog during deck building - Open-match falls back to joining as spectator through TabSupervisor; sub-games now return to their parent tab on close, falling back to the normal leave-game flow when the parent is gone
This commit is contained in:
parent
8f6d18df10
commit
c5fd10087a
13 changed files with 719 additions and 3 deletions
|
|
@ -53,6 +53,7 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/dialogs/dlg_settings.cpp
|
src/interface/widgets/dialogs/dlg_settings.cpp
|
||||||
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
|
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
|
||||||
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
|
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp
|
||||||
|
src/interface/widgets/dialogs/dlg_tournament_settings.cpp
|
||||||
src/interface/widgets/dialogs/dlg_update.cpp
|
src/interface/widgets/dialogs/dlg_update.cpp
|
||||||
src/interface/widgets/dialogs/dlg_view_log.cpp
|
src/interface/widgets/dialogs/dlg_view_log.cpp
|
||||||
src/interface/widgets/dialogs/override_printing_warning.cpp
|
src/interface/widgets/dialogs/override_printing_warning.cpp
|
||||||
|
|
@ -216,6 +217,7 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/deck_editor/deck_state_manager.cpp
|
src/interface/widgets/deck_editor/deck_state_manager.cpp
|
||||||
src/interface/widgets/deck_editor/deck_zone_dialog.cpp
|
src/interface/widgets/deck_editor/deck_zone_dialog.cpp
|
||||||
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
|
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
|
||||||
|
src/interface/widgets/draft/tournament_widget.cpp
|
||||||
src/interface/widgets/general/background_sources.cpp
|
src/interface/widgets/general/background_sources.cpp
|
||||||
src/interface/widgets/general/display/background_plate_widget.cpp
|
src/interface/widgets/general/display/background_plate_widget.cpp
|
||||||
src/interface/widgets/general/display/banner_widget.cpp
|
src/interface/widgets/general/display/banner_widget.cpp
|
||||||
|
|
@ -392,6 +394,7 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/tabs/tab_server.cpp
|
src/interface/widgets/tabs/tab_server.cpp
|
||||||
src/interface/widgets/tabs/tab_supervisor.cpp
|
src/interface/widgets/tabs/tab_supervisor.cpp
|
||||||
src/interface/widgets/tabs/tab_visual_database_display.cpp
|
src/interface/widgets/tabs/tab_visual_database_display.cpp
|
||||||
|
src/interface/widgets/tabs/tournament_tab_game_extension.cpp
|
||||||
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp
|
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp
|
||||||
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp
|
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp
|
||||||
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../../../client/settings/cache_settings.h"
|
#include "../../../client/settings/cache_settings.h"
|
||||||
#include "../interface/widgets/tabs/tab_room.h"
|
#include "../interface/widgets/tabs/tab_room.h"
|
||||||
|
#include "dlg_tournament_settings.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
|
@ -104,14 +105,29 @@ void DlgCreateGame::sharedCtor()
|
||||||
|
|
||||||
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
|
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
|
||||||
|
|
||||||
|
tournamentCheckBox = new QCheckBox(tr("Tournament mode"));
|
||||||
|
tournamentCheckBox->setToolTip(tr("All players submit a deck up front and rounds are paired automatically"));
|
||||||
|
tournamentSettingsButton = new QPushButton(tr("Settings..."));
|
||||||
|
tournamentSettingsButton->setEnabled(false);
|
||||||
|
connect(tournamentCheckBox, &QCheckBox::toggled, tournamentSettingsButton, &QPushButton::setEnabled);
|
||||||
|
connect(tournamentSettingsButton, &QPushButton::clicked, this, [this] {
|
||||||
|
DlgTournamentSettings dlg(this);
|
||||||
|
dlg.setCurrentGamesPerMatch(tournamentSettings.gamesPerMatch);
|
||||||
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
|
tournamentSettings = dlg.getResult();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
|
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
|
||||||
|
|
||||||
auto *gameSetupOptionsLayout = new QGridLayout;
|
auto *gameSetupOptionsLayout = new QGridLayout;
|
||||||
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
|
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
|
||||||
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
|
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
|
||||||
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
|
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
|
||||||
|
gameSetupOptionsLayout->addWidget(tournamentCheckBox, 2, 0);
|
||||||
|
gameSetupOptionsLayout->addWidget(tournamentSettingsButton, 2, 1);
|
||||||
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
|
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
|
||||||
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
|
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 3, 0);
|
||||||
} else {
|
} else {
|
||||||
createGameAsJudgeCheckBox->setChecked(false);
|
createGameAsJudgeCheckBox->setChecked(false);
|
||||||
createGameAsJudgeCheckBox->setHidden(true);
|
createGameAsJudgeCheckBox->setHidden(true);
|
||||||
|
|
@ -188,7 +204,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
|
||||||
}
|
}
|
||||||
|
|
||||||
DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QString> &_gameTypes, QWidget *parent)
|
DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QString> &_gameTypes, QWidget *parent)
|
||||||
: QDialog(parent), room(0), gameTypes(_gameTypes)
|
: QDialog(parent), room(nullptr), gameTypes(_gameTypes)
|
||||||
{
|
{
|
||||||
sharedCtor();
|
sharedCtor();
|
||||||
|
|
||||||
|
|
@ -205,6 +221,8 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
|
||||||
createGameAsSpectatorCheckBox->setEnabled(false);
|
createGameAsSpectatorCheckBox->setEnabled(false);
|
||||||
startingLifeTotalEdit->setEnabled(false);
|
startingLifeTotalEdit->setEnabled(false);
|
||||||
shareDecklistsOnLoadCheckBox->setEnabled(false);
|
shareDecklistsOnLoadCheckBox->setEnabled(false);
|
||||||
|
tournamentCheckBox->setEnabled(false);
|
||||||
|
tournamentSettingsButton->setEnabled(false);
|
||||||
|
|
||||||
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
|
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
|
||||||
maxPlayersEdit->setValue(gameInfo.max_players());
|
maxPlayersEdit->setValue(gameInfo.max_players());
|
||||||
|
|
@ -215,6 +233,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
|
||||||
spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat());
|
spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat());
|
||||||
spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient());
|
spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient());
|
||||||
shareDecklistsOnLoadCheckBox->setChecked(gameInfo.share_decklists_on_load());
|
shareDecklistsOnLoadCheckBox->setChecked(gameInfo.share_decklists_on_load());
|
||||||
|
tournamentCheckBox->setChecked(gameInfo.is_tournament());
|
||||||
|
|
||||||
QSet<int> types;
|
QSet<int> types;
|
||||||
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
|
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
|
||||||
|
|
@ -252,6 +271,8 @@ void DlgCreateGame::actReset()
|
||||||
|
|
||||||
startingLifeTotalEdit->setValue(20);
|
startingLifeTotalEdit->setValue(20);
|
||||||
shareDecklistsOnLoadCheckBox->setChecked(false);
|
shareDecklistsOnLoadCheckBox->setChecked(false);
|
||||||
|
tournamentCheckBox->setChecked(false);
|
||||||
|
tournamentSettings = DlgTournamentSettingsResult{};
|
||||||
createGameAsJudgeCheckBox->setChecked(false);
|
createGameAsJudgeCheckBox->setChecked(false);
|
||||||
|
|
||||||
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
||||||
|
|
@ -282,6 +303,10 @@ void DlgCreateGame::actOK()
|
||||||
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
|
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
|
||||||
cmd.set_starting_life_total(startingLifeTotalEdit->value());
|
cmd.set_starting_life_total(startingLifeTotalEdit->value());
|
||||||
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
|
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
|
||||||
|
cmd.set_is_tournament(tournamentCheckBox->isChecked());
|
||||||
|
if (tournamentCheckBox->isChecked()) {
|
||||||
|
cmd.set_games_per_match(tournamentSettings.gamesPerMatch);
|
||||||
|
}
|
||||||
|
|
||||||
auto _gameTypes = QString();
|
auto _gameTypes = QString();
|
||||||
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#ifndef DLG_CREATEGAME_H
|
#ifndef DLG_CREATEGAME_H
|
||||||
#define DLG_CREATEGAME_H
|
#define DLG_CREATEGAME_H
|
||||||
|
|
||||||
|
#include "dlg_tournament_settings.h"
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <libcockatrice/utility/macros.h>
|
#include <libcockatrice/utility/macros.h>
|
||||||
|
|
@ -48,6 +50,9 @@ private:
|
||||||
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
|
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
|
||||||
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
|
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
|
||||||
QCheckBox *shareDecklistsOnLoadCheckBox;
|
QCheckBox *shareDecklistsOnLoadCheckBox;
|
||||||
|
QCheckBox *tournamentCheckBox;
|
||||||
|
QPushButton *tournamentSettingsButton;
|
||||||
|
DlgTournamentSettingsResult tournamentSettings;
|
||||||
QDialogButtonBox *buttonBox;
|
QDialogButtonBox *buttonBox;
|
||||||
QPushButton *clearButton;
|
QPushButton *clearButton;
|
||||||
QCheckBox *rememberGameSettings;
|
QCheckBox *rememberGameSettings;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include "dlg_tournament_settings.h"
|
||||||
|
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
|
||||||
|
DlgTournamentSettings::DlgTournamentSettings(QWidget *parent) : QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Tournament Settings"));
|
||||||
|
|
||||||
|
auto *mainLayout = new QFormLayout(this);
|
||||||
|
|
||||||
|
gamesPerMatchSpin = new QSpinBox(this);
|
||||||
|
gamesPerMatchSpin->setRange(1, 5);
|
||||||
|
gamesPerMatchSpin->setValue(1);
|
||||||
|
gamesPerMatchSpin->setToolTip(tr("Number of games per match (e.g., 3 for Best of 3)"));
|
||||||
|
mainLayout->addRow(tr("Games per match:"), gamesPerMatchSpin);
|
||||||
|
|
||||||
|
QLabel *hintLabel = new QLabel(tr("Set to 3 for Best of 3, 5 for Best of 5, etc."), this);
|
||||||
|
hintLabel->setStyleSheet("color: palette(placeholderText);");
|
||||||
|
mainLayout->addRow(QString(), hintLabel);
|
||||||
|
|
||||||
|
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgTournamentSettings::actOK);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
mainLayout->addRow(buttonBox);
|
||||||
|
|
||||||
|
setFixedHeight(sizeHint().height());
|
||||||
|
}
|
||||||
|
|
||||||
|
DlgTournamentSettingsResult DlgTournamentSettings::getResult() const
|
||||||
|
{
|
||||||
|
DlgTournamentSettingsResult result;
|
||||||
|
result.gamesPerMatch = gamesPerMatchSpin->value();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DlgTournamentSettings::setCurrentGamesPerMatch(int n)
|
||||||
|
{
|
||||||
|
gamesPerMatchSpin->setValue(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DlgTournamentSettings::actOK()
|
||||||
|
{
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef DLG_TOURNAMENT_SETTINGS_H
|
||||||
|
#define DLG_TOURNAMENT_SETTINGS_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QDialogButtonBox;
|
||||||
|
class QSpinBox;
|
||||||
|
|
||||||
|
struct DlgTournamentSettingsResult
|
||||||
|
{
|
||||||
|
int gamesPerMatch = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DlgTournamentSettings : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DlgTournamentSettings(QWidget *parent = nullptr);
|
||||||
|
DlgTournamentSettingsResult getResult() const;
|
||||||
|
void setCurrentGamesPerMatch(int n);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void actOK();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSpinBox *gamesPerMatchSpin;
|
||||||
|
QDialogButtonBox *buttonBox;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
227
cockatrice/src/interface/widgets/draft/tournament_widget.cpp
Normal file
227
cockatrice/src/interface/widgets/draft/tournament_widget.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
#include "tournament_widget.h"
|
||||||
|
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTableWidgetItem>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const int PLAYER_COLUMN = 0;
|
||||||
|
const int DECK_COLUMN = 1;
|
||||||
|
const int WINS_COLUMN = 2;
|
||||||
|
const int LOSSES_COLUMN = 3;
|
||||||
|
const int DRAWS_COLUMN = 4;
|
||||||
|
|
||||||
|
const int PLAYER1_COLUMN = 0;
|
||||||
|
const int SCORE_COLUMN = 1;
|
||||||
|
const int STATUS_COLUMN = 2;
|
||||||
|
const int PLAYER2_COLUMN = 3;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TournamentWidget::TournamentWidget(QWidget *parent) : QWidget(parent)
|
||||||
|
{
|
||||||
|
auto *mainLayout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
statusLabel = new QLabel(this);
|
||||||
|
statusLabel->setStyleSheet("font-weight: bold;");
|
||||||
|
mainLayout->addWidget(statusLabel);
|
||||||
|
|
||||||
|
roundLabel = new QLabel(this);
|
||||||
|
mainLayout->addWidget(roundLabel);
|
||||||
|
|
||||||
|
standingsTable = new QTableWidget(this);
|
||||||
|
standingsTable->setColumnCount(5);
|
||||||
|
standingsTable->horizontalHeader()->setStretchLastSection(true);
|
||||||
|
standingsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
standingsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
standingsTable->verticalHeader()->setVisible(false);
|
||||||
|
mainLayout->addWidget(standingsTable);
|
||||||
|
|
||||||
|
pairingsTable = new QTableWidget(this);
|
||||||
|
pairingsTable->setColumnCount(4);
|
||||||
|
pairingsTable->horizontalHeader()->setStretchLastSection(true);
|
||||||
|
pairingsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
pairingsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
pairingsTable->verticalHeader()->setVisible(false);
|
||||||
|
mainLayout->addWidget(pairingsTable);
|
||||||
|
|
||||||
|
openMatchButton = new QPushButton(this);
|
||||||
|
openMatchButton->setEnabled(false);
|
||||||
|
connect(openMatchButton, &QPushButton::clicked, this, [this]() { emit openMatchGameRequested(currentGameId); });
|
||||||
|
mainLayout->addWidget(openMatchButton);
|
||||||
|
|
||||||
|
retranslateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentWidget::retranslateUi()
|
||||||
|
{
|
||||||
|
standingsTable->setHorizontalHeaderLabels({tr("Player"), tr("Deck"), tr("W"), tr("L"), tr("D")});
|
||||||
|
pairingsTable->setHorizontalHeaderLabels({tr("Player 1"), tr("Score"), tr("Status"), tr("Player 2")});
|
||||||
|
openMatchButton->setText(tr("Open match game"));
|
||||||
|
|
||||||
|
if (lastState.has_phase()) {
|
||||||
|
updateTournamentState(lastState);
|
||||||
|
} else {
|
||||||
|
statusLabel->setText(tr("Tournament"));
|
||||||
|
roundLabel->setVisible(true);
|
||||||
|
roundLabel->setText(tr("Waiting for the tournament to start"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentWidget::setLocalPlayerId(int playerId)
|
||||||
|
{
|
||||||
|
localPlayerId = playerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TournamentWidget::getPlayerName(const Event_TournamentState &state, int playerId) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < state.players_size(); ++i) {
|
||||||
|
if (state.players(i).player_id() == playerId) {
|
||||||
|
return QString::fromStdString(state.players(i).player_name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tr("Player %1").arg(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentWidget::updateTournamentState(const Event_TournamentState &state)
|
||||||
|
{
|
||||||
|
lastState = state;
|
||||||
|
|
||||||
|
int gamesPerMatch = getGamesPerMatch(state);
|
||||||
|
QString statusText;
|
||||||
|
switch (state.phase()) {
|
||||||
|
case Event_TournamentState::PHASE_DECK_BUILDING:
|
||||||
|
statusText = gamesPerMatch > 1 ? tr("Tournament - Deck building (Best of %1)").arg(gamesPerMatch * 2 - 1)
|
||||||
|
: tr("Tournament - Deck building");
|
||||||
|
break;
|
||||||
|
case Event_TournamentState::PHASE_PLAYING:
|
||||||
|
statusText = gamesPerMatch > 1 ? tr("Tournament - Playing (Best of %1)").arg(gamesPerMatch * 2 - 1)
|
||||||
|
: tr("Tournament - Playing");
|
||||||
|
break;
|
||||||
|
case Event_TournamentState::PHASE_FINISHED:
|
||||||
|
statusText = gamesPerMatch > 1 ? tr("Tournament - Finished (Best of %1)").arg(gamesPerMatch * 2 - 1)
|
||||||
|
: tr("Tournament - Finished");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusText = tr("Tournament");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
statusLabel->setText(statusText);
|
||||||
|
|
||||||
|
// The server does not report rounds before pairing starts; avoid showing "Round 0 of 0".
|
||||||
|
bool roundStarted = state.current_round() > 0 || state.total_rounds() > 0;
|
||||||
|
roundLabel->setVisible(roundStarted);
|
||||||
|
if (roundStarted) {
|
||||||
|
roundLabel->setText(tr("Round %1 of %2").arg(state.current_round()).arg(state.total_rounds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuildTables(state);
|
||||||
|
resolveCurrentGameId(state);
|
||||||
|
updateOpenMatchButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentWidget::updateOpenMatchButton()
|
||||||
|
{
|
||||||
|
bool playingPhase = lastState.phase() == Event_TournamentState::PHASE_PLAYING;
|
||||||
|
openMatchButton->setVisible(playingPhase);
|
||||||
|
if (!playingPhase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOwnLivePairing) {
|
||||||
|
openMatchButton->setEnabled(true);
|
||||||
|
openMatchButton->setText(tr("Open match game"));
|
||||||
|
openMatchButton->setToolTip(tr("Switch to your current match game"));
|
||||||
|
} else if (localPlayerId == -1 && hasAnyLivePairing) {
|
||||||
|
// Spectators have no pairing of their own but may watch any running match.
|
||||||
|
openMatchButton->setEnabled(true);
|
||||||
|
openMatchButton->setText(tr("Spectate live match"));
|
||||||
|
openMatchButton->setToolTip(tr("Watch a match that is currently running"));
|
||||||
|
} else {
|
||||||
|
openMatchButton->setEnabled(false);
|
||||||
|
openMatchButton->setText(tr("Open match game"));
|
||||||
|
openMatchButton->setToolTip(tr("No match game is running for you right now"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentWidget::rebuildTables(const Event_TournamentState &state)
|
||||||
|
{
|
||||||
|
standingsTable->setRowCount(state.players_size());
|
||||||
|
for (int i = 0; i < state.players_size(); ++i) {
|
||||||
|
const auto &player = state.players(i);
|
||||||
|
|
||||||
|
auto *nameItem = new QTableWidgetItem(QString::fromStdString(player.player_name()));
|
||||||
|
nameItem->setToolTip(QString::fromStdString(player.player_name()));
|
||||||
|
standingsTable->setItem(i, PLAYER_COLUMN, nameItem);
|
||||||
|
|
||||||
|
auto *deckItem = new QTableWidgetItem(player.deck_submitted() ? tr("Submitted") : tr("Pending"));
|
||||||
|
deckItem->setToolTip(player.deck_submitted() ? tr("Deck submitted") : tr("Still choosing a deck"));
|
||||||
|
standingsTable->setItem(i, DECK_COLUMN, deckItem);
|
||||||
|
|
||||||
|
standingsTable->setItem(i, WINS_COLUMN, new QTableWidgetItem(QString::number(player.wins())));
|
||||||
|
standingsTable->setItem(i, LOSSES_COLUMN, new QTableWidgetItem(QString::number(player.losses())));
|
||||||
|
standingsTable->setItem(i, DRAWS_COLUMN, new QTableWidgetItem(QString::number(player.draws())));
|
||||||
|
}
|
||||||
|
|
||||||
|
pairingsTable->setRowCount(state.pairings_size());
|
||||||
|
for (int i = 0; i < state.pairings_size(); ++i) {
|
||||||
|
const auto &pairing = state.pairings(i);
|
||||||
|
|
||||||
|
QString name1 = getPlayerName(state, pairing.player1_id());
|
||||||
|
QString name2 = pairing.player2_id() == -1 ? tr("BYE") : getPlayerName(state, pairing.player2_id());
|
||||||
|
|
||||||
|
QString scoreStr;
|
||||||
|
if (getGamesPerMatch(state) > 1 && pairing.player2_id() != -1) {
|
||||||
|
scoreStr = tr("%1 - %2").arg(pairing.player1_match_wins()).arg(pairing.player2_match_wins());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString statusStr;
|
||||||
|
if (pairing.player2_id() == -1) {
|
||||||
|
statusStr = tr("BYE");
|
||||||
|
} else if (pairing.winner_id() != -1) {
|
||||||
|
statusStr = tr("Finished");
|
||||||
|
} else {
|
||||||
|
statusStr = tr("vs");
|
||||||
|
}
|
||||||
|
|
||||||
|
pairingsTable->setItem(i, PLAYER1_COLUMN, new QTableWidgetItem(name1));
|
||||||
|
pairingsTable->setItem(i, SCORE_COLUMN, new QTableWidgetItem(scoreStr));
|
||||||
|
pairingsTable->setItem(i, STATUS_COLUMN, new QTableWidgetItem(statusStr));
|
||||||
|
pairingsTable->setItem(i, PLAYER2_COLUMN, new QTableWidgetItem(name2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TournamentWidget::getGamesPerMatch(const Event_TournamentState &state) const
|
||||||
|
{
|
||||||
|
return state.has_settings() ? state.settings().games_per_match() : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentWidget::resolveCurrentGameId(const Event_TournamentState &state)
|
||||||
|
{
|
||||||
|
currentGameId = -1;
|
||||||
|
hasOwnLivePairing = false;
|
||||||
|
hasAnyLivePairing = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < state.pairings_size(); ++i) {
|
||||||
|
const auto &pairing = state.pairings(i);
|
||||||
|
if (pairing.game_id() == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!hasOwnLivePairing && localPlayerId != -1 &&
|
||||||
|
(pairing.player1_id() == localPlayerId || pairing.player2_id() == localPlayerId)) {
|
||||||
|
currentGameId = pairing.game_id();
|
||||||
|
hasOwnLivePairing = true;
|
||||||
|
}
|
||||||
|
if (!hasAnyLivePairing) {
|
||||||
|
hasAnyLivePairing = true;
|
||||||
|
if (!hasOwnLivePairing) {
|
||||||
|
currentGameId = pairing.game_id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasOwnLivePairing && hasAnyLivePairing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
cockatrice/src/interface/widgets/draft/tournament_widget.h
Normal file
56
cockatrice/src/interface/widgets/draft/tournament_widget.h
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#ifndef TOURNAMENT_WIDGET_H
|
||||||
|
#define TOURNAMENT_WIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
|
|
||||||
|
class QLabel;
|
||||||
|
class QPushButton;
|
||||||
|
class QTableWidget;
|
||||||
|
class QTableWidgetItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class TournamentWidget
|
||||||
|
* @ingroup GameViews
|
||||||
|
* @brief Displays live standings and pairings for a tournament game.
|
||||||
|
*
|
||||||
|
* Updated exclusively via updateTournamentState(); the widget holds no
|
||||||
|
* logic of its own beyond mapping protocol state to table rows. Navigation
|
||||||
|
* decisions (which pairing belongs to the local player) are resolved here so
|
||||||
|
* callers only need to react to openMatchGameRequested().
|
||||||
|
*/
|
||||||
|
class TournamentWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit TournamentWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void updateTournamentState(const Event_TournamentState &state);
|
||||||
|
void setLocalPlayerId(int playerId);
|
||||||
|
void retranslateUi();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/*! Emitted when the user asks to open the match game of their current pairing. */
|
||||||
|
void openMatchGameRequested(int gameId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void rebuildTables(const Event_TournamentState &state);
|
||||||
|
void resolveCurrentGameId(const Event_TournamentState &state);
|
||||||
|
void updateOpenMatchButton();
|
||||||
|
[[nodiscard]] QString getPlayerName(const Event_TournamentState &state, int playerId) const;
|
||||||
|
[[nodiscard]] int getGamesPerMatch(const Event_TournamentState &state) const;
|
||||||
|
|
||||||
|
QLabel *statusLabel;
|
||||||
|
QLabel *roundLabel;
|
||||||
|
QTableWidget *standingsTable;
|
||||||
|
QTableWidget *pairingsTable;
|
||||||
|
QPushButton *openMatchButton;
|
||||||
|
|
||||||
|
int localPlayerId = -1;
|
||||||
|
int currentGameId = -1;
|
||||||
|
bool hasOwnLivePairing = false;
|
||||||
|
bool hasAnyLivePairing = false;
|
||||||
|
Event_TournamentState lastState;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TOURNAMENT_WIDGET_H
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include "card_database_display_model.h"
|
#include "card_database_display_model.h"
|
||||||
#include "card_database_model.h"
|
#include "card_database_model.h"
|
||||||
#include "tab_supervisor.h"
|
#include "tab_supervisor.h"
|
||||||
|
#include "tournament_tab_game_extension.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
@ -134,6 +135,10 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
|
||||||
createMenuItems();
|
createMenuItems();
|
||||||
createViewMenuItems();
|
createViewMenuItems();
|
||||||
|
|
||||||
|
if (game->getGameMetaInfo()->isTournament()) {
|
||||||
|
tournamentExtension = new TournamentTabGameExtension(this);
|
||||||
|
}
|
||||||
|
|
||||||
connectToGameState();
|
connectToGameState();
|
||||||
connectToPlayerManager();
|
connectToPlayerManager();
|
||||||
connectToGameEventHandler();
|
connectToGameEventHandler();
|
||||||
|
|
@ -150,6 +155,10 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
|
||||||
gameTypes.append(game->getGameMetaInfo()->findRoomGameType(i));
|
gameTypes.append(game->getGameMetaInfo()->findRoomGameType(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tournamentExtension) {
|
||||||
|
tournamentExtension->initializeTournamentMode();
|
||||||
|
}
|
||||||
|
|
||||||
QTimer::singleShot(0, this, &TabGame::loadLayout);
|
QTimer::singleShot(0, this, &TabGame::loadLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -400,6 +409,10 @@ void TabGame::retranslateUi()
|
||||||
}
|
}
|
||||||
|
|
||||||
scene->retranslateUi();
|
scene->retranslateUi();
|
||||||
|
|
||||||
|
if (tournamentExtension) {
|
||||||
|
tournamentExtension->retranslateUi();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabGame::refreshShortcuts()
|
void TabGame::refreshShortcuts()
|
||||||
|
|
@ -928,8 +941,22 @@ void TabGame::stopGame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TabGame::switchToGameTab(int gameId)
|
||||||
|
{
|
||||||
|
return tabSupervisor->switchToGameTabIfAlreadyExists(gameId);
|
||||||
|
}
|
||||||
|
|
||||||
void TabGame::closeGame()
|
void TabGame::closeGame()
|
||||||
{
|
{
|
||||||
|
int parentId = game->getGameMetaInfo()->parentGameId();
|
||||||
|
if (parentId >= 0) {
|
||||||
|
if (switchToGameTab(parentId)) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
// If the parent tab is gone, fall through to the normal leave-game
|
||||||
|
// flow instead of stranding the user on a dead sub-game tab.
|
||||||
|
}
|
||||||
|
|
||||||
gameMenu->clear();
|
gameMenu->clear();
|
||||||
gameMenu->addAction(aLeaveGame);
|
gameMenu->addAction(aLeaveGame);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QPointer>
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
|
|
||||||
class CardMenu;
|
class CardMenu;
|
||||||
class ServerInfo_PlayerProperties;
|
class ServerInfo_PlayerProperties;
|
||||||
class TabbedDeckViewContainer;
|
class TabbedDeckViewContainer;
|
||||||
|
class TournamentTabGameExtension;
|
||||||
inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game");
|
inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game");
|
||||||
|
|
||||||
class UserListProxy;
|
class UserListProxy;
|
||||||
|
|
@ -92,6 +94,8 @@ private:
|
||||||
QList<QAction *> phaseActions;
|
QList<QAction *> phaseActions;
|
||||||
QAction *aCardMenu;
|
QAction *aCardMenu;
|
||||||
|
|
||||||
|
QPointer<TournamentTabGameExtension> tournamentExtension;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The actions associated with managing a QDockWidget
|
* @brief The actions associated with managing a QDockWidget
|
||||||
*/
|
*/
|
||||||
|
|
@ -189,6 +193,8 @@ public:
|
||||||
void connectToGameEventHandler();
|
void connectToGameEventHandler();
|
||||||
void connectMessageLogToGameEventHandler();
|
void connectMessageLogToGameEventHandler();
|
||||||
void connectPlayerListToGameEventHandler();
|
void connectPlayerListToGameEventHandler();
|
||||||
|
/*! Brings the tab of the given game to front if it is open in this session. */
|
||||||
|
bool switchToGameTab(int gameId);
|
||||||
TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay);
|
TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay);
|
||||||
~TabGame() override;
|
~TabGame() override;
|
||||||
void retranslateUi() override;
|
void retranslateUi() override;
|
||||||
|
|
@ -202,6 +208,19 @@ public:
|
||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QStackedWidget *getMainWidget() const
|
||||||
|
{
|
||||||
|
return mainWidget;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QVBoxLayout *getDeckViewContainerLayout() const
|
||||||
|
{
|
||||||
|
return deckViewContainerLayout;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QWidget *getDeckViewContainerWidget() const
|
||||||
|
{
|
||||||
|
return deckViewContainerWidget;
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void viewCardInfo(const CardRef &cardRef = {}) const;
|
void viewCardInfo(const CardRef &cardRef = {}) const;
|
||||||
void resetChatAndPhase();
|
void resetChatAndPhase();
|
||||||
|
|
|
||||||
|
|
@ -1016,10 +1016,20 @@ void TabSupervisor::replayLeft(TabGame *tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabSupervisor::joinReportGame(const int gameId, const int roomId)
|
void TabSupervisor::joinReportGame(const int gameId, const int roomId)
|
||||||
|
{
|
||||||
|
startSpectatorJoin(gameId, roomId, tr("Report joins are only available on a remote server."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabSupervisor::spectatorJoinGame(const int gameId, const int roomId)
|
||||||
|
{
|
||||||
|
startSpectatorJoin(gameId, roomId, tr("Spectating is only available on a remote server."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabSupervisor::startSpectatorJoin(const int gameId, const int roomId, const QString &unavailableMessage)
|
||||||
{
|
{
|
||||||
auto *remoteClient = qobject_cast<RemoteClient *>(client);
|
auto *remoteClient = qobject_cast<RemoteClient *>(client);
|
||||||
if (!remoteClient) {
|
if (!remoteClient) {
|
||||||
actShowPopup(tr("Report joins are only available on a remote server."));
|
actShowPopup(unavailableMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,8 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void startSpectatorJoin(int gameId, int roomId, const QString &unavailableMessage);
|
||||||
|
|
||||||
ServerInfo_User *userInfo;
|
ServerInfo_User *userInfo;
|
||||||
AbstractClient *client;
|
AbstractClient *client;
|
||||||
UserListManager *userListManager;
|
UserListManager *userListManager;
|
||||||
|
|
@ -189,6 +191,7 @@ public slots:
|
||||||
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
|
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
|
||||||
void openReplay(GameReplay *replay);
|
void openReplay(GameReplay *replay);
|
||||||
void joinReportGame(int gameId, int roomId);
|
void joinReportGame(int gameId, int roomId);
|
||||||
|
void spectatorJoinGame(int gameId, int roomId);
|
||||||
void openTabModeration(const QString &userName = {});
|
void openTabModeration(const QString &userName = {});
|
||||||
void switchToFirstAvailableNetworkTab();
|
void switchToFirstAvailableNetworkTab();
|
||||||
void maximizeMainWindow();
|
void maximizeMainWindow();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
#include "tournament_tab_game_extension.h"
|
||||||
|
|
||||||
|
#include "../../../game/game_event_handler.h"
|
||||||
|
#include "../../../game/player/player_logic.h"
|
||||||
|
#include "../../widgets/dialogs/dlg_tournament_settings.h"
|
||||||
|
#include "../../widgets/draft/tournament_widget.h"
|
||||||
|
#include "tab_game.h"
|
||||||
|
#include "tab_supervisor.h"
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <libcockatrice/protocol/pb/command_tournament.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
|
|
||||||
|
TournamentTabGameExtension::TournamentTabGameExtension(TabGame *parent) : QObject(parent), tabGame(parent)
|
||||||
|
{
|
||||||
|
tournamentOverviewWidget = new QWidget(parent);
|
||||||
|
auto *overviewLayout = new QVBoxLayout(tournamentOverviewWidget);
|
||||||
|
|
||||||
|
auto *headerLayout = new QHBoxLayout;
|
||||||
|
backToGameButton = new QPushButton(tournamentOverviewWidget);
|
||||||
|
connect(backToGameButton, &QPushButton::clicked, this, &TournamentTabGameExtension::showDeckViewPage);
|
||||||
|
headerLayout->addWidget(backToGameButton);
|
||||||
|
headerLayout->addStretch();
|
||||||
|
overviewLayout->addLayout(headerLayout);
|
||||||
|
|
||||||
|
tournamentWidget = new TournamentWidget(tournamentOverviewWidget);
|
||||||
|
tournamentWidget->setLocalPlayerId(parent->getGame()->getPlayerManager()->getLocalPlayerId());
|
||||||
|
overviewLayout->addWidget(tournamentWidget);
|
||||||
|
|
||||||
|
parent->getMainWidget()->addWidget(tournamentOverviewWidget);
|
||||||
|
|
||||||
|
deckViewStatusLabel = new QLabel(parent->getDeckViewContainerWidget());
|
||||||
|
deckViewStatusLabel->setStyleSheet("color: palette(placeholderText);");
|
||||||
|
deckViewStatusLabel->setVisible(false);
|
||||||
|
|
||||||
|
standingsButton = new QPushButton(parent->getDeckViewContainerWidget());
|
||||||
|
connect(standingsButton, &QPushButton::clicked, this, &TournamentTabGameExtension::showOverviewPage);
|
||||||
|
standingsButton->setVisible(false);
|
||||||
|
|
||||||
|
connectSignals();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TournamentTabGameExtension::isLocalPlayerHost() const
|
||||||
|
{
|
||||||
|
return tabGame->getGame()->getPlayerManager()->getLocalPlayerId() ==
|
||||||
|
tabGame->getGame()->getGameState()->getHostId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::connectSignals()
|
||||||
|
{
|
||||||
|
auto *handler = tabGame->getGame()->getGameEventHandler();
|
||||||
|
connect(handler, &GameEventHandler::tournamentStateChanged, this,
|
||||||
|
&TournamentTabGameExtension::onTournamentStateChanged);
|
||||||
|
connect(tournamentWidget, &TournamentWidget::openMatchGameRequested, this,
|
||||||
|
&TournamentTabGameExtension::openMatchGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::initializeTournamentMode()
|
||||||
|
{
|
||||||
|
if (!tabGame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *deckLayout = tabGame->getDeckViewContainerLayout();
|
||||||
|
int index = 0;
|
||||||
|
deckLayout->insertWidget(index++, deckViewStatusLabel);
|
||||||
|
if (isLocalPlayerHost()) {
|
||||||
|
settingsButton = new QPushButton(tabGame->getDeckViewContainerWidget());
|
||||||
|
connect(settingsButton, &QPushButton::clicked, this, &TournamentTabGameExtension::showTournamentSettingsDialog);
|
||||||
|
deckLayout->insertWidget(index++, settingsButton);
|
||||||
|
}
|
||||||
|
deckLayout->insertWidget(index++, standingsButton);
|
||||||
|
deckLayout->insertSpacing(index, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::retranslateUi()
|
||||||
|
{
|
||||||
|
backToGameButton->setText(tr("Back to game view"));
|
||||||
|
standingsButton->setText(tr("Tournament standings"));
|
||||||
|
if (settingsButton) {
|
||||||
|
settingsButton->setText(tr("Tournament Settings"));
|
||||||
|
}
|
||||||
|
tournamentWidget->retranslateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::updateDeckViewStrip(const Event_TournamentState &state)
|
||||||
|
{
|
||||||
|
int submitted = 0;
|
||||||
|
for (int i = 0; i < state.players_size(); ++i) {
|
||||||
|
if (state.players(i).deck_submitted()) {
|
||||||
|
++submitted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString phaseText;
|
||||||
|
switch (state.phase()) {
|
||||||
|
case Event_TournamentState::PHASE_DECK_BUILDING:
|
||||||
|
phaseText = tr("Deck building");
|
||||||
|
break;
|
||||||
|
case Event_TournamentState::PHASE_PLAYING:
|
||||||
|
phaseText = tr("Round in progress");
|
||||||
|
break;
|
||||||
|
case Event_TournamentState::PHASE_FINISHED:
|
||||||
|
phaseText = tr("Finished");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
phaseText = tr("Unknown phase");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The submission count only matters while players are still picking decks.
|
||||||
|
QString text =
|
||||||
|
state.phase() == Event_TournamentState::PHASE_DECK_BUILDING
|
||||||
|
? tr("Tournament: %1 - %2/%3 decks submitted").arg(phaseText).arg(submitted).arg(state.players_size())
|
||||||
|
: tr("Tournament: %1").arg(phaseText);
|
||||||
|
deckViewStatusLabel->setText(text);
|
||||||
|
deckViewStatusLabel->setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::updateNavigationButtons(const Event_TournamentState &state)
|
||||||
|
{
|
||||||
|
bool showStandings =
|
||||||
|
state.phase() == Event_TournamentState::PHASE_PLAYING || state.phase() == Event_TournamentState::PHASE_FINISHED;
|
||||||
|
standingsButton->setVisible(showStandings);
|
||||||
|
if (settingsButton) {
|
||||||
|
settingsButton->setVisible(state.phase() == Event_TournamentState::PHASE_DECK_BUILDING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::onTournamentStateChanged(const Event_TournamentState &state)
|
||||||
|
{
|
||||||
|
if (!tabGame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDeckViewStrip(state);
|
||||||
|
updateNavigationButtons(state);
|
||||||
|
tournamentWidget->updateTournamentState(state);
|
||||||
|
|
||||||
|
// Switch to the standings page only when the phase itself changes, so score
|
||||||
|
// updates never yank the user away while they are looking at their deck.
|
||||||
|
// Instant switches are the app-wide baseline today; motion and sound cues
|
||||||
|
// for this transition are deferred to the Game dressing phase.
|
||||||
|
bool overviewPhase =
|
||||||
|
state.phase() == Event_TournamentState::PHASE_PLAYING || state.phase() == Event_TournamentState::PHASE_FINISHED;
|
||||||
|
if (overviewPhase && (!hasLastKnownPhase || state.phase() != lastKnownPhase)) {
|
||||||
|
tabGame->getMainWidget()->setCurrentWidget(tournamentOverviewWidget);
|
||||||
|
}
|
||||||
|
lastKnownPhase = state.phase();
|
||||||
|
hasLastKnownPhase = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::showOverviewPage()
|
||||||
|
{
|
||||||
|
if (tabGame) {
|
||||||
|
tabGame->getMainWidget()->setCurrentWidget(tournamentOverviewWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::showDeckViewPage()
|
||||||
|
{
|
||||||
|
if (tabGame) {
|
||||||
|
tabGame->getMainWidget()->setCurrentWidget(tabGame->getDeckViewContainerWidget());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::showTournamentSettingsDialog()
|
||||||
|
{
|
||||||
|
DlgTournamentSettings dlg(tabGame);
|
||||||
|
if (dlg.exec() != QDialog::Accepted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DlgTournamentSettingsResult result = dlg.getResult();
|
||||||
|
|
||||||
|
PlayerLogic *localPlayer = tabGame->getGame()->getPlayerManager()->getActiveLocalPlayer(-1);
|
||||||
|
if (!localPlayer) {
|
||||||
|
TabSupervisor::actShowPopup(tr("You are not an active player in this game."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command_TournamentSettingsSelect cmd;
|
||||||
|
cmd.mutable_settings()->set_games_per_match(result.gamesPerMatch);
|
||||||
|
tabGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, localPlayer->getPlayerInfo()->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TournamentTabGameExtension::openMatchGame(int gameId)
|
||||||
|
{
|
||||||
|
if (!tabGame || gameId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Players are auto-joined into their match game by the server, so the tab
|
||||||
|
// usually already exists and switching is enough. Otherwise (e.g. after
|
||||||
|
// leaving the match) fall back to joining as spectator.
|
||||||
|
if (tabGame->switchToGameTab(gameId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int roomId = tabGame->getGame()->getGameMetaInfo()->proto().room_id();
|
||||||
|
tabGame->getTabSupervisor()->spectatorJoinGame(gameId, roomId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
#ifndef TOURNAMENT_TAB_GAME_EXTENSION_H
|
||||||
|
#define TOURNAMENT_TAB_GAME_EXTENSION_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
|
|
||||||
|
class QLabel;
|
||||||
|
class QPushButton;
|
||||||
|
class QWidget;
|
||||||
|
class TournamentWidget;
|
||||||
|
class TabGame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class TournamentTabGameExtension
|
||||||
|
* @ingroup GameViews
|
||||||
|
* @brief Attaches tournament behavior to a TabGame hosting a tournament hub game.
|
||||||
|
*
|
||||||
|
* Owns the tournament overview page inside the tab's stacked main widget,
|
||||||
|
* mirrors tournament state onto the always-visible deck-view status strip,
|
||||||
|
* and routes user intents (open match game, change settings, switch pages)
|
||||||
|
* between the two views.
|
||||||
|
*/
|
||||||
|
class TournamentTabGameExtension : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit TournamentTabGameExtension(TabGame *parent);
|
||||||
|
|
||||||
|
void initializeTournamentMode();
|
||||||
|
void retranslateUi();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onTournamentStateChanged(const Event_TournamentState &state);
|
||||||
|
void showTournamentSettingsDialog();
|
||||||
|
void showOverviewPage();
|
||||||
|
void showDeckViewPage();
|
||||||
|
void openMatchGame(int gameId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void connectSignals();
|
||||||
|
[[nodiscard]] bool isLocalPlayerHost() const;
|
||||||
|
void updateDeckViewStrip(const Event_TournamentState &state);
|
||||||
|
void updateNavigationButtons(const Event_TournamentState &state);
|
||||||
|
|
||||||
|
QPointer<TabGame> tabGame;
|
||||||
|
TournamentWidget *tournamentWidget = nullptr;
|
||||||
|
QWidget *tournamentOverviewWidget = nullptr;
|
||||||
|
QPushButton *backToGameButton = nullptr;
|
||||||
|
QPushButton *standingsButton = nullptr;
|
||||||
|
QPushButton *settingsButton = nullptr;
|
||||||
|
QLabel *deckViewStatusLabel = nullptr;
|
||||||
|
Event_TournamentState::TournamentPhase lastKnownPhase = Event_TournamentState::PHASE_DECK_BUILDING;
|
||||||
|
bool hasLastKnownPhase = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TOURNAMENT_TAB_GAME_EXTENSION_H
|
||||||
Loading…
Add table
Add a link
Reference in a new issue