From aa4592dc9e26c2ea0d08ae68ab7d3b09b77797a8 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 12 Mar 2026 17:30:01 -0400 Subject: [PATCH 1/4] Add local game options (#6669) * Add local game options dialog. Introduces LocalGameOptions struct and DlgLocalGameOptions dialog to replace the previous QInputDialog for starting local games. Encapsulates game configuration with a simple interface that prevents parameter explosion as options are added. The dialog provides UI with settings persistence via SettingsCache * integrate local game options into main window. Replaces QInputDialog with DlgLocalGameOptions in actSinglePlayer(). The startLocalGame() function now accepts LocalGameOptions, enabling configuration of starting life total and spectator visibility in addition to player count. Also adds user documentation for the local game options flow. * Removed superfluous documentation file * removed spectator option and moved structure definition * Now remember settings separately and & shortcuts removed * re-run checks --- cockatrice/CMakeLists.txt | 1 + .../src/client/settings/cache_settings.cpp | 25 ++++++ .../src/client/settings/cache_settings.h | 21 +++++ .../dialogs/dlg_local_game_options.cpp | 83 +++++++++++++++++++ .../widgets/dialogs/dlg_local_game_options.h | 53 ++++++++++++ cockatrice/src/interface/window_main.cpp | 22 ++--- cockatrice/src/interface/window_main.h | 4 +- .../extra-pages/user_documentation/index.md | 2 +- 8 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index d675b809d..1ca3c77c2 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/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 From 2b2a6db0810937ffaf935170d1001cbbe3305cac Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:22:48 +0100 Subject: [PATCH 2/4] Translate oracle/oracle_en@source.ts in it (#6692) 100% translated source file: 'oracle/oracle_en@source.ts' on 'it'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- oracle/translations/oracle_it.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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'. From 33d5721490f04c2697c8153a560c0300edf6ea06 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 14 Mar 2026 03:41:38 -0700 Subject: [PATCH 3/4] [Game] Fix not using zone-specific card menu for opponent's cards (#6695) --- cockatrice/src/game/player/menu/card_menu.cpp | 57 ++++++++++--------- cockatrice/src/game/player/menu/card_menu.h | 9 +-- 2 files changed, 34 insertions(+), 32 deletions(-) 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; From 8180d2e3b07d45f8c9d65d9c9602bc26d9fe9396 Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 14 Mar 2026 15:12:10 +0100 Subject: [PATCH 4/4] CI: Update compiler cache on hit (#6691) * update ccache * Update desktop-build.yml * Update desktop-build.yml --- .github/workflows/desktop-build.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 233d9e488..f99c91abb 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -202,6 +202,13 @@ jobs: --ccache "$CCACHE_SIZE" $NO_CLIENT .ci/name_build.sh + # 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 --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }} + - name: Save compiler cache (ccache) if: github.ref == 'refs/heads/master' uses: actions/cache/save@v5 @@ -438,6 +445,13 @@ jobs: TARGET_MACOS_VERSION: ${{ matrix.override_target }} run: .ci/compile.sh --server --test --vcpkg + # 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' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit && steps.build.outcome == 'success' + env: + 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' && matrix.use_ccache == 1 uses: actions/cache/save@v5