Merge branch 'master' into ci/use-ninja-win

This commit is contained in:
Bruno Alexandre Rosa 2026-03-14 11:18:01 -03:00
commit bde03fa1be
12 changed files with 252 additions and 64 deletions

View File

@ -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'

View File

@ -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

View File

@ -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<bool>(_notifyaboutupdate);

View File

@ -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<ReleaseChannel *> 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);

View File

@ -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.
*

View File

@ -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;

View File

@ -0,0 +1,83 @@
#include "dlg_local_game_options.h"
#include "../../../client/settings/cache_settings.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QSpinBox>
#include <QVBoxLayout>
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(),
};
}

View File

@ -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 <QDialog>
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

View File

@ -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 <QDesktopServices>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
@ -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<AbstractClient *> 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<google::protobuf::uint32>(numberPlayers));
createCommand.set_max_players(static_cast<google::protobuf::uint32>(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()) {

View File

@ -25,6 +25,8 @@
#ifndef WINDOW_H
#define WINDOW_H
#include "widgets/dialogs/dlg_local_game_options.h"
#include <QList>
#include <QMainWindow>
#include <QMessageBox>
@ -137,7 +139,7 @@ private:
void createCardUpdateProcess(bool background = false);
void exitCardDatabaseUpdate();
void startLocalGame(int numberPlayers);
void startLocalGame(const LocalGameOptions &options);
QList<QMenu *> tabMenus;
QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu;

View File

@ -1,5 +1,5 @@
@page user_reference User Reference
## Deck Management
- @subpage creating_decks

View File

@ -173,7 +173,7 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pages.cpp" line="726"/>
<source>spoiler</source>
<translation type="unfinished"/>
<translation>spoiler</translation>
</message>
<message>
<location filename="src/pages.cpp" line="731"/>
@ -193,7 +193,7 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pages.cpp" line="735"/>
<source>Local file:</source>
<translation type="unfinished"/>
<translation>File nel pc:</translation>
</message>
<message>
<location filename="src/pages.cpp" line="736"/>
@ -203,7 +203,7 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pages.cpp" line="737"/>
<source>Choose file...</source>
<translation type="unfinished"/>
<translation>Scegli file...</translation>
</message>
<message>
<location filename="src/pages.cpp" line="739"/>
@ -231,7 +231,7 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pages.cpp" line="681"/>
<source>tokens</source>
<translation type="unfinished"/>
<translation>pedine</translation>
</message>
<message>
<location filename="src/pages.cpp" line="686"/>
@ -251,7 +251,7 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pages.cpp" line="690"/>
<source>Local file:</source>
<translation type="unfinished"/>
<translation>File nel pc:</translation>
</message>
<message>
<location filename="src/pages.cpp" line="691"/>
@ -261,7 +261,7 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pages.cpp" line="692"/>
<source>Choose file...</source>
<translation type="unfinished"/>
<translation>Scegli file...</translation>
</message>
<message>
<location filename="src/pages.cpp" line="694"/>
@ -392,12 +392,12 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pagetemplates.cpp" line="72"/>
<source>Load %1 file</source>
<translation type="unfinished"/>
<translation>Carica %1 file</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="82"/>
<source>%1 file (%1)</source>
<translation type="unfinished"/>
<translation>%1 file (%1)</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="111"/>
@ -421,12 +421,12 @@ e pedine che verranno usate da Cockatrice.</translation>
<message>
<location filename="src/pagetemplates.cpp" line="129"/>
<source>Please choose a file.</source>
<translation type="unfinished"/>
<translation>Seleziona un file.</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="134"/>
<source>Cannot open file &apos;%1&apos;.</source>
<translation type="unfinished"/>
<translation>Impossibile aprire il file &apos;%1&apos;.</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="159"/>