Compare commits

..

No commits in common. "master" and "2026-06-27-Development-3.1.0-beta.3" have entirely different histories.

70 changed files with 2255 additions and 3452 deletions

View file

@ -83,7 +83,6 @@ set(cockatrice_SOURCES
src/game/game_state.cpp src/game/game_state.cpp
src/game_graphics/game_view.cpp src/game_graphics/game_view.cpp
src/game_graphics/hand_counter.cpp src/game_graphics/hand_counter.cpp
src/game/selection_subtype_tally.cpp
src/game_graphics/log/message_log_widget.cpp src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp src/game/phase.cpp
src/game_graphics/phases_toolbar.cpp src/game_graphics/phases_toolbar.cpp

File diff suppressed because it is too large Load diff

View file

@ -313,7 +313,6 @@ SettingsCache::SettingsCache()
showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool();
showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool();
showSubtypeSelectionTally = settings->value("interface/showsubtypeselectiontally", true).toBool();
showShortcuts = settings->value("menu/showshortcuts", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool();
showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool();
@ -1396,12 +1395,6 @@ void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSele
settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount);
} }
void SettingsCache::setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally)
{
showSubtypeSelectionTally = static_cast<bool>(_showSubtypeSelectionTally);
settings->setValue("interface/showsubtypeselectiontally", showSubtypeSelectionTally);
}
void SettingsCache::loadPaths() void SettingsCache::loadPaths()
{ {
QString dataPath = getDataPath(); QString dataPath = getDataPath();

View file

@ -355,7 +355,6 @@ private:
bool showStatusBar; bool showStatusBar;
bool showDragSelectionCount; bool showDragSelectionCount;
bool showTotalSelectionCount; bool showTotalSelectionCount;
bool showSubtypeSelectionTally;
public: public:
SettingsCache(); SettingsCache();
@ -479,10 +478,6 @@ public:
{ {
return showTotalSelectionCount; return showTotalSelectionCount;
} }
[[nodiscard]] bool getShowSubtypeSelectionTally() const
{
return showSubtypeSelectionTally;
}
[[nodiscard]] bool getNotificationsEnabled() const [[nodiscard]] bool getNotificationsEnabled() const
{ {
return notificationsEnabled; return notificationsEnabled;
@ -1181,6 +1176,5 @@ public slots:
void setRoundCardCorners(bool _roundCardCorners); void setRoundCardCorners(bool _roundCardCorners);
void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount);
void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount);
void setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally);
}; };
#endif #endif

View file

