Rename PlaymatResolution to Info and add enums

Took 8 minutes
This commit is contained in:
Lukas Brübach 2026-08-21 10:02:47 +02:00
parent c4a6c2ed81
commit a62c5b74f4
20 changed files with 181 additions and 153 deletions

View file

@ -310,29 +310,16 @@ void DeckViewContainer::resolveAndSendPlaymat()
const auto &settings = SettingsCache::instance().userInterface(); const auto &settings = SettingsCache::instance().userInterface();
const auto fallbackBehavior = static_cast<PlaymatFallbackMode>(settings.getPlaymatFallbackBehavior()); const auto fallbackBehavior = static_cast<PlaymatFallbackMode>(settings.getPlaymatFallbackBehavior());
PlaymatResolution resolved; QList<PlaymatInfo> fallbackList = settings.getPlaymatFallbackList();
QList<PlaymatResolution> fallbackList = settings.getPlaymatFallbackList();
// In random mode with 2+ entries, remove the last-resolved mat to avoid repeats. // In random mode with 2+ entries, remove the last-resolved mat to avoid repeats.
if (fallbackBehavior == PlaymatFallbackMode::Random && fallbackList.size() > 1) { if (fallbackBehavior == PlaymatFallbackModeRandom && fallbackList.size() > 1) {
fallbackList.removeAll(lastResolvedPlaymat); fallbackList.removeAll(lastResolvedPlaymat);
} }
switch (settings.getPlaymatMode()) { const PlaymatInfo resolved =
case 0: { // Override deck playmat — always use collection resolvePlaymatForDeck(currentDeck, fallbackList, static_cast<PlaymatMode>(settings.getPlaymatMode()),
DeckList emptyDeck; fallbackBehavior, playmatRotationIndex);
resolved = resolveEffectivePlaymat(emptyDeck, {}, fallbackList, fallbackBehavior, playmatRotationIndex);
break;
}
case 1: // Fallback if deck has none — deck > collection > none
resolved = resolveEffectivePlaymat(currentDeck, {}, fallbackList, fallbackBehavior, playmatRotationIndex);
break;
case 2: // Deck only, ignore collection
resolved = currentDeck.getPlaymat();
break;
default:
break;
}
lastResolvedPlaymat = resolved; lastResolvedPlaymat = resolved;

View file

@ -59,7 +59,7 @@ private:
int playerId; int playerId;
int playmatRotationIndex = 0; ///< Per-match cursor for round-robin playmat mode. int playmatRotationIndex = 0; ///< Per-match cursor for round-robin playmat mode.
DeckList currentDeck; ///< Cached deck for live settings re-resolution. DeckList currentDeck; ///< Cached deck for live settings re-resolution.
PlaymatResolution lastResolvedPlaymat; ///< Tracks last sent playmat to avoid repeats in random mode. PlaymatInfo lastResolvedPlaymat; ///< Tracks last sent playmat to avoid repeats in random mode.
void tryCreateVisualDeckStorageWidget(); void tryCreateVisualDeckStorageWidget();
void sendReadyStartCommand(bool ready); void sendReadyStartCommand(bool ready);

View file

