diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index e1bb6606f..ced960442 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -204,12 +204,12 @@ jobs: --ccache "$CCACHE_SIZE" $NO_CLIENT .ci/name_build.sh - - name: Delete stale ccache - if: github.ref == 'refs/heads/master' + # Delete used cache to emulate a cache update. See https://github.com/actions/cache/issues/342. + - name: Delete old compiler cache (ccache) + if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' env: - GH_TOKEN: ${{github.token}} - run: | - gh cache delete "${{ steps.ccache_restore.outputs.cache-primary-key }}" || true + GH_TOKEN: ${{ github.token }} + run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' @@ -446,13 +446,12 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg - # github does not overwrite GHA cache entries when the key is the same, so we need to delete the stale ones before saving - - name: Delete stale ccache - if: github.ref == 'refs/heads/master' + # Delete used cache to emulate a cache update. See https://github.com/actions/cache/issues/342. + - name: Delete old compiler cache (ccache) + if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' env: GH_TOKEN: ${{ github.token }} - run: | - gh cache delete "${{ steps.ccache_restore.outputs.cache-primary-key }}" || true + run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' && steps.build.outcome == 'success' diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 4533448f5..8da6fc314 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -39,6 +39,7 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp src/interface/widgets/dialogs/dlg_load_remote_deck.cpp + src/interface/widgets/dialogs/dlg_local_game_options.cpp src/interface/widgets/dialogs/dlg_manage_sets.cpp src/interface/widgets/dialogs/dlg_register.cpp src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index e7a5d114e..1d8121b19 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -385,6 +385,13 @@ SettingsCache::SettingsCache() defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt(); shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); + + // Local game settings use "localgameoptions/" prefix to keep them separate + // from server game settings which use "game/" prefix + localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool(); + localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt(); + localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt(); + clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString(); clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); } @@ -1242,6 +1249,24 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) settings->setValue("game/remembergamesettings", rememberGameSettings); } +void SettingsCache::setLocalGameRememberSettings(bool value) +{ + localGameRememberSettings = value; + settings->setValue("localgameoptions/remembersettings", value); +} + +void SettingsCache::setLocalGameMaxPlayers(int value) +{ + localGameMaxPlayers = value; + settings->setValue("localgameoptions/maxplayers", value); +} + +void SettingsCache::setLocalGameStartingLifeTotal(int value) +{ + localGameStartingLifeTotal = value; + settings->setValue("localgameoptions/startinglifetotal", value); +} + void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate) { notifyAboutUpdates = static_cast(_notifyaboutupdate); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 5c5054105..2bbf85352 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -330,6 +330,12 @@ private: [[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const; void loadPaths(); bool rememberGameSettings; + + // Local game settings (separate from server game settings in game/*) + bool localGameRememberSettings; + int localGameMaxPlayers; + int localGameStartingLifeTotal; + QList releaseChannels; bool isPortableBuild; bool roundCardCorners; @@ -862,6 +868,18 @@ public: { return rememberGameSettings; } + [[nodiscard]] bool getLocalGameRememberSettings() const + { + return localGameRememberSettings; + } + [[nodiscard]] int getLocalGameMaxPlayers() const + { + return localGameMaxPlayers; + } + [[nodiscard]] int getLocalGameStartingLifeTotal() const + { + return localGameStartingLifeTotal; + } [[nodiscard]] int getKeepAlive() const override { return keepalive; @@ -1089,6 +1107,9 @@ public slots: void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal); void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad); void setRememberGameSettings(const bool _rememberGameSettings); + void setLocalGameRememberSettings(bool value); + void setLocalGameMaxPlayers(int value); + void setLocalGameStartingLifeTotal(int value); void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value); void setStartupCardUpdateCheckPromptForUpdate(bool value); void setStartupCardUpdateCheckAlwaysUpdate(bool value); diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp index ef95f052e..cd77c2968 100644 --- a/cockatrice/src/game/player/menu/card_menu.cpp +++ b/cockatrice/src/game/player/menu/card_menu.cpp @@ -113,32 +113,20 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive addAction(aSelectAll); addAction(aSelectColumn); addRelatedCardView(); - } else if (writeableCard) { - + } else { if (card->getZone()) { if (card->getZone()->getName() == ZoneNames::TABLE) { - createTableMenu(); + createTableMenu(writeableCard); } else if (card->getZone()->getName() == ZoneNames::STACK) { - createStackMenu(); + createStackMenu(writeableCard); } else if (card->getZone()->getName() == ZoneNames::EXILE || card->getZone()->getName() == ZoneNames::GRAVE) { - createGraveyardOrExileMenu(); + createGraveyardOrExileMenu(writeableCard); } else { - createHandOrCustomZoneMenu(); + createHandOrCustomZoneMenu(writeableCard); } } else { - addMenu(new MoveMenu(player)); - } - } else { - if (card->getZone() && card->getZone()->getName() != ZoneNames::HAND) { - addAction(aDrawArrow); - addSeparator(); - addRelatedCardView(); - addRelatedCardActions(); - addSeparator(); - addAction(aClone); - addSeparator(); - addAction(aSelectAll); + createZonelessMenu(writeableCard); } } } @@ -154,11 +142,9 @@ void CardMenu::removePlayer(Player *playerToRemove) } } -void CardMenu::createTableMenu() +void CardMenu::createTableMenu(bool canModifyCard) { // Card is on the battlefield - bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player; - if (!canModifyCard) { addRelatedCardView(); addRelatedCardActions(); @@ -213,10 +199,8 @@ void CardMenu::createTableMenu() addMenu(mCardCounters); } -void CardMenu::createStackMenu() +void CardMenu::createStackMenu(bool canModifyCard) { - bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player; - // Card is on the stack if (canModifyCard) { addAction(aAttach); @@ -238,10 +222,8 @@ void CardMenu::createStackMenu() addRelatedCardActions(); } -void CardMenu::createGraveyardOrExileMenu() +void CardMenu::createGraveyardOrExileMenu(bool canModifyCard) { - bool canModifyCard = player->getPlayerInfo()->judge || card->getOwner() == player; - // Card is in the graveyard or exile if (canModifyCard) { addAction(aPlay); @@ -270,8 +252,20 @@ void CardMenu::createGraveyardOrExileMenu() addRelatedCardActions(); } -void CardMenu::createHandOrCustomZoneMenu() +void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard) { + if (!canModifyCard) { + addAction(aDrawArrow); + addSeparator(); + addRelatedCardView(); + addRelatedCardActions(); + addSeparator(); + addAction(aClone); + addSeparator(); + addAction(aSelectAll); + return; + } + // Card is in hand or a custom zone specified by server addAction(aPlay); addAction(aPlayFacedown); @@ -305,6 +299,13 @@ void CardMenu::createHandOrCustomZoneMenu() } } +void CardMenu::createZonelessMenu(bool canModifyCard) +{ + if (canModifyCard) { + addMenu(new MoveMenu(player)); + } +} + /** * @brief Populates the menu with an action for each active player. * diff --git a/cockatrice/src/game/player/menu/card_menu.h b/cockatrice/src/game/player/menu/card_menu.h index afe24da62..b7f2f8241 100644 --- a/cockatrice/src/game/player/menu/card_menu.h +++ b/cockatrice/src/game/player/menu/card_menu.h @@ -18,10 +18,11 @@ class CardMenu : public QMenu public: explicit CardMenu(Player *player, const CardItem *card, bool shortcutsActive); void removePlayer(Player *playerToRemove); - void createTableMenu(); - void createStackMenu(); - void createGraveyardOrExileMenu(); - void createHandOrCustomZoneMenu(); + void createTableMenu(bool canModifyCard); + void createStackMenu(bool canModifyCard); + void createGraveyardOrExileMenu(bool canModifyCard); + void createHandOrCustomZoneMenu(bool canModifyCard); + void createZonelessMenu(bool canModifyCard); QMenu *mCardCounters; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp new file mode 100644 index 000000000..52466ff10 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp @@ -0,0 +1,83 @@ +#include "dlg_local_game_options.h" + +#include "../../../client/settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include +#include + +DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent) +{ + numberPlayersLabel = new QLabel(tr("Players:"), this); + numberPlayersEdit = new QSpinBox(this); + numberPlayersEdit->setMinimum(1); + numberPlayersEdit->setMaximum(8); + numberPlayersEdit->setValue(1); + numberPlayersLabel->setBuddy(numberPlayersEdit); + + auto *generalGrid = new QGridLayout; + generalGrid->addWidget(numberPlayersLabel, 0, 0); + generalGrid->addWidget(numberPlayersEdit, 0, 1); + generalGroupBox = new QGroupBox(tr("General"), this); + generalGroupBox->setLayout(generalGrid); + + startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this); + startingLifeTotalEdit = new QSpinBox(this); + startingLifeTotalEdit->setMinimum(1); + startingLifeTotalEdit->setMaximum(99999); + startingLifeTotalEdit->setValue(20); + startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); + + auto *gameSetupGrid = new QGridLayout; + gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0); + gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1); + gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this); + gameSetupOptionsGroupBox->setLayout(gameSetupGrid); + + rememberSettingsCheckBox = new QCheckBox(tr("Remember settings"), this); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLocalGameOptions::actOK); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLocalGameOptions::reject); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(generalGroupBox); + mainLayout->addWidget(gameSetupOptionsGroupBox); + mainLayout->addWidget(rememberSettingsCheckBox); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + rememberSettingsCheckBox->setChecked(SettingsCache::instance().getLocalGameRememberSettings()); + if (rememberSettingsCheckBox->isChecked()) { + numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers()); + startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal()); + } + + setWindowTitle(tr("Local game options")); + setFixedHeight(sizeHint().height()); + + numberPlayersEdit->setFocus(); +} + +void DlgLocalGameOptions::actOK() +{ + SettingsCache::instance().setLocalGameRememberSettings(rememberSettingsCheckBox->isChecked()); + if (rememberSettingsCheckBox->isChecked()) { + SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value()); + SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value()); + } + + accept(); +} + +LocalGameOptions DlgLocalGameOptions::getOptions() const +{ + return LocalGameOptions{ + .numberPlayers = numberPlayersEdit->value(), + .startingLifeTotal = startingLifeTotalEdit->value(), + }; +} diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h new file mode 100644 index 000000000..4307581a4 --- /dev/null +++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h @@ -0,0 +1,53 @@ +/** + * @file dlg_local_game_options.h + * @ingroup RoomDialogs + * @brief Dialog for configuring local game options. + * + * Provides a user interface for setting up local games with configurable + * number of players and starting life total. + */ + +#ifndef DLG_LOCAL_GAME_OPTIONS_H +#define DLG_LOCAL_GAME_OPTIONS_H + +#include + +struct LocalGameOptions +{ + int numberPlayers = 1; + int startingLifeTotal = 20; +}; + +class QCheckBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; +class QSpinBox; + +class DlgLocalGameOptions : public QDialog +{ + Q_OBJECT +public: + explicit DlgLocalGameOptions(QWidget *parent = nullptr); + + [[nodiscard]] LocalGameOptions getOptions() const; + +private slots: + void actOK(); + +private: + QGroupBox *generalGroupBox; + QGroupBox *gameSetupOptionsGroupBox; + + QLabel *numberPlayersLabel; + QSpinBox *numberPlayersEdit; + + QLabel *startingLifeTotalLabel; + QSpinBox *startingLifeTotalEdit; + + QCheckBox *rememberSettingsCheckBox; + + QDialogButtonBox *buttonBox; +}; + +#endif // DLG_LOCAL_GAME_OPTIONS_H diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 3724ff29d..fbba6c3f5 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -27,6 +27,7 @@ #include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h" #include "../interface/widgets/dialogs/dlg_forgot_password_request.h" #include "../interface/widgets/dialogs/dlg_forgot_password_reset.h" +#include "../interface/widgets/dialogs/dlg_local_game_options.h" #include "../interface/widgets/dialogs/dlg_manage_sets.h" #include "../interface/widgets/dialogs/dlg_register.h" #include "../interface/widgets/dialogs/dlg_settings.h" @@ -49,7 +50,6 @@ #include #include #include -#include #include #include #include @@ -225,16 +225,15 @@ void MainWindow::actDisconnect() void MainWindow::actSinglePlayer() { - bool ok; - int numberPlayers = - QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 1, 1, 8, 1, &ok); - if (!ok) + DlgLocalGameOptions dlg(this); + if (dlg.exec() != QDialog::Accepted) { return; + } - startLocalGame(numberPlayers); + startLocalGame(dlg.getOptions()); } -void MainWindow::startLocalGame(int numberPlayers) +void MainWindow::startLocalGame(const LocalGameOptions &options) { aConnect->setEnabled(false); aRegister->setEnabled(false); @@ -248,7 +247,7 @@ void MainWindow::startLocalGame(int numberPlayers) QList localClients; localClients.append(mainClient); - for (int i = 0; i < numberPlayers - 1; ++i) { + for (int i = 0; i < options.numberPlayers - 1; ++i) { LocalServerInterface *slaveLsi = localServer->newConnection(); LocalClient *slaveClient = new LocalClient(slaveLsi, tr("Player %1").arg(i + 2), SettingsCache::instance().getClientID(), this); @@ -257,7 +256,8 @@ void MainWindow::startLocalGame(int numberPlayers) tabSupervisor->startLocal(localClients); Command_CreateGame createCommand; - createCommand.set_max_players(static_cast(numberPlayers)); + createCommand.set_max_players(static_cast(options.numberPlayers)); + createCommand.set_starting_life_total(options.startingLifeTotal); mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0)); } @@ -913,7 +913,9 @@ MainWindow::MainWindow(QWidget *parent) void MainWindow::startupConfigCheck() { if (SettingsCache::instance().debug().getLocalGameOnStartup()) { - startLocalGame(SettingsCache::instance().debug().getLocalGamePlayerCount()); + LocalGameOptions options; + options.numberPlayers = SettingsCache::instance().debug().getLocalGamePlayerCount(); + startLocalGame(options); } if (SettingsCache::instance().getCheckUpdatesOnStartup()) { diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 75b0f5062..ed6de5b0d 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -25,6 +25,8 @@ #ifndef WINDOW_H #define WINDOW_H +#include "widgets/dialogs/dlg_local_game_options.h" + #include #include #include @@ -137,7 +139,7 @@ private: void createCardUpdateProcess(bool background = false); void exitCardDatabaseUpdate(); - void startLocalGame(int numberPlayers); + void startLocalGame(const LocalGameOptions &options); QList tabMenus; QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu; diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 3f10a0ac6..d7e9d529d 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -1,5 +1,5 @@ @page user_reference User Reference - + ## Deck Management - @subpage creating_decks diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index 1765bbbca..bea5af275 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -173,7 +173,7 @@ e pedine che verranno usate da Cockatrice. spoiler - + spoiler @@ -193,7 +193,7 @@ e pedine che verranno usate da Cockatrice. Local file: - + File nel pc: @@ -203,7 +203,7 @@ e pedine che verranno usate da Cockatrice. Choose file... - + Scegli file... @@ -231,7 +231,7 @@ e pedine che verranno usate da Cockatrice. tokens - + pedine @@ -251,7 +251,7 @@ e pedine che verranno usate da Cockatrice. Local file: - + File nel pc: @@ -261,7 +261,7 @@ e pedine che verranno usate da Cockatrice. Choose file... - + Scegli file... @@ -392,12 +392,12 @@ e pedine che verranno usate da Cockatrice. Load %1 file - + Carica %1 file %1 file (%1) - + %1 file (%1) @@ -421,12 +421,12 @@ e pedine che verranno usate da Cockatrice. Please choose a file. - + Seleziona un file. Cannot open file '%1'. - + Impossibile aprire il file '%1'.