@ -27,9 +27,8 @@
#include <libcockatrice/protocol/pb/command_shuffle.pb.h> #include <libcockatrice/protocol/pb/command_shuffle.pb.h>
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h> #include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h> #include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/utility/clamped_arithmetic.h>
#include <libcockatrice/utility/counter_limits.h>
#include <libcockatrice/utility/expression.h> #include <libcockatrice/utility/expression.h>
#include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
// milliseconds in between triggers of the move top cards until action // milliseconds in between triggers of the move top cards until action
@ -1019,9 +1018,8 @@ void PlayerActions::actCreateAllRelatedCards()
if (!cardRelationAll->getDoesAttach() && !cardRelationAll->getIsVariable()) { if (!cardRelationAll->getDoesAttach() && !cardRelationAll->getIsVariable()) {
dbName = cardRelationAll->getName(); dbName = cardRelationAll->getName();
bool persistent = cardRelationAll->getIsPersistent(); bool persistent = cardRelationAll->getIsPersistent();
bool faceDown = cardRelationAll->getIsFaceDown();
for (int i = 0; i < cardRelationAll->getDefaultCount(); ++i) { for (int i = 0; i < cardRelationAll->getDefaultCount(); ++i) {
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown); createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
} }
++tokensTypesCreated; ++tokensTypesCreated;
if (tokensTypesCreated == 1) { if (tokensTypesCreated == 1) {
@ -1036,9 +1034,8 @@ void PlayerActions::actCreateAllRelatedCards()
if (!cardRelationNotExcluded->getDoesAttach() && !cardRelationNotExcluded->getIsVariable()) { if (!cardRelationNotExcluded->getDoesAttach() && !cardRelationNotExcluded->getIsVariable()) {
dbName = cardRelationNotExcluded->getName(); dbName = cardRelationNotExcluded->getName();
bool persistent = cardRelationNotExcluded->getIsPersistent(); bool persistent = cardRelationNotExcluded->getIsPersistent();
bool faceDown = cardRelationNotExcluded->getIsFaceDown();
for (int i = 0; i < cardRelationNotExcluded->getDefaultCount(); ++i) { for (int i = 0; i < cardRelationNotExcluded->getDefaultCount(); ++i) {
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown); createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
} }
++tokensTypesCreated; ++tokensTypesCreated;
if (tokensTypesCreated == 1) { if (tokensTypesCreated == 1) {
@ -1076,7 +1073,6 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
const QString dbName = cardRelation->getName(); const QString dbName = cardRelation->getName();
const bool persistent = cardRelation->getIsPersistent(); const bool persistent = cardRelation->getIsPersistent();
const bool faceDown = cardRelation->getIsFaceDown();
// Variable relations always use DoesNotAttach, regardless of the count the user // Variable relations always use DoesNotAttach, regardless of the count the user
// entered. // entered.
@ -1085,7 +1081,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
return false; return false;
} }
for (int i = 0; i < variableCount; ++i) { for (int i = 0; i < variableCount; ++i) {
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown); createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
} }
return true; return true;
} }
@ -1094,7 +1090,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
if (count > 1) { if (count > 1) {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown); createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent);
} }
return true; return true;
} }
@ -1114,7 +1110,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
playCardToTable(sourceCard, false); playCardToTable(sourceCard, false);
} }
createCard(sourceCard, dbName, attachType, persistent, faceDown); createCard(sourceCard, dbName, attachType, persistent);
return true; return true;
} }
@ -1141,8 +1137,7 @@ void PlayerActions::onRelatedCardCreated(const CardItem *sourceCard, const CardR
void PlayerActions::createCard(const CardItem *sourceCard, void PlayerActions::createCard(const CardItem *sourceCard,
const QString &dbCardName, const QString &dbCardName,
CardRelationType attachType, CardRelationType attachType,
bool persistent, bool persistent)
bool faceDown)
{ {
CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(dbCardName); CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(dbCardName);
@ -1177,7 +1172,6 @@ void PlayerActions::createCard(const CardItem *sourceCard,
cmd.set_destroy_on_zone_change(!persistent); cmd.set_destroy_on_zone_change(!persistent);
cmd.set_x(gridPoint.x()); cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y()); cmd.set_y(gridPoint.y());
cmd.set_face_down(faceDown);
ExactCard relatedCard = ExactCard relatedCard =
CardDatabaseManager::query()->getCardFromSameSet(cardInfo->getName(), sourceCard->getCard().getPrinting()); CardDatabaseManager::query()->getCardFromSameSet(cardInfo->getName(), sourceCard->getCard().getPrinting());
@ -1531,15 +1525,12 @@ void PlayerActions::offsetCardCounter(QList<CardItem *> selectedCards, int count
QList<const ::google::protobuf::Message *> commandList; QList<const ::google::protobuf::Message *> commandList;
for (auto card : selectedCards) { for (auto card : selectedCards) {
int oldValue = card->getCounters().value(counterId, 0); int oldValue = card->getCounters().value(counterId, 0);
int newValue = oldValue + offset;
// Overflow-safe clamp to the server-enforced range [0, MAX_COUNTER_VALUE]; // Early exit optimization: server enforces [0, MAX_COUNTERS_ON_CARD].
// a result differing from oldValue also corrects an out-of-range cached value. // Compare clamped value to allow recovery from invalid states.
// Callers only ever pass offset == ±1 (actAddCardCounter / actRemoveCardCounter). int clampedValue = qBound(0, newValue, MAX_COUNTERS_ON_CARD);
// This client-side clamp is a defense-in-depth UX check, consistent with if (clampedValue != oldValue) {
// actSetCardCounter and actIncrementAllCardCounters; the server remains the
// authoritative enforcer of the bounds.
int newValue = addClamped(oldValue, offset, 0, MAX_COUNTER_VALUE);
if (newValue != oldValue) {
auto *cmd = new Command_SetCardCounter; auto *cmd = new Command_SetCardCounter;
cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_zone(card->getZone()->getName().toStdString());
cmd->set_card_id(card->getId()); cmd->set_card_id(card->getId());
@ -1572,7 +1563,7 @@ void PlayerActions::actSetCardCounter(QList<CardItem *> selectedCards, int count
Expression exp(oldValue); Expression exp(oldValue);
double parsed = exp.parse(counterValue); double parsed = exp.parse(counterValue);
// Clamp in double precision first to avoid UB, then cast // Clamp in double precision first to avoid UB, then cast
int number = static_cast<int>(qBound(0.0, parsed, static_cast<double>(MAX_COUNTER_VALUE))); int number = static_cast<int>(qBound(0.0, parsed, static_cast<double>(MAX_COUNTERS_ON_CARD)));
auto *cmd = new Command_SetCardCounter; auto *cmd = new Command_SetCardCounter;
cmd->set_zone(card->getZone()->getName().toStdString()); cmd->set_zone(card->getZone()->getName().toStdString());
@ -1602,7 +1593,7 @@ void PlayerActions::actIncrementAllCardCounters(QList<CardItem *> cardsToUpdate)
counterIterator.next(); counterIterator.next();
int counterId = counterIterator.key(); int counterId = counterIterator.key();
int currentValue = counterIterator.value(); int currentValue = counterIterator.value();
if (currentValue >= MAX_COUNTER_VALUE) { if (currentValue >= MAX_COUNTERS_ON_CARD) {
continue; continue;
} }

View file

@ -240,8 +240,7 @@ private:
void createCard(const CardItem *sourceCard, void createCard(const CardItem *sourceCard,
const QString &dbCardName, const QString &dbCardName,
CardRelationType attach = CardRelationType::DoesNotAttach, CardRelationType attach = CardRelationType::DoesNotAttach,
bool persistent = false, bool persistent = false);
bool faceDown = false);
void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false); void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false);

View file

@ -1,64 +0,0 @@
#include "selection_subtype_tally.h"
#include "../game_graphics/board/card_item.h"
#include <QMap>
#include <algorithm>
namespace
{
/** @brief Extracts subtypes from a single card face's type line. */
QStringList extractSubtypesFromFace(const QString &faceType)
{
// Card type format: "Creature — Goblin Warrior" or "Legendary Enchantment — Saga"
QStringList parts = faceType.split(QStringLiteral(""));
if (parts.size() > 1) {
return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts);
}
return {};
}
} // anonymous namespace
namespace SelectionSubtypeTally
{
QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards)
{
QMap<QString, int> subtypeCounts;
for (CardItem *card : cards) {
if (card->getFaceDown() || card->getCard().isEmpty()) {
continue;
}
QString cardType = card->getCardInfo().getCardType();
// Handle double-faced cards: "Creature — Human // Creature — Werewolf"
QStringList cardFaces = cardType.split(QStringLiteral(" // "));
for (const QString &face : cardFaces) {
QStringList subtypes = extractSubtypesFromFace(face);
for (const QString &subtype : subtypes) {
subtypeCounts[subtype]++;
}
}
}
QList<SubtypeEntry> entries;
for (auto it = subtypeCounts.constBegin(); it != subtypeCounts.constEnd(); ++it) {
entries.append({it.key(), it.value()});
}
// Sort by count ascending, then alphabetically (lowest counts at bottom of display)
std::sort(entries.begin(), entries.end(), [](const SubtypeEntry &a, const SubtypeEntry &b) {
if (a.count != b.count) {
return a.count < b.count;
}
return a.name < b.name;
});
return entries;
}
} // namespace SelectionSubtypeTally

View file

@ -1,36 +0,0 @@
#ifndef SELECTION_SUBTYPE_TALLY_H
#define SELECTION_SUBTYPE_TALLY_H
#include <QList>
#include <QString>
class CardItem;
/** @brief A single subtype (e.g., "Goblin", "Warrior") with its occurrence count. */
struct SubtypeEntry
{
QString name; ///< The subtype name
int count; ///< Number of selected cards with this subtype
bool operator==(const SubtypeEntry &other) const
{
return name == other.name && count == other.count;
}
};
/**
* @brief Extracts and tallies subtypes from selected cards.
*/
namespace SelectionSubtypeTally
{
/**
* @brief Parses card type lines and counts each subtype occurrence.
*
* Skips face-down cards and cards without type info.
* @param cards The list of selected card items to analyze.
* @return Entries sorted by count ascending, then alphabetically.
*/
QList<SubtypeEntry> countSubtypes(const QList<CardItem *> &cards);
} // namespace SelectionSubtypeTally
#endif

View file

@ -12,6 +12,7 @@
#include "abstract_card_item.h" #include "abstract_card_item.h"
#include <libcockatrice/network/server/remote/game/server_card.h> #include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/utility/trice_limits.h>
class CardDatabase; class CardDatabase;
class CardDragItem; class CardDragItem;

View file

@ -19,7 +19,7 @@
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h> #include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
#include <libcockatrice/protocol/pb/response_deck_download.pb.h> #include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false) ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false)
{ {

View file

@ -20,7 +20,7 @@
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/models/database/card_database_model.h> #include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/models/database/token/token_display_model.h> #include <libcockatrice/models/database/token/token_display_model.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent) DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent)
: QDialog(parent), predefinedTokens(_predefinedTokens) : QDialog(parent), predefinedTokens(_predefinedTokens)

View file

@ -5,7 +5,7 @@
#include <QSpinBox> #include <QSpinBox>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QWidget> #include <QWidget>
#include <libcockatrice/utility/dice_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgRollDice::DlgRollDice(QWidget *parent) : QDialog(parent) DlgRollDice::DlgRollDice(QWidget *parent) : QDialog(parent)
{ {

View file

@ -1,16 +1,12 @@
#include "game_view.h" #include "game_view.h"
#include "../client/settings/cache_settings.h" #include "../client/settings/cache_settings.h"
#include "../game/selection_subtype_tally.h"
#include "game_scene.h" #include "game_scene.h"
#include <QAction> #include <QAction>
#include <QGridLayout>
#include <QLabel> #include <QLabel>
#include <QLayout>
#include <QResizeEvent> #include <QResizeEvent>
#include <QRubberBand> #include <QRubberBand>
#include <libcockatrice/utility/qt_utils.h>
// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings.
// This subclass disables that behavior so dragCountLabel can appear above it. // This subclass disables that behavior so dragCountLabel can appear above it.
@ -59,40 +55,31 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
refreshShortcuts(); refreshShortcuts();
rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this);
const QString baseProperties = "color: white; " const QString countLabelStyle = "color: white; "
"font-family: monospace; " "font-size: 14px; "
"background-color: rgba(0, 0, 0, 160); " "font-weight: bold; "
"border-radius: 3px; " "background-color: rgba(0, 0, 0, 160); "
"padding: 1px 2px; " "border-radius: 3px; "
"white-space: pre;"; "padding: 1px 2px;";
const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;";
const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;";
const QString subtypeTallyLabelStyle = baseProperties + "font-size: 12px;";
dragCountLabel = new QLabel(this); dragCountLabel = new QLabel(this);
dragCountLabel->setStyleSheet(dragCountLabelStyle); dragCountLabel->setStyleSheet(countLabelStyle);
dragCountLabel->hide(); dragCountLabel->hide();
dragCountLabel->raise(); dragCountLabel->raise();
totalCountLabel = new QLabel(this); totalCountLabel = new QLabel(this);
totalCountLabel->setStyleSheet(totalCountLabelStyle); totalCountLabel->setStyleSheet(countLabelStyle);
totalCountLabel->hide(); totalCountLabel->hide();
subtypeTallyContainer = new QWidget(this);
subtypeTallyContainer->setStyleSheet(subtypeTallyLabelStyle);
subtypeTallyLayout = new QGridLayout(subtypeTallyContainer);
subtypeTallyLayout->setContentsMargins(2, 2, 2, 2);
subtypeTallyLayout->setSpacing(2);
subtypeTallyContainer->hide();
} }
void GameView::resizeEvent(QResizeEvent *event) void GameView::resizeEvent(QResizeEvent *event)
{ {
QGraphicsView::resizeEvent(event); QGraphicsView::resizeEvent(event);
GameScene *s = static_cast<GameScene *>(scene()); GameScene *s = dynamic_cast<GameScene *>(scene());
s->processViewSizeChange(event->size()); if (s) {
s->processViewSizeChange(event->size());
}
updateSceneRect(scene()->sceneRect()); updateSceneRect(scene()->sceneRect());
updateTotalSelectionCount(event->size()); updateTotalSelectionCount(event->size());
@ -177,114 +164,29 @@ void GameView::refreshShortcuts()
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
} }
void GameView::clearSubtypeLabels()
{
QtUtils::clearLayoutRec(subtypeTallyLayout);
}
QSize GameView::rebuildSubtypeLabels(const QList<SubtypeEntry> &entries)
{
clearSubtypeLabels();
const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;");
const QString countStyle =
QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;");
int totalHeight = 0;
int maxNameWidth = 0;
int maxCountWidth = 0;
int row = 0;
for (const SubtypeEntry &entry : entries) {
auto *nameLabel = new QLabel(entry.name, subtypeTallyContainer);
nameLabel->setStyleSheet(nameStyle);
nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
subtypeTallyLayout->addWidget(nameLabel, row, 0);
auto *countLabel = new QLabel(QString::number(entry.count), subtypeTallyContainer);
countLabel->setStyleSheet(countStyle);
countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
subtypeTallyLayout->addWidget(countLabel, row, 1);
QSize nameSize = nameLabel->sizeHint();
QSize countSize = countLabel->sizeHint();
maxNameWidth = qMax(maxNameWidth, nameSize.width());
maxCountWidth = qMax(maxCountWidth, countSize.width());
totalHeight += qMax(nameSize.height(), countSize.height());
++row;
}
int spacing = subtypeTallyLayout->spacing();
int margins = subtypeTallyLayout->contentsMargins().left() + subtypeTallyLayout->contentsMargins().right();
int verticalMargins = subtypeTallyLayout->contentsMargins().top() + subtypeTallyLayout->contentsMargins().bottom();
int width = maxNameWidth + spacing + maxCountWidth + margins;
int height = totalHeight + (row - 1) * spacing + verticalMargins;
return QSize(width, height);
}
void GameView::updateTotalSelectionCount(const QSize &viewSize) void GameView::updateTotalSelectionCount(const QSize &viewSize)
{ {
constexpr int kMarginInPixels = 10; if (!SettingsCache::instance().getShowTotalSelectionCount()) {
constexpr int kSpacingBetweenLabels = 4; totalCountLabel->hide();
return;
int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width(); }
int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height();
int count = scene()->selectedItems().count(); int count = scene()->selectedItems().count();
if (!SettingsCache::instance().getShowTotalSelectionCount() || count <= 1) { if (count > 1) {
totalCountLabel->hide();
} else {
totalCountLabel->setText(QString::number(count)); totalCountLabel->setText(QString::number(count));
totalCountLabel->adjustSize(); totalCountLabel->adjustSize();
constexpr int kMarginInPixels = 10;
int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width();
int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height();
int x = availableWidth - totalCountLabel->width() - kMarginInPixels; int x = availableWidth - totalCountLabel->width() - kMarginInPixels;
int y = availableHeight - totalCountLabel->height() - kMarginInPixels; int y = availableHeight - totalCountLabel->height() - kMarginInPixels;
totalCountLabel->move(x, y); totalCountLabel->move(x, y);
totalCountLabel->show(); totalCountLabel->show();
}
if (!SettingsCache::instance().getShowSubtypeSelectionTally() || count <= 1) {
subtypeTallyContainer->hide();
cachedSubtypeEntries.clear();
return;
}
GameScene *gameScene = static_cast<GameScene *>(scene());
QList<SubtypeEntry> entries = SelectionSubtypeTally::countSubtypes(gameScene->selectedCards());
if (entries.isEmpty()) {
subtypeTallyContainer->hide();
cachedSubtypeEntries.clear();
return;
}
// Only rebuild labels if entries changed
QSize containerSize;
if (entries != cachedSubtypeEntries) {
cachedSubtypeEntries = entries;
containerSize = rebuildSubtypeLabels(entries);
subtypeTallyContainer->resize(containerSize);
} else { } else {
containerSize = subtypeTallyContainer->size(); totalCountLabel->hide();
} }
int x = availableWidth - containerSize.width() - kMarginInPixels;
int y;
if (totalCountLabel->isVisible()) {
y = totalCountLabel->y() - containerSize.height() - kSpacingBetweenLabels;
} else {
y = availableHeight - containerSize.height() - kMarginInPixels;
}
y = qMax(kMarginInPixels, y);
subtypeTallyContainer->move(x, y);
subtypeTallyContainer->show();
} }
/** /**

View file

@ -7,12 +7,9 @@
#ifndef GAMEVIEW_H #ifndef GAMEVIEW_H
#define GAMEVIEW_H #define GAMEVIEW_H
#include "../game/selection_subtype_tally.h"
#include <QGraphicsView> #include <QGraphicsView>
class GameScene; class GameScene;
class QGridLayout;
class QLabel; class QLabel;
class QRubberBand; class QRubberBand;
@ -24,13 +21,7 @@ private:
QRubberBand *rubberBand; QRubberBand *rubberBand;
QLabel *dragCountLabel; QLabel *dragCountLabel;
QLabel *totalCountLabel; QLabel *totalCountLabel;
QWidget *subtypeTallyContainer;
QGridLayout *subtypeTallyLayout;
QPointF selectionOrigin; QPointF selectionOrigin;
QList<SubtypeEntry> cachedSubtypeEntries; ///< Cached entries to avoid redundant rebuilds
QSize rebuildSubtypeLabels(const QList<SubtypeEntry> &entries);
void clearSubtypeLabels();
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;

View file

@ -8,7 +8,6 @@
#include <QInputDialog> #include <QInputDialog>
#include <libcockatrice/card/relation/card_relation.h> #include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/utility/string_limits.h>
PlayerDialogs::PlayerDialogs(PlayerGraphicsItem *_player, PlayerActions *_playerActions) PlayerDialogs::PlayerDialogs(PlayerGraphicsItem *_player, PlayerActions *_playerActions)
: QObject(_player), player(_player), playerActions(_playerActions) : QObject(_player), player(_player), playerActions(_playerActions)

View file

@ -271,9 +271,6 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
const PaletteConfig &palCfg, const PaletteConfig &palCfg,
const QString &activeScheme) const QString &activeScheme)
{ {
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 0))
Q_UNUSED(activeScheme)
#endif
QString styleName = themeCfg.styleName; QString styleName = themeCfg.styleName;
if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) { if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) {
if (themeName == FUSION_THEME_NAME) { if (themeName == FUSION_THEME_NAME) {
@ -399,7 +396,6 @@ static QString roleBgName(ThemeManager::Role role)
default: default:
Q_ASSERT(false); Q_ASSERT(false);
return {};
} }
} }

View file

@ -11,7 +11,7 @@
#include <QSplitter> #include <QSplitter>
#include <QTextEdit> #include <QTextEdit>
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo) static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
{ {

View file

@ -12,7 +12,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QRadioButton> #include <QRadioButton>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
{ {

View file

@ -17,7 +17,7 @@
#include <QSpinBox> #include <QSpinBox>
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h> #include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
void DlgCreateGame::sharedCtor() void DlgCreateGame::sharedCtor()
{ {

View file

@ -8,7 +8,7 @@
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image() DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image()
{ {

View file

@ -7,7 +7,7 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent) DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
{ {

View file

@ -19,7 +19,7 @@
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card_database_model.h> #include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/models/database/token/token_edit_model.h> #include <libcockatrice/models/database/token/token_edit_model.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr) DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr)
{ {

View file

@ -6,7 +6,7 @@
#include <QGridLayout> #include <QGridLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgEditUser::DlgEditUser(QWidget *parent, QString email, QString country, QString realName) : QDialog(parent) DlgEditUser::DlgEditUser(QWidget *parent, QString email, QString country, QString realName) : QDialog(parent)
{ {

View file

@ -7,7 +7,7 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialog(parent) DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialog(parent)
{ {

View file

@ -7,7 +7,7 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(parent) DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(parent)
{ {

View file

@ -7,7 +7,7 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgForgotPasswordReset::DlgForgotPasswordReset(QWidget *parent) : QDialog(parent) DlgForgotPasswordReset::DlgForgotPasswordReset(QWidget *parent) : QDialog(parent)
{ {

View file

@ -62,7 +62,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
// search field // search field
searchField = new LineEditUnfocusable; searchField = new LineEditUnfocusable;
searchField->setObjectName("searchEdit"); searchField->setObjectName("searchEdit");
searchField->setPlaceholderText(tr("Search by set name, code, type, or release date")); searchField->setPlaceholderText(tr("Search by set name, code, or type"));
searchField->addAction(QPixmap("theme:icons/search"), LineEditUnfocusable::LeadingPosition); searchField->addAction(QPixmap("theme:icons/search"), LineEditUnfocusable::LeadingPosition);
searchField->setClearButtonEnabled(true); searchField->setClearButtonEnabled(true);
setFocusProxy(searchField); setFocusProxy(searchField);

View file

@ -8,7 +8,7 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
{ {

View file

@ -30,7 +30,7 @@
#include <libcockatrice/protocol/pb/response_get_games_of_user.pb.h> #include <libcockatrice/protocol/pb/response_get_games_of_user.pb.h>
#include <libcockatrice/protocol/pb/response_get_user_info.pb.h> #include <libcockatrice/protocol/pb/response_get_user_info.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent) BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent)
{ {

View file

@ -6,7 +6,6 @@
#include <QGridLayout> #include <QGridLayout>
#include <QLineEdit> #include <QLineEdit>
#include <QToolBar> #include <QToolBar>
#include <libcockatrice/utility/string_limits.h>
MessagesSettingsPage::MessagesSettingsPage() MessagesSettingsPage::MessagesSettingsPage()
{ {

View file

@ -68,10 +68,6 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setShowTotalSelectionCount); &SettingsCache::setShowTotalSelectionCount);
showSubtypeSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowSubtypeSelectionTally());
connect(&showSubtypeSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setShowSubtypeSelectionTally);
useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus());
connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
[](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); });
@ -90,9 +86,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); generalGrid->addWidget(&annotateTokensCheckBox, 6, 0);
generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0);
generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0);
generalGrid->addWidget(&showSubtypeSelectionTallyCheckBox, 9, 0); generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0);
generalGrid->addWidget(&useTearOffMenusCheckBox, 10, 0); generalGrid->addWidget(&keepGameChatFocusCheckBox, 10, 0);
generalGrid->addWidget(&keepGameChatFocusCheckBox, 11, 0);
generalGroupBox = new QGroupBox; generalGroupBox = new QGroupBox;
generalGroupBox->setLayout(generalGrid); generalGroupBox->setLayout(generalGrid);
@ -214,9 +209,8 @@ void UserInterfaceSettingsPage::retranslateUi()
closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed"));
focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened"));
annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens"));
showDragSelectionCountCheckBox.setText(tr("Show selection count during drag selection")); showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection"));
showTotalSelectionCountCheckBox.setText(tr("Show total selection count")); showTotalSelectionCountCheckBox.setText(tr("Show total selection counter"));
showSubtypeSelectionTallyCheckBox.setText(tr("Show subtype breakdown in selection tally"));
useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen"));
keepGameChatFocusCheckBox.setText( keepGameChatFocusCheckBox.setText(
tr("Keep game chat focused when clicking in game (Note: disables card view search bar)")); tr("Keep game chat focused when clicking in game (Note: disables card view search bar)"));

View file

@ -29,7 +29,6 @@ private:
QCheckBox annotateTokensCheckBox; QCheckBox annotateTokensCheckBox;
QCheckBox showDragSelectionCountCheckBox; QCheckBox showDragSelectionCountCheckBox;
QCheckBox showTotalSelectionCountCheckBox; QCheckBox showTotalSelectionCountCheckBox;
QCheckBox showSubtypeSelectionTallyCheckBox;
QCheckBox useTearOffMenusCheckBox; QCheckBox useTearOffMenusCheckBox;
QCheckBox keepGameChatFocusCheckBox; QCheckBox keepGameChatFocusCheckBox;
QCheckBox tapAnimationCheckBox; QCheckBox tapAnimationCheckBox;

View file

@ -43,7 +43,7 @@
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h> #include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
#include <libcockatrice/protocol/pb/response.pb.h> #include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
/** /**
* @brief Constructs the AbstractTabDeckEditor. * @brief Constructs the AbstractTabDeckEditor.

View file

@ -17,7 +17,7 @@
#include <libcockatrice/protocol/pb/response_list_users.pb.h> #include <libcockatrice/protocol/pb/response_list_users.pb.h>
#include <libcockatrice/protocol/pb/session_commands.pb.h> #include <libcockatrice/protocol/pb/session_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
TabAccount::TabAccount(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo) TabAccount::TabAccount(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo)
: Tab(_tabSupervisor), client(_client) : Tab(_tabSupervisor), client(_client)

View file

@ -13,7 +13,7 @@
#include <libcockatrice/protocol/pb/event_replay_added.pb.h> #include <libcockatrice/protocol/pb/event_replay_added.pb.h>
#include <libcockatrice/protocol/pb/moderator_commands.pb.h> #include <libcockatrice/protocol/pb/moderator_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent) ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
{ {

View file

@ -21,6 +21,7 @@
#include <libcockatrice/models/database/card_database_model.h> #include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/network/client/abstract/abstract_client.h> #include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
/** /**
* @brief Constructs a new TabDeckEditor object. * @brief Constructs a new TabDeckEditor object.

View file

@ -28,7 +28,6 @@
#include <libcockatrice/protocol/pb/response_deck_download.pb.h> #include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pb/response_deck_upload.pb.h> #include <libcockatrice/protocol/pb/response_deck_upload.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h>
TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
AbstractClient *_client, AbstractClient *_client,

View file

@ -44,7 +44,7 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h> #include <libcockatrice/protocol/pb/game_replay.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h> #include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
: Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr) : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr)

View file

@ -17,7 +17,7 @@
#include <libcockatrice/protocol/pb/moderator_commands.pb.h> #include <libcockatrice/protocol/pb/moderator_commands.pb.h>
#include <libcockatrice/protocol/pb/response_viewlog_history.pb.h> #include <libcockatrice/protocol/pb/response_viewlog_history.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
{ {

View file

@ -17,7 +17,7 @@
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/protocol/pb/session_commands.pb.h> #include <libcockatrice/protocol/pb/session_commands.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
TabMessage::TabMessage(TabSupervisor *_tabSupervisor, TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
AbstractClient *_client, AbstractClient *_client,

View file

@ -31,7 +31,7 @@
#include <libcockatrice/protocol/pb/room_commands.pb.h> #include <libcockatrice/protocol/pb/room_commands.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h> #include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
TabRoom::TabRoom(TabSupervisor *_tabSupervisor, TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
AbstractClient *_client, AbstractClient *_client,

View file

@ -9,7 +9,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QWidget> #include <QWidget>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
QString getTextWithMax(QWidget *parent, QString getTextWithMax(QWidget *parent,
const QString &title, const QString &title,

View file

@ -6,7 +6,6 @@
<xs:attribute type="xs:string" name="exclude" use="optional" /> <xs:attribute type="xs:string" name="exclude" use="optional" />
<xs:attribute type="xs:string" name="attach" use="optional" /> <xs:attribute type="xs:string" name="attach" use="optional" />
<xs:attribute type="xs:string" name="persistent" use="optional" /> <xs:attribute type="xs:string" name="persistent" use="optional" />
<xs:attribute type="xs:string" name="facedown" use="optional"/>
</xs:extension> </xs:extension>
</xs:simpleContent> </xs:simpleContent>
</xs:complexType> </xs:complexType>

View file

@ -85,8 +85,7 @@ void CardDatabase::refreshCachedReverseRelatedCards()
for (auto *rel : card->getReverseRelatedCards()) { for (auto *rel : card->getReverseRelatedCards()) {
if (auto target = cards.value(rel->getName())) { if (auto target = cards.value(rel->getName())) {
auto *newRel = new CardRelation(card->getName(), rel->getAttachType(), rel->getIsCreateAllExclusion(), auto *newRel = new CardRelation(card->getName(), rel->getAttachType(), rel->getIsCreateAllExclusion(),
rel->getIsVariable(), rel->getDefaultCount(), rel->getIsPersistent(), rel->getIsVariable(), rel->getDefaultCount(), rel->getIsPersistent());
rel->getIsFaceDown());
target->addReverseRelatedCards2Me(newRel); target->addReverseRelatedCards2Me(newRel);
} }
} }

View file

@ -329,7 +329,6 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
bool exclude = false; bool exclude = false;
bool variable = false; bool variable = false;
bool persistent = false; bool persistent = false;
bool facedown = false;
int count = 1; int count = 1;
QXmlStreamAttributes attrs = xml.attributes(); QXmlStreamAttributes attrs = xml.attributes();
QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements); QString cardName = xml.readElementText(QXmlStreamReader::IncludeChildElements);
@ -361,12 +360,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
persistent = true; persistent = true;
} }
if (attrs.hasAttribute("facedown")) { auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
facedown = true;
}
auto *relation =
new CardRelation(cardName, attachType, exclude, variable, count, persistent, facedown);
if (xmlName == "reverse-related") { if (xmlName == "reverse-related") {
reverseRelatedCards << relation; reverseRelatedCards << relation;
} else { } else {
@ -516,9 +510,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
if (i->getIsPersistent()) { if (i->getIsPersistent()) {
xml.writeAttribute("persistent", "persistent"); xml.writeAttribute("persistent", "persistent");
} }
if (i->getIsFaceDown()) {
xml.writeAttribute("facedown", "facedown");
}
if (i->getIsVariable()) { if (i->getIsVariable()) {
if (1 == i->getDefaultCount()) { if (1 == i->getDefaultCount()) {
xml.writeAttribute("count", "x"); xml.writeAttribute("count", "x");

View file

@ -7,10 +7,8 @@ CardRelation::CardRelation(const QString &_name,
bool _isCreateAllExclusion, bool _isCreateAllExclusion,
bool _isVariableCount, bool _isVariableCount,
int _defaultCount, int _defaultCount,
bool _isPersistent, bool _isPersistent)
bool _isFaceDown)
: name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion), : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent), isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
isFaceDown(_isFaceDown)
{ {
} }

View file

@ -31,7 +31,6 @@ private:
bool isVariableCount; ///< True if the number of creations is variable. bool isVariableCount; ///< True if the number of creations is variable.
int defaultCount; ///< Default number of cards created or involved. int defaultCount; ///< Default number of cards created or involved.
bool isPersistent; ///< True if this relation persists (i.e. is not destroyed) on zone change. bool isPersistent; ///< True if this relation persists (i.e. is not destroyed) on zone change.
bool isFaceDown; ///< True if this relation creates the tokens facedown
public: public:
/** /**
@ -43,15 +42,13 @@ public:
* @param _isVariableCount Whether the count is variable. * @param _isVariableCount Whether the count is variable.
* @param _defaultCount Default number for creations or transformations. * @param _defaultCount Default number for creations or transformations.
* @param _isPersistent Whether the relation persists across zone changes. * @param _isPersistent Whether the relation persists across zone changes.
* @param _isFaceDown Whether the relation creates the token face down
*/ */
explicit CardRelation(const QString &_name = QString(), explicit CardRelation(const QString &_name = QString(),
CardRelationType _attachType = CardRelationType::DoesNotAttach, CardRelationType _attachType = CardRelationType::DoesNotAttach,
bool _isCreateAllExclusion = false, bool _isCreateAllExclusion = false,
bool _isVariableCount = false, bool _isVariableCount = false,
int _defaultCount = 1, int _defaultCount = 1,
bool _isPersistent = false, bool _isPersistent = false);
bool _isFaceDown = false);
/** /**
* @brief Returns the name of the related card. * @brief Returns the name of the related card.
@ -154,16 +151,6 @@ public:
{ {
return isPersistent; return isPersistent;
} }
/**
* @brief Returns whether the relation creates the token facedown.
*
* @return True if facedown, false otherwise.
*/
[[nodiscard]] bool getIsFaceDown() const
{
return isFaceDown;
}
}; };
#endif // COCKATRICE_CARD_RELATION_H #endif // COCKATRICE_CARD_RELATION_H

View file

@ -303,14 +303,12 @@ bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &source
auto typeIndex = sourceModel()->index(sourceRow, SetsModel::SetTypeCol, sourceParent); auto typeIndex = sourceModel()->index(sourceRow, SetsModel::SetTypeCol, sourceParent);
auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent); auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent);
auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent); auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent);
auto dateIndex = sourceModel()->index(sourceRow, SetsModel::ReleaseDateCol, sourceParent);
const auto filter = filterRegularExpression(); const auto filter = filterRegularExpression();
return (sourceModel()->data(typeIndex).toString().contains(filter) || return (sourceModel()->data(typeIndex).toString().contains(filter) ||
sourceModel()->data(nameIndex).toString().contains(filter) || sourceModel()->data(nameIndex).toString().contains(filter) ||
sourceModel()->data(shortNameIndex).toString().contains(filter) || sourceModel()->data(shortNameIndex).toString().contains(filter));
sourceModel()->data(dateIndex).toString().contains(filter));
} }
bool SetsDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool SetsDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const