@ -372,13 +372,13 @@ void PlayerGraphicsItem::updatePlaymat()
int visibility = SettingsCache::instance().userInterface().getPlaymatVisibility(); int visibility = SettingsCache::instance().userInterface().getPlaymatVisibility();
// "Don't use playmats" — never show // "Don't use playmats" — never show
if (visibility == 0) { if (visibility == PlaymatVisibilityNone) {
clearPlaymat(); clearPlaymat();
return; return;
} }
// "Show own playmat only" — hide playmats for remote players // "Show own playmat only" — hide playmats for remote players
if (visibility == 1 && !player->getPlayerInfo()->getLocal()) { if (visibility == PlaymatVisibilityOwnOnly && !player->getPlayerInfo()->getLocal()) {
clearPlaymat(); clearPlaymat();
return; return;
} }
@ -394,38 +394,15 @@ void PlayerGraphicsItem::updatePlaymat()
// Local player without a server broadcast yet: apply the full // Local player without a server broadcast yet: apply the full
// settings-based resolution chain (mode, fallback list, behavior). // settings-based resolution chain (mode, fallback list, behavior).
const auto &settings = SettingsCache::instance().userInterface(); const auto &settings = SettingsCache::instance().userInterface();
const DeckList &deck = player->getDeck(); const PlaymatInfo resolved = resolvePlaymatForDeck(
const auto fallbackBehavior = static_cast<PlaymatFallbackMode>(settings.getPlaymatFallbackBehavior()); player->getDeck(), settings.getPlaymatFallbackList(), static_cast<PlaymatMode>(settings.getPlaymatMode()),
static_cast<PlaymatFallbackMode>(settings.getPlaymatFallbackBehavior()), 0);
switch (settings.getPlaymatMode()) {
case 0: { // Override deck playmat — always use collection
DeckList emptyDeck;
const PlaymatResolution resolved =
resolveEffectivePlaymat(emptyDeck, {}, settings.getPlaymatFallbackList(), fallbackBehavior, 0);
playmatCard = resolved.card; playmatCard = resolved.card;
params = resolved.params; params = resolved.params;
break;
}
case 1: { // Fallback if deck has none — deck > collection > none
const PlaymatResolution resolved =
resolveEffectivePlaymat(deck, {}, settings.getPlaymatFallbackList(), fallbackBehavior, 0);
playmatCard = resolved.card;
params = resolved.params;
break;
}
case 2: { // Deck only, ignore collection
const PlaymatResolution &deckPlaymat = deck.getPlaymat();
if (!deckPlaymat.card.isEmpty()) {
playmatCard = deckPlaymat.card;
params = deckPlaymat.params;
}
break;
}
}
} else { } else {
// Opponent without a server broadcast: use the deck-embedded playmat. // Opponent without a server broadcast: use the deck-embedded playmat.
const DeckList &deck = player->getDeck(); const DeckList &deck = player->getDeck();
const PlaymatResolution &deckPlaymat = deck.getPlaymat(); const PlaymatInfo &deckPlaymat = deck.getPlaymat();
if (!deckPlaymat.card.isEmpty()) { if (!deckPlaymat.card.isEmpty()) {
playmatCard = deckPlaymat.card; playmatCard = deckPlaymat.card;
params = deckPlaymat.params; params = deckPlaymat.params;

View file

@ -453,7 +453,7 @@ void DeckEditorDeckDockWidget::writeBannerCard(int index)
void DeckEditorDeckDockWidget::openPlaymatSettings() void DeckEditorDeckDockWidget::openPlaymatSettings()
{ {
PlaymatResolution current = deckStateManager->getMetadata().playmat; PlaymatInfo current = deckStateManager->getMetadata().playmat;
PlaymatSettingsDialog dialog(current.card, current.params, this); PlaymatSettingsDialog dialog(current.card, current.params, this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
@ -461,7 +461,7 @@ void DeckEditorDeckDockWidget::openPlaymatSettings()
PlaymatParams newParams = dialog.params(); PlaymatParams newParams = dialog.params();
if (newCard.isEmpty()) { if (newCard.isEmpty()) {
deckStateManager->setPlaymat(PlaymatResolution{}); deckStateManager->setPlaymat(PlaymatInfo{});
} else { } else {
deckStateManager->setPlaymat({newCard, newParams}); deckStateManager->setPlaymat({newCard, newParams});
} }

View file

@ -142,9 +142,9 @@ void DeckStateManager::setBannerCard(const CardRef &bannerCard)
doMetadataModified(); doMetadataModified();
} }
void DeckStateManager::setPlaymat(const PlaymatResolution &playmat) void DeckStateManager::setPlaymat(const PlaymatInfo &playmat)
{ {
PlaymatResolution previous = deckList->getPlaymat(); PlaymatInfo previous = deckList->getPlaymat();
if (previous == playmat) { if (previous == playmat) {
return; return;
} }

View file

@ -171,7 +171,7 @@ public:
void setName(const QString &name); void setName(const QString &name);
void setComments(const QString &comments); void setComments(const QString &comments);
void setBannerCard(const CardRef &bannerCard); void setBannerCard(const CardRef &bannerCard);
void setPlaymat(const PlaymatResolution &playmat); void setPlaymat(const PlaymatInfo &playmat);
void setTags(const QStringList &tags); void setTags(const QStringList &tags);
void setFormat(const QString &format); void setFormat(const QString &format);
///@} ///@}

View file

@ -38,7 +38,7 @@ void PlaymatCollectionDialog::setupUi()
playmats = interfaceSettings.getPlaymatFallbackList(); playmats = interfaceSettings.getPlaymatFallbackList();
playmatList = new QListWidget; playmatList = new QListWidget;
for (const PlaymatResolution &entry : playmats) { for (const PlaymatInfo &entry : playmats) {
playmatList->addItem(entry.card.name); playmatList->addItem(entry.card.name);
} }
connect(playmatList, &QListWidget::itemSelectionChanged, this, &PlaymatCollectionDialog::selectionChanged); connect(playmatList, &QListWidget::itemSelectionChanged, this, &PlaymatCollectionDialog::selectionChanged);
@ -69,9 +69,9 @@ void PlaymatCollectionDialog::setupUi()
listRow->addLayout(listButtons); listRow->addLayout(listButtons);
modeCombo = new QComboBox; modeCombo = new QComboBox;
modeCombo->addItem(QString(), 0); modeCombo->addItem(QString(), PlaymatFallbackModeFixed);
modeCombo->addItem(QString(), 1); modeCombo->addItem(QString(), PlaymatFallbackModeRoundRobin);
modeCombo->addItem(QString(), 2); modeCombo->addItem(QString(), PlaymatFallbackModeRandom);
const int modeIndex = modeCombo->findData(interfaceSettings.getPlaymatFallbackBehavior()); const int modeIndex = modeCombo->findData(interfaceSettings.getPlaymatFallbackBehavior());
if (modeIndex >= 0) { if (modeIndex >= 0) {
modeCombo->setCurrentIndex(modeIndex); modeCombo->setCurrentIndex(modeIndex);
@ -111,7 +111,7 @@ void PlaymatCollectionDialog::addPlaymat()
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
const CardRef card = dialog.card(); const CardRef card = dialog.card();
if (!card.isEmpty()) { if (!card.isEmpty()) {
PlaymatResolution res = {card, dialog.params()}; PlaymatInfo res = {card, dialog.params()};
playmats.append(res); playmats.append(res);
playmatList->addItem(res.card.name); playmatList->addItem(res.card.name);
playmatList->setCurrentRow(playmatList->count() - 1); playmatList->setCurrentRow(playmatList->count() - 1);
@ -126,7 +126,7 @@ void PlaymatCollectionDialog::editPlaymat()
return; return;
} }
const PlaymatResolution &current = playmats.at(row); const PlaymatInfo &current = playmats.at(row);
PlaymatSettingsDialog dialog(current.card, current.params, this); PlaymatSettingsDialog dialog(current.card, current.params, this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
const CardRef card = dialog.card(); const CardRef card = dialog.card();

View file

@ -40,7 +40,7 @@ private:
void retranslateUi(); void retranslateUi();
int currentRow() const; int currentRow() const;
QList<PlaymatResolution> playmats; ///< Working copy edited by the dialog. QList<PlaymatInfo> playmats; ///< Working copy edited by the dialog.
QListWidget *playmatList; QListWidget *playmatList;
QComboBox *modeCombo; QComboBox *modeCombo;
QLabel *modeLabel; QLabel *modeLabel;

View file

@ -329,9 +329,9 @@ AppearanceSettingsPage::AppearanceSettingsPage()
tableGroupBox->setLayout(tableGrid); tableGroupBox->setLayout(tableGrid);
// Playmat settings // Playmat settings
playmatVisibilityCombo.addItem(tr("Show all playmats"), 2); playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
playmatVisibilityCombo.addItem(tr("Show own playmat only"), 1); playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
playmatVisibilityCombo.addItem(tr("Don't use playmats"), 0); playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility()); int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
if (visIdx >= 0) { if (visIdx >= 0) {
playmatVisibilityCombo.setCurrentIndex(visIdx); playmatVisibilityCombo.setCurrentIndex(visIdx);
@ -342,9 +342,9 @@ AppearanceSettingsPage::AppearanceSettingsPage()
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo); playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
// Playmat mode: Override / Fallback / Deck-only // Playmat mode: Override / Fallback / Deck-only
playmatModeCombo.addItem(tr("Override deck playmat"), 0); playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
playmatModeCombo.addItem(tr("Fallback if deck has none"), 1); playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
playmatModeCombo.addItem(tr("Deck only, ignore collection"), 2); playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode()); int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
if (modeIdx >= 0) { if (modeIdx >= 0) {
playmatModeCombo.setCurrentIndex(modeIdx); playmatModeCombo.setCurrentIndex(modeIdx);

View file

@ -35,4 +35,7 @@ add_dependencies(libcockatrice_deck_list libcockatrice_protocol)
target_include_directories(libcockatrice_deck_list PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(libcockatrice_deck_list PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(libcockatrice_deck_list PUBLIC libcockatrice_protocol libcockatrice_utility ${QT_CORE_MODULE}) target_link_libraries(
libcockatrice_deck_list PUBLIC libcockatrice_interfaces libcockatrice_protocol libcockatrice_utility
${QT_CORE_MODULE}
)

View file

@ -82,7 +82,7 @@ bool DeckList::readElement(QXmlStreamReader *xml)
QString vOffStr = xml->attributes().value("verticalOffset").toString(); QString vOffStr = xml->attributes().value("verticalOffset").toString();
QString zoomStr = xml->attributes().value("zoom").toString(); QString zoomStr = xml->attributes().value("zoom").toString();
QString cardName = xml->readElementText(); QString cardName = xml->readElementText();
PlaymatResolution playmat; PlaymatInfo playmat;
playmat.card = {cardName, providerId}; playmat.card = {cardName, providerId};
// Clamp to the same ranges as the settings dialog and the remote // Clamp to the same ranges as the settings dialog and the remote
// player-properties path so malformed deck files cannot produce // player-properties path so malformed deck files cannot produce

View file

@ -70,7 +70,7 @@ public:
CardRef bannerCard; ///< Optional representative card for the deck. CardRef bannerCard; ///< Optional representative card for the deck.
QStringList tags; ///< User-defined tags for deck classification. QStringList tags; ///< User-defined tags for deck classification.
QString lastLoadedTimestamp; ///< Timestamp string of last load. QString lastLoadedTimestamp; ///< Timestamp string of last load.
PlaymatResolution playmat; ///< Optional playmat background for table+stack zones. PlaymatInfo playmat; ///< Optional playmat background for table+stack zones.
/** /**
* @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty. * @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty.
@ -116,7 +116,7 @@ public:
{ {
metadata.bannerCard = _bannerCard; metadata.bannerCard = _bannerCard;
} }
void setPlaymat(const PlaymatResolution &_playmat = {}) void setPlaymat(const PlaymatInfo &_playmat = {})
{ {
metadata.playmat = _playmat; metadata.playmat = _playmat;
} }
@ -175,7 +175,7 @@ public:
{ {
return metadata.bannerCard; return metadata.bannerCard;
} }
PlaymatResolution getPlaymat() const PlaymatInfo getPlaymat() const
{ {
return metadata.playmat; return metadata.playmat;
} }

View file

@ -2,9 +2,9 @@
#include <QRandomGenerator> #include <QRandomGenerator>
PlaymatResolution resolveEffectivePlaymat(const DeckList &deck, PlaymatInfo resolveEffectivePlaymat(const DeckList &deck,
const PlaymatResolution &force, const PlaymatInfo &force,
const QList<PlaymatResolution> &fallbackList, const QList<PlaymatInfo> &fallbackList,
PlaymatFallbackMode fallbackMode, PlaymatFallbackMode fallbackMode,
int rotationIndex) int rotationIndex)
{ {
@ -12,7 +12,7 @@ PlaymatResolution resolveEffectivePlaymat(const DeckList &deck,
return force; return force;
} }
const PlaymatResolution &deckPlaymat = deck.getPlaymat(); const PlaymatInfo &deckPlaymat = deck.getPlaymat();
if (!deckPlaymat.card.isEmpty()) { if (!deckPlaymat.card.isEmpty()) {
return deckPlaymat; return deckPlaymat;
} }
@ -22,13 +22,33 @@ PlaymatResolution resolveEffectivePlaymat(const DeckList &deck,
} }
switch (fallbackMode) { switch (fallbackMode) {
case PlaymatFallbackMode::Fixed: case PlaymatFallbackModeFixed:
return fallbackList.first(); return fallbackList.first();
case PlaymatFallbackMode::RoundRobin: case PlaymatFallbackModeRoundRobin:
return fallbackList.at(rotationIndex % fallbackList.size()); return fallbackList.at(rotationIndex % fallbackList.size());
case PlaymatFallbackMode::Random: case PlaymatFallbackModeRandom:
return fallbackList.at(QRandomGenerator::global()->bounded(fallbackList.size())); return fallbackList.at(QRandomGenerator::global()->bounded(fallbackList.size()));
} }
return {}; return {};
} }
PlaymatInfo resolvePlaymatForDeck(const DeckList &deck,
const QList<PlaymatInfo> &fallbackList,
PlaymatMode mode,
PlaymatFallbackMode fallbackBehavior,
int rotationIndex)
{
switch (mode) {
case PlaymatModeOverrideDeck: {
const DeckList emptyDeck;
return resolveEffectivePlaymat(emptyDeck, {}, fallbackList, fallbackBehavior, rotationIndex);
}
case PlaymatModeFallback:
return resolveEffectivePlaymat(deck, {}, fallbackList, fallbackBehavior, rotationIndex);
case PlaymatModeDeckOnly:
return deck.getPlaymat();
}
return {};
}

View file

@ -3,16 +3,7 @@
#include "deck_list.h" #include "deck_list.h"
/** #include <libcockatrice/interfaces/interface_interface_settings_provider.h>
* @brief How the user-level fallback playmat list is consulted when a deck has
* no playmat configured.
*/
enum class PlaymatFallbackMode
{
Fixed = 0, ///< Always use the first entry of the fallback list.
RoundRobin = 1, ///< Cycle through the list, advancing one entry per resolution.
Random = 2 ///< Pick a random entry per resolution.
};
/** /**
* @brief Resolves the effective playmat for a deck per the resolution chain: * @brief Resolves the effective playmat for a deck per the resolution chain:
@ -20,19 +11,37 @@ enum class PlaymatFallbackMode
* *
* @param deck The deck to resolve a playmat for. * @param deck The deck to resolve a playmat for.
* @param force An optional user-level override; wins over everything. Pass an * @param force An optional user-level override; wins over everything. Pass an
* empty @ref PlaymatResolution::card to skip it. * empty @ref PlaymatInfo::card to skip it.
* @param fallbackList User-level fallback playmats, consulted only when the * @param fallbackList User-level fallback playmats, consulted only when the
* deck has no configured playmat. * deck has no configured playmat.
* @param fallbackMode How @p fallbackList is consulted (ignored when empty). * @param fallbackMode How @p fallbackList is consulted (ignored when empty).
* @param rotationIndex In/out cursor for @c PlaymatFallbackMode::RoundRobin; * @param rotationIndex In/out cursor for @c PlaymatFallbackModeRoundRobin;
* advanced once per call. Unused for the other modes. * advanced once per call. Unused for the other modes.
* @return The effective playmat; an empty @ref PlaymatResolution::card when * @return The effective playmat; an empty @ref PlaymatInfo::card when
* nothing in the chain resolves. * nothing in the chain resolves.
*/ */
PlaymatResolution resolveEffectivePlaymat(const DeckList &deck, PlaymatInfo resolveEffectivePlaymat(const DeckList &deck,
const PlaymatResolution &force, const PlaymatInfo &force,
const QList<PlaymatResolution> &fallbackList, const QList<PlaymatInfo> &fallbackList,
PlaymatFallbackMode fallbackMode, PlaymatFallbackMode fallbackMode,
int rotationIndex); int rotationIndex);
/**
* @brief Resolves the playmat to display for a deck according to the user's
* collection mode (@ref PlaymatMode), combining the deck with the
* given fallback list.
*
* @param deck The deck to resolve a playmat for.
* @param fallbackList User-level fallback playmats.
* @param mode How the collection interacts with the deck-configured playmat.
* @param fallbackBehavior How @p fallbackList is picked from.
* @param rotationIndex Cursor for @c PlaymatFallbackModeRoundRobin.
* @return The effective playmat; an empty @ref PlaymatInfo::card when nothing resolves.
*/
PlaymatInfo resolvePlaymatForDeck(const DeckList &deck,
const QList<PlaymatInfo> &fallbackList,
PlaymatMode mode,
PlaymatFallbackMode fallbackBehavior,
int rotationIndex);
#endif // COCKATRICE_PLAYMAT_RESOLVER_H #endif // COCKATRICE_PLAYMAT_RESOLVER_H

View file

@ -6,6 +6,36 @@
#include <QStringList> #include <QStringList>
#include <libcockatrice/utility/playmat_params.h> #include <libcockatrice/utility/playmat_params.h>
/**
* @brief Whether playmats are rendered in-game, and for whom.
*/
enum PlaymatVisibility
{
PlaymatVisibilityNone = 0, ///< Don't use playmats.
PlaymatVisibilityOwnOnly = 1, ///< Show the local player's playmat only.
PlaymatVisibilityAll = 2 ///< Show playmats for all players.
};
/**
* @brief How the user-level playmat collection interacts with the deck-configured playmat.
*/
enum PlaymatMode
{
PlaymatModeOverrideDeck = 0, ///< Always use the collection, ignoring any deck-configured playmat.
PlaymatModeFallback = 1, ///< Prefer the deck-configured playmat; fall back to the collection when absent.
PlaymatModeDeckOnly = 2 ///< Use only the deck-configured playmat, ignoring the collection.
};
/**
* @brief How the user-level fallback playmat list is consulted when a deck has no playmat configured.
*/
enum PlaymatFallbackMode
{
PlaymatFallbackModeFixed = 0, ///< Always use the first entry of the fallback list.
PlaymatFallbackModeRoundRobin = 1, ///< Cycle through the list, advancing one entry per resolution.
PlaymatFallbackModeRandom = 2 ///< Pick a random entry per resolution.
};
class IInterfaceSettingsProvider class IInterfaceSettingsProvider
{ {
public: public:
@ -45,17 +75,20 @@ public:
[[nodiscard]] virtual bool getLifeCounterAnimationsEnabled() const = 0; [[nodiscard]] virtual bool getLifeCounterAnimationsEnabled() const = 0;
[[nodiscard]] virtual bool getBattlefieldFlashEnabled() const = 0; [[nodiscard]] virtual bool getBattlefieldFlashEnabled() const = 0;
[[nodiscard]] virtual QStringList getUserListExpandedSections() const = 0; [[nodiscard]] virtual QStringList getUserListExpandedSections() const = 0;
/** @brief Who gets playmats rendered: @ref PlaymatVisibility. */
[[nodiscard]] virtual int getPlaymatVisibility() const = 0; [[nodiscard]] virtual int getPlaymatVisibility() const = 0;
/** @brief User-level playmat collection. Used either as a forced playmat /** @brief User-level playmat collection. Used either as a forced playmat
* (mode == Always) or as a fallback when a deck has none (mode == Fallback). */ * (mode == @ref PlaymatModeOverrideDeck) or as a fallback when a deck has none
[[nodiscard]] virtual QList<PlaymatResolution> getPlaymatFallbackList() const = 0; * (mode == @ref PlaymatModeFallback). */
[[nodiscard]] virtual QList<PlaymatInfo> getPlaymatFallbackList() const = 0;
/** @brief How the fallback list is applied: Always (0), Fallback (1), Never (2). */ /** @brief How the fallback list is applied: @ref PlaymatMode. */
[[nodiscard]] virtual int getPlaymatMode() const = 0; [[nodiscard]] virtual int getPlaymatMode() const = 0;
/** @brief How the fallback list is picked from when mode is Fallback /** @brief How the fallback list is picked from when mode is @ref PlaymatModeFallback:
* (0 Fixed, 1 Round-robin, 2 Random). */ * @ref PlaymatFallbackMode. */
[[nodiscard]] virtual int getPlaymatFallbackBehavior() const = 0; [[nodiscard]] virtual int getPlaymatFallbackBehavior() const = 0;
}; };

View file

@ -618,7 +618,7 @@ Server_Player::cmdSetPlaymat(const Command_SetPlaymat &cmd, ResponseContainer &r
if (rawName.length() > MAX_NAME_LENGTH || rawProviderId.length() > MAX_NAME_LENGTH) { if (rawName.length() > MAX_NAME_LENGTH || rawProviderId.length() > MAX_NAME_LENGTH) {
return Response::RespInvalidData; return Response::RespInvalidData;
} }
PlaymatResolution playmat; PlaymatInfo playmat;
playmat.card.name = rawName; playmat.card.name = rawName;
playmat.card.providerId = rawProviderId; playmat.card.providerId = rawProviderId;
playmat.params.marginPctL = qBound(0.0, pp.margin_pct_l(), 0.95); playmat.params.marginPctL = qBound(0.0, pp.margin_pct_l(), 0.95);

View file

@ -2,9 +2,9 @@
namespace namespace
{ {
const QChar PLAYMAT_FIELD_SEP = QChar(0x1F); ///< Separator between PlaymatResolution fields. const QChar PLAYMAT_FIELD_SEP = QChar(0x1F); ///< Separator between PlaymatInfo fields.
QString encodePlaymatResolution(const PlaymatResolution &res) QString encodePlaymatInfo(const PlaymatInfo &res)
{ {
return res.card.name + PLAYMAT_FIELD_SEP + res.card.providerId + PLAYMAT_FIELD_SEP + return res.card.name + PLAYMAT_FIELD_SEP + res.card.providerId + PLAYMAT_FIELD_SEP +
QString::number(res.params.marginPctL, 'f', 4) + PLAYMAT_FIELD_SEP + QString::number(res.params.marginPctL, 'f', 4) + PLAYMAT_FIELD_SEP +
@ -13,13 +13,13 @@ QString encodePlaymatResolution(const PlaymatResolution &res)
QString::number(res.params.zoom, 'f', 4); QString::number(res.params.zoom, 'f', 4);
} }
PlaymatResolution decodePlaymatResolution(const QString &encoded) PlaymatInfo decodePlaymatInfo(const QString &encoded)
{ {
const QStringList fields = encoded.split(PLAYMAT_FIELD_SEP); const QStringList fields = encoded.split(PLAYMAT_FIELD_SEP);
if (fields.size() != 6) { if (fields.size() != 6) {
return {}; return {};
} }
PlaymatResolution res; PlaymatInfo res;
res.card.name = fields.at(0); res.card.name = fields.at(0);
res.card.providerId = fields.at(1); res.card.providerId = fields.at(1);
res.params.marginPctL = fields.at(2).toDouble(); res.params.marginPctL = fields.at(2).toDouble();
@ -192,16 +192,16 @@ bool InterfaceSettings::getShowGameSelectorFilterToolbar() const
int InterfaceSettings::getPlaymatVisibility() const int InterfaceSettings::getPlaymatVisibility() const
{ {
return getValue("playmatvisibility", QString(), QString(), 2).toInt(); return qBound(0, getValue("playmatvisibility", QString(), QString(), 2).toInt(), 2);
} }
QList<PlaymatResolution> InterfaceSettings::getPlaymatFallbackList() const QList<PlaymatInfo> InterfaceSettings::getPlaymatFallbackList() const
{ {
const QStringList entries = getValue("playmatFallbackList", QString(), QString(), QStringList()).toStringList(); const QStringList entries = getValue("playmatFallbackList", QString(), QString(), QStringList()).toStringList();
QList<PlaymatResolution> result; QList<PlaymatInfo> result;
result.reserve(entries.size()); result.reserve(entries.size());
for (const QString &entry : entries) { for (const QString &entry : entries) {
const PlaymatResolution res = decodePlaymatResolution(entry); const PlaymatInfo res = decodePlaymatInfo(entry);
if (!res.card.isEmpty()) { if (!res.card.isEmpty()) {
result.append(res); result.append(res);
} }
@ -411,12 +411,12 @@ void InterfaceSettings::setPlaymatVisibility(int _visibility)
emit playmatVisibilityChanged(_visibility); emit playmatVisibilityChanged(_visibility);
} }
void InterfaceSettings::setPlaymatFallbackList(const QList<PlaymatResolution> &_fallbackList) void InterfaceSettings::setPlaymatFallbackList(const QList<PlaymatInfo> &_fallbackList)
{ {
QStringList entries; QStringList entries;
entries.reserve(_fallbackList.size()); entries.reserve(_fallbackList.size());
for (const PlaymatResolution &res : _fallbackList) { for (const PlaymatInfo &res : _fallbackList) {
entries.append(encodePlaymatResolution(res)); entries.append(encodePlaymatInfo(res));
} }
setValue(entries, "playmatFallbackList"); setValue(entries, "playmatFallbackList");
emit playmatSettingsChanged(); emit playmatSettingsChanged();

View file

@ -43,7 +43,7 @@ public:
[[nodiscard]] bool getShowShortcuts() const override; [[nodiscard]] bool getShowShortcuts() const override;
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const override; [[nodiscard]] bool getShowGameSelectorFilterToolbar() const override;
[[nodiscard]] int getPlaymatVisibility() const override; [[nodiscard]] int getPlaymatVisibility() const override;
[[nodiscard]] QList<PlaymatResolution> getPlaymatFallbackList() const override; [[nodiscard]] QList<PlaymatInfo> getPlaymatFallbackList() const override;
[[nodiscard]] int getPlaymatMode() const override; [[nodiscard]] int getPlaymatMode() const override;
[[nodiscard]] int getPlaymatFallbackBehavior() const override; [[nodiscard]] int getPlaymatFallbackBehavior() const override;
[[nodiscard]] bool getLifeCounterAnimationsEnabled() const override; [[nodiscard]] bool getLifeCounterAnimationsEnabled() const override;
@ -82,7 +82,7 @@ public:
void setShowShortcuts(bool _showShortcuts); void setShowShortcuts(bool _showShortcuts);
void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar); void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar);
void setPlaymatVisibility(int _visibility); void setPlaymatVisibility(int _visibility);
void setPlaymatFallbackList(const QList<PlaymatResolution> &_fallbackList); void setPlaymatFallbackList(const QList<PlaymatInfo> &_fallbackList);
void setPlaymatMode(int _mode); void setPlaymatMode(int _mode);
void setPlaymatFallbackBehavior(int _behavior); void setPlaymatFallbackBehavior(int _behavior);
void setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled); void setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled);

View file

@ -34,16 +34,16 @@ struct PlaymatParams
}; };
/** /**
* @struct PlaymatResolution * @struct PlaymatInfo
* @ingroup Decks * @ingroup Decks
* @brief A resolved playmat (card + positioning parameters). * @brief A resolved playmat (card + positioning parameters).
*/ */
struct PlaymatResolution struct PlaymatInfo
{ {
CardRef card; ///< The card whose art is used as playmat. CardRef card; ///< The card whose art is used as playmat.
PlaymatParams params; ///< Positioning parameters for the playmat card image. PlaymatParams params; ///< Positioning parameters for the playmat card image.
bool operator==(const PlaymatResolution &other) const bool operator==(const PlaymatInfo &other) const
{ {
return card == other.card && params == other.params; return card == other.card && params == other.params;
} }

View file

@ -5,11 +5,11 @@
namespace namespace
{ {
PlaymatResolution makeResolution(const QString &name, const QString &providerId = QString()) PlaymatInfo makePlaymatInfo(const QString &name, const QString &providerId = QString())
{ {
PlaymatResolution resolution; PlaymatInfo info;
resolution.card = {name, providerId}; info.card = {name, providerId};
return resolution; return info;
} }
} // namespace } // namespace
@ -17,7 +17,7 @@ PlaymatResolution makeResolution(const QString &name, const QString &providerId
TEST(PlaymatResolverTest, EmptyChainReturnsEmpty) TEST(PlaymatResolverTest, EmptyChainReturnsEmpty)
{ {
DeckList deck; DeckList deck;
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, {}, {}, PlaymatFallbackMode::Fixed, 0); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, {}, PlaymatFallbackModeFixed, 0);
EXPECT_TRUE(resolved.card.isEmpty()); EXPECT_TRUE(resolved.card.isEmpty());
} }
@ -26,10 +26,10 @@ TEST(PlaymatResolverTest, OverrideWinsOverDeckAndFallback)
DeckList deck; DeckList deck;
deck.setPlaymat({{QStringLiteral("Deck Mat"), QStringLiteral("deck-provider")}, {}}); deck.setPlaymat({{QStringLiteral("Deck Mat"), QStringLiteral("deck-provider")}, {}});
const PlaymatResolution force = makeResolution(QStringLiteral("Force Mat"), QStringLiteral("force-provider")); const PlaymatInfo force = makePlaymatInfo(QStringLiteral("Force Mat"), QStringLiteral("force-provider"));
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("Fallback Mat"))}; const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("Fallback Mat"))};
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, force, fallback, PlaymatFallbackMode::Fixed, 0); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, force, fallback, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Force Mat")); EXPECT_EQ(resolved.card.name, QStringLiteral("Force Mat"));
EXPECT_EQ(resolved.card.providerId, QStringLiteral("force-provider")); EXPECT_EQ(resolved.card.providerId, QStringLiteral("force-provider"));
} }
@ -39,9 +39,9 @@ TEST(PlaymatResolverTest, DeckWinsOverFallback)
DeckList deck; DeckList deck;
deck.setPlaymat({{QStringLiteral("Deck Mat"), QStringLiteral("deck-provider")}, {0.1, 0.2, 0.3, 1.5}}); deck.setPlaymat({{QStringLiteral("Deck Mat"), QStringLiteral("deck-provider")}, {0.1, 0.2, 0.3, 1.5}});
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("Fallback Mat"))}; const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("Fallback Mat"))};
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackMode::Fixed, 0); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Deck Mat")); EXPECT_EQ(resolved.card.name, QStringLiteral("Deck Mat"));
EXPECT_EQ(resolved.card.providerId, QStringLiteral("deck-provider")); EXPECT_EQ(resolved.card.providerId, QStringLiteral("deck-provider"));
EXPECT_DOUBLE_EQ(resolved.params.marginPctL, 0.1); EXPECT_DOUBLE_EQ(resolved.params.marginPctL, 0.1);
@ -51,20 +51,20 @@ TEST(PlaymatResolverTest, DeckWinsOverFallback)
TEST(PlaymatResolverTest, FallbackUsedWhenDeckHasNone) TEST(PlaymatResolverTest, FallbackUsedWhenDeckHasNone)
{ {
DeckList deck; DeckList deck;
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("Fallback Mat"))}; const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("Fallback Mat"))};
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackMode::Fixed, 0); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Fallback Mat")); EXPECT_EQ(resolved.card.name, QStringLiteral("Fallback Mat"));
} }
TEST(PlaymatResolverTest, FixedAlwaysUsesFirst) TEST(PlaymatResolverTest, FixedAlwaysUsesFirst)
{ {
DeckList deck; DeckList deck;
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("First")), const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makeResolution(QStringLiteral("Second"))}; makePlaymatInfo(QStringLiteral("Second"))};
for (int i = 0; i < 5; ++i) { for (int i = 0; i < 5; ++i) {
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackMode::Fixed, i); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeFixed, i);
EXPECT_EQ(resolved.card.name, QStringLiteral("First")); EXPECT_EQ(resolved.card.name, QStringLiteral("First"));
} }
} }
@ -72,15 +72,14 @@ TEST(PlaymatResolverTest, FixedAlwaysUsesFirst)
TEST(PlaymatResolverTest, RoundRobinCyclesAndWraps) TEST(PlaymatResolverTest, RoundRobinCyclesAndWraps)
{ {
DeckList deck; DeckList deck;
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("First")), const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makeResolution(QStringLiteral("Second")), makePlaymatInfo(QStringLiteral("Second")),
makeResolution(QStringLiteral("Third"))}; makePlaymatInfo(QStringLiteral("Third"))};
const QStringList expected = {QStringLiteral("First"), QStringLiteral("Second"), QStringLiteral("Third"), const QStringList expected = {QStringLiteral("First"), QStringLiteral("Second"), QStringLiteral("Third"),
QStringLiteral("First"), QStringLiteral("Second"), QStringLiteral("Third")}; QStringLiteral("First"), QStringLiteral("Second"), QStringLiteral("Third")};
for (int i = 0; i < expected.size(); ++i) { for (int i = 0; i < expected.size(); ++i) {
const PlaymatResolution resolved = const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeRoundRobin, i);
resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackMode::RoundRobin, i);
EXPECT_EQ(resolved.card.name, expected.at(i)); EXPECT_EQ(resolved.card.name, expected.at(i));
} }
} }
@ -88,22 +87,22 @@ TEST(PlaymatResolverTest, RoundRobinCyclesAndWraps)
TEST(PlaymatResolverTest, RoundRobinRespectsCursor) TEST(PlaymatResolverTest, RoundRobinRespectsCursor)
{ {
DeckList deck; DeckList deck;
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("First")), const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makeResolution(QStringLiteral("Second"))}; makePlaymatInfo(QStringLiteral("Second"))};
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackMode::RoundRobin, 5); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeRoundRobin, 5);
EXPECT_EQ(resolved.card.name, QStringLiteral("Second")); // 5 % 2 == 1 EXPECT_EQ(resolved.card.name, QStringLiteral("Second")); // 5 % 2 == 1
} }
TEST(PlaymatResolverTest, RandomStaysWithinList) TEST(PlaymatResolverTest, RandomStaysWithinList)
{ {
DeckList deck; DeckList deck;
const QList<PlaymatResolution> fallback = {makeResolution(QStringLiteral("First")), const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makeResolution(QStringLiteral("Second")), makePlaymatInfo(QStringLiteral("Second")),
makeResolution(QStringLiteral("Third"))}; makePlaymatInfo(QStringLiteral("Third"))};
for (int i = 0; i < 50; ++i) { for (int i = 0; i < 50; ++i) {
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackMode::Random, i); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeRandom, i);
ASSERT_FALSE(resolved.card.name.isEmpty()); ASSERT_FALSE(resolved.card.name.isEmpty());
EXPECT_TRUE(fallback.contains(resolved)); EXPECT_TRUE(fallback.contains(resolved));
} }
@ -115,8 +114,8 @@ TEST(PlaymatResolverTest, ForceWithEmptyCardIgnoresFallbackParamsButNotFallback)
deck.setPlaymat({{QStringLiteral("Deck Mat")}, {}}); deck.setPlaymat({{QStringLiteral("Deck Mat")}, {}});
// An empty force entry must not mask the deck-configured playmat. // An empty force entry must not mask the deck-configured playmat.
const PlaymatResolution emptyForce; const PlaymatInfo emptyForce;
const PlaymatResolution resolved = resolveEffectivePlaymat(deck, emptyForce, {}, PlaymatFallbackMode::Fixed, 0); const PlaymatInfo resolved = resolveEffectivePlaymat(deck, emptyForce, {}, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Deck Mat")); EXPECT_EQ(resolved.card.name, QStringLiteral("Deck Mat"));
} }