diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 3c8e915a8..aeed5da81 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -46,7 +46,7 @@ concurrency: jobs: configure: name: Configure - runs-on: ubuntu-slim + runs-on: ubuntu-latest outputs: tag: ${{steps.configure.outputs.tag}} sha: ${{steps.configure.outputs.sha}} diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml index fe7be0287..433f302a5 100644 --- a/.github/workflows/desktop-lint.yml +++ b/.github/workflows/desktop-lint.yml @@ -20,13 +20,13 @@ on: jobs: format: - runs-on: ubuntu-slim + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v6 with: - fetch-depth: 20 # should be enough to find merge base + fetch-depth: 20 # should be enough to find merge base - name: Install dependencies shell: bash diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index ca9069192..ed61e3b19 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -16,7 +16,7 @@ jobs: if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice' name: Pull languages - runs-on: ubuntu-slim + runs-on: ubuntu-latest steps: - name: Checkout repo diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index e926a58ed..777e9e6ac 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -16,7 +16,7 @@ jobs: if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice' name: Push strings - runs-on: ubuntu-slim + runs-on: ubuntu-latest steps: - name: Checkout repo @@ -46,7 +46,7 @@ jobs: - name: Render template id: template - uses: chuhlomin/render-template/binary@v1 + uses: chuhlomin/render-template@v1 with: template: .ci/update_translation_source_strings_template.md vars: | diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml index ecc6d14d1..8a90325e7 100644 --- a/.github/workflows/web-lint.yml +++ b/.github/workflows/web-lint.yml @@ -10,7 +10,7 @@ on: jobs: ESLint: - runs-on: ubuntu-slim + runs-on: ubuntu-latest defaults: run: diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a5e944c4..fe808a652 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,11 +74,11 @@ endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake -project("Cockatrice" VERSION 3.0.0) +project("Cockatrice" VERSION 2.11.0) # Set release name if not provided via env/cmake var if(NOT DEFINED GIT_TAG_RELEASENAME) - set(GIT_TAG_RELEASENAME "Graduation Day") + set(GIT_TAG_RELEASENAME "Omenpath") endif() # Use c++20 for all targets diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index 7b52b7bcc..2fdc61fb9 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -11,7 +11,6 @@ SetCompressor LZMA Var NormalDestDir Var PortableDestDir Var PortableMode -Var ReinstallMode !include LogicLib.nsh !include FileFunc.nsh @@ -29,23 +28,13 @@ Var ReinstallMode !define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now" !define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico" -!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_WELCOME - -!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE" - Page Custom PortableModePageCreate PortableModePageLeave !define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre !insertmacro MUI_PAGE_COMPONENTS - -!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_DIRECTORY - -!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_INSTFILES - -!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM @@ -84,7 +73,6 @@ ${IfNot} ${Errors} MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\ /PORTABLE : Install in portable mode$\n\ /S : Silent install$\n\ - /R : Silent upgrade$\n\ /D=%directory% : Specify destination directory$\n" Quit ${EndIf} @@ -102,16 +90,6 @@ ${Else} ${EndIf} ${EndIf} -ClearErrors -${GetOptions} $9 "/R" $8 -${IfNot} ${Errors} - StrCpy $ReinstallMode 1 - SetSilent silent - SetAutoClose true -${Else} - StrCpy $ReinstallMode 0 -${EndIf} - ${If} $InstDir == "" ; User did not use /D to specify a directory, ; we need to set a default based on the install mode @@ -119,22 +97,6 @@ ${If} $InstDir == "" ${EndIf} Call SetModeDestinationFromInstdir -; --- Detect portable install when using /R --- -${If} $ReinstallMode = 1 - IfFileExists "$InstDir\portable.dat" 0 not_portable - StrCpy $PortableMode 1 - Goto portable_done - - not_portable: - StrCpy $PortableMode 0 - - portable_done: -${EndIf} - -${If} $ReinstallMode = 1 - Call AutoUninstallIfNeeded -${EndIf} - FunctionEnd Function un.onInit @@ -164,46 +126,8 @@ ${Else} ${EndIf} FunctionEnd -Function SkipIfReinstall -${If} $ReinstallMode = 1 - Abort -${EndIf} -FunctionEnd - -Function AutoUninstallIfNeeded - -SetShellVarContext all - -; --- 32-bit uninstall --- -SetRegView 32 -ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString" - -StrCmp $R0 "" done32 -DetailPrint "Removing previous version (32-bit)..." -ExecWait '$R0' - -done32: - -; --- 64-bit uninstall --- -${If} ${RunningX64} - SetRegView 64 - ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString" - - StrCmp $R0 "" done64 - DetailPrint "Removing previous version (64-bit)..." - ExecWait '$R0' - - done64: -${EndIf} - -FunctionEnd Function PortableModePageCreate - -${If} $ReinstallMode = 1 - Abort -${EndIf} - Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory !insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice." nsDialogs::Create 1018 @@ -235,11 +159,6 @@ ${EndIf} FunctionEnd Function componentsPagePre - -${If} $ReinstallMode = 1 - Return -${EndIf} - ${If} $PortableMode = 0 SetShellVarContext all @@ -249,12 +168,8 @@ ${If} $PortableMode = 0 ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString" StrCmp $R0 "" done32 - ${If} $ReinstallMode = 0 - MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32 - Abort - ${Else} - Goto uninst32 - ${EndIf} + MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32 + Abort uninst32: ClearErrors @@ -269,12 +184,8 @@ ${If} $PortableMode = 0 ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString" StrCmp $R0 "" done64 - ${If} $ReinstallMode = 0 - MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64 - Abort - ${Else} - Goto uninst64 - ${EndIf} + MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64 + Abort uninst64: ClearErrors @@ -366,12 +277,6 @@ ${Else} FileWrite $0 "PORTABLE" FileClose $0 ${EndIf} - -${If} $ReinstallMode = 1 - IfFileExists "$INSTDIR\cockatrice.exe" 0 +2 - Exec '"$INSTDIR\cockatrice.exe"' -${EndIf} - SectionEnd Section "Start menu item" SecStartMenu diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp index af377d176..a30a7f531 100644 --- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp +++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp @@ -89,8 +89,6 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck) QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); - // we interpret the redirect and open it in the browser instead, do not follow redirects - request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); manager->post(request, data); } diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp index 09f6e656b..c38e433eb 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game/log/message_log_widget.cpp @@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position fromStr = tr(" from the top of their library"); } } - } else if (position == zone->getCards().size()) { + } else if (position >= zone->getCards().size() - 1) { if (cardName.isEmpty()) { if (ownerChange) { cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName()); diff --git a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp index f5f343807..456e1533a 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_text_widget.cpp @@ -62,7 +62,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard) text += QString("%1%2") .arg(tr("Name:"), card->getName().toHtmlEscaped()); - if (!exactCard.getPrinting().isEmpty()) { + if (exactCard.getPrinting() != PrintingInfo()) { QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped(); QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped(); diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp index 147675e21..ea61302f0 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.cpp @@ -29,14 +29,10 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal removeButton = new QPushButton(this); saveButton = new QPushButton(this); loadButton = new QPushButton(this); - includeSideboardCheckBox = new QCheckBox(this); - includeSideboardCheckBox->setChecked(false); - controlLayout->addWidget(addButton); controlLayout->addWidget(removeButton); controlLayout->addWidget(saveButton); controlLayout->addWidget(loadButton); - controlLayout->addWidget(includeSideboardCheckBox); layout->addWidget(controlContainer); @@ -44,7 +40,6 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected); connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout); connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout); - connect(includeSideboardCheckBox, &QCheckBox::clicked, this, &DeckAnalyticsWidget::includeSideboardChanged); // Scroll area and container scrollArea = new QScrollArea(this); @@ -71,13 +66,6 @@ void DeckAnalyticsWidget::retranslateUi() removeButton->setText(tr("Remove Panel")); saveButton->setText(tr("Save Layout")); loadButton->setText(tr("Load Layout")); - includeSideboardCheckBox->setText(tr("Include Sideboard")); -} - -void DeckAnalyticsWidget::includeSideboardChanged(bool checked) -{ - statsAnalyzer->getConfig().includeSideboard = checked; - updateDisplays(); } void DeckAnalyticsWidget::updateDisplays() diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h index 09618c3f8..31ee36fbb 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_analytics_widget.h @@ -11,7 +11,6 @@ #include "deck_list_statistics_analyzer.h" #include "resizable_panel.h" -#include #include #include #include @@ -30,7 +29,6 @@ public slots: public: explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer); void retranslateUi(); - void includeSideboardChanged(bool checked); private slots: void onAddPanel(); @@ -59,8 +57,6 @@ private: QPushButton *saveButton; QPushButton *loadButton; - QCheckBox *includeSideboardCheckBox; - QScrollArea *scrollArea; QWidget *panelContainer; QVBoxLayout *panelLayout; diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp index 073b6d25c..ad8afb766 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp @@ -19,13 +19,7 @@ void DeckListStatisticsAnalyzer::analyze() { clearData(); - QList nodes; - - if (config.includeSideboard) { - nodes = model->getCardNodes(); - } else { - nodes = model->getCardNodesForZone(DECK_ZONE_MAIN); - } + QList nodes = model->getCardNodes(); for (auto node : nodes) { CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName()); diff --git a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h index 52ae751bf..946bb0117 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h +++ b/cockatrice/src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.h @@ -17,7 +17,6 @@ struct DeckListStatisticsAnalyzerConfig bool computeCategories = true; bool computeCurveBreakdowns = true; bool computeProbabilities = true; - bool includeSideboard = false; }; class DeckListStatisticsAnalyzer : public QObject @@ -134,11 +133,6 @@ public: return model; } - DeckListStatisticsAnalyzerConfig &getConfig() - { - return config; - } - signals: void statsUpdated(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp index c9adeb270..0a9244dec 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp @@ -219,9 +219,7 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath) { setLabel(tr("Installing...")); // Try to open the installer. If it opens, quit Cockatrice - if (QProcess::startDetached(filepath.toLocalFile(), - QStringList() - << "/R" << QString("/D=%1").arg(QCoreApplication::applicationDirPath()))) { + if (QDesktopServices::openUrl(filepath)) { QMetaObject::invokeMethod(static_cast(parent()), "close", Qt::QueuedConnection); qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice"; close(); diff --git a/cockatrice/src/interface/widgets/server/game_selector.cpp b/cockatrice/src/interface/widgets/server/game_selector.cpp index 0ff2a5542..f14cc6d82 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector.cpp @@ -91,7 +91,6 @@ GameSelector::GameSelector(AbstractClient *_client, bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults(); clearFilterButton->setEnabled(!filtersSetToDefault); connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter); - connect(gameListProxyModel, &GamesProxyModel::filtersChanged, this, &GameSelector::checkClearFilterButtonState); if (room) { createButton = new QPushButton; @@ -189,16 +188,15 @@ void GameSelector::actSetFilter() dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands()); gameListProxyModel->saveFilterParameters(gameTypeMap); - updateTitle(); -} - -void GameSelector::checkClearFilterButtonState() -{ clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults()); + + updateTitle(); } void GameSelector::actClearFilter() { + clearFilterButton->setEnabled(false); + gameListProxyModel->resetFilterParameters(); gameListProxyModel->saveFilterParameters(gameTypeMap); diff --git a/cockatrice/src/interface/widgets/server/game_selector.h b/cockatrice/src/interface/widgets/server/game_selector.h index fa91e5f96..ea0a4feb0 100644 --- a/cockatrice/src/interface/widgets/server/game_selector.h +++ b/cockatrice/src/interface/widgets/server/game_selector.h @@ -40,7 +40,6 @@ private slots: * Updates the proxy model with selected filter parameters and refreshes the displayed game list. */ void actSetFilter(); - void checkClearFilterButtonState(); /** * @brief Clears all filters applied to the game list. diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp index f7eacd636..daab4d6eb 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp @@ -19,46 +19,32 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, mainLayout->setSpacing(5); searchBar = new QLineEdit(this); - searchBar->setText(model->getGameNameFilter()); - connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { - applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, QString &gameNameFilter, auto &, - auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &) { gameNameFilter = text; }); - }); + searchBar->setText(model->getCreatorNameFilters().join(", ")); + connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); }); hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this); - hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideNotBuddyCreatedGames()); + hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames()); connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { - applyFilters([&](auto &, auto &, auto &, auto &, auto &, bool &hideNotBuddyCreatedGames, auto &, auto &, - QStringList &creatorNameFilters, auto &, auto &, auto &, auto &, auto &, auto &, auto &, - auto &) { - hideNotBuddyCreatedGames = checked; - - if (checked) { - QStringList buddyNames; - for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) { - buddyNames << QString::fromStdString(buddy.name()); - } - creatorNameFilters = buddyNames; - } else { - creatorNameFilters.clear(); + if (checked) { + QStringList buddyNames; + for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) { + buddyNames << QString::fromStdString(buddy.name()); } - }); + model->setCreatorNameFilters(buddyNames); + } else { + model->setCreatorNameFilters({}); + } }); hideFullGamesCheckBox = new QCheckBox(this); hideFullGamesCheckBox->setChecked(model->getHideFullGames()); - connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { - applyFilters([&](auto &, auto &, bool &hideFullGames, auto &, auto &, auto &, auto &, auto &, auto &, auto &, - auto &, auto &, auto &, auto &, auto &, auto &, auto &) { hideFullGames = checked; }); - }); + connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { model->setHideFullGames(checked); }); hideStartedGamesCheckBox = new QCheckBox(this); hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted()); - connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) { - applyFilters([&](auto &, auto &, auto &, bool &hideGamesThatStarted, auto &, auto &, auto &, auto &, auto &, - auto &, auto &, auto &, auto &, auto &, auto &, auto &, - auto &) { hideGamesThatStarted = checked; }); - }); + connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { model->setHideGamesThatStarted(checked); }); filterToFormatComboBox = new QComboBox(this); @@ -83,15 +69,13 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, // Update proxy model on selection change connect(filterToFormatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { - applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &, - QSet &gameTypeFilter, auto &, auto &, auto &, auto &, auto &, auto &, auto &) { - QVariant data = filterToFormatComboBox->itemData(index); - if (!data.isValid()) { - gameTypeFilter.clear(); - } else { - gameTypeFilter = {data.toInt()}; - } - }); + QVariant data = filterToFormatComboBox->itemData(index); + if (!data.isValid()) { + model->setGameTypeFilter({}); // empty = no filter + } else { + int typeId = data.toInt(); + model->setGameTypeFilter({typeId}); + } }); hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20); @@ -112,87 +96,9 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent, setLayout(mainLayout); - syncFromModel(); - - connect(model, &GamesProxyModel::filtersChanged, this, &GameSelectorQuickFilterToolBar::syncFromModel); - retranslateUi(); } -void GameSelectorQuickFilterToolBar::syncFromModel() -{ - QSignalBlocker b1(searchBar); - QSignalBlocker b2(filterToFormatComboBox); - QSignalBlocker b3(hideGamesNotCreatedByBuddiesCheckBox); - QSignalBlocker b4(hideFullGamesCheckBox); - QSignalBlocker b5(hideStartedGamesCheckBox); - - searchBar->setText(model->getGameNameFilter()); - - hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideNotBuddyCreatedGames()); - hideFullGamesCheckBox->setChecked(model->getHideFullGames()); - hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted()); - - QSet types = model->getGameTypeFilter(); - if (types.size() == 1) { - int idx = filterToFormatComboBox->findData(*types.begin()); - filterToFormatComboBox->setCurrentIndex(idx >= 0 ? idx : 0); - } else { - filterToFormatComboBox->setCurrentIndex(0); - } -} - -void GameSelectorQuickFilterToolBar::applyFilters(std::function &, - int &, - int &, - QTime &, - bool &, - bool &, - bool &, - bool &)> mutator) -{ - bool hideBuddiesOnlyGames = model->getHideBuddiesOnlyGames(); - bool hideIgnoredUserGames = model->getHideIgnoredUserGames(); - bool hideFullGames = model->getHideFullGames(); - bool hideGamesThatStarted = model->getHideGamesThatStarted(); - bool hidePasswordProtectedGames = model->getHidePasswordProtectedGames(); - bool hideNotBuddyCreatedGames = model->getHideNotBuddyCreatedGames(); - bool hideOpenDecklistGames = model->getHideOpenDecklistGames(); - - QString gameNameFilter = model->getGameNameFilter(); - QStringList creatorNameFilters = model->getCreatorNameFilters(); - QSet gameTypeFilter = model->getGameTypeFilter(); - - int minPlayers = model->getMaxPlayersFilterMin(); - int maxPlayers = model->getMaxPlayersFilterMax(); - QTime maxGameAge = model->getMaxGameAge(); - - bool showOnlyIfSpectatorsCanWatch = model->getShowOnlyIfSpectatorsCanWatch(); - bool showSpectatorPasswordProtected = model->getShowSpectatorPasswordProtected(); - bool showOnlyIfSpectatorsCanChat = model->getShowOnlyIfSpectatorsCanChat(); - bool showOnlyIfSpectatorsCanSeeHands = model->getShowOnlyIfSpectatorsCanSeeHands(); - - mutator(hideBuddiesOnlyGames, hideIgnoredUserGames, hideFullGames, hideGamesThatStarted, hidePasswordProtectedGames, - hideNotBuddyCreatedGames, hideOpenDecklistGames, gameNameFilter, creatorNameFilters, gameTypeFilter, - minPlayers, maxPlayers, maxGameAge, showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, - showOnlyIfSpectatorsCanChat, showOnlyIfSpectatorsCanSeeHands); - - model->setGameFilters(hideBuddiesOnlyGames, hideIgnoredUserGames, hideFullGames, hideGamesThatStarted, - hidePasswordProtectedGames, hideNotBuddyCreatedGames, hideOpenDecklistGames, gameNameFilter, - creatorNameFilters, gameTypeFilter, minPlayers, maxPlayers, maxGameAge, - showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat, - showOnlyIfSpectatorsCanSeeHands); -} - void GameSelectorQuickFilterToolBar::retranslateUi() { searchBar->setPlaceholderText(tr("Filter by game name...")); diff --git a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h index c658418f9..642fdd1c4 100644 --- a/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h +++ b/cockatrice/src/interface/widgets/server/game_selector_quick_filter_toolbar.h @@ -18,24 +18,6 @@ public: TabSupervisor *tabSupervisor, GamesProxyModel *model, const QMap &allGameTypes); - void syncFromModel(); - void applyFilters(std::function &, - int &, - int &, - QTime &, - bool &, - bool &, - bool &, - bool &)> mutator); void retranslateUi(); private: diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index 1f05308b8..05d363fee 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -326,7 +326,6 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames, #else invalidateFilter(); #endif - emit filtersChanged(); } int GamesProxyModel::getNumFilteredGames() const diff --git a/cockatrice/src/interface/widgets/server/games_model.h b/cockatrice/src/interface/widgets/server/games_model.h index c6884093d..56c806fb6 100644 --- a/cockatrice/src/interface/widgets/server/games_model.h +++ b/cockatrice/src/interface/widgets/server/games_model.h @@ -138,9 +138,6 @@ private: bool showOnlyIfSpectatorsCanChat; bool showOnlyIfSpectatorsCanSeeHands; -signals: - void filtersChanged(); - public: /** * @brief Constructs a GamesProxyModel. diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp index f51d0f3e7..7a424de8b 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp @@ -4,29 +4,10 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json) { id = json.value("id").toInt(); - categories.clear(); - auto categoriesJson = json.value("categories").toArray(); - for (const auto &categoryValue : categoriesJson) { - Category cat; - - if (categoryValue.isObject()) { - QJsonObject obj = categoryValue.toObject(); - - cat.id = obj.value("id").toInt(); - cat.name = obj.value("name").toString(); - cat.isPremier = obj.value("isPremier").toBool(); - cat.includedInDeck = obj.value("includedInDeck").toBool(); - cat.includedInPrice = obj.value("includedInPrice").toBool(); - } else if (categoryValue.isString()) { - cat.name = categoryValue.toString(); - - // assume mainboard unless known otherwise - cat.includedInDeck = true; - } - - categories.append(cat); + for (auto category : categoriesJson) { + categories.append(category.toString()); } companion = json.value("companion").toBool(); @@ -46,13 +27,7 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json) void ArchidektApiResponseCardEntry::debugPrint() const { qDebug() << "Id:" << id; - for (auto category : categories) { - qDebug() << "Category ID:" << category.id; - qDebug() << "Category Name:" << category.name; - qDebug() << "Category Premier:" << category.isPremier; - qDebug() << "Category Included in Deck:" << category.includedInDeck; - qDebug() << "Category Included in Price:" << category.includedInPrice; - } + qDebug() << "Categories:" << categories; qDebug() << "Companion:" << companion; qDebug() << "FlippedDefault:" << flippedDefault; qDebug() << "Label:" << label; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h index f3961dc6f..f7f86e9ed 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.h @@ -9,15 +9,6 @@ #include #include -struct Category -{ - int id; - QString name; - bool isPremier; - bool includedInDeck; - bool includedInPrice; -}; - class ArchidektApiResponseCardEntry { public: @@ -35,7 +26,7 @@ public: return card; }; - QList getCategories() const + QStringList getCategories() const { return categories; } @@ -47,7 +38,7 @@ public: private: int id; - QList categories; + QStringList categories; bool companion; bool flippedDefault; QString label; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 66b68d823..8b17cd49e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -63,60 +63,16 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi QString tempDeck; QTextStream deckStream(&tempDeck); - QString mainboardText; - QString sideboardText; - - QTextStream mainStream(&mainboardText); - QTextStream sideStream(&sideboardText); - - for (const auto &card : response.getCards()) { + for (auto card : response.getCards()) { QString fullName = card.getCard().getOracleCard().value("name").toString(); // We don't really care about the second card, the card database already has it as a relation QString cleanName = fullName.split("//").first().trimmed(); - QString line = QString("%1 %2 (%3) %4\n") - .arg(card.getQuantity()) - .arg(cleanName) - .arg(card.getCard().getEdition().getEditionCode().toUpper()) - .arg(card.getCard().getCollectorNumber()); - - bool isCommander = false; - bool isSideboardCategory = false; - bool includedInDeck = false; - - for (const auto &cat : card.getCategories()) { - - if (cat.name.compare("Commander", Qt::CaseInsensitive) == 0) { - isCommander = true; - } - - if (cat.name.compare("Sideboard", Qt::CaseInsensitive) == 0 || - cat.name.compare("Maybeboard", Qt::CaseInsensitive) == 0) { - isSideboardCategory = true; - } - - if (cat.includedInDeck) { - includedInDeck = true; - } - } - - QString target; - - if (isCommander || isSideboardCategory) { - sideStream << line; - } else if (includedInDeck) { - mainStream << line; - } else { - sideStream << line; - } - } - - // Combine with blank line separator - tempDeck = mainboardText; - - if (!sideboardText.isEmpty()) { - tempDeck += "\n"; - tempDeck += sideboardText; + tempDeck += QString("%1 %2 (%3) %4\n") + .arg(card.getQuantity()) + .arg(cleanName) + .arg(card.getCard().getEdition().getEditionCode().toUpper()) + .arg(card.getCard().getCollectorNumber()); } model = new DeckListModel(this); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 161829b35..cf8269069 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -259,9 +259,6 @@ TabGame::~TabGame() if (replayManager) { delete replayManager->replay; } - for (auto &player : game->getPlayerManager()->getPlayers()) { - player->clear(); - } } void TabGame::updatePlayerListDockTitle() diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_button.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_button.h deleted file mode 100644 index 5d9f7f944..000000000 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_button.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H -#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H - -#include - -const QString visualDatabaseDisplayFilterButtonStyle = QString(R"( - QPushButton { - background-color: palette(button); - color: palette(button-text); - padding: 5px 10px; - border-radius: 4px; - border: 1px solid palette(dark); - } - QPushButton:checked { - background-color: palette(highlight); - color: palette(highlighted-text); - border: 1px solid palette(shadow); - } -)"); - -#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp index 633f07af7..0df948016 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp @@ -1,7 +1,6 @@ #include "visual_database_display_format_legality_filter_widget.h" #include "../../../filters/filter_tree_model.h" -#include "visual_database_display_filter_button.h" #include #include @@ -81,7 +80,8 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons() for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) { auto *button = new QPushButton(it.key(), flowWidget); button->setCheckable(true); - button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:checked { background-color: green; color: white; }"); flowWidget->addWidget(button); formatButtons[it.key()] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index c44489c1b..bc8e914bd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -1,7 +1,6 @@ #include "visual_database_display_main_type_filter_widget.h" #include "../../../filters/filter_tree_model.h" -#include "visual_database_display_filter_button.h" #include #include @@ -76,8 +75,8 @@ void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons() for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) { auto *button = new QPushButton(it.key(), flowWidget); button->setCheckable(true); - - button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:checked { background-color: green; color: white; }"); flowWidget->addWidget(button); typeButtons[it.key()] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index 2751ee971..5098696dd 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -3,7 +3,6 @@ #include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h" #include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h" #include "../deck_editor/deck_state_manager.h" -#include "visual_database_display_filter_button.h" #include @@ -96,8 +95,8 @@ void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name // Create a button for the filter auto *button = new QPushButton(name, flowWidget); - - button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:hover { background-color: red; color: white; }"); connect(button, &QPushButton::clicked, this, [this, name]() { removeNameFilter(name); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index b72116461..3339bc561 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../filters/filter_tree_model.h" -#include "visual_database_display_filter_button.h" #include #include @@ -102,8 +101,8 @@ void VisualDatabaseDisplaySetFilterWidget::createSetButtons() auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget); button->setCheckable(true); - - button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:checked { background-color: green; color: white; }"); flowWidget->addWidget(button); setButtons[shortName] = button; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index 6d4bcb58e..57559d12c 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -1,7 +1,6 @@ #include "visual_database_display_sub_type_filter_widget.h" #include "../../../filters/filter_tree_model.h" -#include "visual_database_display_filter_button.h" #include #include @@ -81,8 +80,8 @@ void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons() for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) { auto *button = new QPushButton(it.key(), flowWidget); button->setCheckable(true); - - button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle); + button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }" + "QPushButton:checked { background-color: green; color: white; }"); flowWidget->addWidget(button); typeButtons[it.key()] = button; diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index 021e8d12d..26e515a2d 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -133,7 +133,7 @@ ExactCard CardDatabaseQuerier::getRandomCard() const ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const { // The source card does not have a printing defined, which means we can't get a card from the same set. - if (otherPrinting.isEmpty()) { + if (otherPrinting == PrintingInfo()) { return getCard({cardName}); } @@ -360,4 +360,4 @@ QMap CardDatabaseQuerier::getAllFormatsWithCount() const } return formatCounts; -} +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index ad7b33654..43d82a9cb 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -54,16 +54,6 @@ public: return this->set == other.set && this->properties == other.properties; } - /** - * @brief check if the info is empty, as if default constructed. - * - * @return True if both set and properties are empty, otherwise false. - */ - bool isEmpty() const - { - return set == nullptr && properties.isEmpty(); - } - private: CardSetPtr set; ///< The set this variation belongs to. QVariantHash properties; ///< Key-value store for variation-specific attributes. diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index 7c0437bf0..f04bcc849 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -49,7 +49,6 @@ #include #include #include -#include Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, int _playerId, @@ -229,37 +228,6 @@ shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZ return false; } -/** - * @brief Determines whether a set of moved cards is from the bottom of the deck - */ -static bool shouldBeFromTheBottom(const Server_CardZone *startZone, const std::set &cardsToMove) -{ - if (!startZone) { - return false; - } - - if (startZone->getName() != ZoneNames::DECK) { - return false; - } - - int movedCount = static_cast(cardsToMove.size()); - int tailStart = startZone->getCards().size() - movedCount; - if (tailStart <= 0) { // if the entire deck is moved it should not be considered from the bottom - return false; - } - - // check if the move is a contiguous block at the end of the deck, fail fast when not - int expectedPosition = tailStart; - for (const auto &card : cardsToMove) { - if (card.position != expectedPosition) { - return false; - } - ++expectedPosition; - } - - return true; -} - Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, @@ -276,11 +244,8 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, return Response::RespContextError; } - if (!targetzone->hasCoords()) { - yCoord = 0; - if (xCoord <= -1) { - xCoord = targetzone->getCards().size(); - } + if (!targetzone->hasCoords() && (xCoord <= -1)) { + xCoord = targetzone->getCards().size(); } std::set cardsToMove; @@ -320,21 +285,164 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, bool revealTopStart = false; bool revealTopTarget = false; - bool isFromBottom = shouldBeFromTheBottom(startzone, cardsToMove); + for (auto cardStruct : cardsToMove) { + Server_Card *card = cardStruct.card; + int originalPosition = cardStruct.position; - if (isFromBottom) { - std::ranges::reverse_view reversedCardsToMove{cardsToMove}; - for (auto card : reversedCardsToMove) { - processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget, - isReversed, undoingDraw); + bool sourceBeingLookedAt; + int position = startzone->removeCard(card, sourceBeingLookedAt); + + // Attachment relationships can be retained when moving a card onto the opponent's table + if (startzone->getName() != targetzone->getName()) { + // Delete all attachment relationships + if (card->getParentCard()) { + card->setParentCard(nullptr); + } + + // Make a copy of the list because the original one gets modified during the loop + QList attachedCards = card->getAttachedCards(); + for (auto &attachedCard : attachedCards) { + attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard); + } } - } else { - for (auto card : cardsToMove) { - processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget, - isReversed, undoingDraw); + + if (startzone != targetzone) { + // Delete all arrows from and to the card + for (auto *player : game->getPlayers().values()) { + QList arrowsToDelete; + for (Server_Arrow *arrow : player->getArrows()) { + if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) + arrowsToDelete.append(arrow->getId()); + } + for (int j : arrowsToDelete) { + player->deleteArrow(j); + } + } + } + + if (shouldDestroyOnMove(card, startzone, targetzone)) { + Event_DestroyCard event; + event.set_zone_name(startzone->getName().toStdString()); + event.set_card_id(static_cast(card->getId())); + ges.enqueueGameEvent(event, playerId); + + if (Server_Card *stashedCard = card->takeStashedCard()) { + stashedCard->setId(newCardId()); + ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), + playerId); + card->deleteLater(); + card = stashedCard; + } else { + card->deleteLater(); + card = nullptr; + } + } + + if (card) { + ++xIndex; + int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; + + bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); + + if (targetzone->hasCoords()) { + newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); + } else { + yCoord = 0; + card->resetState(targetzone->getName() == ZoneNames::STACK); + } + + targetzone->insertCard(card, newX, yCoord); + int targetLookedCards = targetzone->getCardsBeingLookedAt(); + bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown()); + if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) { + if (sourceKnownToPlayer) { + targetLookedCards += 1; + } else { + targetLookedCards = newX; + } + targetzone->setCardsBeingLookedAt(targetLookedCards); + } + + bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); + bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); + + int oldCardId = card->getId(); + if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) { + card->setId(targetzone->getPlayer()->newCardId()); + } + card->setFaceDown(faceDown); + + Event_MoveCard eventOthers; + eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); + eventOthers.set_start_zone(startzone->getName().toStdString()); + eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); + if (startzone != targetzone) { + eventOthers.set_target_zone(targetzone->getName().toStdString()); + } + eventOthers.set_y(yCoord); + eventOthers.set_face_down(faceDown); + + Event_MoveCard eventPrivate(eventOthers); + if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone || + startzone->getType() != ServerInfo_Zone::HiddenZone) { + eventPrivate.set_card_id(oldCardId); + eventPrivate.set_new_card_id(card->getId()); + } else { + eventPrivate.set_card_id(-1); + eventPrivate.set_new_card_id(-1); + } + if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) { + QString privateCardName = card->getName(); + eventPrivate.set_card_name(privateCardName.toStdString()); + eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString()); + } + if (startzone->getType() == ServerInfo_Zone::HiddenZone) { + eventPrivate.set_position(position); + } else { + eventPrivate.set_position(-1); + } + + eventPrivate.set_x(newX); + + if ( + // cards from public zones have their id known, their previous position is already known, the event does + // not accomodate for previous locations in zones with coordinates (which are always public) + (startzone->getType() != ServerInfo_Zone::PublicZone) && + // other players are not allowed to be able to track which card is which in private zones like the hand + (startzone->getType() != ServerInfo_Zone::PrivateZone)) { + eventOthers.set_position(position); + } + if ( + // other players are not allowed to be able to track which card is which in private zones like the hand + (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { + eventOthers.set_x(newX); + } + + if ((startzone->getType() == ServerInfo_Zone::PublicZone) || + (targetzone->getType() == ServerInfo_Zone::PublicZone)) { + eventOthers.set_card_id(oldCardId); + if (!(sourceHiddenToOthers && targetHiddenToOthers)) { + QString publicCardName = card->getName(); + eventOthers.set_card_name(publicCardName.toStdString()); + eventOthers.set_new_card_provider_id(card->getProviderId().toStdString()); + } + eventOthers.set_new_card_id(card->getId()); + } + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + if (originalPosition == 0) { + revealTopStart = true; + } + if (newX == 0) { + revealTopTarget = true; + } + + // handle side effects for this card + onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw); } } - if (revealTopStart) { revealTopCardIfNeeded(startzone, ges); } @@ -354,174 +462,6 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, return Response::RespOk; } -void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges, - Server_CardZone *startzone, - Server_CardZone *targetzone, - MoveCardStruct cardStruct, - int xCoord, - int yCoord, - int &xIndex, - bool &revealTopStart, - bool &revealTopTarget, - bool isReversed, - bool undoingDraw) -{ - Server_Card *card = cardStruct.card; - int originalPosition = cardStruct.position; - - bool sourceBeingLookedAt; - int position = startzone->removeCard(card, sourceBeingLookedAt); - - // Attachment relationships can be retained when moving a card onto the opponent's table - if (startzone->getName() != targetzone->getName()) { - // Delete all attachment relationships - if (card->getParentCard()) { - card->setParentCard(nullptr); - } - - // Make a copy of the list because the original one gets modified during the loop - QList attachedCards = card->getAttachedCards(); - for (auto &attachedCard : attachedCards) { - attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard); - } - } - - if (startzone != targetzone) { - // Delete all arrows from and to the card - for (auto *player : game->getPlayers().values()) { - QList arrowsToDelete; - for (Server_Arrow *arrow : player->getArrows()) { - if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) - arrowsToDelete.append(arrow->getId()); - } - for (int j : arrowsToDelete) { - player->deleteArrow(j); - } - } - } - - if (shouldDestroyOnMove(card, startzone, targetzone)) { - Event_DestroyCard event; - event.set_zone_name(startzone->getName().toStdString()); - event.set_card_id(static_cast(card->getId())); - ges.enqueueGameEvent(event, playerId); - - if (Server_Card *stashedCard = card->takeStashedCard()) { - stashedCard->setId(newCardId()); - ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), playerId); - card->deleteLater(); - card = stashedCard; - } else { - card->deleteLater(); - card = nullptr; - } - } - - if (card) { - ++xIndex; - int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; - - bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); - - if (targetzone->hasCoords()) { - newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); - } else { - card->resetState(targetzone->getName() == ZoneNames::STACK); - } - - targetzone->insertCard(card, newX, yCoord); - int targetLookedCards = targetzone->getCardsBeingLookedAt(); - bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown()); - if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) { - if (sourceKnownToPlayer) { - targetLookedCards += 1; - } else { - targetLookedCards = newX; - } - targetzone->setCardsBeingLookedAt(targetLookedCards); - } - - bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); - bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); - - int oldCardId = card->getId(); - if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) { - card->setId(targetzone->getPlayer()->newCardId()); - } - card->setFaceDown(faceDown); - - Event_MoveCard eventOthers; - eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); - eventOthers.set_start_zone(startzone->getName().toStdString()); - eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); - if (startzone != targetzone) { - eventOthers.set_target_zone(targetzone->getName().toStdString()); - } - eventOthers.set_y(yCoord); - eventOthers.set_face_down(faceDown); - - Event_MoveCard eventPrivate(eventOthers); - if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone || - startzone->getType() != ServerInfo_Zone::HiddenZone) { - eventPrivate.set_card_id(oldCardId); - eventPrivate.set_new_card_id(card->getId()); - } else { - eventPrivate.set_card_id(-1); - eventPrivate.set_new_card_id(-1); - } - if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) { - QString privateCardName = card->getName(); - eventPrivate.set_card_name(privateCardName.toStdString()); - eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString()); - } - if (startzone->getType() == ServerInfo_Zone::HiddenZone) { - eventPrivate.set_position(position); - } else { - eventPrivate.set_position(-1); - } - - eventPrivate.set_x(newX); - - if ( - // cards from public zones have their id known, their previous position is already known, the event does - // not accomodate for previous locations in zones with coordinates (which are always public) - (startzone->getType() != ServerInfo_Zone::PublicZone) && - // other players are not allowed to be able to track which card is which in private zones like the hand - (startzone->getType() != ServerInfo_Zone::PrivateZone)) { - eventOthers.set_position(position); - } - if ( - // other players are not allowed to be able to track which card is which in private zones like the hand - (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { - eventOthers.set_x(newX); - } - - if ((startzone->getType() == ServerInfo_Zone::PublicZone) || - (targetzone->getType() == ServerInfo_Zone::PublicZone)) { - eventOthers.set_card_id(oldCardId); - if (!(sourceHiddenToOthers && targetHiddenToOthers)) { - QString publicCardName = card->getName(); - eventOthers.set_card_name(publicCardName.toStdString()); - eventOthers.set_new_card_provider_id(card->getProviderId().toStdString()); - } - eventOthers.set_new_card_id(card->getId()); - } - - ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); - ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); - - if (originalPosition == 0) { - revealTopStart = true; - } - if (newX == 0) { - revealTopTarget = true; - } - - // handle side effects for this card - onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw); - } -} - void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges, const MoveCardStruct &cardStruct, Server_CardZone *startzone, diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h index 9d9809298..40fe84aa1 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h @@ -93,19 +93,6 @@ public: bool fixFreeSpaces = true, bool undoingDraw = false, bool isReversed = false); - - void processMoveCard(GameEventStorage &ges, - Server_CardZone *startzone, - Server_CardZone *targetzone, - MoveCardStruct cardStruct, - int xCoord, - int yCoord, - int &xIndex, - bool &revealTopStart, - bool &revealTopTarget, - bool isReversed, - bool undoingDraw); - virtual void onCardBeingMoved(GameEventStorage &ges, const MoveCardStruct &cardStruct, Server_CardZone *startzone, diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 578afd98d..b5d7b9856 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -366,6 +366,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList auto found_iter = splitCards.find(name + numProperty); if (found_iter == splitCards.end()) { splitCards.insert(name + numProperty, {{split}, name}); + } else if (layout == "adventure" || layout == "prepare") { + found_iter->first.insert(0, split); } else { found_iter->first.append(split); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fffaf1bda..c5346e59f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,5 +65,4 @@ target_link_libraries( add_subdirectory(card_zone_algorithms) add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) -add_subdirectory(movecard_tests) add_subdirectory(oracle) diff --git a/tests/movecard_tests/CMakeLists.txt b/tests/movecard_tests/CMakeLists.txt deleted file mode 100755 index 769047148..000000000 --- a/tests/movecard_tests/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -add_executable(reverse_card_move_test reverse_card_move_test.cpp) - -if(NOT GTEST_FOUND) - add_dependencies(reverse_card_move_test gtest) -endif() - -target_link_libraries( - reverse_card_move_test - PRIVATE libcockatrice_network_server_remote - PRIVATE libcockatrice_rng - PRIVATE Threads::Threads - PRIVATE ${GTEST_BOTH_LIBRARIES} - PRIVATE ${TEST_QT_MODULES} -) - -add_test(NAME reverse_card_move_test COMMAND reverse_card_move_test) diff --git a/tests/movecard_tests/reverse_card_move_test.cpp b/tests/movecard_tests/reverse_card_move_test.cpp deleted file mode 100644 index 2231a7e3b..000000000 --- a/tests/movecard_tests/reverse_card_move_test.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "game/server_abstract_player.h" -#include "game/server_card.h" -#include "game/server_cardzone.h" -#include "game/server_game.h" -#include "server_response_containers.h" -#include "server_room.h" -#include "server_test_helpers.h" - -#include -#include -#include -#include -#include - -RNG_Abstract *rng = nullptr; // this needs to be defined due to other functions in server - -TEST(ReverseCardMoveTest, MoveCardFromBottomTest) -{ - ServerInfo_User user; - user.set_name("test-user"); - - // instantiate a fake server instance - FakeServer server; - Server_Room room(0, 0, "", "", "", "", false, "", {}, &server); - Server_Game game(user, 1, "", "", 2, QList(), false, false, false, false, false, false, 20, false, &room); - Server_AbstractPlayer player(&game, 1, user, false, nullptr); - Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone); - Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone); - - // setup the deck with 20 useless cards - for (int i = 0; i < 20; i++) { - auto *cardUseless = new Server_Card({"Card Useless", "card-Useless"}, player.newCardId(), i, 0); - deckZone.insertCard(cardUseless, i, 0); - } - - // add 4 cards to the end of it - auto *cardA = new Server_Card({"Card A", "card-a"}, player.newCardId(), 20, 0); - auto *cardB = new Server_Card({"Card B", "card-b"}, player.newCardId(), 21, 0); - auto *cardC = new Server_Card({"Card C", "card-c"}, player.newCardId(), 22, 0); - auto *cardD = new Server_Card({"Card D", "card-d"}, player.newCardId(), 23, 0); - - deckZone.insertCard(cardA, 20, 0); - deckZone.insertCard(cardB, 21, 0); - deckZone.insertCard(cardC, 22, 0); - deckZone.insertCard(cardD, 23, 0); - - // try to move them, with the expected client given order (n-3, n-2, n-1, n) - CardToMove moveA; - moveA.set_card_id(cardA->getId()); - CardToMove moveB; - moveB.set_card_id(cardB->getId()); - CardToMove moveC; - moveC.set_card_id(cardC->getId()); - CardToMove moveD; - moveD.set_card_id(cardD->getId()); - - QList cardsToMove = {&moveA, &moveB, &moveC, &moveD}; - GameEventStorage ges; - - const auto response = player.moveCard(ges, &deckZone, cardsToMove, &exileZone, 0, 0, false, false, false); - - EXPECT_EQ(response, Response::RespOk); - - int positionA; - int positionB; - int positionC; - int positionD; - // find the cards in the destination zone and check they are the right card - EXPECT_EQ(exileZone.getCard(cardA->getId(), &positionA), cardA); - EXPECT_EQ(exileZone.getCard(cardB->getId(), &positionB), cardB); - EXPECT_EQ(exileZone.getCard(cardC->getId(), &positionC), cardC); - EXPECT_EQ(exileZone.getCard(cardD->getId(), &positionD), cardD); - - // check that they are at the expected index - EXPECT_EQ(cardA->getX(), 3); - EXPECT_EQ(cardB->getX(), 2); - EXPECT_EQ(cardC->getX(), 1); - EXPECT_EQ(cardD->getX(), 0); - - // also check if the given positions are correct - EXPECT_EQ(positionA, 3); - EXPECT_EQ(positionB, 2); - EXPECT_EQ(positionC, 1); - EXPECT_EQ(positionD, 0); -} - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/movecard_tests/server_test_helpers.h b/tests/movecard_tests/server_test_helpers.h deleted file mode 100644 index fd2ed6c17..000000000 --- a/tests/movecard_tests/server_test_helpers.h +++ /dev/null @@ -1,42 +0,0 @@ -#include "server.h" -#include "server_database_interface.h" - -class MockDatabaseInterface : public Server_DatabaseInterface -{ -public: - AuthenticationResult checkUserPassword(Server_ProtocolHandler *, - const QString &, - const QString &, - const QString &, - QString &, - int &, - bool) override - { - return NotLoggedIn; - } - ServerInfo_User getUserData(const QString &, bool) override - { - return ServerInfo_User(); - } - int getNextGameId() override - { - return 1; - } - int getNextReplayId() override - { - return 1; - } - int getActiveUserCount(QString) override - { - return 1; - } -}; - -class FakeServer : public Server -{ -public: - FakeServer() - { - setDatabaseInterface(new MockDatabaseInterface()); - } -};