From 09381575a799e2a9b7749fc22940c8cf472c9119 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 15 Aug 2025 23:31:05 +0200 Subject: [PATCH] Add option to share decklists on load. (#6029) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add option to share decklists on load. Took 1 hour 58 minutes Took 9 minutes Took 39 minutes * Lint. Took 14 minutes Took 2 minutes * Stuffs Took 39 minutes Took 4 seconds Took 43 minutes * Process local player first. Took 45 minutes * Consider if the setting is set on the game info first. Took 4 minutes * Save an indent level. Took 43 seconds * Don't commit logging config. Took 3 minutes * Remove a debug print. Took 10 seconds Took 7 seconds * Add another optional guard. Took 5 minutes * Hide the tab bar if only one (own deck) is visible. Took 9 minutes * Rename setting label for clarity Took 2 minutes * Capitalization. Took 3 minutes --------- Co-authored-by: Lukas BrĂ¼bach --- cockatrice/CMakeLists.txt | 1 + cockatrice/src/client/tabs/tab_game.cpp | 95 ++++++++++++++----- cockatrice/src/client/tabs/tab_game.h | 3 +- cockatrice/src/dialogs/dlg_create_game.cpp | 11 +++ cockatrice/src/dialogs/dlg_create_game.h | 3 +- cockatrice/src/dialogs/dlg_filter_games.cpp | 9 ++ cockatrice/src/dialogs/dlg_filter_games.h | 3 + .../deckview/tabbed_deck_view_container.cpp | 64 +++++++++++++ .../deckview/tabbed_deck_view_container.h | 23 +++++ cockatrice/src/game/game_selector.cpp | 1 + cockatrice/src/game/games_model.cpp | 16 +++- cockatrice/src/game/games_model.h | 6 ++ cockatrice/src/settings/cache_settings.cpp | 10 +- cockatrice/src/settings/cache_settings.h | 6 ++ .../src/settings/game_filters_settings.cpp | 11 +++ .../src/settings/game_filters_settings.h | 2 + common/pb/context_deck_select.proto | 1 + common/pb/room_commands.proto | 1 + common/pb/serverinfo_game.proto | 1 + common/server_game.cpp | 9 +- common/server_game.h | 8 +- common/server_player.cpp | 3 + common/server_player.h | 4 + common/server_protocolhandler.cpp | 4 +- dbconverter/src/mocks.cpp | 3 + tests/carddatabase/mocks.cpp | 3 + 26 files changed, 268 insertions(+), 33 deletions(-) create mode 100644 cockatrice/src/game/deckview/tabbed_deck_view_container.cpp create mode 100644 cockatrice/src/game/deckview/tabbed_deck_view_container.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 68fff8a90..477e8cced 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -199,6 +199,7 @@ set(cockatrice_SOURCES src/game/cards/exact_card.cpp src/game/deckview/deck_view.cpp src/game/deckview/deck_view_container.cpp + src/game/deckview/tabbed_deck_view_container.cpp src/game/filters/deck_filter_string.cpp src/game/filters/filter_builder.cpp src/game/filters/filter_card.cpp diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index dfa818d74..cce1cde06 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -7,6 +7,7 @@ #include "../../game/cards/card_database.h" #include "../../game/cards/card_database_manager.h" #include "../../game/deckview/deck_view_container.h" +#include "../../game/deckview/tabbed_deck_view_container.h" #include "../../game/game_scene.h" #include "../../game/game_view.h" #include "../../game/player/player.h" @@ -292,9 +293,9 @@ void TabGame::retranslateUi() QMapIterator i(players); while (i.hasNext()) i.next().value()->retranslateUi(); - QMapIterator j(deckViewContainers); + QMapIterator j(deckViewContainers); while (j.hasNext()) - j.next().value()->retranslateUi(); + j.next().value()->playerDeckView->retranslateUi(); scene->retranslateUi(); } @@ -564,7 +565,7 @@ void TabGame::actCompleterChanged() Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) { - bool local = ((clients.size() > 1) || (playerId == localPlayerId)); + bool local = clients.size() > 1 || playerId == localPlayerId; auto *newPlayer = new Player(info, playerId, local, judge, this); connect(newPlayer, SIGNAL(openDeckEditor(const DeckLoader *)), this, SIGNAL(openDeckEditor(const DeckLoader *))); QString newPlayerName = "@" + newPlayer->getName(); @@ -582,8 +583,8 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) if (clients.size() == 1) newPlayer->setShortcutsActive(); - auto *deckView = new DeckViewContainer(playerId, this); - connect(deckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded); + auto *deckView = new TabbedDeckViewContainer(playerId, this); + connect(deckView->playerDeckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded); deckViewContainers.insert(playerId, deckView); deckViewContainerLayout->addWidget(deckView); @@ -591,8 +592,8 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) QString deckPath = SettingsCache::instance().debug().getDeckPathForPlayer(newPlayer->getName()); if (!deckPath.isEmpty()) { QTimer::singleShot(0, this, [deckView, deckPath] { - deckView->loadDeckFromFile(deckPath); - deckView->readyAndUpdate(); + deckView->playerDeckView->loadDeckFromFile(deckPath); + deckView->playerDeckView->readyAndUpdate(); }); } } @@ -777,11 +778,11 @@ void TabGame::startGame(bool _resuming) { currentPhase = -1; - QMapIterator i(deckViewContainers); + QMapIterator i(deckViewContainers); while (i.hasNext()) { i.next(); - i.value()->setReadyStart(false); - i.value()->setVisualDeckStorageExists(false); + i.value()->playerDeckView->setReadyStart(false); + i.value()->playerDeckView->setVisualDeckStorageExists(false); i.value()->hide(); } @@ -803,7 +804,7 @@ void TabGame::stopGame() currentPhase = -1; activePlayer = -1; - QMapIterator i(deckViewContainers); + QMapIterator i(deckViewContainers); while (i.hasNext()) { i.next(); i.value()->show(); @@ -849,6 +850,44 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event, const GameEventContext & /*context*/) { const int playerListSize = event.player_list_size(); + + // Always process the local player first so we have an established deckViewcontainer + + for (int i = 0; i < playerListSize; ++i) { + const ServerInfo_Player &playerInfo = event.player_list(i); + const ServerInfo_PlayerProperties &prop = playerInfo.properties(); + const int playerId = prop.player_id(); + QString playerName = "@" + QString::fromStdString(prop.user_info().name()); + if (sayEdit && !autocompleteUserList.contains(playerName)) { + autocompleteUserList << playerName; + sayEdit->setCompletionList(autocompleteUserList); + } + if (!prop.spectator()) { + Player *player = players.value(playerId, 0); + if (!player) { + if (clients.size() > 1 || playerId == localPlayerId) { + player = addPlayer(playerId, prop.user_info()); + playerListWidget->addPlayer(prop); + player->processPlayerInfo(playerInfo); + if (player->getLocal()) { + TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); + if (playerInfo.has_deck_list()) { + DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list())); + PictureLoader::cacheCardPixmaps( + CardDatabaseManager::getInstance()->getCards(newDeck.getCardRefList())); + deckViewContainer->playerDeckView->setDeck(newDeck); + player->setDeck(newDeck); + } + deckViewContainer->playerDeckView->setReadyStart(prop.ready_start()); + deckViewContainer->playerDeckView->setSideboardLocked(prop.sideboard_locked()); + } + } + } + } + } + + // then process every non-local player. + for (int i = 0; i < playerListSize; ++i) { const ServerInfo_Player &playerInfo = event.player_list(i); const ServerInfo_PlayerProperties &prop = playerInfo.properties(); @@ -870,17 +909,16 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event, playerListWidget->addPlayer(prop); } player->processPlayerInfo(playerInfo); - if (player->getLocal()) { - DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); - if (playerInfo.has_deck_list()) { - DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list())); - PictureLoader::cacheCardPixmaps( - CardDatabaseManager::getInstance()->getCards(newDeck.getCardRefList())); - deckViewContainer->setDeck(newDeck); - player->setDeck(newDeck); - } - deckViewContainer->setReadyStart(prop.ready_start()); - deckViewContainer->setSideboardLocked(prop.sideboard_locked()); + if (player->getLocal() || !gameInfo.share_decklists_on_load()) { + continue; + } + + DeckList loader; + loader.loadFromString_Native(QString::fromStdString(playerInfo.deck_list())); + QMapIterator i(deckViewContainers); + while (i.hasNext()) { + i.next(); + i.value()->addOpponentDeckView(loader, playerId, player->getName()); } } } @@ -926,7 +964,7 @@ void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged & case GameEventContext::READY_START: { bool ready = prop.ready_start(); if (player->getLocal()) - deckViewContainers.value(player->getId())->setReadyStart(ready); + deckViewContainers.value(player->getId())->playerDeckView->setReadyStart(ready); if (ready) messageLog->logReadyStart(player); else @@ -957,11 +995,20 @@ void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged & Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext); messageLog->logDeckSelect(player, QString::fromStdString(deckSelect.deck_hash()), deckSelect.sideboard_size()); + if (gameInfo.share_decklists_on_load() && deckSelect.has_deck_list() && eventPlayerId != localPlayerId) { + DeckList loader; + loader.loadFromString_Native(QString::fromStdString(deckSelect.deck_list())); + QMapIterator i(deckViewContainers); + while (i.hasNext()) { + i.next(); + i.value()->addOpponentDeckView(loader, eventPlayerId, player->getName()); + } + } break; } case GameEventContext::SET_SIDEBOARD_LOCK: { if (player->getLocal()) - deckViewContainers.value(player->getId())->setSideboardLocked(prop.sideboard_locked()); + deckViewContainers.value(player->getId())->playerDeckView->setSideboardLocked(prop.sideboard_locked()); messageLog->logSetSideboardLock(player, prop.sideboard_locked()); break; } diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index b46aac0a1..e7f69485c 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -13,6 +13,7 @@ #include #include +class TabbedDeckViewContainer; inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game"); class UserListProxy; @@ -105,7 +106,7 @@ private: PhasesToolbar *phasesToolbar; GameScene *scene; GameView *gameView; - QMap deckViewContainers; + QMap deckViewContainers; QVBoxLayout *deckViewContainerLayout; QWidget *gamePlayAreaWidget, *deckViewContainerWidget; QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock; diff --git a/cockatrice/src/dialogs/dlg_create_game.cpp b/cockatrice/src/dialogs/dlg_create_game.cpp index e601b1b95..ea1c7a8f7 100644 --- a/cockatrice/src/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/dialogs/dlg_create_game.cpp @@ -102,9 +102,15 @@ void DlgCreateGame::sharedCtor() startingLifeTotalEdit->setValue(20); startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); + shareDecklistsOnLoadLabel = new QLabel(tr("Open decklists in lobby")); + shareDecklistsOnLoadCheckBox = new QCheckBox(); + shareDecklistsOnLoadLabel->setBuddy(shareDecklistsOnLoadCheckBox); + QGridLayout *gameSetupOptionsLayout = new QGridLayout; gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); + gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadLabel, 1, 0); + gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 1); gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); @@ -149,6 +155,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap &_gameType spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanSeeEverything()); createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator()); startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal()); + shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().getShareDecklistsOnLoad()); if (!rememberGameSettings->isChecked()) { actReset(); @@ -181,6 +188,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMapsetEnabled(false); createGameAsSpectatorCheckBox->setEnabled(false); startingLifeTotalEdit->setEnabled(false); + shareDecklistsOnLoadCheckBox->setEnabled(false); descriptionEdit->setText(QString::fromStdString(gameInfo.description())); maxPlayersEdit->setValue(gameInfo.max_players()); @@ -225,6 +233,7 @@ void DlgCreateGame::actReset() createGameAsSpectatorCheckBox->setChecked(false); startingLifeTotalEdit->setValue(20); + shareDecklistsOnLoadCheckBox->setChecked(false); QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); while (gameTypeCheckBoxIterator.hasNext()) { @@ -253,6 +262,7 @@ void DlgCreateGame::actOK() cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier); cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked()); cmd.set_starting_life_total(startingLifeTotalEdit->value()); + cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked()); QString _gameTypes = QString(); QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); @@ -276,6 +286,7 @@ void DlgCreateGame::actOK() SettingsCache::instance().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked()); SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked()); SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value()); + SettingsCache::instance().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked()); SettingsCache::instance().setGameTypes(_gameTypes); } PendingCommand *pend = room->prepareRoomCommand(cmd); diff --git a/cockatrice/src/dialogs/dlg_create_game.h b/cockatrice/src/dialogs/dlg_create_game.h index e14278aef..ae10bf01b 100644 --- a/cockatrice/src/dialogs/dlg_create_game.h +++ b/cockatrice/src/dialogs/dlg_create_game.h @@ -36,12 +36,13 @@ private: QMap gameTypeCheckBoxes; QGroupBox *generalGroupBox, *spectatorsGroupBox, *gameSetupOptionsGroupBox; - QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel, *startingLifeTotalLabel; + QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel, *startingLifeTotalLabel, *shareDecklistsOnLoadLabel; QLineEdit *descriptionEdit, *passwordEdit; QSpinBox *maxPlayersEdit, *startingLifeTotalEdit; QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, *spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox; + QCheckBox *shareDecklistsOnLoadCheckBox; QDialogButtonBox *buttonBox; QPushButton *clearButton; QCheckBox *rememberGameSettings; diff --git a/cockatrice/src/dialogs/dlg_filter_games.cpp b/cockatrice/src/dialogs/dlg_filter_games.cpp index f0fdaaa92..0c5191f99 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/dialogs/dlg_filter_games.cpp @@ -40,6 +40,9 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddy")); hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames()); + hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists")); + hideOpenDecklistGames->setChecked(gamesProxyModel->getHideOpenDecklistGames()); + maxGameAgeComboBox = new QComboBox(); maxGameAgeComboBox->setEditable(false); maxGameAgeComboBox->addItems(gameAgeMap.values()); @@ -115,6 +118,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, restrictionsLayout->addWidget(hideBuddiesOnlyGames, 3, 0); restrictionsLayout->addWidget(hideIgnoredUserGames, 4, 0); restrictionsLayout->addWidget(hideNotBuddyCreatedGames, 5, 0); + restrictionsLayout->addWidget(hideOpenDecklistGames, 6, 0); auto *restrictionsGroupBox = new QGroupBox(tr("Restrictions")); restrictionsGroupBox->setLayout(restrictionsLayout); @@ -218,6 +222,11 @@ bool DlgFilterGames::getHideNotBuddyCreatedGames() const return hideNotBuddyCreatedGames->isChecked(); } +bool DlgFilterGames::getHideOpenDecklistGames() const +{ + return hideOpenDecklistGames->isChecked(); +} + QString DlgFilterGames::getGameNameFilter() const { return gameNameFilterEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_filter_games.h b/cockatrice/src/dialogs/dlg_filter_games.h index 373af389f..1120a64ee 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.h +++ b/cockatrice/src/dialogs/dlg_filter_games.h @@ -27,6 +27,7 @@ private: QCheckBox *hidePasswordProtectedGames; QCheckBox *hideIgnoredUserGames; QCheckBox *hideNotBuddyCreatedGames; + QCheckBox *hideOpenDecklistGames; QLineEdit *gameNameFilterEdit; QLineEdit *creatorNameFilterEdit; QMap gameTypeFilterCheckBoxes; @@ -57,6 +58,8 @@ public: void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); bool getHideBuddiesOnlyGames() const; void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames); + bool getHideOpenDecklistGames() const; + void setHideOpenDecklistGames(bool _hideOpenDecklistGames); bool getHideIgnoredUserGames() const; void setHideIgnoredUserGames(bool _hideIgnoredUserGames); bool getHideNotBuddyCreatedGames() const; diff --git a/cockatrice/src/game/deckview/tabbed_deck_view_container.cpp b/cockatrice/src/game/deckview/tabbed_deck_view_container.cpp new file mode 100644 index 000000000..fc3c63acf --- /dev/null +++ b/cockatrice/src/game/deckview/tabbed_deck_view_container.cpp @@ -0,0 +1,64 @@ +#include "tabbed_deck_view_container.h" + +#include "../../client/tabs/tab_game.h" +#include "deck_view.h" + +TabbedDeckViewContainer::TabbedDeckViewContainer(int _playerId, TabGame *parent) + : QTabWidget(nullptr), playerId(_playerId), parentGame(parent) +{ + setTabsClosable(true); + connect(this, &QTabWidget::tabCloseRequested, this, &TabbedDeckViewContainer::closeTab); + + playerDeckView = new DeckViewContainer(playerId, parentGame); + int playerTabIndex = addTab(playerDeckView, "Your Deck"); + tabBar()->setTabButton(playerTabIndex, QTabBar::RightSide, nullptr); + updateTabBarVisibility(); +} + +void TabbedDeckViewContainer::addOpponentDeckView(const DeckList &opponentDeck, int opponentId, QString opponentName) +{ + if (opponentDeckViews.contains(opponentId)) { + opponentDeckViews[opponentId]->setDeck(opponentDeck); + } else { + auto *opponentDeckView = new DeckView(this); + opponentDeckView->setDeck(opponentDeck); + + addTab(opponentDeckView, QString("%1's Deck").arg(opponentName)); + + opponentDeckViews.insert(opponentId, opponentDeckView); + } + updateTabBarVisibility(); +} + +void TabbedDeckViewContainer::closeTab(int index) +{ + QWidget *widgetToClose = widget(index); + + // Prevent removing the player tab + if (widgetToClose == playerDeckView) { + return; + } + + // Remove it from map if it's an opponent + auto it = opponentDeckViews.begin(); + while (it != opponentDeckViews.end()) { + if (it.value() == widgetToClose) { + it = opponentDeckViews.erase(it); + } else { + ++it; + } + } + + removeTab(index); + widgetToClose->deleteLater(); + updateTabBarVisibility(); +} + +void TabbedDeckViewContainer::updateTabBarVisibility() +{ + if (tabBar()->count() <= 1) { + tabBar()->hide(); + } else { + tabBar()->show(); + } +} diff --git a/cockatrice/src/game/deckview/tabbed_deck_view_container.h b/cockatrice/src/game/deckview/tabbed_deck_view_container.h new file mode 100644 index 000000000..4b4769ece --- /dev/null +++ b/cockatrice/src/game/deckview/tabbed_deck_view_container.h @@ -0,0 +1,23 @@ +#ifndef TABBED_DECK_VIEW_CONTAINER_H +#define TABBED_DECK_VIEW_CONTAINER_H +#include "deck_view_container.h" + +#include + +class TabbedDeckViewContainer : public QTabWidget +{ + Q_OBJECT + +public: + explicit TabbedDeckViewContainer(int _playerId, TabGame *parent); + void closeTab(int index); + void updateTabBarVisibility(); + void addOpponentDeckView(const DeckList &opponentDeck, int opponentId, QString opponentName); + int playerId; + TabGame *parentGame; + DeckViewContainer *playerDeckView; + + QMap opponentDeckViews; +}; + +#endif // TABBED_DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/game/game_selector.cpp b/cockatrice/src/game/game_selector.cpp index 5c3e287e6..e03feae36 100644 --- a/cockatrice/src/game/game_selector.cpp +++ b/cockatrice/src/game/game_selector.cpp @@ -161,6 +161,7 @@ void GameSelector::actSetFilter() gameListProxyModel->setHidePasswordProtectedGames(dlg.getHidePasswordProtectedGames()); gameListProxyModel->setHideIgnoredUserGames(dlg.getHideIgnoredUserGames()); gameListProxyModel->setHideNotBuddyCreatedGames(dlg.getHideNotBuddyCreatedGames()); + gameListProxyModel->setHideOpenDecklistGames(dlg.getHideOpenDecklistGames()); gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter()); gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter()); gameListProxyModel->setGameTypeFilter(dlg.getGameTypeFilter()); diff --git a/cockatrice/src/game/games_model.cpp b/cockatrice/src/game/games_model.cpp index 03c72b584..fa908344c 100644 --- a/cockatrice/src/game/games_model.cpp +++ b/cockatrice/src/game/games_model.cpp @@ -153,6 +153,8 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const result.append(tr("buddies only")); if (gameentry.only_registered()) result.append(tr("reg. users only")); + if (gameentry.share_decklists_on_load()) + result.append(tr("open decklists")); return result.join(", "); } case Qt::DecorationRole: { @@ -320,6 +322,12 @@ void GamesProxyModel::setHideNotBuddyCreatedGames(bool value) invalidateFilter(); } +void GamesProxyModel::setHideOpenDecklistGames(bool _hideOpenDecklistGames) +{ + hideOpenDecklistGames = _hideOpenDecklistGames; + invalidateFilter(); +} + void GamesProxyModel::setGameNameFilter(const QString &_gameNameFilter) { gameNameFilter = _gameNameFilter; @@ -398,6 +406,7 @@ void GamesProxyModel::resetFilterParameters() hideBuddiesOnlyGames = false; hideIgnoredUserGames = false; hideNotBuddyCreatedGames = false; + hideOpenDecklistGames = false; gameNameFilter = QString(); creatorNameFilter = QString(); gameTypeFilter.clear(); @@ -415,7 +424,7 @@ void GamesProxyModel::resetFilterParameters() bool GamesProxyModel::areFilterParametersSetToDefaults() const { return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && - !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() && + !hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() && creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && @@ -431,6 +440,7 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType hideIgnoredUserGames = gameFilters.isHideIgnoredUserGames(); hideBuddiesOnlyGames = gameFilters.isHideBuddiesOnlyGames(); hideNotBuddyCreatedGames = gameFilters.isHideNotBuddyCreatedGames(); + hideOpenDecklistGames = gameFilters.isHideOpenDecklistGames(); gameNameFilter = gameFilters.getGameNameFilter(); creatorNameFilter = gameFilters.getCreatorNameFilter(); maxPlayersFilterMin = gameFilters.getMinPlayers(); @@ -461,6 +471,7 @@ void GamesProxyModel::saveFilterParameters(const QMap &allGameType gameFilters.setHidePasswordProtectedGames(hidePasswordProtectedGames); gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames); gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames); + gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames); gameFilters.setGameNameFilter(gameNameFilter); gameFilters.setCreatorNameFilter(creatorNameFilter); @@ -504,6 +515,9 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const if (hideBuddiesOnlyGames && game.only_buddies()) { return false; } + if (hideOpenDecklistGames && game.share_decklists_on_load()) { + return false; + } if (hideIgnoredUserGames && userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) { return false; } diff --git a/cockatrice/src/game/games_model.h b/cockatrice/src/game/games_model.h index f2a5d24d6..4b82a9056 100644 --- a/cockatrice/src/game/games_model.h +++ b/cockatrice/src/game/games_model.h @@ -81,6 +81,7 @@ private: bool hideGamesThatStarted; bool hidePasswordProtectedGames; bool hideNotBuddyCreatedGames; + bool hideOpenDecklistGames; QString gameNameFilter, creatorNameFilter; QSet gameTypeFilter; quint32 maxPlayersFilterMin, maxPlayersFilterMax; @@ -121,6 +122,11 @@ public: return hideNotBuddyCreatedGames; } void setHideNotBuddyCreatedGames(bool value); + bool getHideOpenDecklistGames() const + { + return hideOpenDecklistGames; + } + void setHideOpenDecklistGames(bool _hideOpenDecklistGames); QString getGameNameFilter() const { return gameNameFilter; diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 2ad735128..6bfe04e2f 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -355,6 +355,7 @@ SettingsCache::SettingsCache() spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool(); createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool(); defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt(); + shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString(); clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); @@ -1360,7 +1361,13 @@ void SettingsCache::setDefaultStartingLifeTotal(const int _defaultStartingLifeTo { defaultStartingLifeTotal = _defaultStartingLifeTotal; settings->setValue("game/defaultstartinglifetotal", defaultStartingLifeTotal); -}; +} + +void SettingsCache::setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad) +{ + shareDecklistsOnLoad = _shareDecklistsOnLoad; + settings->setValue("game/sharedecklistsonload", shareDecklistsOnLoad); +} void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value) { @@ -1373,6 +1380,7 @@ void SettingsCache::setStartupCardUpdateCheckPromptForUpdate(bool value) startupCardUpdateCheckPromptForUpdate = value; settings->setValue("personal/startupCardUpdateCheckPromptForUpdate", startupCardUpdateCheckPromptForUpdate); } + void SettingsCache::setStartupCardUpdateCheckAlwaysUpdate(bool value) { startupCardUpdateCheckAlwaysUpdate = value; diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 16822d755..ec8b506c5 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -302,6 +302,7 @@ private: bool spectatorsCanSeeEverything; bool createGameAsSpectator; int defaultStartingLifeTotal; + bool shareDecklistsOnLoad; int keepalive; int timeout; void translateLegacySettings(); @@ -805,6 +806,10 @@ public: { return defaultStartingLifeTotal; } + bool getShareDecklistsOnLoad() const + { + return shareDecklistsOnLoad; + } bool getCreateGameAsSpectator() const { return createGameAsSpectator; @@ -1032,6 +1037,7 @@ public slots: void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setCreateGameAsSpectator(const bool _createGameAsSpectator); void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal); + void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad); void setRememberGameSettings(const bool _rememberGameSettings); void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value); void setStartupCardUpdateCheckPromptForUpdate(bool value); diff --git a/cockatrice/src/settings/game_filters_settings.cpp b/cockatrice/src/settings/game_filters_settings.cpp index 041a9a74a..e82b18a47 100644 --- a/cockatrice/src/settings/game_filters_settings.cpp +++ b/cockatrice/src/settings/game_filters_settings.cpp @@ -83,6 +83,17 @@ bool GameFiltersSettings::isHideNotBuddyCreatedGames() return !(previous == QVariant()) && previous.toBool(); } +void GameFiltersSettings::setHideOpenDecklistGames(bool hide) +{ + setValue(hide, "hide_open_decklist_games", "filter_games"); +} + +bool GameFiltersSettings::isHideOpenDecklistGames() +{ + QVariant previous = getValue("hide_open_decklist_games", "filter_games"); + return !(previous == QVariant()) && previous.toBool(); +} + void GameFiltersSettings::setGameNameFilter(QString gameName) { setValue(gameName, "game_name_filter", "filter_games"); diff --git a/cockatrice/src/settings/game_filters_settings.h b/cockatrice/src/settings/game_filters_settings.h index 8ab7d8e5e..a243c74e9 100644 --- a/cockatrice/src/settings/game_filters_settings.h +++ b/cockatrice/src/settings/game_filters_settings.h @@ -15,6 +15,8 @@ public: bool isHidePasswordProtectedGames(); bool isHideIgnoredUserGames(); bool isHideNotBuddyCreatedGames(); + void setHideOpenDecklistGames(bool hide); + bool isHideOpenDecklistGames(); QString getGameNameFilter(); QString getCreatorNameFilter(); int getMinPlayers(); diff --git a/common/pb/context_deck_select.proto b/common/pb/context_deck_select.proto index dbd4ce16e..44abd8583 100644 --- a/common/pb/context_deck_select.proto +++ b/common/pb/context_deck_select.proto @@ -7,4 +7,5 @@ message Context_DeckSelect { } optional string deck_hash = 1; optional int32 sideboard_size = 2 [default = -1]; + optional string deck_list = 3; } diff --git a/common/pb/room_commands.proto b/common/pb/room_commands.proto index fce90e32e..5e8c158ff 100644 --- a/common/pb/room_commands.proto +++ b/common/pb/room_commands.proto @@ -39,6 +39,7 @@ message Command_CreateGame { optional bool join_as_judge = 11; optional bool join_as_spectator = 12; optional uint32 starting_life_total = 13; + optional bool share_decklists_on_load = 14; } message Command_JoinGame { diff --git a/common/pb/serverinfo_game.proto b/common/pb/serverinfo_game.proto index 8ba29eeeb..68739abff 100644 --- a/common/pb/serverinfo_game.proto +++ b/common/pb/serverinfo_game.proto @@ -16,6 +16,7 @@ message ServerInfo_Game { optional bool spectators_need_password = 12; optional bool spectators_can_chat = 13; optional bool spectators_omniscient = 14; + optional bool share_decklists_on_load = 15; optional uint32 player_count = 30; optional uint32 spectators_count = 31; optional bool started = 50; diff --git a/common/server_game.cpp b/common/server_game.cpp index f27772bbb..95d97f1da 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -21,6 +21,7 @@ #include "decklist.h" #include "pb/context_connection_state_changed.pb.h" +#include "pb/context_deck_select.pb.h" #include "pb/context_ping_changed.pb.h" #include "pb/event_delete_arrow.pb.h" #include "pb/event_game_closed.pb.h" @@ -62,15 +63,16 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, int _startingLifeTotal, + bool _shareDecklistsOnLoad, Server_Room *_room) : QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)), gameStarted(false), gameClosed(false), gameId(_gameId), password(_password), maxPlayers(_maxPlayers), gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies), onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed), spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk), - spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal), inactivityCounter(0), - startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), turnOrderReversed(false), - startTime(QDateTime::currentDateTime()), pingClock(nullptr), + spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal), + shareDecklistsOnLoad(_shareDecklistsOnLoad), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), + firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) gameMutex() #else @@ -800,6 +802,7 @@ void Server_Game::getInfo(ServerInfo_Game &result) const result.set_spectators_need_password(getSpectatorsNeedPassword()); result.set_spectators_can_chat(spectatorsCanTalk); result.set_spectators_omniscient(spectatorsSeeEverything); + result.set_share_decklists_on_load(shareDecklistsOnLoad); result.set_spectators_count(getSpectatorCount()); result.set_start_time(startTime.toSecsSinceEpoch()); } diff --git a/common/server_game.h b/common/server_game.h index 2f8c005b6..226a14149 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -67,6 +67,7 @@ private: bool spectatorsCanTalk; bool spectatorsSeeEverything; int startingLifeTotal; + bool shareDecklistsOnLoad; int inactivityCounter; int startTimeOfThisGame, secondsElapsed; bool firstGameStarted; @@ -106,7 +107,8 @@ public: bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, - int startingLifeTotal, + int _startingLifeTotal, + bool _shareDecklistsOnLoad, Server_Room *parent); ~Server_Game() override; Server_Room *getRoom() const @@ -168,6 +170,10 @@ public: { return startingLifeTotal; } + bool getShareDecklistsOnLoad() const + { + return shareDecklistsOnLoad; + } Response::ResponseCode checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge); bool containsUser(const QString &userName) const; diff --git a/common/server_player.cpp b/common/server_player.cpp index 5f0667dee..67c5ef34c 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -836,6 +836,9 @@ Server_Player::cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &r Context_DeckSelect context; context.set_deck_hash(deck->getDeckHash().toStdString()); context.set_sideboard_size(deck->getSideboardSize()); + if (game->getShareDecklistsOnLoad()) { + context.set_deck_list(deck->writeToString_Native().toStdString()); + } ges.setGameEventContext(context); auto *re = new Response_DeckDownload; diff --git a/common/server_player.h b/common/server_player.h index b9d414482..45736957d 100644 --- a/common/server_player.h +++ b/common/server_player.h @@ -96,6 +96,10 @@ public: Server_AbstractUserInterface *_handler); ~Server_Player() override; void prepareDestroy(); + const DeckList *getDeckList() const + { + return deck; + } Server_AbstractUserInterface *getUserInterface() const { return userInterface; diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 075b3901e..d69ff9ba0 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -818,6 +818,8 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room QString description = nameFromStdString(cmd.description()); 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; + const int gameId = databaseInterface->getNextGameId(); if (gameId == -1) { return Response::RespInternalError; @@ -828,7 +830,7 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room Server_Game *game = new Server_Game( copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes, cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(), - cmd.spectators_can_talk(), cmd.spectators_see_everything(), startingLifeTotal, room); + cmd.spectators_can_talk(), cmd.spectators_see_everything(), startingLifeTotal, shareDecklistsOnLoad, room); game->addPlayer(this, rc, asSpectator, asJudge, false); room->addGame(game); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 738a17695..ade0922e4 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -402,6 +402,9 @@ void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectato void SettingsCache::setDefaultStartingLifeTotal(const int /* _startingLifeTotal */) { } +void SettingsCache::setShareDecklistsOnLoad(const bool /* _shareDecklistsOnLoad */) +{ +} void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 9d158217e..4c3a81322 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -406,6 +406,9 @@ void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectato void SettingsCache::setDefaultStartingLifeTotal(const int /* _startingLifeTotal */) { } +void SettingsCache::setShareDecklistsOnLoad(const bool /* _shareDecklistsOnLoad */) +{ +} void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */) { }