mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-02 03:23:56 -07:00
Compare commits
10 commits
2026-06-26
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baddbfae14 | ||
|
|
9b0348240d | ||
|
|
18b23b19a7 | ||
|
|
4a384f2a75 | ||
|
|
05ae6f47a6 | ||
|
|
fcac7493ad | ||
|
|
055ba9a16f | ||
|
|
ad4922537d | ||
|
|
4cbc00b9c4 | ||
|
|
6dc974a05d |
89 changed files with 3717 additions and 2345 deletions
12
.github/workflows/desktop-build.yml
vendored
12
.github/workflows/desktop-build.yml
vendored
|
|
@ -162,7 +162,7 @@ jobs:
|
||||||
|
|
||||||
- name: "Restore compiler cache (ccache)"
|
- name: "Restore compiler cache (ccache)"
|
||||||
id: ccache_restore
|
id: ccache_restore
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
env:
|
env:
|
||||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
with:
|
with:
|
||||||
|
|
@ -215,7 +215,7 @@ jobs:
|
||||||
|
|
||||||
- name: "Save updated compiler cache (ccache)"
|
- name: "Save updated compiler cache (ccache)"
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: actions/cache/save@v5
|
uses: actions/cache/save@v6
|
||||||
with:
|
with:
|
||||||
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
||||||
path: ${{ env.CACHE }}
|
path: ${{ env.CACHE }}
|
||||||
|
|
@ -365,7 +365,7 @@ jobs:
|
||||||
- name: "[macOS] Restore compiler cache (ccache)"
|
- name: "[macOS] Restore compiler cache (ccache)"
|
||||||
if: matrix.os == 'macOS' && matrix.use_ccache == 1
|
if: matrix.os == 'macOS' && matrix.use_ccache == 1
|
||||||
id: ccache_restore
|
id: ccache_restore
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
env:
|
env:
|
||||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
with:
|
with:
|
||||||
|
|
@ -387,7 +387,7 @@ jobs:
|
||||||
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
|
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
|
||||||
if: matrix.os == 'macOS'
|
if: matrix.os == 'macOS'
|
||||||
id: restore_qt
|
id: restore_qt
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
|
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
|
||||||
path: ${{ github.workspace }}/Qt
|
path: ${{ github.workspace }}/Qt
|
||||||
|
|
@ -410,7 +410,7 @@ jobs:
|
||||||
|
|
||||||
- name: "[macOS] Cache thin Qt libraries"
|
- name: "[macOS] Cache thin Qt libraries"
|
||||||
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
|
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache/save@v5
|
uses: actions/cache/save@v6
|
||||||
with:
|
with:
|
||||||
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
|
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
|
||||||
path: ${{ github.workspace }}/Qt
|
path: ${{ github.workspace }}/Qt
|
||||||
|
|
@ -473,7 +473,7 @@ jobs:
|
||||||
|
|
||||||
- name: "[macOS] Save updated compiler cache (ccache)"
|
- name: "[macOS] Save updated compiler cache (ccache)"
|
||||||
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
|
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
|
||||||
uses: actions/cache/save@v5
|
uses: actions/cache/save@v6
|
||||||
with:
|
with:
|
||||||
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ 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
|
|
@ -313,6 +313,7 @@ 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();
|
||||||
|
|
@ -1395,6 +1396,12 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,7 @@ private:
|
||||||
bool showStatusBar;
|
bool showStatusBar;
|
||||||
bool showDragSelectionCount;
|
bool showDragSelectionCount;
|
||||||
bool showTotalSelectionCount;
|
bool showTotalSelectionCount;
|
||||||
|
bool showSubtypeSelectionTally;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SettingsCache();
|
SettingsCache();
|
||||||
|
|
@ -478,6 +479,10 @@ public:
|
||||||
{
|
{
|
||||||
return showTotalSelectionCount;
|
return showTotalSelectionCount;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool getShowSubtypeSelectionTally() const
|
||||||
|
{
|
||||||
|
return showSubtypeSelectionTally;
|
||||||
|
}
|
||||||
[[nodiscard]] bool getNotificationsEnabled() const
|
[[nodiscard]] bool getNotificationsEnabled() const
|
||||||
{
|
{
|
||||||
return notificationsEnabled;
|
return notificationsEnabled;
|
||||||
|
|
@ -1176,5 +1181,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@
|
||||||
#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
|
||||||
|
|
@ -1018,8 +1019,9 @@ 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);
|
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown);
|
||||||
}
|
}
|
||||||
++tokensTypesCreated;
|
++tokensTypesCreated;
|
||||||
if (tokensTypesCreated == 1) {
|
if (tokensTypesCreated == 1) {
|
||||||
|
|
@ -1034,8 +1036,9 @@ 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);
|
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown);
|
||||||
}
|
}
|
||||||
++tokensTypesCreated;
|
++tokensTypesCreated;
|
||||||
if (tokensTypesCreated == 1) {
|
if (tokensTypesCreated == 1) {
|
||||||
|
|
@ -1073,6 +1076,7 @@ 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.
|
||||||
|
|
@ -1081,7 +1085,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);
|
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1090,7 +1094,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);
|
createCard(sourceCard, dbName, CardRelationType::DoesNotAttach, persistent, faceDown);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1110,7 +1114,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
|
||||||
playCardToTable(sourceCard, false);
|
playCardToTable(sourceCard, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
createCard(sourceCard, dbName, attachType, persistent);
|
createCard(sourceCard, dbName, attachType, persistent, faceDown);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1137,7 +1141,8 @@ 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);
|
||||||
|
|
||||||
|
|
@ -1172,6 +1177,7 @@ 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());
|
||||||
|
|
@ -1525,12 +1531,15 @@ 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;
|
|
||||||
|
|
||||||
// Early exit optimization: server enforces [0, MAX_COUNTERS_ON_CARD].
|
// Overflow-safe clamp to the server-enforced range [0, MAX_COUNTER_VALUE];
|
||||||
// Compare clamped value to allow recovery from invalid states.
|
// a result differing from oldValue also corrects an out-of-range cached value.
|
||||||
int clampedValue = qBound(0, newValue, MAX_COUNTERS_ON_CARD);
|
// Callers only ever pass offset == ±1 (actAddCardCounter / actRemoveCardCounter).
|
||||||
if (clampedValue != oldValue) {
|
// This client-side clamp is a defense-in-depth UX check, consistent with
|
||||||
|
// 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());
|
||||||
|
|
@ -1563,7 +1572,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_COUNTERS_ON_CARD)));
|
int number = static_cast<int>(qBound(0.0, parsed, static_cast<double>(MAX_COUNTER_VALUE)));
|
||||||
|
|
||||||
auto *cmd = new Command_SetCardCounter;
|
auto *cmd = new Command_SetCardCounter;
|
||||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||||
|
|
@ -1593,7 +1602,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_COUNTERS_ON_CARD) {
|
if (currentValue >= MAX_COUNTER_VALUE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,8 @@ 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);
|
||||||
|
|
||||||
|
|
|
||||||
64
cockatrice/src/game/selection_subtype_tally.cpp
Normal file
64
cockatrice/src/game/selection_subtype_tally.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#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
|
||||||
36
cockatrice/src/game/selection_subtype_tally.h
Normal file
36
cockatrice/src/game/selection_subtype_tally.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#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
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
#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;
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false)
|
ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent)
|
DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent)
|
||||||
: QDialog(parent), predefinedTokens(_predefinedTokens)
|
: QDialog(parent), predefinedTokens(_predefinedTokens)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/dice_limits.h>
|
||||||
|
|
||||||
DlgRollDice::DlgRollDice(QWidget *parent) : QDialog(parent)
|
DlgRollDice::DlgRollDice(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
#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.
|
||||||
|
|
@ -55,31 +59,40 @@ 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 countLabelStyle = "color: white; "
|
const QString baseProperties = "color: white; "
|
||||||
"font-size: 14px; "
|
"font-family: monospace; "
|
||||||
"font-weight: bold; "
|
|
||||||
"background-color: rgba(0, 0, 0, 160); "
|
"background-color: rgba(0, 0, 0, 160); "
|
||||||
"border-radius: 3px; "
|
"border-radius: 3px; "
|
||||||
"padding: 1px 2px;";
|
"padding: 1px 2px; "
|
||||||
|
"white-space: pre;";
|
||||||
|
|
||||||
|
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(countLabelStyle);
|
dragCountLabel->setStyleSheet(dragCountLabelStyle);
|
||||||
dragCountLabel->hide();
|
dragCountLabel->hide();
|
||||||
dragCountLabel->raise();
|
dragCountLabel->raise();
|
||||||
|
|
||||||
totalCountLabel = new QLabel(this);
|
totalCountLabel = new QLabel(this);
|
||||||
totalCountLabel->setStyleSheet(countLabelStyle);
|
totalCountLabel->setStyleSheet(totalCountLabelStyle);
|
||||||
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 = dynamic_cast<GameScene *>(scene());
|
GameScene *s = static_cast<GameScene *>(scene());
|
||||||
if (s) {
|
|
||||||
s->processViewSizeChange(event->size());
|
s->processViewSizeChange(event->size());
|
||||||
}
|
|
||||||
|
|
||||||
updateSceneRect(scene()->sceneRect());
|
updateSceneRect(scene()->sceneRect());
|
||||||
updateTotalSelectionCount(event->size());
|
updateTotalSelectionCount(event->size());
|
||||||
|
|
@ -164,29 +177,114 @@ 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)
|
||||||
{
|
{
|
||||||
if (!SettingsCache::instance().getShowTotalSelectionCount()) {
|
constexpr int kMarginInPixels = 10;
|
||||||
totalCountLabel->hide();
|
constexpr int kSpacingBetweenLabels = 4;
|
||||||
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 (count > 1) {
|
if (!SettingsCache::instance().getShowTotalSelectionCount() || 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();
|
||||||
} else {
|
|
||||||
totalCountLabel->hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
containerSize = subtypeTallyContainer->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,12 @@
|
||||||
#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;
|
||||||
|
|
||||||
|
|
@ -21,7 +24,13 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#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)
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,9 @@ 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) {
|
||||||
|
|
@ -396,6 +399,7 @@ static QString roleBgName(ThemeManager::Role role)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Q_ASSERT(false);
|
Q_ASSERT(false);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
|
static int findRestoreIndex(const CardRef &wanted, const QComboBox *combo)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QRadioButton>
|
#include <QRadioButton>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
|
DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
void DlgCreateGame::sharedCtor()
|
void DlgCreateGame::sharedCtor()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image()
|
DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
|
DlgEditPassword::DlgEditPassword(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr)
|
DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialog(parent)
|
DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(parent)
|
DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgForgotPasswordReset::DlgForgotPasswordReset(QWidget *parent) : QDialog(parent)
|
DlgForgotPasswordReset::DlgForgotPasswordReset(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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, or type"));
|
searchField->setPlaceholderText(tr("Search by set name, code, type, or release date"));
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
|
DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
|
||||||
static QString makeKey(const QString &user, const QString &card)
|
static QString makeKey(const QString &user, const QString &card, const QString &providerId)
|
||||||
{
|
{
|
||||||
return user + u'|' + card;
|
return user + u'|' + card + u'|' + providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCardArtProvider::UserCardArtProvider(QObject *parent) : QObject(parent)
|
UserCardArtProvider::UserCardArtProvider(QObject *parent) : QObject(parent)
|
||||||
|
|
@ -31,13 +31,13 @@ const QMap<QString, QPixmap> &UserCardArtProvider::cache() const
|
||||||
return cardArtCache;
|
return cardArtCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserCardArtProvider::requestCardArt(const QString &userName, const QString &cardName)
|
void UserCardArtProvider::requestCardArt(const QString &userName, const QString &cardName, const QString &providerId)
|
||||||
{
|
{
|
||||||
if (cardName.isEmpty()) {
|
if (cardName.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString key = makeKey(userName, cardName);
|
const QString key = makeKey(userName, cardName, providerId);
|
||||||
|
|
||||||
if (cardArtCache.contains(key) || pending.contains(key)) {
|
if (cardArtCache.contains(key) || pending.contains(key)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -83,15 +83,16 @@ void UserCardArtProvider::processQueue()
|
||||||
const QString key = queue.dequeue();
|
const QString key = queue.dequeue();
|
||||||
|
|
||||||
const QStringList parts = key.split(u'|');
|
const QStringList parts = key.split(u'|');
|
||||||
if (parts.size() != 2) {
|
if (parts.size() != 3) {
|
||||||
pending.remove(key);
|
pending.remove(key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString userName = parts.at(0);
|
const QString userName = parts.at(0);
|
||||||
const QString cardName = parts.at(1);
|
const QString cardName = parts.at(1);
|
||||||
|
const QString providerId = parts.at(2);
|
||||||
|
|
||||||
ExactCard card = CardDatabaseManager::query()->getCard({cardName});
|
ExactCard card = CardDatabaseManager::query()->getCard({cardName, providerId});
|
||||||
|
|
||||||
if (!card) {
|
if (!card) {
|
||||||
pending.remove(key);
|
pending.remove(key);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class UserCardArtProvider : public QObject
|
||||||
public:
|
public:
|
||||||
explicit UserCardArtProvider(QObject *parent = nullptr);
|
explicit UserCardArtProvider(QObject *parent = nullptr);
|
||||||
|
|
||||||
void requestCardArt(const QString &userName, const QString &cardName);
|
void requestCardArt(const QString &userName, const QString &cardName, const QString &providerId);
|
||||||
const QMap<QString, QPixmap> &cache() const;
|
const QMap<QString, QPixmap> &cache() const;
|
||||||
static QPixmap cropCardArt(const QPixmap &fullRes);
|
static QPixmap cropCardArt(const QPixmap &fullRes);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ void CardArtPreviewWidget::paintEvent(QPaintEvent *)
|
||||||
QString(), // userName not needed for override path
|
QString(), // userName not needed for override path
|
||||||
nullptr, // no cache
|
nullptr, // no cache
|
||||||
params,
|
params,
|
||||||
&sourcePixmap // 👈 direct pixmap
|
&sourcePixmap // direct pixmap
|
||||||
);
|
);
|
||||||
|
|
||||||
// Avatar placeholder so the left-margin interaction is visible
|
// Avatar placeholder so the left-margin interaction is visible
|
||||||
|
|
@ -174,6 +174,13 @@ void UserCardArtSettingsDialog::setupUi()
|
||||||
{
|
{
|
||||||
initializeSearchBar();
|
initializeSearchBar();
|
||||||
|
|
||||||
|
providerComboBox = new QComboBox;
|
||||||
|
connect(providerComboBox, &QComboBox::currentIndexChanged, this, [this]() {
|
||||||
|
currentParams.cardProviderId = providerComboBox->currentData().toString();
|
||||||
|
reloadPreview();
|
||||||
|
onParamChanged();
|
||||||
|
});
|
||||||
|
|
||||||
marginLSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctL, 0.01);
|
marginLSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctL, 0.01);
|
||||||
marginRSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctR, 0.01);
|
marginRSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctR, 0.01);
|
||||||
verticalOffsetSpin = makeSpinBox(0.0, 1.0, currentParams.verticalOffset, 0.01);
|
verticalOffsetSpin = makeSpinBox(0.0, 1.0, currentParams.verticalOffset, 0.01);
|
||||||
|
|
@ -181,6 +188,7 @@ void UserCardArtSettingsDialog::setupUi()
|
||||||
|
|
||||||
auto *form = new QFormLayout;
|
auto *form = new QFormLayout;
|
||||||
form->addRow(tr("Card name:"), searchBar);
|
form->addRow(tr("Card name:"), searchBar);
|
||||||
|
form->addRow(tr("Card ProviderId:"), providerComboBox);
|
||||||
form->addRow(tr("Left margin (%):"), marginLSpin);
|
form->addRow(tr("Left margin (%):"), marginLSpin);
|
||||||
form->addRow(tr("Right margin (%):"), marginRSpin);
|
form->addRow(tr("Right margin (%):"), marginRSpin);
|
||||||
form->addRow(tr("Vertical offset:"), verticalOffsetSpin);
|
form->addRow(tr("Vertical offset:"), verticalOffsetSpin);
|
||||||
|
|
@ -219,6 +227,32 @@ void UserCardArtSettingsDialog::setupUi()
|
||||||
connect(zoomSpin, &QDoubleSpinBox::valueChanged, this, &UserCardArtSettingsDialog::onParamChanged);
|
connect(zoomSpin, &QDoubleSpinBox::valueChanged, this, &UserCardArtSettingsDialog::onParamChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UserCardArtSettingsDialog::populateProviderCombo(const QString &cardName)
|
||||||
|
{
|
||||||
|
providerComboBox->clear();
|
||||||
|
|
||||||
|
auto card = CardDatabaseManager::query()->getCard({cardName});
|
||||||
|
|
||||||
|
const auto &sets = card.getInfo().getSets();
|
||||||
|
|
||||||
|
for (const auto &printings : sets) {
|
||||||
|
for (const auto &p : printings) {
|
||||||
|
|
||||||
|
QString setName = p.getSet()->getLongName();
|
||||||
|
QString collector = p.getProperty("num");
|
||||||
|
QString uuid = p.getUuid();
|
||||||
|
|
||||||
|
QString label = setName;
|
||||||
|
|
||||||
|
if (!collector.isEmpty()) {
|
||||||
|
label += " #" + collector;
|
||||||
|
}
|
||||||
|
|
||||||
|
providerComboBox->addItem(label, uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UserCardArtSettingsDialog::onCardNameChanged(const QString &name)
|
void UserCardArtSettingsDialog::onCardNameChanged(const QString &name)
|
||||||
{
|
{
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
|
|
@ -231,27 +265,68 @@ void UserCardArtSettingsDialog::onCardNameChanged(const QString &name)
|
||||||
if (!card) {
|
if (!card) {
|
||||||
currentPixmap = QPixmap();
|
currentPixmap = QPixmap();
|
||||||
preview->setPixmap(currentPixmap);
|
preview->setPixmap(currentPixmap);
|
||||||
|
providerComboBox->clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentParams.cardName = name;
|
currentParams.cardName = name;
|
||||||
|
|
||||||
|
populateProviderCombo(name);
|
||||||
|
|
||||||
|
if (providerComboBox->count() == 0) {
|
||||||
|
// No printings found for this card; nothing to preview.
|
||||||
|
currentPixmap = QPixmap();
|
||||||
|
preview->setPixmap(currentPixmap);
|
||||||
|
currentParams.cardProviderId.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentParams.cardProviderId = providerComboBox->currentData().toString();
|
||||||
|
reloadPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserCardArtSettingsDialog::reloadPreview()
|
||||||
|
{
|
||||||
|
if (currentParams.cardName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExactCard card = CardDatabaseManager::query()->getCard({currentParams.cardName, currentParams.cardProviderId});
|
||||||
|
if (!card) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CardPictureLoader::getPixmap() is async on a cache miss: it enqueues a
|
||||||
|
// background download and returns a null pixmap immediately. When that
|
||||||
|
// download finishes, CardPictureLoader::imageLoaded() caches the result
|
||||||
|
// and calls card.emitPixmapUpdated(), which emits pixmapUpdated() on the
|
||||||
|
// underlying CardInfo (see exact_card.h). Listen for that, scoped to
|
||||||
|
// whichever CardInfo we just asked for, so the preview catches up once
|
||||||
|
// the image actually arrives instead of staying on the placeholder.
|
||||||
|
//
|
||||||
|
// Disconnect any previous listener first -- otherwise switching cards
|
||||||
|
// repeatedly stacks up connections to old CardInfo objects, each of
|
||||||
|
// which would still fire reloadPreview() (harmlessly, but wastefully)
|
||||||
|
// whenever ITS art finishes loading later.
|
||||||
|
disconnect(pixmapUpdatedConnection);
|
||||||
|
|
||||||
QPixmap fullRes;
|
QPixmap fullRes;
|
||||||
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
|
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
|
||||||
|
|
||||||
if (fullRes.isNull()) {
|
if (fullRes.isNull()) {
|
||||||
connect(card.getCardPtr().data(), &CardInfo::pixmapUpdated, this, [this, card](const PrintingInfo &) {
|
// Not loaded yet -- wait for the signal instead of giving up.
|
||||||
disconnect(card.getCardPtr().data(), &CardInfo::pixmapUpdated, this, nullptr);
|
// card.getCardPtr() is a CardInfoPtr (QSharedPointer<CardInfo>);
|
||||||
QPixmap loaded;
|
// .data() gives the raw QObject* needed for connect().
|
||||||
CardPictureLoader::getPixmap(loaded, card, QSize(745, 1040));
|
CardInfo *cardInfo = card.getCardPtr().data();
|
||||||
currentPixmap = UserCardArtProvider::cropCardArt(loaded);
|
if (cardInfo) {
|
||||||
preview->setPixmap(currentPixmap);
|
pixmapUpdatedConnection = connect(cardInfo, &CardInfo::pixmapUpdated, this, [this]() { reloadPreview(); });
|
||||||
});
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPixmap = UserCardArtProvider::cropCardArt(fullRes);
|
currentPixmap = UserCardArtProvider::cropCardArt(fullRes);
|
||||||
preview->setPixmap(currentPixmap);
|
preview->setPixmap(currentPixmap);
|
||||||
|
preview->setParams(currentParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserCardArtSettingsDialog::onParamChanged()
|
void UserCardArtSettingsDialog::onParamChanged()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "user_list_painter.h"
|
#include "user_list_painter.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
|
||||||
|
|
@ -43,10 +44,12 @@ public:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onCardNameChanged(const QString &name);
|
void onCardNameChanged(const QString &name);
|
||||||
|
void reloadPreview();
|
||||||
void onParamChanged();
|
void onParamChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupUi();
|
void setupUi();
|
||||||
|
void populateProviderCombo(const QString &cardName);
|
||||||
void initializeSearchBar();
|
void initializeSearchBar();
|
||||||
QDoubleSpinBox *makeSpinBox(double min, double max, double value, double step);
|
QDoubleSpinBox *makeSpinBox(double min, double max, double value, double step);
|
||||||
|
|
||||||
|
|
@ -57,6 +60,10 @@ private:
|
||||||
CardSearchModel *searchModel;
|
CardSearchModel *searchModel;
|
||||||
CardCompleterProxyModel *proxyModel;
|
CardCompleterProxyModel *proxyModel;
|
||||||
|
|
||||||
|
QComboBox *providerComboBox;
|
||||||
|
|
||||||
|
QMetaObject::Connection pixmapUpdatedConnection;
|
||||||
|
|
||||||
QDoubleSpinBox *marginLSpin;
|
QDoubleSpinBox *marginLSpin;
|
||||||
QDoubleSpinBox *marginRSpin;
|
QDoubleSpinBox *marginRSpin;
|
||||||
QDoubleSpinBox *verticalOffsetSpin;
|
QDoubleSpinBox *verticalOffsetSpin;
|
||||||
|
|
|
||||||
|
|
@ -335,6 +335,7 @@ void UserInfoBox::actBannerCard()
|
||||||
Command_SetCardArtParams cmd;
|
Command_SetCardArtParams cmd;
|
||||||
cmd.set_card_name(p.cardName.toStdString());
|
cmd.set_card_name(p.cardName.toStdString());
|
||||||
if (!p.cardName.isEmpty()) {
|
if (!p.cardName.isEmpty()) {
|
||||||
|
cmd.set_card_provider_id(p.cardProviderId.toStdString());
|
||||||
cmd.set_margin_pct_l(p.marginPctL);
|
cmd.set_margin_pct_l(p.marginPctL);
|
||||||
cmd.set_margin_pct_r(p.marginPctR);
|
cmd.set_margin_pct_r(p.marginPctR);
|
||||||
cmd.set_vertical_offset(p.verticalOffset);
|
cmd.set_vertical_offset(p.verticalOffset);
|
||||||
|
|
|
||||||
|
|
@ -542,7 +542,7 @@ void UserInfoPopup::showForUser(const QString &userName,
|
||||||
const CardArtParams params = (m_cardArtParamsMap && m_cardArtParamsMap->contains(userName))
|
const CardArtParams params = (m_cardArtParamsMap && m_cardArtParamsMap->contains(userName))
|
||||||
? m_cardArtParamsMap->value(userName)
|
? m_cardArtParamsMap->value(userName)
|
||||||
: CardArtParams{};
|
: CardArtParams{};
|
||||||
const QString artKey = userName + u'|' + params.cardName;
|
const QString artKey = userName + u'|' + params.cardName + u'|' + params.cardProviderId;
|
||||||
const QPixmap cardArt = (m_cardArtCache && !params.cardName.isEmpty()) ? m_cardArtCache->value(artKey) : QPixmap{};
|
const QPixmap cardArt = (m_cardArtCache && !params.cardName.isEmpty()) ? m_cardArtCache->value(artKey) : QPixmap{};
|
||||||
m_header->setUserData(userInfo, online, avatar, cardArt, params);
|
m_header->setUserData(userInfo, online, avatar, cardArt, params);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,9 @@ void UserListPainter::drawBackground(QPainter *painter,
|
||||||
painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2);
|
painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString makeKey(const QString &user, const QString &card)
|
static QString makeKey(const QString &user, const QString &card, const QString &providerId)
|
||||||
{
|
{
|
||||||
return user + u'|' + card;
|
return user + u'|' + card + u'|' + providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserListPainter::drawCardArt(QPainter *painter,
|
void UserListPainter::drawCardArt(QPainter *painter,
|
||||||
|
|
@ -95,7 +95,7 @@ void UserListPainter::drawCardArt(QPainter *painter,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString key = makeKey(userName, params.cardName);
|
const QString key = makeKey(userName, params.cardName, params.cardProviderId);
|
||||||
|
|
||||||
if (!cardArtCache->contains(key)) {
|
if (!cardArtCache->contains(key)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class ServerInfo_User;
|
||||||
struct CardArtParams
|
struct CardArtParams
|
||||||
{
|
{
|
||||||
QString cardName = "";
|
QString cardName = "";
|
||||||
|
QString cardProviderId = "";
|
||||||
double marginPctL = 0.33;
|
double marginPctL = 0.33;
|
||||||
double marginPctR = 0.02;
|
double marginPctR = 0.02;
|
||||||
double verticalOffset = 0.35;
|
double verticalOffset = 0.35;
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent)
|
BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
@ -767,13 +767,17 @@ void UserListWidget::showPopupForUser(const QString &userName)
|
||||||
|
|
||||||
m_userInfoPopup->showForUser(userName, info, online, isBuddy, isIgn);
|
m_userInfoPopup->showForUser(userName, info, online, isBuddy, isIgn);
|
||||||
|
|
||||||
positionPopup(userName);
|
// Realize the native window at opacity 0 before positioning so that:
|
||||||
|
// 1) move() applies to an existing native handle (not overridden by Qt's
|
||||||
|
// default centering logic on first show)
|
||||||
|
// 2) adjustSize() inside positionPopup() can measure the final laid-out
|
||||||
|
// geometry correctly
|
||||||
|
m_userInfoPopup->setWindowOpacity(0.0);
|
||||||
m_userInfoPopup->show();
|
m_userInfoPopup->show();
|
||||||
m_userInfoPopup->raise();
|
m_userInfoPopup->raise();
|
||||||
|
|
||||||
// Fade in
|
positionPopup(userName); // geometry is now accurate; move() sticks
|
||||||
m_userInfoPopup->setWindowOpacity(0.0);
|
|
||||||
auto *fade = new QPropertyAnimation(m_userInfoPopup, "windowOpacity", m_userInfoPopup);
|
auto *fade = new QPropertyAnimation(m_userInfoPopup, "windowOpacity", m_userInfoPopup);
|
||||||
fade->setDuration(120);
|
fade->setDuration(120);
|
||||||
fade->setStartValue(0.0);
|
fade->setStartValue(0.0);
|
||||||
|
|
@ -790,11 +794,10 @@ void UserListWidget::positionPopup(const QString &userName)
|
||||||
|
|
||||||
QWidget *vp = userTree->viewport();
|
QWidget *vp = userTree->viewport();
|
||||||
const QRect itemR = userTree->visualItemRect(item);
|
const QRect itemR = userTree->visualItemRect(item);
|
||||||
const QPoint itemBR = vp->mapToGlobal(itemR.bottomRight());
|
const QPoint itemTL = vp->mapToGlobal(itemR.topLeft());
|
||||||
const QPoint vpTL = vp->mapToGlobal(vp->rect().topLeft());
|
const QPoint vpTL = vp->mapToGlobal(vp->rect().topLeft());
|
||||||
const QPoint vpTR = vp->mapToGlobal(vp->rect().topRight());
|
const QPoint vpTR = vp->mapToGlobal(vp->rect().topRight());
|
||||||
|
|
||||||
// Force a fresh size calculation so popH is accurate
|
|
||||||
m_userInfoPopup->adjustSize();
|
m_userInfoPopup->adjustSize();
|
||||||
const int popW = m_userInfoPopup->width();
|
const int popW = m_userInfoPopup->width();
|
||||||
const int popH = m_userInfoPopup->height();
|
const int popH = m_userInfoPopup->height();
|
||||||
|
|
@ -802,19 +805,32 @@ void UserListWidget::positionPopup(const QString &userName)
|
||||||
|
|
||||||
const QRect screen = QGuiApplication::primaryScreen()->availableGeometry();
|
const QRect screen = QGuiApplication::primaryScreen()->availableGeometry();
|
||||||
|
|
||||||
// ── X: left of the list if there's room, otherwise right ─────────────────
|
// ── X: prefer the side with more space ───────────────────────────────────
|
||||||
int x = (vpTL.x() >= popW + margin) ? vpTL.x() - popW - margin : vpTR.x() + margin;
|
const int spaceLeft = vpTL.x() - screen.left() - margin;
|
||||||
|
const int spaceRight = screen.right() - vpTR.x() - margin;
|
||||||
|
int x;
|
||||||
|
if (spaceLeft >= spaceRight) {
|
||||||
|
x = (spaceLeft >= popW) ? (vpTL.x() - margin - popW) : (vpTR.x() + margin);
|
||||||
|
} else {
|
||||||
|
x = (spaceRight >= popW) ? (vpTR.x() + margin) : (vpTL.x() - margin - popW);
|
||||||
|
}
|
||||||
x = qBound(screen.left() + margin, x, screen.right() - popW - margin);
|
x = qBound(screen.left() + margin, x, screen.right() - popW - margin);
|
||||||
|
|
||||||
// ── Y: bottom of popup aligns with bottom of hovered row, grows upward ───
|
// ── Y: grow down if there's room, otherwise grow up ───────────────────────
|
||||||
int y = itemBR.y() - popH;
|
const int itemTopY = itemTL.y();
|
||||||
|
const int spaceBelow = screen.bottom() - itemTopY - margin;
|
||||||
|
const int spaceAbove = itemTopY - screen.top() - margin;
|
||||||
|
|
||||||
// Clamp: never above the screen top
|
int y;
|
||||||
y = qMax(y, screen.top() + margin);
|
if (spaceBelow >= popH) {
|
||||||
|
y = itemTopY; // top edges align, popup grows downward
|
||||||
// Clamp: never below the screen bottom (e.g. if the popup is taller
|
} else if (spaceAbove >= popH) {
|
||||||
// than the space above the row, let it spill downward rather than clip)
|
y = itemTopY - popH; // bottom of popup meets top of item, grows upward
|
||||||
y = qMin(y, screen.bottom() - popH - margin);
|
} else {
|
||||||
|
// Neither side fits cleanly — pick the roomier side and let clamp handle the rest
|
||||||
|
y = (spaceBelow >= spaceAbove) ? itemTopY : (itemTopY - popH);
|
||||||
|
}
|
||||||
|
y = qBound(screen.top() + margin, y, screen.bottom() - popH - margin);
|
||||||
|
|
||||||
m_userInfoPopup->move(x, y);
|
m_userInfoPopup->move(x, y);
|
||||||
}
|
}
|
||||||
|
|
@ -904,12 +920,13 @@ void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)
|
||||||
const auto &cap = user.card_art_params();
|
const auto &cap = user.card_art_params();
|
||||||
CardArtParams params;
|
CardArtParams params;
|
||||||
params.cardName = QString::fromStdString(cap.card_name());
|
params.cardName = QString::fromStdString(cap.card_name());
|
||||||
|
params.cardProviderId = QString::fromStdString(cap.card_provider_id());
|
||||||
params.marginPctL = cap.margin_pct_l();
|
params.marginPctL = cap.margin_pct_l();
|
||||||
params.marginPctR = cap.margin_pct_r();
|
params.marginPctR = cap.margin_pct_r();
|
||||||
params.verticalOffset = cap.vertical_offset();
|
params.verticalOffset = cap.vertical_offset();
|
||||||
params.zoom = cap.zoom();
|
params.zoom = cap.zoom();
|
||||||
cardArtParamsMap.insert(userName, params);
|
cardArtParamsMap.insert(userName, params);
|
||||||
cardArtProvider->requestCardArt(userName, params.cardName);
|
cardArtProvider->requestCardArt(userName, params.cardName, params.cardProviderId);
|
||||||
} else {
|
} else {
|
||||||
cardArtParamsMap.remove(userName); // clear stale params on removal
|
cardArtParamsMap.remove(userName); // clear stale params on removal
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QToolBar>
|
#include <QToolBar>
|
||||||
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
MessagesSettingsPage::MessagesSettingsPage()
|
MessagesSettingsPage::MessagesSettingsPage()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ 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); });
|
||||||
|
|
@ -86,8 +90,9 @@ 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(&useTearOffMenusCheckBox, 9, 0);
|
generalGrid->addWidget(&showSubtypeSelectionTallyCheckBox, 9, 0);
|
||||||
generalGrid->addWidget(&keepGameChatFocusCheckBox, 10, 0);
|
generalGrid->addWidget(&useTearOffMenusCheckBox, 10, 0);
|
||||||
|
generalGrid->addWidget(&keepGameChatFocusCheckBox, 11, 0);
|
||||||
|
|
||||||
generalGroupBox = new QGroupBox;
|
generalGroupBox = new QGroupBox;
|
||||||
generalGroupBox->setLayout(generalGrid);
|
generalGroupBox->setLayout(generalGrid);
|
||||||
|
|
@ -209,8 +214,9 @@ 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 counter during drag selection"));
|
showDragSelectionCountCheckBox.setText(tr("Show selection count during drag selection"));
|
||||||
showTotalSelectionCountCheckBox.setText(tr("Show total selection counter"));
|
showTotalSelectionCountCheckBox.setText(tr("Show total selection count"));
|
||||||
|
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)"));
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs the AbstractTabDeckEditor.
|
* @brief Constructs the AbstractTabDeckEditor.
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_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)
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
|
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ int CardArtRulesModel::rowCount(const QModelIndex &parent) const
|
||||||
int CardArtRulesModel::columnCount(const QModelIndex &parent) const
|
int CardArtRulesModel::columnCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
return 3;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant CardArtRulesModel::data(const QModelIndex &index, int role) const
|
QVariant CardArtRulesModel::data(const QModelIndex &index, int role) const
|
||||||
|
|
@ -43,8 +43,10 @@ QVariant CardArtRulesModel::data(const QModelIndex &index, int role) const
|
||||||
case 0:
|
case 0:
|
||||||
return e.cardName;
|
return e.cardName;
|
||||||
case 1:
|
case 1:
|
||||||
return e.mode;
|
return e.cardProviderId;
|
||||||
case 2:
|
case 2:
|
||||||
|
return e.mode;
|
||||||
|
case 3:
|
||||||
return e.reason;
|
return e.reason;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,8 +64,10 @@ QVariant CardArtRulesModel::headerData(int section, Qt::Orientation orientation,
|
||||||
case 0:
|
case 0:
|
||||||
return tr("Card");
|
return tr("Card");
|
||||||
case 1:
|
case 1:
|
||||||
return tr("Mode");
|
return tr("ProviderId");
|
||||||
case 2:
|
case 2:
|
||||||
|
return tr("Mode");
|
||||||
|
case 3:
|
||||||
return tr("Reason");
|
return tr("Reason");
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -97,6 +101,15 @@ QString CardArtRulesModel::cardAt(int row) const
|
||||||
return entries[row].cardName;
|
return entries[row].cardName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CardArtRulesModel::Entry *CardArtRulesModel::entryAt(int row) const
|
||||||
|
{
|
||||||
|
if (row < 0 || row >= static_cast<int>(entries.size())) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entries[row];
|
||||||
|
}
|
||||||
|
|
||||||
void CardArtRulesModel::onRefreshFinished(const Response &r)
|
void CardArtRulesModel::onRefreshFinished(const Response &r)
|
||||||
{
|
{
|
||||||
if (r.response_code() != Response::RespOk) {
|
if (r.response_code() != Response::RespOk) {
|
||||||
|
|
@ -109,8 +122,8 @@ void CardArtRulesModel::onRefreshFinished(const Response &r)
|
||||||
entries.clear();
|
entries.clear();
|
||||||
|
|
||||||
for (const auto &e : resp.entries()) {
|
for (const auto &e : resp.entries()) {
|
||||||
entries.push_back({QString::fromStdString(e.card_name()), QString::fromStdString(e.mode()),
|
entries.push_back({QString::fromStdString(e.card_name()), QString::fromStdString(e.card_provider_id()),
|
||||||
QString::fromStdString(e.reason())});
|
QString::fromStdString(e.mode()), QString::fromStdString(e.reason())});
|
||||||
}
|
}
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
@ -128,6 +141,7 @@ void TabCardArtRules::setupUi()
|
||||||
|
|
||||||
initSearchBar();
|
initSearchBar();
|
||||||
|
|
||||||
|
providerComboBox = new QComboBox;
|
||||||
modeBox = new QComboBox;
|
modeBox = new QComboBox;
|
||||||
reasonEdit = new QLineEdit;
|
reasonEdit = new QLineEdit;
|
||||||
|
|
||||||
|
|
@ -146,6 +160,7 @@ void TabCardArtRules::setupUi()
|
||||||
|
|
||||||
auto *form = new QFormLayout;
|
auto *form = new QFormLayout;
|
||||||
form->addRow(tr("Card:"), searchEdit);
|
form->addRow(tr("Card:"), searchEdit);
|
||||||
|
form->addRow(tr("ProviderId:"), providerComboBox);
|
||||||
form->addRow(tr("Mode:"), modeBox);
|
form->addRow(tr("Mode:"), modeBox);
|
||||||
form->addRow(tr("Reason:"), reasonEdit);
|
form->addRow(tr("Reason:"), reasonEdit);
|
||||||
|
|
||||||
|
|
@ -204,6 +219,34 @@ void TabCardArtRules::initSearchBar()
|
||||||
});
|
});
|
||||||
connect(searchCompleter, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
|
connect(searchCompleter, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
|
||||||
[this](const QString &name) { searchEdit->setText(name); });
|
[this](const QString &name) { searchEdit->setText(name); });
|
||||||
|
connect(searchEdit, &QLineEdit::editingFinished, this,
|
||||||
|
[this]() { populateProviderCombo(searchEdit->text().trimmed()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabCardArtRules::populateProviderCombo(const QString &cardName)
|
||||||
|
{
|
||||||
|
providerComboBox->clear();
|
||||||
|
|
||||||
|
auto card = CardDatabaseManager::query()->getCard({cardName});
|
||||||
|
|
||||||
|
const auto &sets = card.getInfo().getSets();
|
||||||
|
|
||||||
|
for (const auto &printings : sets) {
|
||||||
|
for (const auto &p : printings) {
|
||||||
|
|
||||||
|
QString setName = p.getSet()->getLongName();
|
||||||
|
QString collector = p.getProperty("num");
|
||||||
|
QString uuid = p.getUuid();
|
||||||
|
|
||||||
|
QString label = setName;
|
||||||
|
|
||||||
|
if (!collector.isEmpty()) {
|
||||||
|
label += " #" + collector;
|
||||||
|
}
|
||||||
|
|
||||||
|
providerComboBox->addItem(label, uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabCardArtRules::retranslateUi()
|
void TabCardArtRules::retranslateUi()
|
||||||
|
|
@ -222,6 +265,7 @@ void TabCardArtRules::addRule()
|
||||||
{
|
{
|
||||||
Command_AddCardArtRule cmd;
|
Command_AddCardArtRule cmd;
|
||||||
cmd.set_card_name(searchEdit->text().toStdString());
|
cmd.set_card_name(searchEdit->text().toStdString());
|
||||||
|
cmd.set_card_provider_id(providerComboBox->currentData().toString().toStdString());
|
||||||
cmd.set_mode(modeBox->currentText().toStdString());
|
cmd.set_mode(modeBox->currentText().toStdString());
|
||||||
cmd.set_reason(reasonEdit->text().toStdString());
|
cmd.set_reason(reasonEdit->text().toStdString());
|
||||||
|
|
||||||
|
|
@ -238,7 +282,10 @@ void TabCardArtRules::removeSelected()
|
||||||
}
|
}
|
||||||
|
|
||||||
Command_RemoveCardArtRule cmd;
|
Command_RemoveCardArtRule cmd;
|
||||||
cmd.set_card_name(tableModel->cardAt(idx.row()).toStdString());
|
const auto e = tableModel->entryAt(idx.row());
|
||||||
|
|
||||||
|
cmd.set_card_name(e->cardName.toStdString());
|
||||||
|
cmd.set_card_provider_id(e->cardProviderId.toStdString());
|
||||||
|
|
||||||
client->sendCommand(client->prepareModeratorCommand(cmd));
|
client->sendCommand(client->prepareModeratorCommand(cmd));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public:
|
||||||
struct Entry
|
struct Entry
|
||||||
{
|
{
|
||||||
QString cardName;
|
QString cardName;
|
||||||
|
QString cardProviderId;
|
||||||
QString mode;
|
QString mode;
|
||||||
QString reason;
|
QString reason;
|
||||||
};
|
};
|
||||||
|
|
@ -35,6 +36,7 @@ public:
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
QString cardAt(int row) const;
|
QString cardAt(int row) const;
|
||||||
|
const Entry *entryAt(int row) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRefreshFinished(const Response &r);
|
void onRefreshFinished(const Response &r);
|
||||||
|
|
@ -70,11 +72,13 @@ private:
|
||||||
|
|
||||||
QLineEdit *searchEdit;
|
QLineEdit *searchEdit;
|
||||||
void initSearchBar();
|
void initSearchBar();
|
||||||
|
void populateProviderCombo(const QString &cardName);
|
||||||
QCompleter *searchCompleter;
|
QCompleter *searchCompleter;
|
||||||
CardDatabaseModel *cardDbModel;
|
CardDatabaseModel *cardDbModel;
|
||||||
CardDatabaseDisplayModel *cardDbDisplayModel;
|
CardDatabaseDisplayModel *cardDbDisplayModel;
|
||||||
CardSearchModel *cardSearchModel;
|
CardSearchModel *cardSearchModel;
|
||||||
CardCompleterProxyModel *cardProxyModel;
|
CardCompleterProxyModel *cardProxyModel;
|
||||||
|
QComboBox *providerComboBox;
|
||||||
QComboBox *modeBox;
|
QComboBox *modeBox;
|
||||||
QLineEdit *reasonEdit;
|
QLineEdit *reasonEdit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
#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.
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
#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,
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_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)
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
|
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
|
TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
|
||||||
AbstractClient *_client,
|
AbstractClient *_client,
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||||
AbstractClient *_client,
|
AbstractClient *_client,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
QString getTextWithMax(QWidget *parent,
|
QString getTextWithMax(QWidget *parent,
|
||||||
const QString &title,
|
const QString &title,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<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>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,8 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,7 @@ 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);
|
||||||
|
|
@ -360,7 +361,12 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
|
||||||
persistent = true;
|
persistent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
|
if (attrs.hasAttribute("facedown")) {
|
||||||
|
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 {
|
||||||
|
|
@ -510,6 +516,9 @@ 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");
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ 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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ 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:
|
||||||
/**
|
/**
|
||||||
|
|
@ -42,13 +43,15 @@ 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.
|
||||||
|
|
@ -151,6 +154,16 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -303,12 +303,14 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game,
|
Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game,
|
||||||
int _playerId,
|
int _playerId,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@
|
||||||
#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/trice_limits.h>
|
#include <libcockatrice/utility/dice_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>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
#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/trice_limits.h>
|
#include <libcockatrice/utility/clamped_arithmetic.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)
|
||||||
|
|
@ -114,8 +115,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_COUNTERS_ON_CARD]
|
// Clamp to valid card counter range [0, MAX_COUNTER_VALUE]
|
||||||
value = qBound(0, value, MAX_COUNTERS_ON_CARD);
|
value = qBound(0, value, MAX_COUNTER_VALUE);
|
||||||
|
|
||||||
const int oldValue = counters.value(_id, 0);
|
const int oldValue = counters.value(_id, 0);
|
||||||
if (value == oldValue) {
|
if (value == oldValue) {
|
||||||
|
|
@ -139,10 +140,8 @@ 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);
|
||||||
const auto result = static_cast<int64_t>(oldValue) + static_cast<int64_t>(delta);
|
// Clamp to [0, MAX_COUNTER_VALUE] for card counters
|
||||||
// Clamp to [0, MAX_COUNTERS_ON_CARD] for card counters
|
const int newValue = addClamped(oldValue, delta, 0, MAX_COUNTER_VALUE);
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -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_COUNTERS_ON_CARD]; 0 removes the counter).
|
* @param value The desired value (clamped to [0, MAX_COUNTER_VALUE]; 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_COUNTERS_ON_CARD].
|
* @note Clamps result to [0, MAX_COUNTER_VALUE].
|
||||||
*/
|
*/
|
||||||
[[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)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,12 @@
|
||||||
#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);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
#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;
|
||||||
|
|
||||||
|
|
@ -92,7 +94,12 @@ 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.
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_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,
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
Server_ProtocolHandler::Server_ProtocolHandler(Server *_server,
|
Server_ProtocolHandler::Server_ProtocolHandler(Server *_server,
|
||||||
Server_DatabaseInterface *_databaseInterface,
|
Server_DatabaseInterface *_databaseInterface,
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
|
|
||||||
Server_Room::Server_Room(int _id,
|
Server_Room::Server_Room(int _id,
|
||||||
int _chatHistorySize,
|
int _chatHistorySize,
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_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
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,9 @@ message Command_AddCardArtRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string card_name = 1;
|
optional string card_name = 1;
|
||||||
optional string mode = 2; // "ALLOW" or "DENY"
|
optional string card_provider_id = 2;
|
||||||
optional string reason = 3;
|
optional string mode = 3; // "ALLOW" or "DENY"
|
||||||
|
optional string reason = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Command_RemoveCardArtRule {
|
message Command_RemoveCardArtRule {
|
||||||
|
|
@ -126,6 +127,7 @@ message Command_RemoveCardArtRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string card_name = 1;
|
optional string card_name = 1;
|
||||||
|
optional string card_provider_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Command_ListCardArtRules {
|
message Command_ListCardArtRules {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import "response.proto";
|
||||||
|
|
||||||
message Response_CardArtRuleEntry {
|
message Response_CardArtRuleEntry {
|
||||||
optional string card_name = 1;
|
optional string card_name = 1;
|
||||||
optional string mode = 2;
|
optional string card_provider_id = 2;
|
||||||
optional string reason = 3;
|
optional string mode = 3;
|
||||||
|
optional string reason = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Response_ListCardArtRules {
|
message Response_ListCardArtRules {
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@ message ServerInfo_User {
|
||||||
};
|
};
|
||||||
message CardArtParams {
|
message CardArtParams {
|
||||||
optional string card_name = 1;
|
optional string card_name = 1;
|
||||||
optional double margin_pct_l = 2 [default = 0.33];
|
optional string card_provider_id = 2;
|
||||||
optional double margin_pct_r = 3 [default = 0.02];
|
optional double margin_pct_l = 3 [default = 0.33];
|
||||||
optional double vertical_offset = 4 [default = 0.35];
|
optional double margin_pct_r = 4 [default = 0.02];
|
||||||
optional double zoom = 5 [default = 1.0];
|
optional double vertical_offset = 5 [default = 0.35];
|
||||||
|
optional double zoom = 6 [default = 1.0];
|
||||||
};
|
};
|
||||||
|
|
||||||
optional string name = 1;
|
optional string name = 1;
|
||||||
|
|
|
||||||
|
|
@ -212,8 +212,9 @@ message Command_SetCardArtParams {
|
||||||
optional Command_SetCardArtParams ext = 1025;
|
optional Command_SetCardArtParams ext = 1025;
|
||||||
}
|
}
|
||||||
optional string card_name = 1;
|
optional string card_name = 1;
|
||||||
optional double margin_pct_l = 2;
|
optional string card_provider_id = 2;
|
||||||
optional double margin_pct_r = 3;
|
optional double margin_pct_l = 3;
|
||||||
optional double vertical_offset = 4;
|
optional double margin_pct_r = 4;
|
||||||
optional double zoom = 5;
|
optional double vertical_offset = 5;
|
||||||
|
optional double zoom = 6;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ 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/trice_limits.h
|
libcockatrice/utility/string_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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#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
|
||||||
17
libcockatrice_utility/libcockatrice/utility/counter_limits.h
Normal file
17
libcockatrice_utility/libcockatrice/utility/counter_limits.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#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
|
||||||
15
libcockatrice_utility/libcockatrice/utility/dice_limits.h
Normal file
15
libcockatrice_utility/libcockatrice/utility/dice_limits.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#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
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#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
|
||||||
|
|
|
||||||
31
libcockatrice_utility/libcockatrice/utility/string_limits.h
Normal file
31
libcockatrice_utility/libcockatrice/utility/string_limits.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#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
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -278,7 +278,7 @@
|
||||||
<context>
|
<context>
|
||||||
<name>OracleImporter</name>
|
<name>OracleImporter</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
<location filename="src/oracleimporter.cpp" line="542"/>
|
||||||
<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="97"/>
|
<location filename="src/oraclewizard.cpp" line="101"/>
|
||||||
<source>Oracle Importer</source>
|
<source>Oracle Importer</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ ALTER TABLE `cockatrice_users` ADD COLUMN `card_art_params` TEXT DEFAULT NULL, A
|
||||||
CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` (
|
CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` (
|
||||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`card_name` varchar(255) NOT NULL,
|
`card_name` varchar(255) NOT NULL,
|
||||||
|
`card_provider_id` varchar(255) NOT NULL,
|
||||||
`mode` enum('ALLOW','DENY') NOT NULL,
|
`mode` enum('ALLOW','DENY') NOT NULL,
|
||||||
`reason` varchar(255) DEFAULT NULL,
|
`reason` varchar(255) DEFAULT NULL,
|
||||||
`created_by` int(7) unsigned DEFAULT NULL,
|
`created_by` int(7) unsigned DEFAULT NULL,
|
||||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uniq_card_name` (`card_name`),
|
UNIQUE KEY `uniq_provider_card_name` (`card_provider_id`, `card_name`),
|
||||||
KEY `idx_mode` (`mode`),
|
KEY `idx_mode` (`mode`),
|
||||||
FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`)
|
FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`)
|
||||||
ON DELETE SET NULL
|
ON DELETE SET NULL
|
||||||
|
|
|
||||||
|
|
@ -305,12 +305,13 @@ CREATE TABLE IF NOT EXISTS `cockatrice_audit` (
|
||||||
CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` (
|
CREATE TABLE IF NOT EXISTS `cockatrice_card_art_name_rules` (
|
||||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`card_name` varchar(255) NOT NULL,
|
`card_name` varchar(255) NOT NULL,
|
||||||
|
`card_provider_id` varchar(255) NOT NULL,
|
||||||
`mode` enum('ALLOW','DENY') NOT NULL,
|
`mode` enum('ALLOW','DENY') NOT NULL,
|
||||||
`reason` varchar(255) DEFAULT NULL,
|
`reason` varchar(255) DEFAULT NULL,
|
||||||
`created_by` int(7) unsigned DEFAULT NULL,
|
`created_by` int(7) unsigned DEFAULT NULL,
|
||||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uniq_card_name` (`card_name`),
|
UNIQUE KEY `uniq_provider_card_name` (`card_provider_id`, `card_name`),
|
||||||
KEY `idx_mode` (`mode`),
|
KEY `idx_mode` (`mode`),
|
||||||
FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`)
|
FOREIGN KEY (`created_by`) REFERENCES `cockatrice_users`(`id`)
|
||||||
ON DELETE SET NULL
|
ON DELETE SET NULL
|
||||||
|
|
|
||||||
|
|
@ -693,6 +693,9 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer
|
||||||
if (obj.contains("card_name")) {
|
if (obj.contains("card_name")) {
|
||||||
cap->set_card_name(obj["card_name"].toString().toStdString());
|
cap->set_card_name(obj["card_name"].toString().toStdString());
|
||||||
}
|
}
|
||||||
|
if (obj.contains("card_provider_id")) {
|
||||||
|
cap->set_card_provider_id(obj["card_provider_id"].toString().toStdString());
|
||||||
|
}
|
||||||
if (obj.contains("marginPctL")) {
|
if (obj.contains("marginPctL")) {
|
||||||
cap->set_margin_pct_l(obj["marginPctL"].toDouble(0.33));
|
cap->set_margin_pct_l(obj["marginPctL"].toDouble(0.33));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/string_limits.h>
|
||||||
#include <server_response_containers.h>
|
#include <server_response_containers.h>
|
||||||
#include <server_room.h>
|
#include <server_room.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -1577,11 +1577,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Comm
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AbstractServerSocketInterface::isCardNameAllowed(const QString &cardName)
|
bool AbstractServerSocketInterface::isCardNameAllowed(const QString &cardName, const QString &cardProviderId)
|
||||||
{
|
{
|
||||||
QSqlQuery *q = sqlInterface->prepareQuery("SELECT mode FROM {prefix}_card_art_name_rules WHERE card_name = :name");
|
QSqlQuery *q = sqlInterface->prepareQuery(
|
||||||
|
"SELECT mode FROM {prefix}_card_art_name_rules WHERE card_name = :name AND card_provider_id = :provider");
|
||||||
|
|
||||||
q->bindValue(":name", cardName);
|
q->bindValue(":name", cardName);
|
||||||
|
q->bindValue(":provider", cardProviderId);
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(q)) {
|
if (!sqlInterface->execSqlQuery(q)) {
|
||||||
qWarning() << "Card art rule lookup failed; failing open for" << cardName;
|
qWarning() << "Card art rule lookup failed; failing open for" << cardName;
|
||||||
|
|
@ -1603,8 +1605,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString cardName = QString::fromStdString(cmd.card_name());
|
const QString cardName = QString::fromStdString(cmd.card_name());
|
||||||
|
const QString cardProviderId = QString::fromStdString(cmd.card_provider_id());
|
||||||
|
|
||||||
if (cardName.length() > MAX_NAME_LENGTH) {
|
if (cardName.length() > MAX_NAME_LENGTH || cardProviderId.length() > MAX_NAME_LENGTH) {
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1620,7 +1623,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCardNameAllowed(cardName)) {
|
if (!isCardNameAllowed(cardName, cardProviderId)) {
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1633,6 +1636,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
|
||||||
|
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj["card_name"] = cardName;
|
obj["card_name"] = cardName;
|
||||||
|
obj["card_provider_id"] = cardProviderId;
|
||||||
obj["marginPctL"] = marginPctL;
|
obj["marginPctL"] = marginPctL;
|
||||||
obj["marginPctR"] = marginPctR;
|
obj["marginPctR"] = marginPctR;
|
||||||
obj["verticalOffset"] = verticalOffset;
|
obj["verticalOffset"] = verticalOffset;
|
||||||
|
|
@ -1649,6 +1653,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const
|
||||||
// Keep the in-memory userInfo in sync
|
// Keep the in-memory userInfo in sync
|
||||||
auto *cap = userInfo->mutable_card_art_params();
|
auto *cap = userInfo->mutable_card_art_params();
|
||||||
cap->set_card_name(cmd.card_name());
|
cap->set_card_name(cmd.card_name());
|
||||||
|
cap->set_card_provider_id(cmd.card_provider_id());
|
||||||
cap->set_margin_pct_l(marginPctL);
|
cap->set_margin_pct_l(marginPctL);
|
||||||
cap->set_margin_pct_r(marginPctR);
|
cap->set_margin_pct_r(marginPctR);
|
||||||
cap->set_vertical_offset(verticalOffset);
|
cap->set_vertical_offset(verticalOffset);
|
||||||
|
|
@ -1664,21 +1669,23 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAddCardArtRule(const Co
|
||||||
ResponseContainer &)
|
ResponseContainer &)
|
||||||
{
|
{
|
||||||
const QString cardName = QString::fromStdString(cmd.card_name());
|
const QString cardName = QString::fromStdString(cmd.card_name());
|
||||||
|
const QString cardProviderId = QString::fromStdString(cmd.card_provider_id());
|
||||||
const QString mode = QString::fromStdString(cmd.mode());
|
const QString mode = QString::fromStdString(cmd.mode());
|
||||||
|
|
||||||
if (mode != "ALLOW" && mode != "DENY") {
|
if (mode != "ALLOW" && mode != "DENY") {
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
if (cardName.isEmpty() || cardName.length() > MAX_NAME_LENGTH) {
|
if (cardName.isEmpty() || cardName.length() > MAX_NAME_LENGTH || cardProviderId.length() > MAX_NAME_LENGTH) {
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSqlQuery *q = sqlInterface->prepareQuery("INSERT INTO {prefix}_card_art_name_rules "
|
QSqlQuery *q = sqlInterface->prepareQuery("INSERT INTO {prefix}_card_art_name_rules "
|
||||||
"(card_name, mode, reason, created_by) "
|
"(card_name, card_provider_id, mode, reason, created_by) "
|
||||||
"VALUES (:name, :mode, :reason, :uid) "
|
"VALUES (:name, :provider, :mode, :reason, :uid) "
|
||||||
"ON DUPLICATE KEY UPDATE mode=:mode2, reason=:reason2");
|
"ON DUPLICATE KEY UPDATE mode=:mode2, reason=:reason2");
|
||||||
|
|
||||||
q->bindValue(":name", cardName);
|
q->bindValue(":name", cardName);
|
||||||
|
q->bindValue(":provider", cardProviderId);
|
||||||
q->bindValue(":mode", mode);
|
q->bindValue(":mode", mode);
|
||||||
q->bindValue(":mode2", mode);
|
q->bindValue(":mode2", mode);
|
||||||
q->bindValue(":reason", QString::fromStdString(cmd.reason()));
|
q->bindValue(":reason", QString::fromStdString(cmd.reason()));
|
||||||
|
|
@ -1696,12 +1703,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveCardArtRule(const
|
||||||
ResponseContainer &)
|
ResponseContainer &)
|
||||||
{
|
{
|
||||||
auto cardName = QString::fromStdString(cmd.card_name());
|
auto cardName = QString::fromStdString(cmd.card_name());
|
||||||
if (cardName.length() > MAX_NAME_LENGTH) {
|
auto cardProviderId = QString::fromStdString(cmd.card_provider_id());
|
||||||
|
if (cardName.length() > MAX_NAME_LENGTH || cardProviderId.length() > MAX_NAME_LENGTH) {
|
||||||
return Response::RespInvalidData;
|
return Response::RespInvalidData;
|
||||||
}
|
}
|
||||||
QSqlQuery *q = sqlInterface->prepareQuery("DELETE FROM {prefix}_card_art_name_rules WHERE card_name=:name");
|
QSqlQuery *q = sqlInterface->prepareQuery(
|
||||||
|
"DELETE FROM {prefix}_card_art_name_rules WHERE card_name=:name AND card_provider_id=:provider");
|
||||||
|
|
||||||
q->bindValue(":name", cardName);
|
q->bindValue(":name", cardName);
|
||||||
|
q->bindValue(":provider", cardProviderId);
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(q)) {
|
if (!sqlInterface->execSqlQuery(q)) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
|
|
@ -1713,7 +1723,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveCardArtRule(const
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdListCardArtRules(const Command_ListCardArtRules &,
|
Response::ResponseCode AbstractServerSocketInterface::cmdListCardArtRules(const Command_ListCardArtRules &,
|
||||||
ResponseContainer &rc)
|
ResponseContainer &rc)
|
||||||
{
|
{
|
||||||
QSqlQuery *q = sqlInterface->prepareQuery("SELECT card_name, mode, reason FROM {prefix}_card_art_name_rules");
|
QSqlQuery *q = sqlInterface->prepareQuery(
|
||||||
|
"SELECT card_name, card_provider_id, mode, reason FROM {prefix}_card_art_name_rules");
|
||||||
|
|
||||||
if (!sqlInterface->execSqlQuery(q)) {
|
if (!sqlInterface->execSqlQuery(q)) {
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
|
|
@ -1724,8 +1735,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdListCardArtRules(const
|
||||||
while (q->next()) {
|
while (q->next()) {
|
||||||
auto *entry = re->add_entries();
|
auto *entry = re->add_entries();
|
||||||
entry->set_card_name(q->value(0).toString().toStdString());
|
entry->set_card_name(q->value(0).toString().toStdString());
|
||||||
entry->set_mode(q->value(1).toString().toStdString());
|
entry->set_card_provider_id(q->value(1).toString().toStdString());
|
||||||
entry->set_reason(q->value(2).toString().toStdString());
|
entry->set_mode(q->value(2).toString().toStdString());
|
||||||
|
entry->set_reason(q->value(3).toString().toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.setResponseExtension(re);
|
rc.setResponseExtension(re);
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ private:
|
||||||
|
|
||||||
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
|
||||||
bool isCardNameAllowed(const QString &cardName);
|
bool isCardNameAllowed(const QString &cardName, const QString &cardProviderId);
|
||||||
Response::ResponseCode cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer &);
|
Response::ResponseCode cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer &);
|
||||||
Response::ResponseCode cmdAddCardArtRule(const Command_AddCardArtRule &cmd, ResponseContainer &);
|
Response::ResponseCode cmdAddCardArtRule(const Command_AddCardArtRule &cmd, ResponseContainer &);
|
||||||
Response::ResponseCode cmdRemoveCardArtRule(const Command_RemoveCardArtRule &cmd, ResponseContainer &);
|
Response::ResponseCode cmdRemoveCardArtRule(const Command_RemoveCardArtRule &cmd, ResponseContainer &);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ 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)
|
||||||
|
|
@ -16,6 +17,7 @@ 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)
|
||||||
|
|
@ -49,6 +51,7 @@ 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)
|
||||||
|
|
@ -59,6 +62,9 @@ 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}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
44
tests/clamped_arithmetic_test.cpp
Normal file
44
tests/clamped_arithmetic_test.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/** @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();
|
||||||
|
}
|
||||||
|
|
@ -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/trice_limits.h>
|
#include <libcockatrice/utility/counter_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_COUNTERS_ON_CARD));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE));
|
||||||
EXPECT_FALSE(card.incrementCounter(1, 1));
|
EXPECT_FALSE(card.incrementCounter(1, 1));
|
||||||
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
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_COUNTERS_ON_CARD - 5));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE - 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_COUNTERS_ON_CARD);
|
EXPECT_EQ(event.counter_value(), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
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_COUNTERS_ON_CARD));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE));
|
||||||
|
|
||||||
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_COUNTERS_ON_CARD);
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
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_COUNTERS_ON_CARD - 5));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE - 5));
|
||||||
EXPECT_TRUE(card.incrementCounter(1, 10));
|
EXPECT_TRUE(card.incrementCounter(1, 10));
|
||||||
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue