Compare commits

..

No commits in common. "master" and "2026-04-18-Development-2.11.0-beta.60" have entirely different histories.

40 changed files with 224 additions and 807 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@ on:
jobs:
ESLint:
runs-on: ubuntu-slim
runs-on: ubuntu-latest
defaults:
run:

View file

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

View file

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

View file

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

View file

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

View file

@ -62,7 +62,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
.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();

View file

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

View file

@ -11,7 +11,6 @@
#include "deck_list_statistics_analyzer.h"
#include "resizable_panel.h"
#include <QCheckBox>
#include <QJsonObject>
#include <QScrollArea>
#include <QVBoxLayout>
@ -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;

View file

@ -19,13 +19,7 @@ void DeckListStatisticsAnalyzer::analyze()
{
clearData();
QList<const DecklistCardNode *> nodes;
if (config.includeSideboard) {
nodes = model->getCardNodes();
} else {
nodes = model->getCardNodesForZone(DECK_ZONE_MAIN);
}
QList<const DecklistCardNode *> nodes = model->getCardNodes();
for (auto node : nodes) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());

View file

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

View file

@ -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<MainWindow *>(parent()), "close", Qt::QueuedConnection);
qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice";
close();

View file

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

View file

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

View file

@ -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<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
QSet<int> &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<int> 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<void(bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
QString &,
QStringList &,
QSet<int> &,
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<int> 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..."));

View file

@ -18,24 +18,6 @@ public:
TabSupervisor *tabSupervisor,
GamesProxyModel *model,
const QMap<int, QString> &allGameTypes);
void syncFromModel();
void applyFilters(std::function<void(bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
bool &,
QString &,
QStringList &,
QSet<int> &,
int &,
int &,
QTime &,
bool &,
bool &,
bool &,
bool &)> mutator);
void retranslateUi();
private:

View file

@ -326,7 +326,6 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
#else
invalidateFilter();
#endif
emit filtersChanged();
}
int GamesProxyModel::getNumFilteredGames() const

View file

@ -138,9 +138,6 @@ private:
bool showOnlyIfSpectatorsCanChat;
bool showOnlyIfSpectatorsCanSeeHands;
signals:
void filtersChanged();
public:
/**
* @brief Constructs a GamesProxyModel.

View file

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

View file

@ -9,15 +9,6 @@
#include <QString>
#include <QVector>
struct Category
{
int id;
QString name;
bool isPremier;
bool includedInDeck;
bool includedInPrice;
};
class ArchidektApiResponseCardEntry
{
public:
@ -35,7 +26,7 @@ public:
return card;
};
QList<Category> getCategories() const
QStringList getCategories() const
{
return categories;
}
@ -47,7 +38,7 @@ public:
private:
int id;
QList<Category> categories;
QStringList categories;
bool companion;
bool flippedDefault;
QString label;

View file

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

View file

@ -259,9 +259,6 @@ TabGame::~TabGame()
if (replayManager) {
delete replayManager->replay;
}
for (auto &player : game->getPlayerManager()->getPlayers()) {
player->clear();
}
}
void TabGame::updatePlayerListDockTitle()

View file

@ -1,21 +0,0 @@
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
#include <QString>
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

View file

@ -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 <QLabel>
#include <QPushButton>
@ -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;

View file

@ -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 <QLabel>
#include <QPushButton>
@ -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;

View file

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

View file

@ -2,7 +2,6 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../filters/filter_tree_model.h"
#include "visual_database_display_filter_button.h"
#include <QLineEdit>
#include <QPushButton>
@ -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;

View file

@ -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 <QLabel>
#include <QLineEdit>
@ -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;

View file

@ -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<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
}
return formatCounts;
}
}

View file

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

View file

@ -49,7 +49,6 @@
#include <libcockatrice/rng/rng_abstract.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/zone_names.h>
#include <ranges>
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<MoveCardStruct> &cardsToMove)
{
if (!startZone) {
return false;
}
if (startZone->getName() != ZoneNames::DECK) {
return false;
}
int movedCount = static_cast<int>(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<const CardToMove *> &_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<MoveCardStruct> 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<Server_Card *> 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<int> 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<google::protobuf::uint32>(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<Server_Card *> 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<int> 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<google::protobuf::uint32>(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,

View file

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

View file

@ -366,6 +366,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, 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);
}

View file

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

View file

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

View file

@ -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 <gtest/gtest.h>
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/rng/rng_abstract.h>
#include <libcockatrice/utility/zone_names.h>
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<int>(), 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<const CardToMove *> 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();
}

View file

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