View file

@ -48,7 +48,7 @@
#include <libcockatrice/protocol/pb/response.pb.h> #include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h> #include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game, Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game,
int _playerId, int _playerId,

View file

@ -47,8 +47,7 @@
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h> #include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/rng/rng_abstract.h> #include <libcockatrice/rng/rng_abstract.h>
#include <libcockatrice/utility/dice_limits.h> #include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/string_limits.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
#include <limits> #include <limits>
#include <ranges> #include <ranges>

View file

@ -26,8 +26,7 @@
#include <libcockatrice/protocol/pb/event_set_card_attr.pb.h> #include <libcockatrice/protocol/pb/event_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h> #include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h> #include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
#include <libcockatrice/utility/clamped_arithmetic.h> #include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/counter_limits.h>
#include <limits> #include <limits>
Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone) Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone)
@ -115,8 +114,8 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue
bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event) bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
{ {
// Clamp to valid card counter range [0, MAX_COUNTER_VALUE] // Clamp to valid card counter range [0, MAX_COUNTERS_ON_CARD]
value = qBound(0, value, MAX_COUNTER_VALUE); value = qBound(0, value, MAX_COUNTERS_ON_CARD);
const int oldValue = counters.value(_id, 0); const int oldValue = counters.value(_id, 0);
if (value == oldValue) { if (value == oldValue) {
@ -140,8 +139,10 @@ bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
bool Server_Card::incrementCounter(int counterId, int delta, Event_SetCardCounter *event) bool Server_Card::incrementCounter(int counterId, int delta, Event_SetCardCounter *event)
{ {
const int oldValue = counters.value(counterId, 0); const int oldValue = counters.value(counterId, 0);
// Clamp to [0, MAX_COUNTER_VALUE] for card counters const auto result = static_cast<int64_t>(oldValue) + static_cast<int64_t>(delta);
const int newValue = addClamped(oldValue, delta, 0, MAX_COUNTER_VALUE); // Clamp to [0, MAX_COUNTERS_ON_CARD] for card counters
const int newValue =
static_cast<int>(qBound(static_cast<int64_t>(0), result, static_cast<int64_t>(MAX_COUNTERS_ON_CARD)));
if (newValue == oldValue) { if (newValue == oldValue) {
return false; return false;

View file

@ -156,7 +156,7 @@ public:
/** /**
* @brief Sets a card counter to an exact value with clamping. * @brief Sets a card counter to an exact value with clamping.
* @param _id The counter ID. * @param _id The counter ID.
* @param value The desired value (clamped to [0, MAX_COUNTER_VALUE]; 0 removes the counter). * @param value The desired value (clamped to [0, MAX_COUNTERS_ON_CARD]; 0 removes the counter).
* @param event Optional event to populate with counter state. * @param event Optional event to populate with counter state.
* @return true if the value changed, false otherwise. * @return true if the value changed, false otherwise.
*/ */
@ -168,7 +168,7 @@ public:
* @param event Optional event to populate with counter state. * @param event Optional event to populate with counter state.
* @return true if the value changed, false otherwise. * @return true if the value changed, false otherwise.
* @note If counter does not exist, starts from 0. Counter is removed if result is 0. * @note If counter does not exist, starts from 0. Counter is removed if result is 0.
* @note Clamps result to [0, MAX_COUNTER_VALUE]. * @note Clamps result to [0, MAX_COUNTERS_ON_CARD].
*/ */
[[nodiscard]] bool incrementCounter(int counterId, int delta, Event_SetCardCounter *event = nullptr); [[nodiscard]] bool incrementCounter(int counterId, int delta, Event_SetCardCounter *event = nullptr);
void setTapped(bool _tapped) void setTapped(bool _tapped)

View file

@ -1,12 +1,24 @@
#include "server_counter.h" #include "server_counter.h"
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h> #include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
#include <limits>
Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count) Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count)
: id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count) : id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count)
{ {
} }
//! \todo Extract overflow-safe arithmetic into shared helper.
//! Duplicated in Server_Card::incrementCounter() - keep in sync if modified.
bool Server_Counter::incrementCount(int delta)
{
const int oldCount = count;
const auto result = static_cast<int64_t>(count) + static_cast<int64_t>(delta);
count = static_cast<int>(qBound(static_cast<int64_t>(std::numeric_limits<int>::min()), result,
static_cast<int64_t>(std::numeric_limits<int>::max())));
return count != oldCount;
}
void Server_Counter::getInfo(ServerInfo_Counter *info) void Server_Counter::getInfo(ServerInfo_Counter *info)
{ {
info->set_id(id); info->set_id(id);

View file

@ -22,8 +22,6 @@
#include <QString> #include <QString>
#include <libcockatrice/protocol/pb/color.pb.h> #include <libcockatrice/protocol/pb/color.pb.h>
#include <libcockatrice/utility/clamped_arithmetic.h>
#include <limits>
class ServerInfo_Counter; class ServerInfo_Counter;
@ -94,12 +92,7 @@ public:
* @return true if the value changed, false otherwise. * @return true if the value changed, false otherwise.
* @note Clamps result to [INT_MIN, INT_MAX] to prevent overflow. * @note Clamps result to [INT_MIN, INT_MAX] to prevent overflow.
*/ */
[[nodiscard]] bool incrementCount(int delta) [[nodiscard]] bool incrementCount(int delta);
{
const int oldCount = count;
count = addClamped(count, delta, std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
return count != oldCount;
}
/** /**
* @brief Populates info with this counter's current state for network serialization. * @brief Populates info with this counter's current state for network serialization.

View file

@ -47,7 +47,7 @@
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/rng/rng_abstract.h> #include <libcockatrice/rng/rng_abstract.h>
#include <libcockatrice/utility/color.h> #include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
Server_Player::Server_Player(Server_Game *_game, Server_Player::Server_Player(Server_Game *_game,

View file

@ -26,7 +26,7 @@
#include <libcockatrice/protocol/pb/response_list_users.pb.h> #include <libcockatrice/protocol/pb/response_list_users.pb.h>
#include <libcockatrice/protocol/pb/response_login.pb.h> #include <libcockatrice/protocol/pb/response_login.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_ProtocolHandler::Server_ProtocolHandler(Server *_server,
Server_DatabaseInterface *_databaseInterface, Server_DatabaseInterface *_databaseInterface,

View file

@ -15,7 +15,7 @@
#include <libcockatrice/protocol/pb/room_commands.pb.h> #include <libcockatrice/protocol/pb/room_commands.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_chat_message.pb.h> #include <libcockatrice/protocol/pb/serverinfo_chat_message.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h> #include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
Server_Room::Server_Room(int _id, Server_Room::Server_Room(int _id,
int _chatHistorySize, int _chatHistorySize,

View file

@ -5,7 +5,7 @@
#include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h> #include <google/protobuf/message.h>
#include <google/protobuf/text_format.h> #include <google/protobuf/text_format.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
// FastFieldValuePrinter is added in protobuf 3.4, going out of our way to add the old FieldValuePrinter is not worth it // FastFieldValuePrinter is added in protobuf 3.4, going out of our way to add the old FieldValuePrinter is not worth it
#if GOOGLE_PROTOBUF_VERSION > 3004000 #if GOOGLE_PROTOBUF_VERSION > 3004000

View file

@ -15,10 +15,7 @@ set(UTILITY_HEADERS
libcockatrice/utility/levenshtein.h libcockatrice/utility/levenshtein.h
libcockatrice/utility/macros.h libcockatrice/utility/macros.h
libcockatrice/utility/passwordhasher.h libcockatrice/utility/passwordhasher.h
libcockatrice/utility/string_limits.h libcockatrice/utility/trice_limits.h
libcockatrice/utility/dice_limits.h
libcockatrice/utility/counter_limits.h
libcockatrice/utility/clamped_arithmetic.h
libcockatrice/utility/zone_names.h libcockatrice/utility/zone_names.h
libcockatrice/utility/days_years_between.h libcockatrice/utility/days_years_between.h
) )

View file

@ -1,22 +0,0 @@
#ifndef CLAMPED_ARITHMETIC_H
#define CLAMPED_ARITHMETIC_H
#include <QtGlobal>
#include <cstdint>
/**
* @brief Overflow-safe clamped addition: returns value + delta bounded to [minValue, maxValue].
*
* Uses a 64-bit intermediate so the addition cannot overflow int. Shared by the bounded
* counter arithmetic in both the client and the server.
*
* @note Requires minValue <= maxValue. Bounds come from trusted compile-time call sites;
* qBound() asserts this internally in debug builds.
*/
inline int addClamped(int value, int delta, int minValue, int maxValue)
{
const auto result = static_cast<int64_t>(value) + static_cast<int64_t>(delta);
return static_cast<int>(qBound(static_cast<int64_t>(minValue), result, static_cast<int64_t>(maxValue)));
}
#endif // CLAMPED_ARITHMETIC_H

View file

@ -1,17 +0,0 @@
#ifndef COUNTER_LIMITS_H
#define COUNTER_LIMITS_H
/**
* @brief Upper bound for a bounded counter's value: [0, MAX_COUNTER_VALUE].
*
* Caps an individual counter's VALUE (e.g. a +1/+1 counter at 999), not how many counters
* something holds. Applies to counters that are constrained to a non-negative display range,
* such as card counters and commander tax. Unbounded counters (e.g. a player's life total)
* do not use this limit and may go negative, saturating only at the int range.
*
* The max of 999 is a display constraint (3-digit rendering) and a reasonable gameplay limit.
* The server enforces these bounds; the client may also check them for UX optimization.
*/
constexpr int MAX_COUNTER_VALUE = 999;
#endif // COUNTER_LIMITS_H

View file

@ -1,15 +0,0 @@
#ifndef DICE_LIMITS_H
#define DICE_LIMITS_H
#include <QtGlobal> // for uint
/** @brief Fewest sides a rollable die may have. */
constexpr uint MINIMUM_DIE_SIDES = 2;
/** @brief Most sides a rollable die may have. */
constexpr uint MAXIMUM_DIE_SIDES = 1000000;
/** @brief Fewest dice that may be rolled at once. */
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
/** @brief Most dice that may be rolled at once. */
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
#endif // DICE_LIMITS_H

View file

@ -1,6 +1,5 @@
#ifndef COCKATRICE_QT_UTILS_H #ifndef COCKATRICE_QT_UTILS_H
#define COCKATRICE_QT_UTILS_H #define COCKATRICE_QT_UTILS_H
#include <QLayout>
#include <QObject> #include <QObject>
namespace QtUtils namespace QtUtils

View file

@ -1,31 +0,0 @@
#ifndef STRING_LIMITS_H
#define STRING_LIMITS_H
#include <QString>
#include <algorithm>
#include <string>
/** @brief Max size for short strings, like names and things that are generally a single phrase. */
constexpr int MAX_NAME_LENGTH = 0xff;
/** @brief Max size for chat messages and text contents. */
constexpr int MAX_TEXT_LENGTH = 0xfff;
/** @brief Max size for deck files and pictures (about 2 megabytes). */
constexpr int MAX_FILE_LENGTH = 0x1fffff;
/** @brief Returns a QString from a std::string, truncated to at most MAX_NAME_LENGTH bytes. */
inline QString nameFromStdString(const std::string &_string)
{
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_NAME_LENGTH));
}
/** @brief Returns a QString from a std::string, truncated to at most MAX_TEXT_LENGTH bytes. */
inline QString textFromStdString(const std::string &_string)
{
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_TEXT_LENGTH));
}
/** @brief Returns a QString from a std::string, truncated to at most MAX_FILE_LENGTH bytes. */
inline QString fileFromStdString(const std::string &_string)
{
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_FILE_LENGTH));
}
#endif // STRING_LIMITS_H

View file

@ -0,0 +1,38 @@
#ifndef TRICE_LIMITS_H
#define TRICE_LIMITS_H
#include <QString>
// max size for short strings, like names and things that are generally a single phrase
constexpr int MAX_NAME_LENGTH = 0xff;
// max size for chat messages and text contents
constexpr int MAX_TEXT_LENGTH = 0xfff;
// max size for deck files and pictures
constexpr int MAX_FILE_LENGTH = 0x1fffff; // about 2 megabytes
constexpr uint MINIMUM_DIE_SIDES = 2;
constexpr uint MAXIMUM_DIE_SIDES = 1000000;
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
// Card counter value bounds [0, MAX_COUNTERS_ON_CARD].
// Counters on cards (e.g., +1/+1 counters, charge counters) are non-negative physical game objects.
// The max of 999 is a display constraint (3-digit rendering) and reasonable gameplay limit.
// Server enforces these bounds; client may also check for UX optimization.
constexpr int MAX_COUNTERS_ON_CARD = 999;
// optimized functions to get qstrings that are at most that long
static inline QString nameFromStdString(const std::string &_string)
{
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_NAME_LENGTH));
}
static inline QString textFromStdString(const std::string &_string)
{
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_TEXT_LENGTH));
}
static inline QString fileFromStdString(const std::string &_string)
{
return QString::fromUtf8(_string.data(), std::min(int(_string.size()), MAX_FILE_LENGTH));
}
#endif // TRICE_LIMITS_H

View file

@ -278,7 +278,7 @@
<context> <context>
<name>OracleImporter</name> <name>OracleImporter</name>
<message> <message>
<location filename="src/oracleimporter.cpp" line="542"/> <location filename="src/oracleimporter.cpp" line="540"/>
<source>Dummy set containing tokens</source> <source>Dummy set containing tokens</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -286,7 +286,7 @@
<context> <context>
<name>OracleWizard</name> <name>OracleWizard</name>
<message> <message>
<location filename="src/oraclewizard.cpp" line="101"/> <location filename="src/oraclewizard.cpp" line="97"/>
<source>Oracle Importer</source> <source>Oracle Importer</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>

View file

@ -83,7 +83,7 @@
#include <libcockatrice/protocol/pb/serverinfo_deckstorage.pb.h> #include <libcockatrice/protocol/pb/serverinfo_deckstorage.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_replay.pb.h> #include <libcockatrice/protocol/pb/serverinfo_replay.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h> #include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/string_limits.h> #include <libcockatrice/utility/trice_limits.h>
#include <server_response_containers.h> #include <server_response_containers.h>
#include <server_room.h> #include <server_room.h>
#include <string> #include <string>

View file

@ -4,7 +4,6 @@ enable_testing()
add_test(NAME dummy_test COMMAND dummy_test) add_test(NAME dummy_test COMMAND dummy_test)
add_test(NAME expression_test COMMAND expression_test) add_test(NAME expression_test COMMAND expression_test)
add_test(NAME clamped_arithmetic_test COMMAND clamped_arithmetic_test)
add_test(NAME test_age_formatting COMMAND test_age_formatting) add_test(NAME test_age_formatting COMMAND test_age_formatting)
add_test(NAME password_hash_test COMMAND password_hash_test) add_test(NAME password_hash_test COMMAND password_hash_test)
add_test(NAME server_card_counter_test COMMAND server_card_counter_test) add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
@ -17,7 +16,6 @@ set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5)
add_executable(dummy_test dummy_test.cpp) add_executable(dummy_test dummy_test.cpp)
add_executable(expression_test expression_test.cpp) add_executable(expression_test expression_test.cpp)
add_executable(clamped_arithmetic_test clamped_arithmetic_test.cpp)
add_executable(test_age_formatting test_age_formatting.cpp) add_executable(test_age_formatting test_age_formatting.cpp)
add_executable(password_hash_test password_hash_test.cpp) add_executable(password_hash_test password_hash_test.cpp)
add_executable(deck_hash_performance_test deck_hash_performance_test.cpp) add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
@ -51,7 +49,6 @@ if(NOT GTEST_FOUND)
set(GTEST_BOTH_LIBRARIES gtest) set(GTEST_BOTH_LIBRARIES gtest)
add_dependencies(dummy_test gtest) add_dependencies(dummy_test gtest)
add_dependencies(expression_test gtest) add_dependencies(expression_test gtest)
add_dependencies(clamped_arithmetic_test gtest)
add_dependencies(test_age_formatting gtest) add_dependencies(test_age_formatting gtest)
add_dependencies(password_hash_test gtest) add_dependencies(password_hash_test gtest)
add_dependencies(deck_hash_performance_test gtest) add_dependencies(deck_hash_performance_test gtest)
@ -62,9 +59,6 @@ endif()
include_directories(${GTEST_INCLUDE_DIRS}) include_directories(${GTEST_INCLUDE_DIRS})
target_link_libraries(dummy_test Threads::Threads ${GTEST_BOTH_LIBRARIES}) target_link_libraries(dummy_test Threads::Threads ${GTEST_BOTH_LIBRARIES})
target_link_libraries(expression_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) target_link_libraries(expression_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
target_link_libraries(
clamped_arithmetic_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
)
target_link_libraries( target_link_libraries(
test_age_formatting libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} test_age_formatting libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
) )

View file

@ -1,44 +0,0 @@
/** @file clamped_arithmetic_test.cpp
* @brief Tests for shared helpers in clamped_arithmetic.h.
* @ingroup Tests
*/
#include <gtest/gtest.h>
#include <libcockatrice/utility/clamped_arithmetic.h>
#include <limits>
TEST(AddClamped, AddsWithinBounds)
{
EXPECT_EQ(addClamped(5, 3, 0, 100), 8);
EXPECT_EQ(addClamped(10, -3, 0, 100), 7);
}
TEST(AddClamped, ClampsToUpperAndLowerBound)
{
EXPECT_EQ(addClamped(99, 5, 0, 100), 100); // saturates at max
EXPECT_EQ(addClamped(2, -10, 0, 100), 0); // saturates at min
EXPECT_EQ(addClamped(999, 1, 0, 999), 999); // crossing the counter cap holds at the bound
}
TEST(AddClamped, IntOverflowDoesNotWrap)
{
// The 64-bit intermediate must prevent signed-int overflow UB.
constexpr int intMax = std::numeric_limits<int>::max();
constexpr int intMin = std::numeric_limits<int>::min();
EXPECT_EQ(addClamped(intMax, 1, intMin, intMax), intMax);
EXPECT_EQ(addClamped(intMax, intMax, intMin, intMax), intMax);
}
TEST(AddClamped, IntUnderflowDoesNotWrap)
{
constexpr int intMax = std::numeric_limits<int>::max();
constexpr int intMin = std::numeric_limits<int>::min();
EXPECT_EQ(addClamped(intMin, -1, intMin, intMax), intMin);
EXPECT_EQ(addClamped(intMin, intMin, intMin, intMax), intMin);
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -7,7 +7,7 @@
#include <libcockatrice/network/server/remote/game/server_card.h> #include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h> #include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
#include <libcockatrice/utility/card_ref.h> #include <libcockatrice/utility/card_ref.h>
#include <libcockatrice/utility/counter_limits.h> #include <libcockatrice/utility/trice_limits.h>
#include <limits> #include <limits>
TEST(ServerCardCounter, IncrementNewCounter) TEST(ServerCardCounter, IncrementNewCounter)
@ -28,9 +28,9 @@ TEST(ServerCardCounter, IncrementExistingCounter)
TEST(ServerCardCounter, IncrementOverflowProtection) TEST(ServerCardCounter, IncrementOverflowProtection)
{ {
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE)); ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD));
EXPECT_FALSE(card.incrementCounter(1, 1)); EXPECT_FALSE(card.incrementCounter(1, 1));
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE); EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
} }
TEST(ServerCardCounter, DecrementUnderflowProtection) TEST(ServerCardCounter, DecrementUnderflowProtection)
@ -113,13 +113,13 @@ TEST(ServerCardCounter, IncrementCounterPopulatesEvent)
TEST(ServerCardCounter, IncrementCounterEventReflectsClampedValue) TEST(ServerCardCounter, IncrementCounterEventReflectsClampedValue)
{ {
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE - 5)); ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5));
Event_SetCardCounter event; Event_SetCardCounter event;
EXPECT_TRUE(card.incrementCounter(1, 10, &event)); EXPECT_TRUE(card.incrementCounter(1, 10, &event));
EXPECT_EQ(event.counter_id(), 1); EXPECT_EQ(event.counter_id(), 1);
EXPECT_EQ(event.counter_value(), MAX_COUNTER_VALUE); EXPECT_EQ(event.counter_value(), MAX_COUNTERS_ON_CARD);
} }
TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr) TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr)
@ -133,7 +133,7 @@ TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr)
TEST(ServerCardCounter, IncrementCounterEventNotPopulatedWhenUnchanged) TEST(ServerCardCounter, IncrementCounterEventNotPopulatedWhenUnchanged)
{ {
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE)); ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD));
Event_SetCardCounter event; Event_SetCardCounter event;
event.set_counter_id(999); event.set_counter_id(999);
@ -156,7 +156,7 @@ TEST(ServerCardCounter, SetCounterClampsAboveMaxToMax)
{ {
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
EXPECT_TRUE(card.setCounter(1, 1500)); EXPECT_TRUE(card.setCounter(1, 1500));
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE); EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
} }
TEST(ServerCardCounter, IncrementDoesNotGoBelowZero) TEST(ServerCardCounter, IncrementDoesNotGoBelowZero)
@ -171,9 +171,9 @@ TEST(ServerCardCounter, IncrementDoesNotGoBelowZero)
TEST(ServerCardCounter, IncrementDoesNotExceedMax) TEST(ServerCardCounter, IncrementDoesNotExceedMax)
{ {
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE - 5)); ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5));
EXPECT_TRUE(card.incrementCounter(1, 10)); EXPECT_TRUE(card.incrementCounter(1, 10));
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE); EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
} }
int main(int argc, char **argv) int main(int argc, char **argv)