Compare commits

...

12 Commits

Author SHA1 Message Date
ebbit1q
5309dd17be fix typo (#6572) 2026-01-25 20:44:03 -05:00
transifex-integration[bot]
364470b3c8 Updates for project Cockatrice and language en_US (#6543)
* Translate oracle/oracle_en@source.ts in en_US

100% translated source file: 'oracle/oracle_en@source.ts'
on 'en_US'.

* Translate cockatrice_en@source.ts in en_US

100% translated source file: 'cockatrice_en@source.ts'
on 'en_US'.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-25 16:51:03 -05:00
transifex-integration[bot]
915da79cad Translate cockatrice/cockatrice_en@source.ts in de (#6541)
100% translated source file: 'cockatrice/cockatrice_en@source.ts'
on 'de'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2026-01-25 16:50:40 -05:00
BruebachL
630c71f123 [VDE] Insert at correct index onDataChanged() instead of just appending. (#6556)
Took 25 minutes

Took 3 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-01-25 16:06:06 -05:00
ebbit1q
0b4e7be596 followup to #6563 (#6569)
* mess with the font rendering of the home screen until it works

* add more fonts

* increase font weight

* fix outline on the text
2026-01-25 16:05:53 -05:00
ebbit1q
303bd8b607 detect recursive redirects (#6570)
* detect recursive redirects

* handle failure with normal failure handler
2026-01-25 16:05:19 -05:00
RickyRister
c02cf5e89e [VDE] Fix crash from alt-click when card has unknown set (#6566) 2026-01-25 01:36:10 -08:00
ebbit1q
92f02fa4ee mess with the font rendering of the home screen until it works (#6563)
* mess with the font rendering of the home screen until it works

* add more fonts
2026-01-25 01:37:19 -05:00
ebbit1q
49e6cf95c4 fix package description (#6565) 2026-01-24 22:34:53 -05:00
ebbit1q
8a126263a9 do not ignore return value of opening log in servatrice when rotating (#6564) 2026-01-24 15:05:26 -05:00
ebbit1q
afdb385770 move returning cards to server_game (#6561)
lock the game mutex instead of player mutex when returning cards
2026-01-24 12:54:29 -05:00
tooomm
5b8897231d CI: Include submodules in repo checkout (Docs deployment) (#6557)
* Enable submodule checkout in documentation build

* Change submodule checkout to recursive
2026-01-24 13:24:19 +01:00
13 changed files with 424 additions and 388 deletions

View File

@@ -22,6 +22,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Graphviz
run: |

View File

@@ -254,7 +254,7 @@ endif()
set(CPACK_PACKAGE_CONTACT "Zach Halpern <zach@cockatrice.us>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md")
set(CPACK_PACKAGE_DESCRIPTION "Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")

View File

@@ -70,16 +70,22 @@ void CardPictureLoaderWorkerWork::picDownloadFailed()
void CardPictureLoaderWorkerWork::handleNetworkReply(QNetworkReply *reply)
{
QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
bool redirectFailure = false;
if (redirectTarget.isValid()) {
QUrl url = reply->request().url();
QUrl redirectUrl = redirectTarget.toUrl();
if (redirectUrl.isRelative()) {
redirectUrl = url.resolved(redirectUrl);
}
emit urlRedirected(url, redirectUrl);
if (url == redirectUrl) {
qCWarning(CardPictureLoaderWorkerWorkLog) << "recursive redirect detected!";
redirectFailure = true;
} else {
emit urlRedirected(url, redirectUrl);
}
}
if (reply->error()) {
if (redirectFailure || reply->error()) {
handleFailedReply(reply);
} else {
handleSuccessfulReply(reply);

View File

@@ -325,11 +325,22 @@ void CardGroupDisplayWidget::onDataChanged(const QModelIndex &topLeft,
indexToWidgetMap.remove(persistent);
}
} else {
// Add new widgets
int toAdd = newAmount - currentWidgetCount;
int insertBase = 0;
// Count widgets belonging to rows before this one
for (int r = 0; r < row; ++r) {
QModelIndex prevIdx = deckListModel->index(r, 0, trackedIndex);
QPersistentModelIndex prevPersistent(prevIdx);
if (indexToWidgetMap.contains(prevPersistent)) {
insertBase += indexToWidgetMap.value(prevPersistent).size();
}
}
// Insert after existing copies of this card
for (int i = 0; i < toAdd; ++i) {
addToLayout(constructWidgetForIndex(persistent));
insertIntoLayout(constructWidgetForIndex(persistent), insertBase + currentWidgetCount + i);
}
}

View File

@@ -175,9 +175,11 @@ QModelIndex DeckStateManager::addCard(const ExactCard &card, const QString &zone
QString zone = card.getInfo().getIsToken() ? DECK_ZONE_TOKENS : zoneName;
QString reason = tr("Added (%1): %2 (%3) %4")
.arg(zone, card.getName(), card.getPrinting().getSet()->getCorrectedShortName(),
card.getPrinting().getProperty("num"));
CardSetPtr set = card.getPrinting().getSet();
QString setName = set ? set->getCorrectedShortName() : "";
QString reason =
tr("Added (%1): %2 (%3) %4").arg(zone, card.getName(), setName, card.getPrinting().getProperty("num"));
QModelIndex idx = modifyDeck(reason, [&card, &zone](auto model) { return model->addCard(card, zone); });

View File

@@ -41,6 +41,8 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair<QColor, QColor> &
return QString(R"(
QPushButton {
font-size: 34px;
font-weight: bold;
font-family: sans-serif, "Segoe UI", "Helvetica Neue";
padding: 30px;
color: white;
border: 2px solid %1;
@@ -88,18 +90,14 @@ void HomeStyledButton::paintEvent(QPaintEvent *event)
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::TextAntialiasing);
QFont font = this->font();
font.setBold(true);
painter.setFont(font);
QFontMetrics fm(font);
QSize textSize = fm.size(Qt::TextSingleLine, this->text());
QFontMetrics fm(font());
QSize textSize = fm.size(Qt::TextSingleLine, text());
QPointF center((width() - textSize.width()) / 2.0, (height() + textSize.height() / 2.0) / 2.0);
QPainterPath path;
path.addText(center, font, this->text());
path.addText(center, font(), text());
painter.setPen(QPen(Qt::black, 2.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.setBrush(Qt::white);
painter.drawPath(path);
QPen pen(Qt::black, 4.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.strokePath(path, pen);
painter.fillPath(path, Qt::white);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -575,51 +575,8 @@ Server_AbstractPlayer::cmdConcede(const Command_Concede & /*cmd*/, ResponseConta
setConceded(true);
game->removeArrowsRelatedToPlayer(ges, this);
game->unattachCards(ges, this);
game->returnCardsFromPlayer(ges, this);
playerMutex.lock();
// Return cards to their rightful owners before conceding the game
static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"};
for (const auto &card : zones.value("table")->getCards()) {
if (card == nullptr) {
continue;
}
const auto &regexResult = ownerRegex.match(card->getAnnotation());
if (!regexResult.hasMatch()) {
continue;
}
CardToMove cardToMove;
cardToMove.set_card_id(card->getId());
for (const auto *player : game->getPlayers()) {
if (player == nullptr || player->getUserInfo() == nullptr) {
continue;
}
const auto &ownerToReturnTo = regexResult.captured(1);
const auto &correctOwner = QString::compare(QString::fromStdString(player->getUserInfo()->name()),
ownerToReturnTo, Qt::CaseInsensitive) == 0;
if (!correctOwner) {
continue;
}
const auto &startZone = zones.value("table");
const auto &targetZone = player->getZones().value("table");
if (startZone == nullptr || targetZone == nullptr) {
continue;
}
moveCard(ges, startZone, QList<const CardToMove *>() << &cardToMove, targetZone, 0, 0, false);
break;
}
}
playerMutex.unlock();
// All borrowed cards have been returned, can now continue cleanup process
clearZones();
Event_PlayerPropertiesChanged event;

View File

@@ -23,6 +23,7 @@
#include "../server_database_interface.h"
#include "../server_protocolhandler.h"
#include "../server_room.h"
#include "libcockatrice/protocol/pb/command_move_card.pb.h"
#include "server_abstract_player.h"
#include "server_arrow.h"
#include "server_card.h"
@@ -31,6 +32,7 @@
#include "server_spectator.h"
#include <QDebug>
#include <QRegularExpression>
#include <QTimer>
#include <google/protobuf/descriptor.h>
#include <libcockatrice/deck_list/deck_list.h>
@@ -824,3 +826,46 @@ void Server_Game::getInfo(ServerInfo_Game &result) const
result.set_start_time(startTime.toSecsSinceEpoch());
}
}
void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player)
{
QMutexLocker locker(&gameMutex);
// Return cards to their rightful owners before conceding the game
static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"};
const auto &playerTable = player->getZones().value("table");
for (const auto &card : playerTable->getCards()) {
if (card == nullptr) {
continue;
}
const auto &regexResult = ownerRegex.match(card->getAnnotation());
if (!regexResult.hasMatch()) {
continue;
}
CardToMove cardToMove;
cardToMove.set_card_id(card->getId());
for (const auto *otherPlayer : getPlayers()) {
if (otherPlayer == nullptr || otherPlayer->getUserInfo() == nullptr) {
continue;
}
const auto &ownerToReturnTo = regexResult.captured(1);
const auto &correctOwner = QString::compare(QString::fromStdString(otherPlayer->getUserInfo()->name()),
ownerToReturnTo, Qt::CaseInsensitive) == 0;
if (!correctOwner) {
continue;
}
const auto &targetZone = otherPlayer->getZones().value("table");
if (playerTable == nullptr || targetZone == nullptr) {
continue;
}
player->moveCard(ges, playerTable, QList<const CardToMove *>() << &cardToMove, targetZone, 0, 0, false);
break;
}
}
}

View File

@@ -218,6 +218,7 @@ public:
GameEventStorageItem::EventRecipients recipients = GameEventStorageItem::SendToPrivate |
GameEventStorageItem::SendToOthers,
int privatePlayerId = -1);
void returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPlayer *player);
};
#endif

View File

@@ -172,7 +172,7 @@
<message>
<location filename="src/pages.cpp" line="726"/>
<source>spoiler</source>
<translation type="unfinished"/>
<translation>spoiler</translation>
</message>
<message>
<location filename="src/pages.cpp" line="731"/>
@@ -192,7 +192,7 @@
<message>
<location filename="src/pages.cpp" line="735"/>
<source>Local file:</source>
<translation type="unfinished"/>
<translation>Local file:</translation>
</message>
<message>
<location filename="src/pages.cpp" line="736"/>
@@ -202,7 +202,7 @@
<message>
<location filename="src/pages.cpp" line="737"/>
<source>Choose file...</source>
<translation type="unfinished"/>
<translation>Choose file...</translation>
</message>
<message>
<location filename="src/pages.cpp" line="739"/>
@@ -230,7 +230,7 @@
<message>
<location filename="src/pages.cpp" line="681"/>
<source>tokens</source>
<translation type="unfinished"/>
<translation>tokens</translation>
</message>
<message>
<location filename="src/pages.cpp" line="686"/>
@@ -250,7 +250,7 @@
<message>
<location filename="src/pages.cpp" line="690"/>
<source>Local file:</source>
<translation type="unfinished"/>
<translation>Local file:</translation>
</message>
<message>
<location filename="src/pages.cpp" line="691"/>
@@ -260,7 +260,7 @@
<message>
<location filename="src/pages.cpp" line="692"/>
<source>Choose file...</source>
<translation type="unfinished"/>
<translation>Choose file...</translation>
</message>
<message>
<location filename="src/pages.cpp" line="694"/>
@@ -391,12 +391,12 @@
<message>
<location filename="src/pagetemplates.cpp" line="72"/>
<source>Load %1 file</source>
<translation type="unfinished"/>
<translation>Load %1 file</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="82"/>
<source>%1 file (%1)</source>
<translation type="unfinished"/>
<translation>%1 file (%1)</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="111"/>
@@ -420,12 +420,12 @@
<message>
<location filename="src/pagetemplates.cpp" line="129"/>
<source>Please choose a file.</source>
<translation type="unfinished"/>
<translation>Please choose a file.</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="134"/>
<source>Cannot open file &apos;%1&apos;.</source>
<translation type="unfinished"/>
<translation>Cannot open file &apos;%1&apos;.</translation>
</message>
<message>
<location filename="src/pagetemplates.cpp" line="159"/>

View File

@@ -116,7 +116,9 @@ void ServerLogger::rotateLogs()
flushBuffer();
logFile->close();
logFile->open(QIODevice::Append);
if (!logFile->open(QIODevice::Append)) {
std::cerr << "ERROR: Failed to open log file for writing!" << std::endl;
}
}
QFile *ServerLogger::logFile;