mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-27 08:24:39 -07:00
Compare commits
6 commits
54b6b273a4
...
4b106419f5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b106419f5 | ||
|
|
18742ae15e | ||
|
|
fb397bbb9b | ||
|
|
3c03d740f3 | ||
|
|
03de1af678 | ||
|
|
3dc9dba67a |
37 changed files with 1339 additions and 174 deletions
|
|
@ -26,6 +26,7 @@
|
||||||
#include <libcockatrice/protocol/pb/event_reverse_turn.pb.h>
|
#include <libcockatrice/protocol/pb/event_reverse_turn.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_set_active_phase.pb.h>
|
#include <libcockatrice/protocol/pb/event_set_active_phase.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_set_active_player.pb.h>
|
#include <libcockatrice/protocol/pb/event_set_active_player.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
|
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
|
||||||
#include <libcockatrice/protocol/pending_command.h>
|
#include <libcockatrice/protocol/pending_command.h>
|
||||||
|
|
||||||
|
|
@ -158,6 +159,9 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
|
||||||
case GameEvent::REVERSE_TURN:
|
case GameEvent::REVERSE_TURN:
|
||||||
eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context);
|
eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context);
|
||||||
break;
|
break;
|
||||||
|
case GameEvent::TOURNAMENT_STATE:
|
||||||
|
emit tournamentStateChanged(event.GetExtension(Event_TournamentState::ext));
|
||||||
|
break;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
|
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
|
||||||
|
|
@ -263,6 +267,12 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
|
||||||
int /*eventPlayerId*/,
|
int /*eventPlayerId*/,
|
||||||
const GameEventContext & /*context*/)
|
const GameEventContext & /*context*/)
|
||||||
{
|
{
|
||||||
|
// Sub-games of a tournament report their parent hub game so the client can
|
||||||
|
// route "close game" back to the parent tab.
|
||||||
|
if (event.parent_game_id() != -1) {
|
||||||
|
game->getGameMetaInfo()->setParentGameId(event.parent_game_id());
|
||||||
|
}
|
||||||
|
|
||||||
const int playerListSize = event.player_list_size();
|
const int playerListSize = event.player_list_size();
|
||||||
|
|
||||||
QVector<QPair<int, QPair<QString, QString>>> opponentDecksToDisplay;
|
QVector<QPair<int, QPair<QString, QString>>> opponentDecksToDisplay;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class Event_SetActivePhase;
|
||||||
class Event_GameSay;
|
class Event_GameSay;
|
||||||
class Event_Kicked;
|
class Event_Kicked;
|
||||||
class Event_ReverseTurn;
|
class Event_ReverseTurn;
|
||||||
|
class Event_TournamentState;
|
||||||
class Event_Ping;
|
class Event_Ping;
|
||||||
|
|
||||||
inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler");
|
inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler");
|
||||||
|
|
@ -329,6 +330,7 @@ signals:
|
||||||
|
|
||||||
void gameStopped();
|
void gameStopped();
|
||||||
void gameClosed();
|
void gameClosed();
|
||||||
|
void tournamentStateChanged(const Event_TournamentState &state);
|
||||||
void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId);
|
void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId);
|
||||||
void playerJoined(const ServerInfo_PlayerProperties &playerInfo);
|
void playerJoined(const ServerInfo_PlayerProperties &playerInfo);
|
||||||
void playerLeft(int leavingPlayerId);
|
void playerLeft(int leavingPlayerId);
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,26 @@ public:
|
||||||
return roomGameTypes.find(gameInfo_.game_types(index)).value();
|
return roomGameTypes.find(gameInfo_.game_types(index)).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isTournament() const
|
||||||
|
{
|
||||||
|
return gameInfo_.is_tournament();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setIsTournament(bool t)
|
||||||
|
{
|
||||||
|
gameInfo_.set_is_tournament(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
int parentGameId() const
|
||||||
|
{
|
||||||
|
return parentGameId_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParentGameId(int id)
|
||||||
|
{
|
||||||
|
parentGameId_ = id;
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setStarted(bool s)
|
void setStarted(bool s)
|
||||||
{
|
{
|
||||||
|
|
@ -108,6 +128,7 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServerInfo_Game gameInfo_;
|
ServerInfo_Game gameInfo_;
|
||||||
|
int parentGameId_ = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GAME_META_INFO_H
|
#endif // GAME_META_INFO_H
|
||||||
|
|
|
||||||
|
|
@ -90,10 +90,10 @@ public:
|
||||||
* @param context Additional context (undo, judge, etc.).
|
* @param context Additional context (undo, judge, etc.).
|
||||||
* @param options Processing options (UI suppression, reveal behavior).
|
* @param options Processing options (UI suppression, reveal behavior).
|
||||||
*/
|
*/
|
||||||
void processGameEvent(GameEvent::GameEventType type,
|
virtual void processGameEvent(GameEvent::GameEventType type,
|
||||||
const GameEvent &event,
|
const GameEvent &event,
|
||||||
const GameEventContext &context,
|
const GameEventContext &context,
|
||||||
EventProcessingOptions options);
|
EventProcessingOptions options);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
|
|
@ -266,7 +266,7 @@ signals:
|
||||||
void cardZoneChanged(CardItem *card, bool sameZone);
|
void cardZoneChanged(CardItem *card, bool sameZone);
|
||||||
void requestCardMenuUpdate(const CardItem *card);
|
void requestCardMenuUpdate(const CardItem *card);
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
/** Owning player instance. */
|
/** Owning player instance. */
|
||||||
PlayerLogic *player;
|
PlayerLogic *player;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,19 @@ PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool
|
||||||
initializeZones();
|
initializeZones();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayerLogic::PlayerLogic(const ServerInfo_User &info,
|
||||||
|
int _id,
|
||||||
|
bool _local,
|
||||||
|
bool _judge,
|
||||||
|
AbstractGame *_parent,
|
||||||
|
PlayerEventHandler *customEventHandler)
|
||||||
|
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
|
||||||
|
playerEventHandler(customEventHandler), playerActions(new PlayerActions(this)), active(false), conceded(false),
|
||||||
|
zoneId(0), dialogSemaphore(false)
|
||||||
|
{
|
||||||
|
initializeZones();
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerLogic::initializeZones()
|
void PlayerLogic::initializeZones()
|
||||||
{
|
{
|
||||||
addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this));
|
addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this));
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,30 @@ public:
|
||||||
PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent);
|
PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent);
|
||||||
~PlayerLogic() override;
|
~PlayerLogic() override;
|
||||||
|
|
||||||
void initializeZones();
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Constructor for subclasses that need a custom event handler (e.g. DraftPlayerLogic).
|
||||||
|
*
|
||||||
|
* @p customEventHandler must be non-null and either QObject-parented to this PlayerLogic
|
||||||
|
* or deleted externally; it is not owned by PlayerLogic. The handler connects to @c player
|
||||||
|
* during its own constructor, so passing a freshly built subclass handler from an
|
||||||
|
* initializer list is safe.
|
||||||
|
*/
|
||||||
|
PlayerLogic(const ServerInfo_User &info,
|
||||||
|
int _id,
|
||||||
|
bool _local,
|
||||||
|
bool _judge,
|
||||||
|
AbstractGame *_parent,
|
||||||
|
PlayerEventHandler *customEventHandler);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Creates the standard zone set.
|
||||||
|
*
|
||||||
|
* Not virtually dispatched from constructors — subclasses overriding this must add
|
||||||
|
* their extra zones in their own constructor body.
|
||||||
|
*/
|
||||||
|
virtual void initializeZones();
|
||||||
void updateZones();
|
void updateZones();
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,48 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
homeTabGroupBox = new QGroupBox;
|
homeTabGroupBox = new QGroupBox;
|
||||||
homeTabGroupBox->setLayout(homeTabGrid);
|
homeTabGroupBox->setLayout(homeTabGrid);
|
||||||
|
|
||||||
|
// Playmat settings
|
||||||
|
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
|
||||||
|
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
|
||||||
|
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
|
||||||
|
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
|
||||||
|
if (visIdx >= 0) {
|
||||||
|
playmatVisibilityCombo.setCurrentIndex(visIdx);
|
||||||
|
}
|
||||||
|
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||||
|
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
|
||||||
|
});
|
||||||
|
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
|
||||||
|
|
||||||
|
// Playmat mode: Override / Fallback / Deck-only
|
||||||
|
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
|
||||||
|
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
|
||||||
|
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
|
||||||
|
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
|
||||||
|
if (modeIdx >= 0) {
|
||||||
|
playmatModeCombo.setCurrentIndex(modeIdx);
|
||||||
|
}
|
||||||
|
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||||
|
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
|
||||||
|
});
|
||||||
|
playmatModeLabel.setBuddy(&playmatModeCombo);
|
||||||
|
|
||||||
|
// User-level playmat settings: fallback collection.
|
||||||
|
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
|
||||||
|
&AppearanceSettingsPage::openPlaymatCollectionDialog);
|
||||||
|
|
||||||
|
auto *playmatGrid = new QGridLayout;
|
||||||
|
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
|
||||||
|
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
|
||||||
|
|
||||||
|
playmatGroupBox = new QGroupBox;
|
||||||
|
playmatGroupBox->setLayout(playmatGrid);
|
||||||
|
|
||||||
|
// Styling settings
|
||||||
styleUserListCheckBox.setChecked(settings.appearance().getStyleUserList());
|
styleUserListCheckBox.setChecked(settings.appearance().getStyleUserList());
|
||||||
connect(&styleUserListCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
|
connect(&styleUserListCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
|
||||||
&AppearanceSettings::setStyleUserList);
|
&AppearanceSettings::setStyleUserList);
|
||||||
|
|
@ -259,7 +301,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
cardLayoutGroupBox->setLayout(cardLayoutGrid);
|
cardLayoutGroupBox->setLayout(cardLayoutGrid);
|
||||||
|
|
||||||
// Card counter colors
|
// Card counter colors
|
||||||
|
|
||||||
auto *cardCounterColorsLayout = new QGridLayout;
|
auto *cardCounterColorsLayout = new QGridLayout;
|
||||||
cardCounterColorsLayout->setColumnStretch(1, 1);
|
cardCounterColorsLayout->setColumnStretch(1, 1);
|
||||||
cardCounterColorsLayout->setColumnStretch(3, 1);
|
cardCounterColorsLayout->setColumnStretch(3, 1);
|
||||||
|
|
@ -339,47 +380,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
tableGroupBox = new QGroupBox;
|
tableGroupBox = new QGroupBox;
|
||||||
tableGroupBox->setLayout(tableGrid);
|
tableGroupBox->setLayout(tableGrid);
|
||||||
|
|
||||||
// Playmat settings
|
|
||||||
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
|
|
||||||
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
|
|
||||||
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
|
|
||||||
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
|
|
||||||
if (visIdx >= 0) {
|
|
||||||
playmatVisibilityCombo.setCurrentIndex(visIdx);
|
|
||||||
}
|
|
||||||
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
|
||||||
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
|
|
||||||
});
|
|
||||||
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
|
|
||||||
|
|
||||||
// Playmat mode: Override / Fallback / Deck-only
|
|
||||||
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
|
|
||||||
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
|
|
||||||
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
|
|
||||||
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
|
|
||||||
if (modeIdx >= 0) {
|
|
||||||
playmatModeCombo.setCurrentIndex(modeIdx);
|
|
||||||
}
|
|
||||||
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
|
||||||
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
|
|
||||||
});
|
|
||||||
playmatModeLabel.setBuddy(&playmatModeCombo);
|
|
||||||
|
|
||||||
// User-level playmat settings: fallback collection.
|
|
||||||
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
|
|
||||||
&AppearanceSettingsPage::openPlaymatCollectionDialog);
|
|
||||||
|
|
||||||
auto *playmatGrid = new QGridLayout;
|
|
||||||
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
|
|
||||||
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
|
|
||||||
|
|
||||||
playmatGroupBox = new QGroupBox;
|
|
||||||
playmatGroupBox->setLayout(playmatGrid);
|
|
||||||
|
|
||||||
// putting it all together
|
// putting it all together
|
||||||
auto *mainLayout = new QVBoxLayout;
|
auto *mainLayout = new QVBoxLayout;
|
||||||
mainLayout->addWidget(themeGroupBox);
|
mainLayout->addWidget(themeGroupBox);
|
||||||
|
|
@ -512,6 +512,12 @@ void AppearanceSettingsPage::retranslateUi()
|
||||||
homeTabButtonColorSourceBox.setToolTip(
|
homeTabButtonColorSourceBox.setToolTip(
|
||||||
tr("Automatic: extract from background if present, otherwise use theme default"));
|
tr("Automatic: extract from background if present, otherwise use theme default"));
|
||||||
|
|
||||||
|
playmatGroupBox->setTitle(tr("Playmat settings"));
|
||||||
|
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
|
||||||
|
playmatModeLabel.setText(tr("Default collection behavior:"));
|
||||||
|
playmatDefaultLabel.setText(tr("Default playmat collection:"));
|
||||||
|
playmatDefaultEditButton.setText(tr("Edit..."));
|
||||||
|
|
||||||
stylingGroupBox->setTitle(tr("Styling settings"));
|
stylingGroupBox->setTitle(tr("Styling settings"));
|
||||||
styleUserListCheckBox.setText(tr("Style user list"));
|
styleUserListCheckBox.setText(tr("Style user list"));
|
||||||
|
|
||||||
|
|
@ -554,9 +560,4 @@ void AppearanceSettingsPage::retranslateUi()
|
||||||
tableGroupBox->setTitle(tr("Table grid layout"));
|
tableGroupBox->setTitle(tr("Table grid layout"));
|
||||||
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
|
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
|
||||||
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
|
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
|
||||||
playmatGroupBox->setTitle(tr("Playmat settings"));
|
|
||||||
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
|
|
||||||
playmatModeLabel.setText(tr("Default collection behavior:"));
|
|
||||||
playmatDefaultLabel.setText(tr("Default playmat collection:"));
|
|
||||||
playmatDefaultEditButton.setText(tr("Edit..."));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,46 +44,55 @@ private:
|
||||||
QLabel homeTabButtonColorSourceLabel;
|
QLabel homeTabButtonColorSourceLabel;
|
||||||
QComboBox homeTabButtonColorSourceBox;
|
QComboBox homeTabButtonColorSourceBox;
|
||||||
|
|
||||||
QCheckBox styleUserListCheckBox;
|
|
||||||
QCheckBox showShortcutsCheckBox;
|
|
||||||
QCheckBox showGameSelectorFilterToolbarCheckBox;
|
|
||||||
QLabel minPlayersForMultiColumnLayoutLabel;
|
|
||||||
QLabel maxFontSizeForCardsLabel;
|
|
||||||
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
|
|
||||||
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
|
|
||||||
QCheckBox displayCardNamesCheckBox;
|
|
||||||
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
|
|
||||||
QCheckBox cardScalingCheckBox;
|
|
||||||
QCheckBox roundCardCornersCheckBox;
|
|
||||||
QLabel verticalCardOverlapPercentLabel;
|
|
||||||
QSpinBox verticalCardOverlapPercentBox;
|
|
||||||
QLabel cardViewInitialRowsMaxLabel;
|
|
||||||
QSpinBox cardViewInitialRowsMaxBox;
|
|
||||||
QLabel cardViewExpandedRowsMaxLabel;
|
|
||||||
QSpinBox cardViewExpandedRowsMaxBox;
|
|
||||||
QCheckBox horizontalHandCheckBox;
|
|
||||||
QCheckBox leftJustifiedHandCheckBox;
|
|
||||||
QCheckBox invertVerticalCoordinateCheckBox;
|
|
||||||
QLabel playmatVisibilityLabel;
|
QLabel playmatVisibilityLabel;
|
||||||
QComboBox playmatVisibilityCombo;
|
QComboBox playmatVisibilityCombo;
|
||||||
QLabel playmatModeLabel;
|
QLabel playmatModeLabel;
|
||||||
QComboBox playmatModeCombo;
|
QComboBox playmatModeCombo;
|
||||||
QLabel playmatDefaultLabel;
|
QLabel playmatDefaultLabel;
|
||||||
QPushButton playmatDefaultEditButton;
|
QPushButton playmatDefaultEditButton;
|
||||||
|
|
||||||
|
QCheckBox styleUserListCheckBox;
|
||||||
|
|
||||||
|
QCheckBox showShortcutsCheckBox;
|
||||||
|
QCheckBox showGameSelectorFilterToolbarCheckBox;
|
||||||
|
|
||||||
|
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
|
||||||
|
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
|
||||||
|
|
||||||
|
QCheckBox displayCardNamesCheckBox;
|
||||||
|
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
|
||||||
|
QCheckBox cardScalingCheckBox;
|
||||||
|
QCheckBox roundCardCornersCheckBox;
|
||||||
|
QLabel maxFontSizeForCardsLabel;
|
||||||
|
QSpinBox maxFontSizeForCardsEdit;
|
||||||
|
|
||||||
|
QLabel verticalCardOverlapPercentLabel;
|
||||||
|
QSpinBox verticalCardOverlapPercentBox;
|
||||||
|
QLabel cardViewInitialRowsMaxLabel;
|
||||||
|
QSpinBox cardViewInitialRowsMaxBox;
|
||||||
|
QLabel cardViewExpandedRowsMaxLabel;
|
||||||
|
QSpinBox cardViewExpandedRowsMaxBox;
|
||||||
|
|
||||||
|
QList<QLabel *> cardCounterNames;
|
||||||
|
|
||||||
|
QCheckBox horizontalHandCheckBox;
|
||||||
|
QCheckBox leftJustifiedHandCheckBox;
|
||||||
|
|
||||||
|
QCheckBox invertVerticalCoordinateCheckBox;
|
||||||
|
QLabel minPlayersForMultiColumnLayoutLabel;
|
||||||
|
QSpinBox minPlayersForMultiColumnLayoutEdit;
|
||||||
|
|
||||||
QGroupBox *themeGroupBox;
|
QGroupBox *themeGroupBox;
|
||||||
QGroupBox *homeTabGroupBox;
|
QGroupBox *homeTabGroupBox;
|
||||||
|
QGroupBox *playmatGroupBox;
|
||||||
QGroupBox *stylingGroupBox;
|
QGroupBox *stylingGroupBox;
|
||||||
QGroupBox *menuGroupBox;
|
QGroupBox *menuGroupBox;
|
||||||
QGroupBox *printingsGroupBox;
|
QGroupBox *printingsGroupBox;
|
||||||
QGroupBox *cardsGroupBox;
|
QGroupBox *cardsGroupBox;
|
||||||
QGroupBox *cardLayoutGroupBox;
|
QGroupBox *cardLayoutGroupBox;
|
||||||
QGroupBox *handGroupBox;
|
|
||||||
QGroupBox *playmatGroupBox;
|
|
||||||
QGroupBox *tableGroupBox;
|
|
||||||
QGroupBox *cardCountersGroupBox;
|
QGroupBox *cardCountersGroupBox;
|
||||||
QList<QLabel *> cardCounterNames;
|
QGroupBox *handGroupBox;
|
||||||
QSpinBox minPlayersForMultiColumnLayoutEdit;
|
QGroupBox *tableGroupBox;
|
||||||
QSpinBox maxFontSizeForCardsEdit;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AppearanceSettingsPage();
|
AppearanceSettingsPage();
|
||||||
|
|
|
||||||
|
|
@ -425,29 +425,28 @@ void GeneralSettingsPage::updateStartupServerControlsVisibility()
|
||||||
|
|
||||||
void GeneralSettingsPage::retranslateUi()
|
void GeneralSettingsPage::retranslateUi()
|
||||||
{
|
{
|
||||||
|
const auto &settings = SettingsCache::instance();
|
||||||
|
|
||||||
languageGroupBox->setTitle(tr("Language settings"));
|
languageGroupBox->setTitle(tr("Language settings"));
|
||||||
languageLabel.setText(tr("Language:"));
|
languageLabel.setText(tr("Language:"));
|
||||||
|
|
||||||
versionGroupBox->setTitle(tr("Version settings"));
|
|
||||||
cardDatabaseGroupBox->setTitle(tr("Card database"));
|
|
||||||
startupGroupBox->setTitle(tr("Startup settings"));
|
|
||||||
|
|
||||||
if (SettingsCache::instance().getIsPortableBuild()) {
|
|
||||||
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
|
|
||||||
} else {
|
|
||||||
pathsGroupBox->setTitle(tr("Paths"));
|
|
||||||
}
|
|
||||||
advertiseTranslationPageLabel.setText(
|
advertiseTranslationPageLabel.setText(
|
||||||
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
|
||||||
deckPathLabel.setText(tr("Decks directory:"));
|
|
||||||
filtersPathLabel.setText(tr("Filters directory:"));
|
versionGroupBox->setTitle(tr("Version settings"));
|
||||||
replaysPathLabel.setText(tr("Replays directory:"));
|
|
||||||
picsPathLabel.setText(tr("Pictures directory:"));
|
|
||||||
cardDatabasePathLabel.setText(tr("Card database:"));
|
|
||||||
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
|
|
||||||
tokenDatabasePathLabel.setText(tr("Token database:"));
|
|
||||||
updateReleaseChannelLabel.setText(tr("Update channel"));
|
updateReleaseChannelLabel.setText(tr("Update channel"));
|
||||||
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
|
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
|
||||||
|
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
|
||||||
|
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
|
||||||
|
|
||||||
|
// We can't change the strings after they're put into the QComboBox, so this is our workaround
|
||||||
|
int oldIndex = updateReleaseChannelBox.currentIndex();
|
||||||
|
updateReleaseChannelBox.clear();
|
||||||
|
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
|
||||||
|
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
|
||||||
|
}
|
||||||
|
updateReleaseChannelBox.setCurrentIndex(oldIndex);
|
||||||
|
|
||||||
|
cardDatabaseGroupBox->setTitle(tr("Card database"));
|
||||||
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
|
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
|
||||||
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
|
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
|
||||||
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
|
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
|
||||||
|
|
@ -456,8 +455,13 @@ void GeneralSettingsPage::retranslateUi()
|
||||||
tr("Always update in the background"));
|
tr("Always update in the background"));
|
||||||
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
|
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
|
||||||
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
|
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
|
||||||
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
|
|
||||||
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
|
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
|
||||||
|
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
|
||||||
|
lastCardUpdateCheckDateLabel.setText(
|
||||||
|
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
|
||||||
|
|
||||||
|
startupGroupBox->setTitle(tr("Startup settings"));
|
||||||
showTipsOnStartup.setText(tr("Show tips on startup"));
|
showTipsOnStartup.setText(tr("Show tips on startup"));
|
||||||
startupTabLabel.setText(tr("Startup tab:"));
|
startupTabLabel.setText(tr("Startup tab:"));
|
||||||
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
|
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
|
||||||
|
|
@ -473,21 +477,18 @@ void GeneralSettingsPage::retranslateUi()
|
||||||
startupServerLabel.setText(tr("Server:"));
|
startupServerLabel.setText(tr("Server:"));
|
||||||
startupRoomLabel.setText(tr("Room:"));
|
startupRoomLabel.setText(tr("Room:"));
|
||||||
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
|
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
|
||||||
resetAllPathsButton->setText(tr("Reset all paths"));
|
|
||||||
|
|
||||||
const auto &settings = SettingsCache::instance();
|
if (settings.getIsPortableBuild()) {
|
||||||
|
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
|
||||||
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
|
} else {
|
||||||
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
|
pathsGroupBox->setTitle(tr("Paths"));
|
||||||
|
|
||||||
lastCardUpdateCheckDateLabel.setText(
|
|
||||||
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
|
|
||||||
|
|
||||||
// We can't change the strings after they're put into the QComboBox, so this is our workaround
|
|
||||||
int oldIndex = updateReleaseChannelBox.currentIndex();
|
|
||||||
updateReleaseChannelBox.clear();
|
|
||||||
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
|
|
||||||
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
|
|
||||||
}
|
}
|
||||||
updateReleaseChannelBox.setCurrentIndex(oldIndex);
|
deckPathLabel.setText(tr("Decks directory:"));
|
||||||
}
|
filtersPathLabel.setText(tr("Filters directory:"));
|
||||||
|
replaysPathLabel.setText(tr("Replays directory:"));
|
||||||
|
picsPathLabel.setText(tr("Pictures directory:"));
|
||||||
|
cardDatabasePathLabel.setText(tr("Card database:"));
|
||||||
|
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
|
||||||
|
tokenDatabasePathLabel.setText(tr("Token database:"));
|
||||||
|
resetAllPathsButton->setText(tr("Reset all paths"));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,37 @@ private:
|
||||||
QGroupBox *startupGroupBox;
|
QGroupBox *startupGroupBox;
|
||||||
QGroupBox *pathsGroupBox;
|
QGroupBox *pathsGroupBox;
|
||||||
|
|
||||||
|
QLabel languageLabel;
|
||||||
|
QComboBox languageBox;
|
||||||
|
QLabel advertiseTranslationPageLabel;
|
||||||
|
|
||||||
|
QLabel updateReleaseChannelLabel;
|
||||||
|
QComboBox updateReleaseChannelBox;
|
||||||
|
QCheckBox startupUpdateCheckCheckBox;
|
||||||
|
QCheckBox updateNotificationCheckBox;
|
||||||
|
QCheckBox newVersionOracleCheckBox;
|
||||||
|
|
||||||
|
QLabel startupCardUpdateCheckBehaviorLabel;
|
||||||
|
QComboBox startupCardUpdateCheckBehaviorSelector;
|
||||||
|
QLabel cardUpdateCheckIntervalLabel;
|
||||||
|
QSpinBox cardUpdateCheckIntervalSpinBox;
|
||||||
|
QLabel lastCardUpdateCheckDateLabel;
|
||||||
|
|
||||||
|
QCheckBox showTipsOnStartup;
|
||||||
|
QLabel startupTabLabel;
|
||||||
|
QComboBox startupTabSelector;
|
||||||
|
QLabel startupServerLabel;
|
||||||
|
QComboBox startupServerSelector;
|
||||||
|
QLabel startupRoomLabel;
|
||||||
|
QLineEdit *startupRoomNameEdit;
|
||||||
|
|
||||||
|
QLabel deckPathLabel;
|
||||||
|
QLabel filtersPathLabel;
|
||||||
|
QLabel replaysPathLabel;
|
||||||
|
QLabel picsPathLabel;
|
||||||
|
QLabel cardDatabasePathLabel;
|
||||||
|
QLabel customCardDatabasePathLabel;
|
||||||
|
QLabel tokenDatabasePathLabel;
|
||||||
QLineEdit *deckPathEdit;
|
QLineEdit *deckPathEdit;
|
||||||
QLineEdit *filtersPathEdit;
|
QLineEdit *filtersPathEdit;
|
||||||
QLineEdit *replaysPathEdit;
|
QLineEdit *replaysPathEdit;
|
||||||
|
|
@ -51,33 +82,6 @@ private:
|
||||||
QLineEdit *tokenDatabasePathEdit;
|
QLineEdit *tokenDatabasePathEdit;
|
||||||
QPushButton *resetAllPathsButton;
|
QPushButton *resetAllPathsButton;
|
||||||
QLabel *allPathsResetLabel;
|
QLabel *allPathsResetLabel;
|
||||||
QComboBox languageBox;
|
|
||||||
QCheckBox startupUpdateCheckCheckBox;
|
|
||||||
QLabel startupCardUpdateCheckBehaviorLabel;
|
|
||||||
QComboBox startupCardUpdateCheckBehaviorSelector;
|
|
||||||
QLabel cardUpdateCheckIntervalLabel;
|
|
||||||
QSpinBox cardUpdateCheckIntervalSpinBox;
|
|
||||||
QLabel lastCardUpdateCheckDateLabel;
|
|
||||||
QCheckBox updateNotificationCheckBox;
|
|
||||||
QCheckBox newVersionOracleCheckBox;
|
|
||||||
QComboBox updateReleaseChannelBox;
|
|
||||||
QLabel languageLabel;
|
|
||||||
QLabel deckPathLabel;
|
|
||||||
QLabel filtersPathLabel;
|
|
||||||
QLabel replaysPathLabel;
|
|
||||||
QLabel picsPathLabel;
|
|
||||||
QLabel cardDatabasePathLabel;
|
|
||||||
QLabel customCardDatabasePathLabel;
|
|
||||||
QLabel tokenDatabasePathLabel;
|
|
||||||
QLabel updateReleaseChannelLabel;
|
|
||||||
QLabel advertiseTranslationPageLabel;
|
|
||||||
QCheckBox showTipsOnStartup;
|
|
||||||
QLabel startupTabLabel;
|
|
||||||
QComboBox startupTabSelector;
|
|
||||||
QLabel startupServerLabel;
|
|
||||||
QComboBox startupServerSelector;
|
|
||||||
QLabel startupRoomLabel;
|
|
||||||
QLineEdit *startupRoomNameEdit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H
|
#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,7 @@ enum visualDeckStoragePromptForConversionIndex
|
||||||
|
|
||||||
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||||
{
|
{
|
||||||
// general settings and notification settings
|
// general settings
|
||||||
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
|
||||||
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
|
||||||
&InterfaceSettings::setNotificationsEnabled);
|
|
||||||
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
|
|
||||||
&UserInterfaceSettingsPage::setNotificationEnabled);
|
|
||||||
|
|
||||||
specNotificationsEnabledCheckBox.setChecked(
|
|
||||||
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
|
|
||||||
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
|
||||||
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
|
||||||
&InterfaceSettings::setSpectatorNotificationsEnabled);
|
|
||||||
|
|
||||||
buddyConnectNotificationsEnabledCheckBox.setChecked(
|
|
||||||
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
|
|
||||||
buddyConnectNotificationsEnabledCheckBox.setEnabled(
|
|
||||||
SettingsCache::instance().userInterface().getNotificationsEnabled());
|
|
||||||
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
|
|
||||||
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
|
|
||||||
|
|
||||||
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().userInterface().getDoubleClickToPlay());
|
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().userInterface().getDoubleClickToPlay());
|
||||||
connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
&InterfaceSettings::setDoubleClickToPlay);
|
&InterfaceSettings::setDoubleClickToPlay);
|
||||||
|
|
@ -103,6 +84,26 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||||
generalGroupBox = new QGroupBox;
|
generalGroupBox = new QGroupBox;
|
||||||
generalGroupBox->setLayout(generalGrid);
|
generalGroupBox->setLayout(generalGrid);
|
||||||
|
|
||||||
|
// notification settings
|
||||||
|
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
||||||
|
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
|
&InterfaceSettings::setNotificationsEnabled);
|
||||||
|
connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
|
||||||
|
&UserInterfaceSettingsPage::setNotificationEnabled);
|
||||||
|
|
||||||
|
specNotificationsEnabledCheckBox.setChecked(
|
||||||
|
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
|
||||||
|
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
|
||||||
|
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
|
&InterfaceSettings::setSpectatorNotificationsEnabled);
|
||||||
|
|
||||||
|
buddyConnectNotificationsEnabledCheckBox.setChecked(
|
||||||
|
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
|
||||||
|
buddyConnectNotificationsEnabledCheckBox.setEnabled(
|
||||||
|
SettingsCache::instance().userInterface().getNotificationsEnabled());
|
||||||
|
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
|
||||||
|
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
|
||||||
|
|
||||||
auto *notificationsGrid = new QGridLayout;
|
auto *notificationsGrid = new QGridLayout;
|
||||||
notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0);
|
notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0);
|
||||||
notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
|
notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
|
||||||
|
|
@ -355,6 +356,7 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
|
notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
|
||||||
specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
|
specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
|
||||||
buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
|
buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
|
||||||
|
|
||||||
animationGroupBox->setTitle(tr("Animation settings"));
|
animationGroupBox->setTitle(tr("Animation settings"));
|
||||||
enableAllAnimationsButton.setText(tr("&Enable all animations"));
|
enableAllAnimationsButton.setText(tr("&Enable all animations"));
|
||||||
disableAllAnimationsButton.setText(tr("&Disable all animations"));
|
disableAllAnimationsButton.setText(tr("&Disable all animations"));
|
||||||
|
|
@ -362,6 +364,7 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
|
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
|
||||||
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
|
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
|
||||||
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
|
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
|
||||||
|
|
||||||
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
|
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
|
||||||
openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default"));
|
openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default"));
|
||||||
visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby"));
|
visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby"));
|
||||||
|
|
@ -397,8 +400,8 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
0, CommanderBracketNames::CommanderSpellbookBracketNames);
|
0, CommanderBracketNames::CommanderSpellbookBracketNames);
|
||||||
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
|
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
|
||||||
1, CommanderBracketNames::OfficialCommanderBracketNames);
|
1, CommanderBracketNames::OfficialCommanderBracketNames);
|
||||||
|
|
||||||
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
|
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
|
||||||
|
|
||||||
replayGroupBox->setTitle(tr("Replay settings"));
|
replayGroupBox->setTitle(tr("Replay settings"));
|
||||||
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
|
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
|
||||||
rewindBufferingMsBox.setSuffix(" ms");
|
rewindBufferingMsBox.setSuffix(" ms");
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ private slots:
|
||||||
void updateCommanderSpellbookUiState();
|
void updateCommanderSpellbookUiState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QCheckBox notificationsEnabledCheckBox;
|
|
||||||
QCheckBox specNotificationsEnabledCheckBox;
|
|
||||||
QCheckBox buddyConnectNotificationsEnabledCheckBox;
|
|
||||||
QCheckBox doubleClickToPlayCheckBox;
|
QCheckBox doubleClickToPlayCheckBox;
|
||||||
QCheckBox clickPlaysAllSelectedCheckBox;
|
QCheckBox clickPlaysAllSelectedCheckBox;
|
||||||
QCheckBox playToStackCheckBox;
|
QCheckBox playToStackCheckBox;
|
||||||
|
|
@ -37,12 +34,18 @@ private:
|
||||||
QCheckBox showTotalSelectionCountCheckBox;
|
QCheckBox showTotalSelectionCountCheckBox;
|
||||||
QCheckBox useTearOffMenusCheckBox;
|
QCheckBox useTearOffMenusCheckBox;
|
||||||
QCheckBox keepGameChatFocusCheckBox;
|
QCheckBox keepGameChatFocusCheckBox;
|
||||||
|
|
||||||
|
QCheckBox notificationsEnabledCheckBox;
|
||||||
|
QCheckBox specNotificationsEnabledCheckBox;
|
||||||
|
QCheckBox buddyConnectNotificationsEnabledCheckBox;
|
||||||
|
|
||||||
QPushButton enableAllAnimationsButton;
|
QPushButton enableAllAnimationsButton;
|
||||||
QPushButton disableAllAnimationsButton;
|
QPushButton disableAllAnimationsButton;
|
||||||
QCheckBox tapAnimationCheckBox;
|
QCheckBox tapAnimationCheckBox;
|
||||||
QCheckBox arrowDrawAnimationCheckBox;
|
QCheckBox arrowDrawAnimationCheckBox;
|
||||||
QCheckBox lifeCounterAnimationsCheckBox;
|
QCheckBox lifeCounterAnimationsCheckBox;
|
||||||
QCheckBox battlefieldFlashCheckBox;
|
QCheckBox battlefieldFlashCheckBox;
|
||||||
|
|
||||||
QCheckBox openDeckInNewTabCheckBox;
|
QCheckBox openDeckInNewTabCheckBox;
|
||||||
QLabel visualDeckStoragePromptForConversionLabel;
|
QLabel visualDeckStoragePromptForConversionLabel;
|
||||||
QComboBox visualDeckStoragePromptForConversionSelector;
|
QComboBox visualDeckStoragePromptForConversionSelector;
|
||||||
|
|
@ -57,8 +60,10 @@ private:
|
||||||
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
|
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
|
||||||
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
|
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
|
||||||
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
|
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
|
||||||
|
|
||||||
QLabel rewindBufferingMsLabel;
|
QLabel rewindBufferingMsLabel;
|
||||||
QSpinBox rewindBufferingMsBox;
|
QSpinBox rewindBufferingMsBox;
|
||||||
|
|
||||||
QGroupBox *generalGroupBox;
|
QGroupBox *generalGroupBox;
|
||||||
QGroupBox *notificationsGroupBox;
|
QGroupBox *notificationsGroupBox;
|
||||||
QGroupBox *animationGroupBox;
|
QGroupBox *animationGroupBox;
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,7 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
|
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
|
||||||
if (matches == deckPreviewWidget->isHidden()) {
|
deckPreviewWidget->setVisible(matches);
|
||||||
deckPreviewWidget->setVisible(matches);
|
|
||||||
}
|
|
||||||
if (matches) {
|
if (matches) {
|
||||||
++visibleDeckCount;
|
++visibleDeckCount;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,12 @@ set(HEADERS
|
||||||
game/server_deck_validation_strategy.h
|
game/server_deck_validation_strategy.h
|
||||||
game/server_game.h
|
game/server_game.h
|
||||||
game/server_game_lifecycle_strategy.h
|
game/server_game_lifecycle_strategy.h
|
||||||
game/server_match_result_strategy.h
|
|
||||||
game/server_match_game_factory.h
|
game/server_match_game_factory.h
|
||||||
|
game/server_match_result_strategy.h
|
||||||
game/server_player.h
|
game/server_player.h
|
||||||
|
game/server_tournament.h
|
||||||
|
game/server_tournament_lifecycle_strategy.h
|
||||||
|
game/server_tournament_match_result_strategy.h
|
||||||
game/server_spectator.h
|
game/server_spectator.h
|
||||||
server.h
|
server.h
|
||||||
server_abstractuserinterface.h
|
server_abstractuserinterface.h
|
||||||
|
|
@ -43,6 +46,9 @@ add_library(
|
||||||
game/server_game.cpp
|
game/server_game.cpp
|
||||||
game/server_player.cpp
|
game/server_player.cpp
|
||||||
game/server_spectator.cpp
|
game/server_spectator.cpp
|
||||||
|
game/server_tournament.cpp
|
||||||
|
game/server_tournament_lifecycle_strategy.cpp
|
||||||
|
game/server_tournament_match_result_strategy.cpp
|
||||||
server.cpp
|
server.cpp
|
||||||
server_abstractuserinterface.cpp
|
server_abstractuserinterface.cpp
|
||||||
server_database_interface.cpp
|
server_database_interface.cpp
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_tournament.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
|
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/context_connection_state_changed.pb.h>
|
#include <libcockatrice/protocol/pb/context_connection_state_changed.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_game_say.pb.h>
|
#include <libcockatrice/protocol/pb/event_game_say.pb.h>
|
||||||
|
|
@ -536,6 +537,15 @@ Server_AbstractParticipant::processGameCommand(const GameCommand &command, Respo
|
||||||
case GameCommand::SET_PLAYMAT:
|
case GameCommand::SET_PLAYMAT:
|
||||||
return cmdSetPlaymat(command.GetExtension(Command_SetPlaymat::ext), rc, ges);
|
return cmdSetPlaymat(command.GetExtension(Command_SetPlaymat::ext), rc, ges);
|
||||||
break;
|
break;
|
||||||
|
case GameCommand::REPORT_MATCH_RESULT:
|
||||||
|
return cmdReportMatchResult(command.GetExtension(Command_ReportMatchResult::ext), rc, ges);
|
||||||
|
break;
|
||||||
|
case GameCommand::ADVANCE_TOURNAMENT:
|
||||||
|
return cmdAdvanceTournament(command.GetExtension(Command_AdvanceTournament::ext), rc, ges);
|
||||||
|
break;
|
||||||
|
case GameCommand::TOURNAMENT_SETTINGS_SELECT:
|
||||||
|
return cmdTournamentSettingsSelect(command.GetExtension(Command_TournamentSettingsSelect::ext), rc, ges);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return Response::RespInvalidCommand;
|
return Response::RespInvalidCommand;
|
||||||
}
|
}
|
||||||
|
|
@ -570,7 +580,7 @@ void Server_AbstractParticipant::setUserInterface(Server_AbstractUserInterface *
|
||||||
void Server_AbstractParticipant::disconnectClient()
|
void Server_AbstractParticipant::disconnectClient()
|
||||||
{
|
{
|
||||||
bool isRegistered = userInfo->user_level() & ServerInfo_User::IsRegistered;
|
bool isRegistered = userInfo->user_level() & ServerInfo_User::IsRegistered;
|
||||||
if (!isRegistered || spectator) {
|
if (!isRegistered || spectator || game->getDisconnectRemovesPlayer()) {
|
||||||
game->removeParticipant(this, Event_Leave::USER_DISCONNECTED);
|
game->removeParticipant(this, Event_Leave::USER_DISCONNECTED);
|
||||||
} else {
|
} else {
|
||||||
setUserInterface(nullptr);
|
setUserInterface(nullptr);
|
||||||
|
|
@ -584,3 +594,25 @@ void Server_AbstractParticipant::getInfo(ServerInfo_Player *info,
|
||||||
{
|
{
|
||||||
getProperties(*info->mutable_properties(), withUserInfo);
|
getProperties(*info->mutable_properties(), withUserInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdReportMatchResult(const Command_ReportMatchResult & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdAdvanceTournament(const Command_AdvanceTournament & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
Server_AbstractParticipant::cmdTournamentSettingsSelect(const Command_TournamentSettingsSelect & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ class Command_DeckSelect;
|
||||||
class Command_SetSideboardLock;
|
class Command_SetSideboardLock;
|
||||||
class Command_ChangeZoneProperties;
|
class Command_ChangeZoneProperties;
|
||||||
class Command_SetPlaymat;
|
class Command_SetPlaymat;
|
||||||
|
class Command_ReportMatchResult;
|
||||||
|
class Command_AdvanceTournament;
|
||||||
|
class Command_TournamentSettingsSelect;
|
||||||
|
|
||||||
class Server_AbstractParticipant : public Server_ArrowTarget, public ServerInfo_User_Container
|
class Server_AbstractParticipant : public Server_ArrowTarget, public ServerInfo_User_Container
|
||||||
{
|
{
|
||||||
|
|
@ -175,6 +178,13 @@ public:
|
||||||
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
|
cmdReportMatchResult(const Command_ReportMatchResult &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
|
cmdAdvanceTournament(const Command_AdvanceTournament &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode cmdTournamentSettingsSelect(const Command_TournamentSettingsSelect &cmd,
|
||||||
|
ResponseContainer &rc,
|
||||||
|
GameEventStorage &ges);
|
||||||
|
|
||||||
Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges);
|
Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
void sendGameEvent(const GameEventContainer &event);
|
void sendGameEvent(const GameEventContainer &event);
|
||||||
|
|
|
||||||
|
|
@ -1663,3 +1663,9 @@ void Server_AbstractPlayer::getPlayerProperties(ServerInfo_PlayerProperties &res
|
||||||
playmatParams->set_zoom(playmat.params.zoom);
|
playmatParams->set_zoom(playmat.params.zoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Server_AbstractPlayer::setDeck(DeckList *_deck)
|
||||||
|
{
|
||||||
|
delete deck;
|
||||||
|
deck = _deck;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ public:
|
||||||
{
|
{
|
||||||
return deck;
|
return deck;
|
||||||
}
|
}
|
||||||
|
void setDeck(DeckList *_deck);
|
||||||
bool getReadyStart() const
|
bool getReadyStart() const
|
||||||
{
|
{
|
||||||
return readyStart;
|
return readyStart;
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,14 @@
|
||||||
#include "server_cardzone.h"
|
#include "server_cardzone.h"
|
||||||
#include "server_player.h"
|
#include "server_player.h"
|
||||||
#include "server_spectator.h"
|
#include "server_spectator.h"
|
||||||
|
#include "server_tournament.h"
|
||||||
|
#include "server_tournament_lifecycle_strategy.h"
|
||||||
|
#include "server_tournament_match_result_strategy.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <algorithm>
|
||||||
#include <google/protobuf/descriptor.h>
|
#include <google/protobuf/descriptor.h>
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
#include <libcockatrice/protocol/pb/context_connection_state_changed.pb.h>
|
#include <libcockatrice/protocol/pb/context_connection_state_changed.pb.h>
|
||||||
|
|
@ -62,8 +66,9 @@ Server_Game::Server_Game(const GameConfig &config, Server_Room *_room)
|
||||||
spectatorsCanTalk(config.spectatorsCanTalk), spectatorsSeeEverything(config.spectatorsSeeEverything),
|
spectatorsCanTalk(config.spectatorsCanTalk), spectatorsSeeEverything(config.spectatorsSeeEverything),
|
||||||
startingLifeTotal(config.startingLifeTotal), shareDecklistsOnLoad(config.shareDecklistsOnLoad),
|
startingLifeTotal(config.startingLifeTotal), shareDecklistsOnLoad(config.shareDecklistsOnLoad),
|
||||||
inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false),
|
inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false),
|
||||||
turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
|
turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), isTournament(false),
|
||||||
deckValidationStrategy(new Server_DefaultDeckValidationStrategy),
|
tournament(nullptr), tournamentParentGame(nullptr), tournamentMatchPlayer1Id(-1), tournamentMatchPlayer2Id(-1),
|
||||||
|
disconnectRemovesPlayer(false), deckValidationStrategy(new Server_DefaultDeckValidationStrategy),
|
||||||
lifecycleStrategy(new Server_DefaultLifecycleStrategy), matchResultStrategy(new Server_NullMatchResultStrategy),
|
lifecycleStrategy(new Server_DefaultLifecycleStrategy), matchResultStrategy(new Server_NullMatchResultStrategy),
|
||||||
gameMutex()
|
gameMutex()
|
||||||
{
|
{
|
||||||
|
|
@ -265,6 +270,12 @@ void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event,
|
||||||
event->set_game_started(false);
|
event->set_game_started(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event->set_is_tournament(isTournament);
|
||||||
|
|
||||||
|
if (tournamentParentGame) {
|
||||||
|
event->set_parent_game_id(tournamentParentGame->getGameId());
|
||||||
|
}
|
||||||
|
|
||||||
for (Server_AbstractParticipant *participant : participants.values()) {
|
for (Server_AbstractParticipant *participant : participants.values()) {
|
||||||
participant->getInfo(event->add_player_list(), recipient, omniscient, withUserInfo);
|
participant->getInfo(event->add_player_list(), recipient, omniscient, withUserInfo);
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +324,7 @@ void Server_Game::doStartGameIfReady(bool forceStartGame)
|
||||||
Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface();
|
Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface();
|
||||||
QMutexLocker locker(&gameMutex);
|
QMutexLocker locker(&gameMutex);
|
||||||
|
|
||||||
if (getPlayerCount() < maxPlayers && !forceStartGame) {
|
if (!isTournament && getPlayerCount() < maxPlayers && !forceStartGame) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -861,6 +872,7 @@ void Server_Game::getInfo(ServerInfo_Game &result) const
|
||||||
result.set_share_decklists_on_load(shareDecklistsOnLoad);
|
result.set_share_decklists_on_load(shareDecklistsOnLoad);
|
||||||
result.set_spectators_count(getSpectatorCount());
|
result.set_spectators_count(getSpectatorCount());
|
||||||
result.set_start_time(startTime.toSecsSinceEpoch());
|
result.set_start_time(startTime.toSecsSinceEpoch());
|
||||||
|
result.set_is_tournament(isTournament);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -911,3 +923,86 @@ void Server_Game::setDeckValidationStrategy(Server_DeckValidationStrategy *strat
|
||||||
{
|
{
|
||||||
deckValidationStrategy.reset(strategy);
|
deckValidationStrategy.reset(strategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Server_Game::setMatchResultStrategy(Server_MatchResultStrategy *strategy)
|
||||||
|
{
|
||||||
|
matchResultStrategy.reset(strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Game::setIsTournamentGame(bool _isTournament)
|
||||||
|
{
|
||||||
|
isTournament = _isTournament;
|
||||||
|
if (isTournament) {
|
||||||
|
tournament = new Server_Tournament(this, this, this);
|
||||||
|
lifecycleStrategy.reset(new Server_TournamentLifecycleStrategy);
|
||||||
|
matchResultStrategy.reset(new Server_TournamentMatchResultStrategy);
|
||||||
|
} else if (tournament) {
|
||||||
|
delete tournament;
|
||||||
|
tournament = nullptr;
|
||||||
|
lifecycleStrategy.reset(new Server_DefaultLifecycleStrategy);
|
||||||
|
matchResultStrategy.reset(new Server_NullMatchResultStrategy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Game::startTournament()
|
||||||
|
{
|
||||||
|
if (!tournament) {
|
||||||
|
tournament = new Server_Tournament(this, this, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tournament->isStarted()) {
|
||||||
|
// Add all current players to the tournament
|
||||||
|
auto players = getPlayers();
|
||||||
|
for (auto *player : players.values()) {
|
||||||
|
tournament->addPlayer(player->getPlayerId(), QString::fromStdString(player->getUserInfo()->name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
tournament->startTournament();
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEventStorage ges;
|
||||||
|
tournament->broadcastTournamentState(ges);
|
||||||
|
ges.sendToGame(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Game::setPlayerTournamentDeck(int playerId, DeckList *deck)
|
||||||
|
{
|
||||||
|
if (tournament) {
|
||||||
|
tournament->setPlayerDeck(playerId, deck);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Game::setTournamentMatchInfo(Server_Game *parentGame, int p1Id, int p2Id)
|
||||||
|
{
|
||||||
|
tournamentParentGame = parentGame;
|
||||||
|
tournamentMatchPlayer1Id = p1Id;
|
||||||
|
tournamentMatchPlayer2Id = p2Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server_Game *Server_Game::createMatchGame(const GameConfig &config, int &outGameId)
|
||||||
|
{
|
||||||
|
Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface();
|
||||||
|
outGameId = databaseInterface->getNextGameId();
|
||||||
|
if (outGameId == -1) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameConfig matchConfig = config;
|
||||||
|
matchConfig.gameId = outGameId;
|
||||||
|
auto *game = new Server_Game(matchConfig, room);
|
||||||
|
// Sub-games carry the tournament flag (for protocol fields) but keep the default
|
||||||
|
// strategies; the parent tournament drives them through the match result strategy
|
||||||
|
// installed by Server_Tournament::createMatchGame.
|
||||||
|
game->isTournament = true;
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server_AbstractUserInterface *Server_Game::getUserInterface(const QString &playerName)
|
||||||
|
{
|
||||||
|
return room->getUserInterfaceByName(playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Game::addGameToRoom(Server_Game *game)
|
||||||
|
{
|
||||||
|
room->addGame(game);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include "game_config.h"
|
#include "game_config.h"
|
||||||
#include "server_deck_validation_strategy.h"
|
#include "server_deck_validation_strategy.h"
|
||||||
#include "server_game_lifecycle_strategy.h"
|
#include "server_game_lifecycle_strategy.h"
|
||||||
|
#include "server_match_game_factory.h"
|
||||||
#include "server_match_result_strategy.h"
|
#include "server_match_result_strategy.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
@ -34,21 +35,26 @@
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <libcockatrice/protocol/pb/event_leave.pb.h>
|
#include <libcockatrice/protocol/pb/event_leave.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
class DeckList;
|
||||||
class GameEventContainer;
|
class GameEventContainer;
|
||||||
|
class GameEventStorage;
|
||||||
class GameReplay;
|
class GameReplay;
|
||||||
class Server_Room;
|
class Server_Room;
|
||||||
class Server_AbstractPlayer;
|
class Server_AbstractPlayer;
|
||||||
class Server_AbstractParticipant;
|
class Server_AbstractParticipant;
|
||||||
|
class Server_Card;
|
||||||
|
class Server_Tournament;
|
||||||
class ServerInfo_User;
|
class ServerInfo_User;
|
||||||
class ServerInfo_Game;
|
class ServerInfo_Game;
|
||||||
class Server_AbstractUserInterface;
|
class Server_AbstractUserInterface;
|
||||||
class Event_GameStateChanged;
|
class Event_GameStateChanged;
|
||||||
|
|
||||||
class Server_Game : public QObject
|
class Server_Game : public QObject, public Server_MatchGameFactory
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
|
|
@ -83,6 +89,14 @@ private:
|
||||||
QList<GameReplay *> replayList;
|
QList<GameReplay *> replayList;
|
||||||
GameReplay *currentReplay;
|
GameReplay *currentReplay;
|
||||||
|
|
||||||
|
bool isTournament;
|
||||||
|
TournamentSettings tournamentSettings;
|
||||||
|
Server_Tournament *tournament;
|
||||||
|
Server_Game *tournamentParentGame;
|
||||||
|
int tournamentMatchPlayer1Id;
|
||||||
|
int tournamentMatchPlayer2Id;
|
||||||
|
bool disconnectRemovesPlayer;
|
||||||
|
|
||||||
QScopedPointer<Server_DeckValidationStrategy> deckValidationStrategy;
|
QScopedPointer<Server_DeckValidationStrategy> deckValidationStrategy;
|
||||||
|
|
||||||
QScopedPointer<Server_GameLifecycleStrategy> lifecycleStrategy;
|
QScopedPointer<Server_GameLifecycleStrategy> lifecycleStrategy;
|
||||||
|
|
@ -220,6 +234,45 @@ public:
|
||||||
void returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player);
|
void returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player);
|
||||||
|
|
||||||
/** @brief Get the current deck validation strategy (non-owning). */
|
/** @brief Get the current deck validation strategy (non-owning). */
|
||||||
|
bool getIsTournamentGame() const
|
||||||
|
{
|
||||||
|
return isTournament;
|
||||||
|
}
|
||||||
|
void setIsTournamentGame(bool _isTournament);
|
||||||
|
bool getIsTournament() const
|
||||||
|
{
|
||||||
|
return tournament != nullptr;
|
||||||
|
}
|
||||||
|
Server_Tournament *getTournament() const
|
||||||
|
{
|
||||||
|
return tournament;
|
||||||
|
}
|
||||||
|
void startTournament();
|
||||||
|
void setPlayerTournamentDeck(int playerId, DeckList *deck);
|
||||||
|
void setTournamentMatchInfo(Server_Game *parentGame, int p1Id, int p2Id);
|
||||||
|
Server_Game *getTournamentParentGame() const
|
||||||
|
{
|
||||||
|
return tournamentParentGame;
|
||||||
|
}
|
||||||
|
bool getDisconnectRemovesPlayer() const
|
||||||
|
{
|
||||||
|
return disconnectRemovesPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server_MatchGameFactory implementation
|
||||||
|
Server_Game *createMatchGame(const GameConfig &config, int &outGameId) override;
|
||||||
|
Server_AbstractUserInterface *getUserInterface(const QString &playerName) override;
|
||||||
|
void addGameToRoom(Server_Game *game) override;
|
||||||
|
|
||||||
|
const TournamentSettings &getTournamentSettings() const
|
||||||
|
{
|
||||||
|
return tournamentSettings;
|
||||||
|
}
|
||||||
|
void setTournamentSettings(const TournamentSettings &settings)
|
||||||
|
{
|
||||||
|
tournamentSettings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
Server_DeckValidationStrategy *getDeckValidationStrategy() const
|
Server_DeckValidationStrategy *getDeckValidationStrategy() const
|
||||||
{
|
{
|
||||||
return deckValidationStrategy.data();
|
return deckValidationStrategy.data();
|
||||||
|
|
@ -232,6 +285,8 @@ public:
|
||||||
{
|
{
|
||||||
return lifecycleStrategy.data();
|
return lifecycleStrategy.data();
|
||||||
}
|
}
|
||||||
|
/** @brief Replace the match result strategy; takes ownership of @p strategy. */
|
||||||
|
void setMatchResultStrategy(Server_MatchResultStrategy *strategy);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,556 @@
|
||||||
|
#include "server_tournament.h"
|
||||||
|
|
||||||
|
#include "../server_abstractuserinterface.h"
|
||||||
|
#include "../server_response_containers.h"
|
||||||
|
#include "../serverinfo_user_container.h"
|
||||||
|
#include "game_config.h"
|
||||||
|
#include "server_abstract_player.h"
|
||||||
|
#include "server_game.h"
|
||||||
|
#include "server_match_game_factory.h"
|
||||||
|
#include "server_player.h"
|
||||||
|
#include "server_tournament_match_result_strategy.h"
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
|
||||||
|
|
||||||
|
inline Q_LOGGING_CATEGORY(TournamentLog, "tournament");
|
||||||
|
|
||||||
|
Server_Tournament::Server_Tournament(Server_Game *_parentGame, Server_MatchGameFactory *_factory, QObject *parent)
|
||||||
|
: QObject(parent), parentGame(_parentGame), matchGameFactory(_factory), currentRound(0), totalRounds(0),
|
||||||
|
started(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Server_Tournament::~Server_Tournament()
|
||||||
|
{
|
||||||
|
qDeleteAll(submittedDecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::addPlayer(int playerId, const QString &playerName)
|
||||||
|
{
|
||||||
|
TournamentPlayerData data;
|
||||||
|
data.playerId = playerId;
|
||||||
|
data.playerName = playerName;
|
||||||
|
players[playerId] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::setPlayerDeck(int playerId, DeckList *deck)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
delete submittedDecks.value(playerId, nullptr);
|
||||||
|
submittedDecks[playerId] = deck;
|
||||||
|
if (players.contains(playerId)) {
|
||||||
|
players[playerId].deckSubmitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::removePlayer(int playerId)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
players.remove(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::startTournament()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
if (started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
totalRounds = calculateTotalRounds();
|
||||||
|
started = true;
|
||||||
|
currentRound = 0;
|
||||||
|
generateSwissPairings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn the first round's match games
|
||||||
|
enqueueMatchGameCreation();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server_Tournament::isAllDecksSubmitted() const
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
for (auto it = players.constBegin(); it != players.constEnd(); ++it) {
|
||||||
|
if (!it->deckSubmitted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Server_Tournament::getTournamentPlayerIdByName(const QString &name) const
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
for (auto it = players.constBegin(); it != players.constEnd(); ++it) {
|
||||||
|
if (it->playerName == name) {
|
||||||
|
return it->playerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::generateSwissPairings()
|
||||||
|
{
|
||||||
|
currentPairings.clear();
|
||||||
|
QList<int> available;
|
||||||
|
for (auto it = players.constBegin(); it != players.constEnd(); ++it) {
|
||||||
|
available.append(it->playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by wins descending (and by record for tie-breaking)
|
||||||
|
std::sort(available.begin(), available.end(), [this](int a, int b) {
|
||||||
|
const auto &pa = players[a];
|
||||||
|
const auto &pb = players[b];
|
||||||
|
if (pa.wins != pb.wins) {
|
||||||
|
return pa.wins > pb.wins;
|
||||||
|
}
|
||||||
|
if (pa.losses != pb.losses) {
|
||||||
|
return pa.losses < pb.losses;
|
||||||
|
}
|
||||||
|
return a < b;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simple greedy Swiss pairing
|
||||||
|
QSet<int> paired;
|
||||||
|
for (int i = 0; i < available.size(); ++i) {
|
||||||
|
if (paired.contains(available[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int j = i + 1; j < available.size(); ++j) {
|
||||||
|
if (paired.contains(available[j])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!havePlayed(available[i], available[j])) {
|
||||||
|
TournamentPairingData pairing;
|
||||||
|
pairing.player1Id = available[i];
|
||||||
|
pairing.player2Id = available[j];
|
||||||
|
currentPairings.append(pairing);
|
||||||
|
paired.insert(available[i]);
|
||||||
|
paired.insert(available[j]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bye for unpaired player if odd count
|
||||||
|
for (int i = 0; i < available.size(); ++i) {
|
||||||
|
if (!paired.contains(available[i])) {
|
||||||
|
// Player gets a bye (auto-win)
|
||||||
|
TournamentPairingData bye;
|
||||||
|
bye.player1Id = available[i];
|
||||||
|
bye.player2Id = -1;
|
||||||
|
bye.winnerId = available[i];
|
||||||
|
bye.player1MatchWins = gamesPerMatch; // Match immediately decided
|
||||||
|
currentPairings.append(bye);
|
||||||
|
players[available[i]].wins += 1;
|
||||||
|
allPreviousPairings.append(qMakePair(available[i], -1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Server_Tournament::calculateTotalRounds() const
|
||||||
|
{
|
||||||
|
int n = players.size();
|
||||||
|
if (n <= 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Standard Swiss rounds: ceil(log2(n))
|
||||||
|
int rounds = 0;
|
||||||
|
while ((1 << rounds) < n) {
|
||||||
|
++rounds;
|
||||||
|
}
|
||||||
|
return rounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server_Tournament::havePlayed(int p1, int p2) const
|
||||||
|
{
|
||||||
|
for (const auto &pair : allPreviousPairings) {
|
||||||
|
if ((pair.first == p1 && pair.second == p2) || (pair.first == p2 && pair.second == p1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::advanceRound(GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
++currentRound;
|
||||||
|
if (currentRound >= totalRounds) {
|
||||||
|
broadcastTournamentState(ges);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
generateSwissPairings();
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueMatchGameCreation();
|
||||||
|
broadcastTournamentState(ges);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server_Tournament::allPairingsDecided() const
|
||||||
|
{
|
||||||
|
for (const auto &pairing : currentPairings) {
|
||||||
|
if (pairing.winnerId == -2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::enqueueMatchGameCreation()
|
||||||
|
{
|
||||||
|
QList<QPair<int, int>> planned;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
for (const auto &pairing : currentPairings) {
|
||||||
|
if (pairing.player2Id != -1 && pairing.winnerId == -2 &&
|
||||||
|
pairing.matchGameIds.size() < static_cast<int>(gamesPerMatch)) {
|
||||||
|
planned.append(qMakePair(pairing.player1Id, pairing.player2Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (planned.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the games from the event loop instead of the caller's stack: command
|
||||||
|
// processing holds game mutexes, and room registration takes gamesLock, so spawning
|
||||||
|
// synchronously would nest lock orders. The queued job runs once this object's
|
||||||
|
// owning thread returns to its event loop with no locks held; it is dropped if this
|
||||||
|
// tournament is destroyed first.
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this,
|
||||||
|
[this, planned] {
|
||||||
|
for (const auto &pair : planned) {
|
||||||
|
createMatchGame(pair.first, pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEventStorage ges;
|
||||||
|
broadcastTournamentState(ges);
|
||||||
|
ges.sendToGame(parentGame);
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::createMatchGame(int player1Id, int player2Id)
|
||||||
|
{
|
||||||
|
if (!matchGameFactory || player2Id == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString player1Name;
|
||||||
|
QString player2Name;
|
||||||
|
QString deck1Native;
|
||||||
|
QString deck2Native;
|
||||||
|
int round = 0;
|
||||||
|
int gameNumber = 1;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
player1Name = players.value(player1Id).playerName;
|
||||||
|
player2Name = players.value(player2Id).playerName;
|
||||||
|
|
||||||
|
if (submittedDecks.contains(player1Id)) {
|
||||||
|
deck1Native = submittedDecks.value(player1Id)->writeToString_Native();
|
||||||
|
}
|
||||||
|
if (submittedDecks.contains(player2Id)) {
|
||||||
|
deck2Native = submittedDecks.value(player2Id)->writeToString_Native();
|
||||||
|
}
|
||||||
|
|
||||||
|
round = currentRound;
|
||||||
|
for (const auto &pairing : currentPairings) {
|
||||||
|
if (pairing.player1Id == player1Id && pairing.player2Id == player2Id) {
|
||||||
|
gameNumber = pairing.matchGameIds.size() + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defense in depth: never exceed the configured series length
|
||||||
|
for (const auto &pairing : currentPairings) {
|
||||||
|
if (pairing.player1Id == player1Id && pairing.player2Id == player2Id &&
|
||||||
|
pairing.matchGameIds.size() >= static_cast<int>(gamesPerMatch)) {
|
||||||
|
qCWarning(TournamentLog) << "Refusing to exceed series length for pairing" << player1Id << player2Id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a sub-game for this match via the factory
|
||||||
|
ServerInfo_User creatorInfo;
|
||||||
|
creatorInfo.set_name(player1Name.toStdString());
|
||||||
|
creatorInfo.set_user_level(ServerInfo_User::IsAdmin | ServerInfo_User::IsRegistered);
|
||||||
|
|
||||||
|
QString gameDesc = gamesPerMatch > 1
|
||||||
|
? QString("R%1 Match - Game %2 of %3").arg(round).arg(gameNumber).arg(gamesPerMatch)
|
||||||
|
: QString("Tournament Round %1").arg(round);
|
||||||
|
|
||||||
|
GameConfig matchConfig;
|
||||||
|
matchConfig.creatorInfo = creatorInfo;
|
||||||
|
matchConfig.description = gameDesc;
|
||||||
|
matchConfig.maxPlayers = 2;
|
||||||
|
matchConfig.startingLifeTotal = parentGame->getStartingLifeTotal();
|
||||||
|
|
||||||
|
int matchGameId = -1;
|
||||||
|
auto *matchGame = matchGameFactory->createMatchGame(matchConfig, matchGameId);
|
||||||
|
if (!matchGame || matchGameId == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchGame->setTournamentMatchInfo(parentGame, player1Id, player2Id);
|
||||||
|
matchGame->setMatchResultStrategy(new Server_TournamentMatchResultStrategy);
|
||||||
|
matchGameFactory->addGameToRoom(matchGame);
|
||||||
|
|
||||||
|
// Store the game ID in the pairing
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
for (auto &pairing : currentPairings) {
|
||||||
|
if (pairing.player1Id == player1Id && pairing.player2Id == player2Id) {
|
||||||
|
pairing.gameId = matchGameId;
|
||||||
|
pairing.matchGameIds.append(matchGameId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-join both players, sending the join event directly through their UIs.
|
||||||
|
QMap<int, QPair<Server_AbstractUserInterface *, ResponseContainer *>> joiners;
|
||||||
|
|
||||||
|
auto joinAndSetupPlayer = [&](int pid, const QString &name) {
|
||||||
|
Server_AbstractUserInterface *ui = matchGameFactory->getUserInterface(name);
|
||||||
|
if (ui) {
|
||||||
|
auto *rc = new ResponseContainer(0);
|
||||||
|
matchGame->addPlayer(ui, *rc, false, false, false);
|
||||||
|
joiners[pid] = qMakePair(ui, rc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
joinAndSetupPlayer(player1Id, player1Name);
|
||||||
|
joinAndSetupPlayer(player2Id, player2Name);
|
||||||
|
|
||||||
|
// Now send the enqueued GameJoined + GameStateChanged events to each player's client.
|
||||||
|
for (auto it = joiners.constBegin(); it != joiners.constEnd(); ++it) {
|
||||||
|
it.value().first->sendResponseContainer(*it.value().second, Response::RespNothing);
|
||||||
|
delete it.value().second;
|
||||||
|
}
|
||||||
|
joiners.clear();
|
||||||
|
|
||||||
|
// Set decks and mark players as ready in the match game
|
||||||
|
auto matchPlayers = matchGame->getPlayers();
|
||||||
|
for (auto *matchPlayer : matchPlayers) {
|
||||||
|
const QString name = QString::fromStdString(matchPlayer->getUserInfo()->name());
|
||||||
|
QString deckNative;
|
||||||
|
if (name == player1Name) {
|
||||||
|
deckNative = deck1Native;
|
||||||
|
} else if (name == player2Name) {
|
||||||
|
deckNative = deck2Native;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deckNative.isEmpty()) {
|
||||||
|
matchPlayer->setDeck(new DeckList(deckNative));
|
||||||
|
matchPlayer->setReadyStart(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the match game
|
||||||
|
matchGame->startGameIfReady(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::recordMatchResult(int playerId1, int playerId2, int winnerId, GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
|
||||||
|
// Find the pairing and set the winner
|
||||||
|
for (auto &pairing : currentPairings) {
|
||||||
|
if ((pairing.player1Id == playerId1 && pairing.player2Id == playerId2) ||
|
||||||
|
(pairing.player1Id == playerId2 && pairing.player2Id == playerId1)) {
|
||||||
|
if (pairing.winnerId != -2) {
|
||||||
|
return; // Already recorded — defense in depth against double-call
|
||||||
|
}
|
||||||
|
pairing.winnerId = winnerId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update player records
|
||||||
|
if (winnerId == -1) {
|
||||||
|
// Draw
|
||||||
|
players[playerId1].draws += 1;
|
||||||
|
players[playerId2].draws += 1;
|
||||||
|
} else if (winnerId == playerId1) {
|
||||||
|
players[playerId1].wins += 1;
|
||||||
|
players[playerId2].losses += 1;
|
||||||
|
} else if (winnerId == playerId2) {
|
||||||
|
players[playerId2].wins += 1;
|
||||||
|
players[playerId1].losses += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store for future pairing avoidance
|
||||||
|
allPreviousPairings.append(qMakePair(playerId1, playerId2));
|
||||||
|
|
||||||
|
// Check if all pairings in current round have results
|
||||||
|
bool allDecided = true;
|
||||||
|
for (const auto &pairing : currentPairings) {
|
||||||
|
if (pairing.winnerId == -2) {
|
||||||
|
allDecided = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastTournamentState(ges);
|
||||||
|
|
||||||
|
if (allDecided) {
|
||||||
|
advanceRound(ges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server_Tournament::recordMatchResultByGameId(int gameId, int winnerId, GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
bool matchDecided = false;
|
||||||
|
bool seriesContinues = false;
|
||||||
|
int p1 = -1;
|
||||||
|
int p2 = -1;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
|
||||||
|
// Find the pairing that owns this game
|
||||||
|
TournamentPairingData *pairingPtr = nullptr;
|
||||||
|
for (auto &pairing : currentPairings) {
|
||||||
|
if (pairing.matchGameIds.contains(gameId)) {
|
||||||
|
pairingPtr = &pairing;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pairingPtr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the match is already decided, ignore further sub-game results
|
||||||
|
if (pairingPtr->winnerId != -2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment per-match wins
|
||||||
|
if (winnerId == pairingPtr->player1Id) {
|
||||||
|
pairingPtr->player1MatchWins += 1;
|
||||||
|
} else if (winnerId == pairingPtr->player2Id) {
|
||||||
|
pairingPtr->player2MatchWins += 1;
|
||||||
|
}
|
||||||
|
// Draw (winnerId == -1): no match wins incremented
|
||||||
|
|
||||||
|
// Check if match is decided
|
||||||
|
const int gamesNeeded = static_cast<int>(gamesPerMatch);
|
||||||
|
matchDecided = (pairingPtr->player1MatchWins >= gamesNeeded) || (pairingPtr->player2MatchWins >= gamesNeeded);
|
||||||
|
|
||||||
|
if (matchDecided) {
|
||||||
|
// Determine match winner
|
||||||
|
int matchWinnerId;
|
||||||
|
if (pairingPtr->player1MatchWins >= gamesNeeded) {
|
||||||
|
matchWinnerId = pairingPtr->player1Id;
|
||||||
|
} else {
|
||||||
|
matchWinnerId = pairingPtr->player2Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the match winner on the pairing
|
||||||
|
pairingPtr->winnerId = matchWinnerId;
|
||||||
|
|
||||||
|
// Update tournament-level player records
|
||||||
|
if (matchWinnerId == pairingPtr->player1Id) {
|
||||||
|
players[pairingPtr->player1Id].wins += 1;
|
||||||
|
players[pairingPtr->player2Id].losses += 1;
|
||||||
|
} else {
|
||||||
|
players[pairingPtr->player2Id].wins += 1;
|
||||||
|
players[pairingPtr->player1Id].losses += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store for future pairing avoidance
|
||||||
|
allPreviousPairings.append(qMakePair(pairingPtr->player1Id, pairingPtr->player2Id));
|
||||||
|
} else {
|
||||||
|
// Match not decided — spawn the next sub-game outside all locks
|
||||||
|
seriesContinues = true;
|
||||||
|
p1 = pairingPtr->player1Id;
|
||||||
|
p2 = pairingPtr->player2Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastTournamentState(ges);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seriesContinues) {
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this,
|
||||||
|
[this, p1, p2] {
|
||||||
|
createMatchGame(p1, p2);
|
||||||
|
|
||||||
|
GameEventStorage nextGes;
|
||||||
|
broadcastTournamentState(nextGes);
|
||||||
|
nextGes.sendToGame(parentGame);
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndAdvanceRound(ges);
|
||||||
|
|
||||||
|
return matchDecided;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::checkAndAdvanceRound(GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
bool roundComplete = false;
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
roundComplete = allPairingsDecided();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roundComplete) {
|
||||||
|
advanceRound(ges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server_Tournament::broadcastTournamentState(GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&tournamentMutex);
|
||||||
|
|
||||||
|
Event_TournamentState state;
|
||||||
|
|
||||||
|
if (started && currentRound >= totalRounds) {
|
||||||
|
state.set_phase(Event_TournamentState::PHASE_FINISHED);
|
||||||
|
} else if (started) {
|
||||||
|
state.set_phase(Event_TournamentState::PHASE_PLAYING);
|
||||||
|
} else {
|
||||||
|
state.set_phase(Event_TournamentState::PHASE_DECK_BUILDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.set_current_round(currentRound);
|
||||||
|
state.set_total_rounds(totalRounds);
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
TournamentSettings *settings = state.mutable_settings();
|
||||||
|
settings->set_games_per_match(gamesPerMatch);
|
||||||
|
|
||||||
|
for (auto it = players.constBegin(); it != players.constEnd(); ++it) {
|
||||||
|
TournamentPlayer *p = state.add_players();
|
||||||
|
p->set_player_id(it->playerId);
|
||||||
|
p->set_player_name(it->playerName.toStdString());
|
||||||
|
p->set_wins(it->wins);
|
||||||
|
p->set_losses(it->losses);
|
||||||
|
p->set_draws(it->draws);
|
||||||
|
p->set_deck_submitted(it->deckSubmitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &pairing : currentPairings) {
|
||||||
|
TournamentPairing *p = state.add_pairings();
|
||||||
|
p->set_player1_id(pairing.player1Id);
|
||||||
|
p->set_player2_id(pairing.player2Id);
|
||||||
|
p->set_game_id(pairing.gameId);
|
||||||
|
// Map internal sentinel: -2 (undecided) -> -1 (no winner yet in proto)
|
||||||
|
p->set_winner_id(pairing.winnerId == -2 ? -1 : pairing.winnerId);
|
||||||
|
p->set_player1_match_wins(pairing.player1MatchWins);
|
||||||
|
p->set_player2_match_wins(pairing.player2MatchWins);
|
||||||
|
}
|
||||||
|
|
||||||
|
ges.enqueueGameEvent(state, -1);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
#ifndef SERVER_TOURNAMENT_H
|
||||||
|
#define SERVER_TOURNAMENT_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QRecursiveMutex>
|
||||||
|
#include <QSet>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
|
|
||||||
|
class DeckList;
|
||||||
|
class Server_Game;
|
||||||
|
class Server_MatchGameFactory;
|
||||||
|
class Server_AbstractParticipant;
|
||||||
|
class Server_AbstractUserInterface;
|
||||||
|
class GameEventStorage;
|
||||||
|
|
||||||
|
/** @brief Maximum number of games per match a tournament can be configured with. */
|
||||||
|
constexpr int MAX_GAMES_PER_MATCH = 5;
|
||||||
|
|
||||||
|
class Server_Tournament : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit Server_Tournament(Server_Game *_parentGame, Server_MatchGameFactory *_factory, QObject *parent = nullptr);
|
||||||
|
~Server_Tournament() override;
|
||||||
|
|
||||||
|
void addPlayer(int playerId, const QString &playerName);
|
||||||
|
void removePlayer(int playerId);
|
||||||
|
void startTournament();
|
||||||
|
void advanceRound(GameEventStorage &ges);
|
||||||
|
void recordMatchResult(int playerId1, int playerId2, int winnerId, GameEventStorage &ges);
|
||||||
|
bool recordMatchResultByGameId(int gameId, int winnerId, GameEventStorage &ges);
|
||||||
|
void broadcastTournamentState(GameEventStorage &ges);
|
||||||
|
|
||||||
|
bool isStarted() const
|
||||||
|
{
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
bool isAllDecksSubmitted() const;
|
||||||
|
int getPlayerCount() const
|
||||||
|
{
|
||||||
|
return players.size();
|
||||||
|
}
|
||||||
|
int getTournamentPlayerIdByName(const QString &name) const;
|
||||||
|
void setPlayerDeckSubmitted(int playerId)
|
||||||
|
{
|
||||||
|
if (players.contains(playerId)) {
|
||||||
|
players[playerId].deckSubmitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setPlayerDeck(int playerId, DeckList *deck);
|
||||||
|
|
||||||
|
void setGamesPerMatch(uint32_t n)
|
||||||
|
{
|
||||||
|
gamesPerMatch = n;
|
||||||
|
}
|
||||||
|
uint32_t getGamesPerMatch() const
|
||||||
|
{
|
||||||
|
return gamesPerMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TournamentPlayerData
|
||||||
|
{
|
||||||
|
int playerId;
|
||||||
|
QString playerName;
|
||||||
|
int wins = 0;
|
||||||
|
int losses = 0;
|
||||||
|
int draws = 0;
|
||||||
|
bool deckSubmitted = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TournamentPairingData
|
||||||
|
{
|
||||||
|
int player1Id;
|
||||||
|
int player2Id;
|
||||||
|
int gameId = -1;
|
||||||
|
int winnerId = -2; // -2 = undecided, -1 = draw, >= 0 = winner player id
|
||||||
|
int player1MatchWins = 0;
|
||||||
|
int player2MatchWins = 0;
|
||||||
|
QList<int> matchGameIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Server_Game *parentGame;
|
||||||
|
Server_MatchGameFactory *matchGameFactory;
|
||||||
|
mutable QRecursiveMutex tournamentMutex;
|
||||||
|
QMap<int, TournamentPlayerData> players;
|
||||||
|
QMap<int, DeckList *> submittedDecks;
|
||||||
|
QList<TournamentPairingData> currentPairings;
|
||||||
|
QList<QPair<int, int>> allPreviousPairings;
|
||||||
|
int currentRound;
|
||||||
|
int totalRounds;
|
||||||
|
bool started;
|
||||||
|
uint32_t gamesPerMatch = 1;
|
||||||
|
|
||||||
|
void generateSwissPairings();
|
||||||
|
int calculateTotalRounds() const;
|
||||||
|
bool havePlayed(int p1, int p2) const;
|
||||||
|
bool allPairingsDecided() const;
|
||||||
|
void createMatchGame(int player1Id, int player2Id);
|
||||||
|
void enqueueMatchGameCreation();
|
||||||
|
void checkAndAdvanceRound(GameEventStorage &ges);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SERVER_TOURNAMENT_H
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include "server_tournament_lifecycle_strategy.h"
|
||||||
|
|
||||||
|
#include "server_abstract_player.h"
|
||||||
|
#include "server_game.h"
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
inline Q_LOGGING_CATEGORY(TournamentLifecycleLog, "tournament_lifecycle");
|
||||||
|
|
||||||
|
Server_GameLifecycleStrategy::StartAction Server_TournamentLifecycleStrategy::onGameStarting(Server_Game *game)
|
||||||
|
{
|
||||||
|
// Match sub-games start through the normal flow; only the tournament hub game is
|
||||||
|
// managed by this lifecycle.
|
||||||
|
if (game->getTournamentParentGame() != nullptr) {
|
||||||
|
return StartAction::ProceedNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto *player : game->getPlayers().values()) {
|
||||||
|
if (!player->getDeckList()) {
|
||||||
|
qCWarning(TournamentLifecycleLog)
|
||||||
|
<< "Tournament cannot start: player" << player->getUserInfo()->name().c_str() << "has no deck";
|
||||||
|
return StartAction::Handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game->getIsTournamentGame()) {
|
||||||
|
qCWarning(TournamentLifecycleLog) << "Tournament lifecycle used for non-tournament game — falling back";
|
||||||
|
return StartAction::ProceedNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
game->startTournament();
|
||||||
|
return StartAction::Handled;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef SERVER_TOURNAMENT_LIFECYCLE_STRATEGY_H
|
||||||
|
#define SERVER_TOURNAMENT_LIFECYCLE_STRATEGY_H
|
||||||
|
|
||||||
|
#include "server_game_lifecycle_strategy.h"
|
||||||
|
|
||||||
|
class Server_TournamentLifecycleStrategy : public Server_GameLifecycleStrategy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StartAction onGameStarting(Server_Game *game) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#include "server_tournament_match_result_strategy.h"
|
||||||
|
|
||||||
|
#include "../server_response_containers.h"
|
||||||
|
#include "server_abstract_player.h"
|
||||||
|
#include "server_game.h"
|
||||||
|
#include "server_tournament.h"
|
||||||
|
|
||||||
|
#include <libcockatrice/protocol/pb/event_game_closed.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
|
||||||
|
|
||||||
|
bool Server_TournamentMatchResultStrategy::onGameFinished(Server_Game *game,
|
||||||
|
int playing,
|
||||||
|
Server_AbstractPlayer *lastPlayer)
|
||||||
|
{
|
||||||
|
auto *parentGame = game->getTournamentParentGame();
|
||||||
|
if (!parentGame || !parentGame->getTournament()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int winnerId;
|
||||||
|
if (playing == 0) {
|
||||||
|
winnerId = -1;
|
||||||
|
} else {
|
||||||
|
QString winnerName = QString::fromStdString(lastPlayer->getUserInfo()->name());
|
||||||
|
auto *tournament = parentGame->getTournament();
|
||||||
|
winnerId = tournament->getTournamentPlayerIdByName(winnerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEventStorage parentGes;
|
||||||
|
bool matchDecided = parentGame->getTournament()->recordMatchResultByGameId(game->getGameId(), winnerId, parentGes);
|
||||||
|
parentGes.sendToGame(parentGame);
|
||||||
|
|
||||||
|
return matchDecided;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef SERVER_TOURNAMENT_MATCH_RESULT_STRATEGY_H
|
||||||
|
#define SERVER_TOURNAMENT_MATCH_RESULT_STRATEGY_H
|
||||||
|
|
||||||
|
#include "server_match_result_strategy.h"
|
||||||
|
|
||||||
|
class Server_TournamentMatchResultStrategy : public Server_MatchResultStrategy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool onGameFinished(Server_Game *game, int playing, Server_AbstractPlayer *lastPlayer) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "game/game_config.h"
|
#include "game/game_config.h"
|
||||||
#include "game/server_game.h"
|
#include "game/server_game.h"
|
||||||
#include "game/server_player.h"
|
#include "game/server_player.h"
|
||||||
|
#include "game/server_tournament.h"
|
||||||
#include "server_database_interface.h"
|
#include "server_database_interface.h"
|
||||||
#include "server_room.h"
|
#include "server_room.h"
|
||||||
|
|
||||||
|
|
@ -916,6 +917,8 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
||||||
int startingLifeTotal = cmd.has_starting_life_total() ? cmd.starting_life_total() : 20;
|
int startingLifeTotal = cmd.has_starting_life_total() ? cmd.starting_life_total() : 20;
|
||||||
|
|
||||||
bool shareDecklistsOnLoad = cmd.has_share_decklists_on_load() ? cmd.share_decklists_on_load() : false;
|
bool shareDecklistsOnLoad = cmd.has_share_decklists_on_load() ? cmd.share_decklists_on_load() : false;
|
||||||
|
bool isTournament = cmd.has_is_tournament() ? cmd.is_tournament() : false;
|
||||||
|
int gamesPerMatch = cmd.has_games_per_match() ? static_cast<int>(cmd.games_per_match()) : 1;
|
||||||
|
|
||||||
const int gameId = databaseInterface->getNextGameId();
|
const int gameId = databaseInterface->getNextGameId();
|
||||||
if (gameId == -1) {
|
if (gameId == -1) {
|
||||||
|
|
@ -940,6 +943,10 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
||||||
.shareDecklistsOnLoad = shareDecklistsOnLoad};
|
.shareDecklistsOnLoad = shareDecklistsOnLoad};
|
||||||
|
|
||||||
auto *game = new Server_Game(config, room);
|
auto *game = new Server_Game(config, room);
|
||||||
|
game->setIsTournamentGame(isTournament);
|
||||||
|
if (isTournament && game->getTournament()) {
|
||||||
|
game->getTournament()->setGamesPerMatch(static_cast<uint32_t>(qBound(1, gamesPerMatch, MAX_GAMES_PER_MATCH)));
|
||||||
|
}
|
||||||
|
|
||||||
game->addPlayer(this, rc, asSpectator, asJudge, false);
|
game->addPlayer(this, rc, asSpectator, asJudge, false);
|
||||||
room->addGame(game);
|
room->addGame(game);
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,18 @@ void Server_Room::broadcastGameListUpdate(const ServerInfo_Game &gameInfo, bool
|
||||||
sendRoomEvent(prepareRoomEvent(event), sendToIsl);
|
sendRoomEvent(prepareRoomEvent(event), sendToIsl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Server_AbstractUserInterface *Server_Room::getUserInterfaceByName(const QString &name) const
|
||||||
|
{
|
||||||
|
usersLock.lockForRead();
|
||||||
|
auto it = users.constFind(name);
|
||||||
|
Server_AbstractUserInterface *result = nullptr;
|
||||||
|
if (it != users.constEnd()) {
|
||||||
|
result = it.value();
|
||||||
|
}
|
||||||
|
usersLock.unlock();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void Server_Room::addGame(Server_Game *game)
|
void Server_Room::addGame(Server_Game *game)
|
||||||
{
|
{
|
||||||
ServerInfo_Room roomInfo;
|
ServerInfo_Room roomInfo;
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,8 @@ public:
|
||||||
void addGame(Server_Game *game);
|
void addGame(Server_Game *game);
|
||||||
void removeGame(Server_Game *game);
|
void removeGame(Server_Game *game);
|
||||||
|
|
||||||
|
Server_AbstractUserInterface *getUserInterfaceByName(const QString &name) const;
|
||||||
|
|
||||||
void sendRoomEvent(RoomEvent *event, bool sendToIsl = true);
|
void sendRoomEvent(RoomEvent *event, bool sendToIsl = true);
|
||||||
RoomEvent *prepareRoomEvent(const ::google::protobuf::Message &roomEvent);
|
RoomEvent *prepareRoomEvent(const ::google::protobuf::Message &roomEvent);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ set(PROTO_FILES
|
||||||
command_set_sideboard_lock.proto
|
command_set_sideboard_lock.proto
|
||||||
command_set_sideboard_plan.proto
|
command_set_sideboard_plan.proto
|
||||||
command_shuffle.proto
|
command_shuffle.proto
|
||||||
|
command_tournament.proto
|
||||||
command_undo_draw.proto
|
command_undo_draw.proto
|
||||||
commands.proto
|
commands.proto
|
||||||
context_concede.proto
|
context_concede.proto
|
||||||
|
|
@ -118,6 +119,7 @@ set(PROTO_FILES
|
||||||
event_set_card_counter.proto
|
event_set_card_counter.proto
|
||||||
event_set_counter.proto
|
event_set_counter.proto
|
||||||
event_shuffle.proto
|
event_shuffle.proto
|
||||||
|
event_tournament_state.proto
|
||||||
event_user_joined.proto
|
event_user_joined.proto
|
||||||
event_user_left.proto
|
event_user_left.proto
|
||||||
event_user_message.proto
|
event_user_message.proto
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_commands.proto";
|
||||||
|
import "event_tournament_state.proto";
|
||||||
|
|
||||||
|
message Command_ReportMatchResult {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_ReportMatchResult ext = 1037;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional sint32 game_id = 1 [default = -1];
|
||||||
|
optional sint32 winner_id = 2 [default = -1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message Command_AdvanceTournament {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_AdvanceTournament ext = 1038;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Command_TournamentSettingsSelect {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_TournamentSettingsSelect ext = 1039;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional TournamentSettings settings = 1;
|
||||||
|
}
|
||||||
|
|
@ -24,4 +24,10 @@ message Event_GameStateChanged {
|
||||||
|
|
||||||
// the amount of seconds since the game started
|
// the amount of seconds since the game started
|
||||||
optional uint32 seconds_elapsed = 5;
|
optional uint32 seconds_elapsed = 5;
|
||||||
|
|
||||||
|
// whether this game is a tournament game
|
||||||
|
optional bool is_tournament = 9;
|
||||||
|
|
||||||
|
// for tournament sub-games: the ID of the parent tournament game (-1 if not a sub-game)
|
||||||
|
optional sint32 parent_game_id = 10 [default = -1];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_event.proto";
|
||||||
|
|
||||||
|
message TournamentPlayer {
|
||||||
|
optional sint32 player_id = 1;
|
||||||
|
optional string player_name = 2;
|
||||||
|
optional uint32 wins = 3;
|
||||||
|
optional uint32 losses = 4;
|
||||||
|
optional uint32 draws = 5;
|
||||||
|
optional bool deck_submitted = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TournamentPairing {
|
||||||
|
optional sint32 player1_id = 1;
|
||||||
|
optional sint32 player2_id = 2 [default = -1];
|
||||||
|
optional sint32 game_id = 3;
|
||||||
|
optional sint32 winner_id = 4 [default = -1];
|
||||||
|
optional sint32 player1_match_wins = 5 [default = 0];
|
||||||
|
optional sint32 player2_match_wins = 6 [default = 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
message TournamentSettings {
|
||||||
|
optional uint32 games_per_match = 1 [default = 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message Event_TournamentState {
|
||||||
|
extend GameEvent {
|
||||||
|
optional Event_TournamentState ext = 2027;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TournamentPhase {
|
||||||
|
PHASE_DECK_BUILDING = 0;
|
||||||
|
PHASE_PLAYING = 1;
|
||||||
|
PHASE_FINISHED = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional TournamentPhase phase = 1;
|
||||||
|
optional uint32 current_round = 2;
|
||||||
|
optional uint32 total_rounds = 3;
|
||||||
|
repeated TournamentPlayer players = 4;
|
||||||
|
repeated TournamentPairing pairings = 5;
|
||||||
|
optional TournamentSettings settings = 6;
|
||||||
|
}
|
||||||
|
|
@ -180,6 +180,15 @@ message GameCommand {
|
||||||
/// Server: Server_Player::cmdSetPlaymat
|
/// Server: Server_Player::cmdSetPlaymat
|
||||||
/// Client: reflected via player properties changed event
|
/// Client: reflected via player properties changed event
|
||||||
SET_PLAYMAT = 1035;
|
SET_PLAYMAT = 1035;
|
||||||
|
|
||||||
|
/// Report the result of a tournament match sub-game.
|
||||||
|
REPORT_MATCH_RESULT = 1037;
|
||||||
|
|
||||||
|
/// Advance the tournament to the next round.
|
||||||
|
ADVANCE_TOURNAMENT = 1038;
|
||||||
|
|
||||||
|
/// Select tournament settings.
|
||||||
|
TOURNAMENT_SETTINGS_SELECT = 1039;
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ message GameEvent {
|
||||||
CHANGE_ZONE_PROPERTIES = 2020;
|
CHANGE_ZONE_PROPERTIES = 2020;
|
||||||
REVERSE_TURN = 2021;
|
REVERSE_TURN = 2021;
|
||||||
GAME_LOG_NOTICE = 2022;
|
GAME_LOG_NOTICE = 2022;
|
||||||
|
TOURNAMENT_STATE = 2027;
|
||||||
}
|
}
|
||||||
optional sint32 player_id = 1 [default = -1];
|
optional sint32 player_id = 1 [default = -1];
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,12 @@ message Command_CreateGame {
|
||||||
|
|
||||||
// share decklists with all players when selected
|
// share decklists with all players when selected
|
||||||
optional bool share_decklists_on_load = 14;
|
optional bool share_decklists_on_load = 14;
|
||||||
|
|
||||||
|
// number of games per match in tournament mode (e.g. 3 for best of 3)
|
||||||
|
optional uint32 games_per_match = 15 [default = 1];
|
||||||
|
|
||||||
|
// whether this is a tournament game
|
||||||
|
optional bool is_tournament = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Command_JoinGame {
|
message Command_JoinGame {
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,7 @@ message ServerInfo_Game {
|
||||||
|
|
||||||
// the current host of the game, which may differ from the creator after a host transfer
|
// the current host of the game, which may differ from the creator after a host transfer
|
||||||
optional ServerInfo_User host_info = 53;
|
optional ServerInfo_User host_info = 53;
|
||||||
|
|
||||||
|
// whether this game is a tournament game
|
||||||
|
optional bool is_tournament = 54;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue