diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index cc58c5b43..7d0e22fd8 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -99,7 +99,6 @@ set(cockatrice_SOURCES src/game_graphics/player/menu/sideboard_menu.cpp src/game_graphics/player/menu/tally_menu.cpp src/game_graphics/player/menu/utility_menu.cpp - src/game_graphics/tally/stats_tally.cpp src/game_graphics/tally/subtype_tally.cpp src/game_graphics/tally/tally.cpp src/game/player/player_actions.cpp @@ -230,7 +229,6 @@ set(cockatrice_SOURCES src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp src/interface/widgets/quick_settings/settings_button_widget.cpp src/interface/widgets/quick_settings/settings_popup_widget.cpp - src/interface/widgets/replay/replay_manager.cpp src/interface/widgets/replay/replay_quick_settings_widget.cpp src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/replay/replay_widget.cpp diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp index 6aa2ab28f..c20003ece 100644 --- a/cockatrice/src/game/abstract_game.cpp +++ b/cockatrice/src/game/abstract_game.cpp @@ -31,7 +31,7 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const } } -void AbstractGame::loadReplay(const GameReplay *replay) +void AbstractGame::loadReplay(GameReplay *replay) { gameMetaInfo->setFromProto(replay->game_info()); gameMetaInfo->setSpectatorsOmniscient(true); diff --git a/cockatrice/src/game/abstract_game.h b/cockatrice/src/game/abstract_game.h index fcf764492..5115ed5ca 100644 --- a/cockatrice/src/game/abstract_game.h +++ b/cockatrice/src/game/abstract_game.h @@ -53,7 +53,7 @@ public: AbstractClient *getClientForPlayer(int playerId) const; - void loadReplay(const GameReplay *replay); + void loadReplay(GameReplay *replay); CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; diff --git a/cockatrice/src/game/replay.cpp b/cockatrice/src/game/replay.cpp index dcf3e9b9b..69f9d8b20 100644 --- a/cockatrice/src/game/replay.cpp +++ b/cockatrice/src/game/replay.cpp @@ -2,7 +2,7 @@ #include "../interface/widgets/tabs/tab_game.h" -Replay::Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent) +Replay::Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent) { gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false); connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); diff --git a/cockatrice/src/game/replay.h b/cockatrice/src/game/replay.h index 1c269b273..ecb3a10d0 100644 --- a/cockatrice/src/game/replay.h +++ b/cockatrice/src/game/replay.h @@ -15,7 +15,7 @@ class Replay : public AbstractGame Q_OBJECT public: - explicit Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame); + explicit Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame); }; #endif // COCKATRICE_REPLAY_H diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index ed190552e..bda5ea76d 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -256,7 +256,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) GameScene *gameScene = static_cast(scene()); QList entries = Tally::compute(gameScene->selectedCards(), tallyType); - if (entries.isEmpty()) { + if (entries.isEmpty() || count <= 1) { tallyContainer->hide(); cachedTallyRows.clear(); return; diff --git a/cockatrice/src/game_graphics/player/menu/tally_menu.cpp b/cockatrice/src/game_graphics/player/menu/tally_menu.cpp index 2bf02904e..b8616c75a 100644 --- a/cockatrice/src/game_graphics/player/menu/tally_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/tally_menu.cpp @@ -11,12 +11,10 @@ TallyMenu::TallyMenu() aTallyNone = createTallyAction(TallyType::None); aTallySubtypes = createTallyAction(TallyType::Subtypes); - aTallyTotalPower = createTallyAction(TallyType::TotalPower); addAction(aTallyNone); addSeparator(); addAction(aTallySubtypes); - addAction(aTallyTotalPower); retranslateUi(); } @@ -53,5 +51,4 @@ void TallyMenu::retranslateUi() aTallyNone->setText(tr("None")); aTallySubtypes->setText(tr("Subtypes")); - aTallyTotalPower->setText(tr("Total Power")); } diff --git a/cockatrice/src/game_graphics/player/menu/tally_menu.h b/cockatrice/src/game_graphics/player/menu/tally_menu.h index acd1daf67..28c056f44 100644 --- a/cockatrice/src/game_graphics/player/menu/tally_menu.h +++ b/cockatrice/src/game_graphics/player/menu/tally_menu.h @@ -23,7 +23,6 @@ private: QAction *aTallyNone = nullptr; QAction *aTallySubtypes = nullptr; - QAction *aTallyTotalPower = nullptr; QAction *createTallyAction(TallyType tallyType); }; diff --git a/cockatrice/src/game_graphics/tally/stats_tally.cpp b/cockatrice/src/game_graphics/tally/stats_tally.cpp deleted file mode 100644 index e7a6621fa..000000000 --- a/cockatrice/src/game_graphics/tally/stats_tally.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "stats_tally.h" - -#include "../board/card_item.h" - -#include -#include -#include - -static int sumPowers(const QList &cards) -{ - // calculate total power; - int total = 0; - for (auto card : cards) { - QVariantList parsed = CardItem::parsePT(card->getPT()); - if (!parsed.isEmpty()) { - int power = parsed.first().toInt(); // toInt will default to 0 if it's not an int - total += qMax(power, 0); - } - } - return total; -} - -QList StatsTally::computeTotalPower(const QList &cards) -{ - // don't bother if none of the cards have pt - bool hasPT = - std::any_of(cards.cbegin(), cards.cend(), [](const CardItem *card) { return !card->getPT().isEmpty(); }); - if (!hasPT) { - return {}; - } - - int total = sumPowers(cards); - - QString name = QCoreApplication::translate("StatsTally", "Total Power"); - return {TallyRow{name, QString::number(total)}}; -} diff --git a/cockatrice/src/game_graphics/tally/stats_tally.h b/cockatrice/src/game_graphics/tally/stats_tally.h deleted file mode 100644 index 4c3d93b56..000000000 --- a/cockatrice/src/game_graphics/tally/stats_tally.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef COCKATRICE_STATS_TALLY_H -#define COCKATRICE_STATS_TALLY_H -#include "tally.h" - -/** - * @brief Extracts and tallies stats from selected cards. - */ -namespace StatsTally -{ - -/** - * @brief Sums the power of all selected cards - * - * @param cards The list of selected card items to analyze. - * @return A single row containing the total, or an empty list if none of the cards have pt - */ -QList computeTotalPower(const QList &cards); - -} // namespace StatsTally - -#endif // COCKATRICE_STATS_TALLY_H diff --git a/cockatrice/src/game_graphics/tally/tally.cpp b/cockatrice/src/game_graphics/tally/tally.cpp index aa2cae024..f9389d0d6 100644 --- a/cockatrice/src/game_graphics/tally/tally.cpp +++ b/cockatrice/src/game_graphics/tally/tally.cpp @@ -1,6 +1,5 @@ #include "tally.h" -#include "stats_tally.h" #include "subtype_tally.h" TallyType Tally::intToType(int value) @@ -19,8 +18,6 @@ QList Tally::compute(const QList &cards, const TallyType t return {}; case TallyType::Subtypes: return SubtypeTally::countSubtypes(cards); - case TallyType::TotalPower: - return StatsTally::computeTotalPower(cards); } return {}; } diff --git a/cockatrice/src/game_graphics/tally/tally.h b/cockatrice/src/game_graphics/tally/tally.h index 97406cddb..d0fd77127 100644 --- a/cockatrice/src/game_graphics/tally/tally.h +++ b/cockatrice/src/game_graphics/tally/tally.h @@ -20,8 +20,7 @@ enum class TallyType { None, Subtypes, - TotalPower, - MaxValue = TotalPower // sentinel value + MaxValue = Subtypes // sentinel value }; namespace Tally diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp deleted file mode 100644 index 1037d36a8..000000000 --- a/cockatrice/src/interface/widgets/replay/replay_manager.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "replay_manager.h" - -#include "../../../client/settings/cache_settings.h" - -#include -#include - -static constexpr int TIMER_INTERVAL_MS = 200; - -static QList createReplayTimeline(const GameReplay *replay) -{ - // Create list: event number -> time [ms] - unsigned int lastEventTimestamp = 0; - const int eventCount = replay->event_list_size(); - - QList replayTimeline; - for (int i = 0; i < eventCount; ++i) { - int nextSecondIndex = i + 1; - while (nextSecondIndex < eventCount && - replay->event_list(nextSecondIndex).seconds_elapsed() == lastEventTimestamp) { - ++nextSecondIndex; - } - - // Distribute simultaneous events evenly across 1 second. - const int numberEventsThisSecond = nextSecondIndex - i; - for (int k = 0; k < numberEventsThisSecond; ++k) { - int eventMs = replay->event_list(i + k).seconds_elapsed() * 1000; - int distributionMs = static_cast(static_cast(k) / numberEventsThisSecond * 1000); - replayTimeline.append(eventMs + distributionMs); - } - - if (nextSecondIndex < eventCount) { - lastEventTimestamp = replay->event_list(nextSecondIndex).seconds_elapsed(); - } - i += numberEventsThisSecond - 1; - } - - return replayTimeline; -} - -ReplayManager::ReplayManager(QObject *parent, GameReplay *replay) - : QObject(parent), replay(replay), replayTimeline(createReplayTimeline(replay)) -{ - maxTime = replayTimeline.isEmpty() ? 0 : replayTimeline.last(); - - replayTimer = new QTimer(this); - replayTimer->setInterval(TIMER_INTERVAL_MS); - connect(replayTimer, &QTimer::timeout, this, &ReplayManager::replayTimerTimeout); - - rewindBufferingTimer = new QTimer(this); - rewindBufferingTimer->setSingleShot(true); - connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayManager::processRewind); -} - -ReplayManager::~ReplayManager() -{ - delete replay; -} - -void ReplayManager::skipToTime(int newTime, bool doRewindBuffering) -{ - // check boundary conditions - if (newTime < 0) { - newTime = 0; - } - if (newTime > maxTime) { - newTime = maxTime; - } - - newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval - - const bool isBackwardsSkip = newTime < currentProcessedTime; - currentVisualTime = newTime; - - if (isBackwardsSkip) { - handleBackwardsSkip(doRewindBuffering); - } else { - processNewEvents(FORWARD_SKIP); - } - - timeChanged(currentVisualTime); -} - -/** - * @brief Handles a backwards skip in the replay timeline. - * - * @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind - * is processed at the end. When false, the backwards skip will always cause an immediate rewind. - */ -void ReplayManager::handleBackwardsSkip(bool doRewindBuffering) -{ - if (doRewindBuffering) { - // We use a one-shot timer to implement the rewind buffering. - // The rewind only happens once the timer runs out. - // If another backwards skip happens, the timer will just get reset instead of rewinding. - rewindBufferingTimer->stop(); - rewindBufferingTimer->start(SettingsCache::instance().interface().getRewindBufferingMs()); - } else { - // otherwise, process the rewind immediately - processRewind(); - } -} - -void ReplayManager::processRewind() -{ - // stop any queued-up rewinds - rewindBufferingTimer->stop(); - - // process the rewind - currentEvent = 0; - emit rewound(); - processNewEvents(BACKWARD_SKIP); -} - -void ReplayManager::replayTimerTimeout() -{ - currentVisualTime += TIMER_INTERVAL_MS; - - processNewEvents(NORMAL_PLAYBACK); - - timeChanged(currentVisualTime); -} - -/** @brief Processes all unprocessed events up to the current time. */ -void ReplayManager::processNewEvents(PlaybackMode playbackMode) -{ - currentProcessedTime = currentVisualTime; - - while (currentEvent < replayTimeline.size() && replayTimeline[currentEvent] < currentProcessedTime) { - EventProcessingOptions options; - - // backwards skip => always skip reveal windows - // forwards skip => skip reveal windows that don't happen within a big skip of the target - if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) { - options |= SKIP_REVEAL_WINDOW; - } - - // backwards skip => always skip tap animation - if (playbackMode == BACKWARD_SKIP) { - options |= SKIP_TAP_ANIMATION; - } - - emit eventReplayed(replay->event_list(currentEvent), options); - ++currentEvent; - } - if (currentEvent == replayTimeline.size()) { - emit replayFinished(); - replayTimer->stop(); - } -} - -void ReplayManager::setTimeScaleFactor(qreal _timeScaleFactor) -{ - timeScaleFactor = _timeScaleFactor; - int interval = std::max(1, qRound(TIMER_INTERVAL_MS / timeScaleFactor)); - replayTimer->setInterval(interval); -} - -void ReplayManager::startReplay() -{ - replayTimer->start(); -} - -void ReplayManager::stopReplay() -{ - replayTimer->stop(); -} - -void ReplayManager::setTime(int time) -{ - // don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering - skipToTime(time, false); -} - -void ReplayManager::skipByAmount(int amount) -{ - skipToTime(currentVisualTime + amount, amount < 0); -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.h b/cockatrice/src/interface/widgets/replay/replay_manager.h deleted file mode 100644 index 16d3591ba..000000000 --- a/cockatrice/src/interface/widgets/replay/replay_manager.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef COCKATRICE_REPLAY_MANAGER_H -#define COCKATRICE_REPLAY_MANAGER_H - -#include "../../../game/player/event_processing_options.h" - -#include -#include - -class GameReplay; -class QTimer; - -/** - * @brief This class handles all logic to do with playing back replays - */ -class ReplayManager : public QObject -{ - Q_OBJECT - - enum PlaybackMode - { - NORMAL_PLAYBACK, - FORWARD_SKIP, - BACKWARD_SKIP - }; - - GameReplay *replay; - QList replayTimeline; ///< timestamp of each event, with the indexes corresponding - int maxTime; - - QTimer *replayTimer; - QTimer *rewindBufferingTimer; - - qreal timeScaleFactor = 1.0; - - int currentVisualTime = 0; ///< time currently displayed by the timeline - int currentProcessedTime = 0; ///< time that events are currently processed up to. Could differ from visual time due - ///< to rewind buffering - int currentEvent = 0; ///< current event's index - - void skipToTime(int newTime, bool doRewindBuffering); - void handleBackwardsSkip(bool doRewindBuffering); - void processRewind(); - void processNewEvents(PlaybackMode playbackMode); - -private slots: - void replayTimerTimeout(); - -public: - static constexpr int SMALL_SKIP_MS = 1000; - static constexpr int BIG_SKIP_MS = 10000; - - /** - * @param parent The parent QObject - * @param replay Cannot be null. Takes ownership of the object. - */ - explicit ReplayManager(QObject *parent, GameReplay *replay); - - ~ReplayManager() override; - - const QList &getReplayTimeline() const - { - return replayTimeline; - } - - void setTimeScaleFactor(qreal _timeScaleFactor); - -public slots: - void startReplay(); - void stopReplay(); - void setTime(int time); - void skipByAmount(int amount); // use a negative amount to skip backwards - -signals: - void timeChanged(int time); - void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options); - void replayFinished(); - void rewound(); -}; - -#endif // COCKATRICE_REPLAY_MANAGER_H diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp index b5c7bf301..9c699d300 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp @@ -4,19 +4,26 @@ #include #include +#include +#include -static constexpr int BIN_LENGTH = 5000; -static constexpr int MIN_RESOLUTION_MS = 1000; - -ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) : QWidget(parent) +ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) + : QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0), + currentEvent(0) { + replayTimer = new QTimer(this); + replayTimer->setInterval(TIMER_INTERVAL_MS); + connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout); + + rewindBufferingTimer = new QTimer(this); + rewindBufferingTimer->setSingleShot(true); + connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind); } -void ReplayTimelineWidget::setTimeline(const QList &replayTimeline) +void ReplayTimelineWidget::setTimeline(const QList &_replayTimeline) { + replayTimeline = _replayTimeline; histogram.clear(); - currentTime = 0; - int binEndTime = BIN_LENGTH - 1; int binValue = 0; for (int i : replayTimeline) { @@ -59,7 +66,7 @@ void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */) painter.fillPath(path, Qt::black); const QColor barColor = QColor::fromHsv(120, 255, 255, 100); - quint64 w = (quint64)(width() - 1) * (quint64)currentTime / maxTime; + quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime; painter.fillRect(0, 0, static_cast(w), height() - 1, barColor); } @@ -70,24 +77,63 @@ void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event) #else int newTime = static_cast((qint64)maxTime * (qint64)event->x() / width()); #endif - emit timeClicked(newTime); + // don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering + skipToTime(newTime, false); } -void ReplayTimelineWidget::setCurrentTime(int time) +void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering) { - int newTime = qBound(0, time, maxTime); - - if (currentTime == newTime) { - return; + // check boundary conditions + if (newTime < 0) { + newTime = 0; + } + if (newTime > maxTime) { + newTime = maxTime; } - bool doUpdate = currentTime / MIN_RESOLUTION_MS != newTime / MIN_RESOLUTION_MS; + newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval - currentTime = newTime; + const bool isBackwardsSkip = newTime < currentProcessedTime; + currentVisualTime = newTime; - if (doUpdate) { - update(); + if (isBackwardsSkip) { + handleBackwardsSkip(doRewindBuffering); + } else { + processNewEvents(FORWARD_SKIP); } + + update(); +} + +/** + * @brief Handles a backwards skip in the replay timeline. + * + * @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind + * is processed at the end. When false, the backwards skip will always cause an immediate rewind. + */ +void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering) +{ + if (doRewindBuffering) { + // We use a one-shot timer to implement the rewind buffering. + // The rewind only happens once the timer runs out. + // If another backwards skip happens, the timer will just get reset instead of rewinding. + rewindBufferingTimer->stop(); + rewindBufferingTimer->start(SettingsCache::instance().interface().getRewindBufferingMs()); + } else { + // otherwise, process the rewind immediately + processRewind(); + } +} + +void ReplayTimelineWidget::processRewind() +{ + // stop any queued-up rewinds + rewindBufferingTimer->stop(); + + // process the rewind + currentEvent = 0; + emit rewound(); + processNewEvents(BACKWARD_SKIP); } QSize ReplayTimelineWidget::sizeHint() const @@ -99,3 +145,64 @@ QSize ReplayTimelineWidget::minimumSizeHint() const { return {400, 50}; } + +void ReplayTimelineWidget::replayTimerTimeout() +{ + currentVisualTime += TIMER_INTERVAL_MS; + + processNewEvents(NORMAL_PLAYBACK); + + if (!(currentVisualTime % 1000)) { + update(); + } +} + +/** @brief Processes all unprocessed events up to the current time. */ +void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) +{ + currentProcessedTime = currentVisualTime; + + while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) { + EventProcessingOptions options; + + // backwards skip => always skip reveal windows + // forwards skip => skip reveal windows that don't happen within a big skip of the target + if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) { + options |= SKIP_REVEAL_WINDOW; + } + + // backwards skip => always skip tap animation + if (playbackMode == BACKWARD_SKIP) { + options |= SKIP_TAP_ANIMATION; + } + + emit processNextEvent(options); + ++currentEvent; + } + if (currentEvent == replayTimeline.size()) { + emit replayFinished(); + replayTimer->stop(); + } +} + +void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor) +{ + timeScaleFactor = _timeScaleFactor; + int interval = std::max(1, qRound(TIMER_INTERVAL_MS / timeScaleFactor)); + replayTimer->setInterval(interval); +} + +void ReplayTimelineWidget::startReplay() +{ + replayTimer->start(); +} + +void ReplayTimelineWidget::stopReplay() +{ + replayTimer->stop(); +} + +void ReplayTimelineWidget::skipByAmount(int amount) +{ + skipToTime(currentVisualTime + amount, amount < 0); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h index 47d19a741..6cdb8bcb2 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h @@ -18,25 +18,57 @@ class QTimer; class ReplayTimelineWidget : public QWidget { Q_OBJECT - signals: - void timeClicked(int newTime); + void processNextEvent(EventProcessingOptions options); + void replayFinished(); + void rewound(); private: - QList histogram; - int maxBinValue = 1; - int maxTime = 1; + enum PlaybackMode + { + NORMAL_PLAYBACK, + FORWARD_SKIP, + BACKWARD_SKIP + }; - int currentTime = 0; + static constexpr int TIMER_INTERVAL_MS = 200; + static constexpr int BIN_LENGTH = 5000; + + QTimer *replayTimer; + QTimer *rewindBufferingTimer; + QList replayTimeline; + QList histogram; + int maxBinValue, maxTime; + qreal timeScaleFactor; + int currentVisualTime; // time currently displayed by the timeline + int currentProcessedTime; // time that events are currently processed up to. Could differ from visual time due to + // rewind buffering + int currentEvent; + + void skipToTime(int newTime, bool doRewindBuffering); + void handleBackwardsSkip(bool doRewindBuffering); + void processRewind(); + void processNewEvents(PlaybackMode playbackMode); +private slots: + void replayTimerTimeout(); public: + static constexpr int SMALL_SKIP_MS = 1000; + static constexpr int BIG_SKIP_MS = 10000; + explicit ReplayTimelineWidget(QWidget *parent = nullptr); - void setTimeline(const QList &replayTimeline); + void setTimeline(const QList &_replayTimeline); [[nodiscard]] QSize sizeHint() const override; [[nodiscard]] QSize minimumSizeHint() const override; - + void setTimeScaleFactor(qreal _timeScaleFactor); + [[nodiscard]] int getCurrentEvent() const + { + return currentEvent; + } public slots: - void setCurrentTime(int time); + void startReplay(); + void stopReplay(); + void skipByAmount(int amount); // use a negative amount to skip backwards protected: void paintEvent(QPaintEvent *event) override; diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_widget.cpp index f5768a7aa..4dc7b1380 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_widget.cpp @@ -1,50 +1,70 @@ #include "replay_widget.h" -#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" #include "../interface/widgets/tabs/tab_game.h" -#include "replay_manager.h" #include "replay_quick_settings_widget.h" #include #include -ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) - : QWidget(parent), replayPlayButton(nullptr), replayFastForwardButton(nullptr), aReplaySkipForward(nullptr), - aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr) +ReplayWidget::ReplayWidget(TabGame *parent, GameReplay *_replay) + : QWidget(parent), game(parent), replay(_replay), replayPlayButton(nullptr), replayFastForwardButton(nullptr), + aReplaySkipForward(nullptr), aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), + aReplaySkipBackwardBig(nullptr) { - // replay manager - replayManager = new ReplayManager(this, replay); - connect(replayManager, &ReplayManager::eventReplayed, this, &ReplayWidget::eventReplayed); - connect(replayManager, &ReplayManager::replayFinished, this, &ReplayWidget::replayFinished); - connect(replayManager, &ReplayManager::rewound, this, &ReplayWidget::rewound); + if (replay) { + game->getGame()->loadReplay(replay); + + // Create list: event number -> time [ms] + // Distribute simultaneous events evenly across 1 second. + unsigned int lastEventTimestamp = 0; + const int eventCount = replay->event_list_size(); + for (int i = 0; i < eventCount; ++i) { + int j = i + 1; + while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) { + ++j; + } + + const int numberEventsThisSecond = j - i; + for (int k = 0; k < numberEventsThisSecond; ++k) { + replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + + (int)((qreal)k / (qreal)numberEventsThisSecond * 1000)); + } + + if (j < eventCount) { + lastEventTimestamp = replay->event_list(j).seconds_elapsed(); + } + i += numberEventsThisSecond - 1; + } + } // timeline widget timelineWidget = new ReplayTimelineWidget; - timelineWidget->setTimeline(replayManager->getReplayTimeline()); - connect(replayManager, &ReplayManager::timeChanged, timelineWidget, &ReplayTimelineWidget::setCurrentTime); - connect(timelineWidget, &ReplayTimelineWidget::timeClicked, replayManager, &ReplayManager::setTime); + timelineWidget->setTimeline(replayTimeline); + connect(timelineWidget, &ReplayTimelineWidget::processNextEvent, this, &ReplayWidget::replayNextEvent); + connect(timelineWidget, &ReplayTimelineWidget::replayFinished, this, &ReplayWidget::replayFinished); + connect(timelineWidget, &ReplayTimelineWidget::rewound, this, &ReplayWidget::replayRewind); // timeline skip shortcuts aReplaySkipForward = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipForward); connect(aReplaySkipForward, &QAction::triggered, this, - [this] { replayManager->skipByAmount(ReplayManager::SMALL_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); }); aReplaySkipBackward = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipBackward); connect(aReplaySkipBackward, &QAction::triggered, this, - [this] { replayManager->skipByAmount(-ReplayManager::SMALL_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); }); aReplaySkipForwardBig = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipForwardBig); connect(aReplaySkipForwardBig, &QAction::triggered, this, - [this] { replayManager->skipByAmount(ReplayManager::BIG_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); }); aReplaySkipBackwardBig = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipBackwardBig); connect(aReplaySkipBackwardBig, &QAction::triggered, this, - [this] { replayManager->skipByAmount(-ReplayManager::BIG_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); }); // buttons replayPlayButton = new QToolButton; @@ -77,11 +97,18 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) setObjectName("replayControlWidget"); setLayout(replayControlLayout); + connect(this, &ReplayWidget::requestChatAndPhaseReset, game, &TabGame::resetChatAndPhase); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &ReplayWidget::refreshShortcuts); refreshShortcuts(); } +void ReplayWidget::replayNextEvent(EventProcessingOptions options) +{ + emit eventReplayed(replay->event_list(timelineWidget->getCurrentEvent()), options); +} + void ReplayWidget::replayFinished() { replayPlayButton->setChecked(false); @@ -90,16 +117,24 @@ void ReplayWidget::replayFinished() void ReplayWidget::replayPlayButtonToggled(bool checked) { if (checked) { // start replay - replayManager->startReplay(); + timelineWidget->startReplay(); } else { // pause replay - replayManager->stopReplay(); + timelineWidget->stopReplay(); } } void ReplayWidget::updateTimeScaleFactor(bool isFastForward) { qreal factor = isFastForward ? SettingsCache::instance().interface().getFastForwardSpeed() : 1.0; - replayManager->setTimeScaleFactor(factor); + timelineWidget->setTimeScaleFactor(factor); +} + +/** + * @brief Handles everything that needs to be reset when doing a replay rewind. + */ +void ReplayWidget::replayRewind() +{ + emit requestChatAndPhaseReset(); } void ReplayWidget::refreshShortcuts() diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.h b/cockatrice/src/interface/widgets/replay/replay_widget.h index 6d2b7a043..eca356e9e 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_widget.h @@ -14,12 +14,11 @@ #include #include -class ReplayManager; class ReplayQuickSettingsWidget; class TabGame; /** - * @brief The top-level widget that is put in the replay dock widget. + * @brief The top-level that is put in the replay dock widget. * Contains the replay timeline as well as the buttons. */ class ReplayWidget : public QWidget @@ -27,28 +26,29 @@ class ReplayWidget : public QWidget Q_OBJECT public: - /** - * @param parent The parent widget - * @param replay Cannot be null. Takes ownership of the replay. - */ - ReplayWidget(QWidget *parent, GameReplay *replay); + ReplayWidget(TabGame *parent, GameReplay *replay); + TabGame *game; + GameReplay *replay; signals: - void rewound(); + void requestChatAndPhaseReset(); void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options); private: - ReplayManager *replayManager; - + // Replay related members + int currentReplayStep = 0; + QList replayTimeline; ReplayTimelineWidget *timelineWidget; QToolButton *replayPlayButton, *replayFastForwardButton; ReplayQuickSettingsWidget *settingsWidget; QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig; private slots: + void replayNextEvent(EventProcessingOptions options); void replayFinished(); void replayPlayButtonToggled(bool checked); void updateTimeScaleFactor(bool checked); + void replayRewind(); void refreshShortcuts(); }; diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 8203dee58..fa27ec198 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -241,15 +241,15 @@ void UserInterfaceSettingsPage::retranslateUi() visualDeckStoragePromptForConversionLabel.setText( tr("When adding a tag in the visual deck storage to a .txt deck:")); visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexNone, - tr("Do nothing")); + tr("do nothing")); visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexPrompt, - tr("Ask to convert to .cod")); + tr("ask to convert to .cod")); visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexAlways, - tr("Always convert to .cod")); + tr("always convert to .cod")); defaultDeckEditorTypeLabel.setText(tr("Default deck editor type")); defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor")); defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor")); replayGroupBox->setTitle(tr("Replay settings")); rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); rewindBufferingMsBox.setSuffix(" ms"); -} +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index d3f3a1735..c9ebf06c8 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -265,6 +265,9 @@ void TabGame::emitUserEvent() TabGame::~TabGame() { + if (replayWidget) { + delete replayWidget->replay; + } for (auto &player : game->getPlayerManager()->getPlayers()) { player->clear(); } @@ -1180,7 +1183,6 @@ void TabGame::createReplayDock(GameReplay *replay) replayDock->setWidget(replayWidget); replayDock->setFloating(false); - connect(replayWidget, &ReplayWidget::rewound, this, &TabGame::resetChatAndPhase); connect(replayWidget, &ReplayWidget::eventReplayed, game->getGameEventHandler(), [this](const auto &event, auto options) { game->getGameEventHandler()->processGameEventContainer(event, nullptr, options); diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index 786c6cd9f..d37408a58 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -23,7 +24,7 @@ using CardInfoPtr = QSharedPointer; namespace { -QByteArray serializeProperties(const QHash &props) +QByteArray serializeProperties(const QVariantHash &props) { QByteArray blob; QDataStream out(&blob, QIODevice::WriteOnly); @@ -47,7 +48,7 @@ void CardInfo::ensurePropertiesLoaded() const propertiesLoaded = true; } -const QHash &CardInfo::getPropertiesHash() const +const QVariantHash &CardInfo::getPropertiesHash() const { ensurePropertiesLoaded(); return propertiesCache; @@ -56,7 +57,7 @@ const QHash &CardInfo::getPropertiesHash() const void CardInfo::setProperty(const QString &_name, const QString &_value) { ensurePropertiesLoaded(); - if (propertiesCache.value(_name) == _value) { + if (propertiesCache.value(_name).toString() == _value) { return; } propertiesCache.insert(_name, _value); @@ -64,7 +65,7 @@ void CardInfo::setProperty(const QString &_name, const QString &_value) emit cardInfoChanged(smartThis); } -void CardInfo::setProperties(const QHash &_props) +void CardInfo::setProperties(const QVariantHash &_props) { ensurePropertiesLoaded(); propertiesCache = _props; @@ -75,7 +76,7 @@ void CardInfo::setProperties(const QHash &_props) CardInfo::CardInfo(const QString &_name, const QString &_text, bool _isToken, - QHash _properties, + QVariantHash _properties, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, @@ -121,7 +122,7 @@ CardInfoPtr CardInfo::newInstance(const QString &_name) CardInfoPtr CardInfo::newInstance(const QString &_name, const QString &_text, bool _isToken, - QHash _properties, + QVariantHash _properties, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, @@ -209,10 +210,10 @@ void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info) refreshCachedSets(); } -void CardInfo::combineLegalities(const QHash &props) +void CardInfo::combineLegalities(const QVariantHash &props) { ensurePropertiesLoaded(); - QHashIterator it(props); + QHashIterator it(props); while (it.hasNext()) { it.next(); if (it.key().startsWith("format-")) { diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index e625a0748..ad99864c8 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -76,12 +76,12 @@ private: QString text; ///< Text description or rules text of the card. bool isToken; ///< Whether this card is a token or not. // Properties are stored as a pre-serialized blob (cheap to load) and the - // QHash is materialized on first query, so database load avoids - // constructing thousands of QStrings per card. - mutable QByteArray propertiesBlob; ///< Serialized properties (load form). - mutable QHash propertiesCache; ///< Materialized properties (query form). - mutable bool propertiesLoaded = false; ///< Whether propertiesCache is valid. - mutable QMutex propertiesMutex; ///< Guards lazy materialization. + // QVariantHash is materialized on first query, so database load avoids + // constructing thousands of QVariants per card. + mutable QByteArray propertiesBlob; ///< Serialized properties (load form). + mutable QVariantHash propertiesCache; ///< Materialized properties (query form). + mutable bool propertiesLoaded = false; ///< Whether propertiesCache is valid. + mutable QMutex propertiesMutex; ///< Guards lazy materialization. /** * @brief Materializes propertiesCache from propertiesBlob if not already done. @@ -114,7 +114,7 @@ public: explicit CardInfo(const QString &_name, const QString &_text, bool _isToken, - QHash _properties, + QVariantHash _properties, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, @@ -127,7 +127,7 @@ public: * Used by the binary cache reader to skip recomputing @p _simpleName and * @p _altNames (which otherwise require a Unicode normalization and a full * printing scan). Properties are supplied as a pre-serialized @p _propertiesBlob - * so the QHash is not built at load time (it is materialized on first + * so the QVariantHash is not built at load time (it is materialized on first * query). * * @param _name The card name. @@ -194,7 +194,7 @@ public: static CardInfoPtr newInstance(const QString &_name, const QString &_text, bool _isToken, - QHash _properties, + QVariantHash _properties, const QList &_relatedCards, const QList &_reverseRelatedCards, SetToPrintingsMap _sets, @@ -288,12 +288,12 @@ public: { return getPropertiesHash().keys(); } - [[nodiscard]] const QHash &getPropertiesHash() const; + [[nodiscard]] const QVariantHash &getPropertiesHash() const; /** * @brief Stores the pre-serialized properties blob and invalidates the * materialized cache. Used by the binary cache reader so the - * QHash is not built at load time. + * QVariantHash is not built at load time. * @param _blob The serialized properties (as written by the cache writer). */ void setPropertiesBlob(QByteArray _blob) const @@ -305,10 +305,10 @@ public: } [[nodiscard]] QString getProperty(const QString &propertyName) const { - return getPropertiesHash().value(propertyName); + return getPropertiesHash().value(propertyName).toString(); } void setProperty(const QString &_name, const QString &_value); - void setProperties(const QHash &_props); + void setProperties(const QVariantHash &_props); [[nodiscard]] bool hasProperty(const QString &propertyName) const { return getPropertiesHash().contains(propertyName); @@ -415,7 +415,7 @@ public: * * @param props Key-value mapping of format legalities. */ - void combineLegalities(const QHash &props); + void combineLegalities(const QVariantHash &props); /** * @brief Refreshes all cached fields that are calculated from the contained sets and printings. diff --git a/libcockatrice_card/libcockatrice/card/card_info_comparator.cpp b/libcockatrice_card/libcockatrice/card/card_info_comparator.cpp index f4a74194c..821cc8675 100644 --- a/libcockatrice_card/libcockatrice/card/card_info_comparator.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info_comparator.cpp @@ -66,7 +66,7 @@ QVariant CardInfoComparator::getProperty(const CardInfoPtr &card, const QString return card->getIsToken(); } - // Otherwise, check if it's a custom property in the properties hash + // Otherwise, check if it's a custom property in the QVariantHash if (card->hasProperty(property)) { return card->getProperty(property); } diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp index dfa684dbe..0b68a389a 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp @@ -13,11 +13,12 @@ #include #include #include +#include namespace { constexpr quint32 CACHE_MAGIC = 0x43445243; // "CDRC" -constexpr quint32 CACHE_VERSION = 2; +constexpr quint32 CACHE_VERSION = 1; // ---- Primitives ----------------------------------------------------------- @@ -33,11 +34,11 @@ QString readString(QDataStream &in) return s; } -// Stores a QHash as a single pre-serialized blob. The reader keeps the -// blob as-is and materializes the QHash lazily on first query, which is +// Stores a QVariantHash as a single pre-serialized blob. The reader keeps the +// blob as-is and materializes the QVariantHash lazily on first query, which is // what removes the allocation storm from database load (see card_info.cpp / // printing_info.cpp). -void writeHashBlob(QDataStream &out, const QHash &h) +void writeHashBlob(QDataStream &out, const QVariantHash &h) { QByteArray blob; QDataStream blobOut(&blob, QIODevice::WriteOnly); diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp index e403a641d..2dd5e91e9 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp @@ -172,7 +172,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) if (xmlName == "card") { QString name = QString(""); QString text = QString(""); - QHash properties; + QVariantHash properties = QVariantHash(); QString colors = QString(""); QList relatedCards, reverseRelatedCards; auto _sets = SetToPrintingsMap(); @@ -229,7 +229,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) // behaviour. Without this check, disabling a set has no effect on v3 databases. if (set->getEnabled()) { PrintingInfo setInfo(set); - QHash printingProps; + QVariantHash printingProps; if (attrs.hasAttribute("muId")) { printingProps.insert("muid", attrs.value("muId").toString()); } diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp index df92618da..129bae9bc 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -243,9 +243,9 @@ void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml) } } -QHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml) +QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &xml) { - QHash properties; + QVariantHash properties = QVariantHash(); while (!xml.atEnd()) { if (xml.readNext() == QXmlStreamReader::EndElement) { break; @@ -272,7 +272,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) if (xmlName == "card") { QString name = QString(""); QString text = QString(""); - QHash properties; + QVariantHash properties = QVariantHash(); QList relatedCards, reverseRelatedCards; auto _sets = SetToPrintingsMap(); int tableRow = 0; @@ -315,7 +315,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) auto set = internalAddSet(setName); if (set->getEnabled()) { PrintingInfo printingInfo(set); - QHash printingProps; + QVariantHash printingProps; for (QXmlStreamAttribute attr : attrs) { QString attrName = attr.name().toString(); if (attrName == "picURL") { diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h index 55f7c5a3b..92c967a0c 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.h @@ -19,7 +19,7 @@ inline Q_LOGGING_CATEGORY(CockatriceXml4Log, "cockatrice_xml.xml_4_parser"); * making the parser more extensible and schema-compliant. * * @note Differences from v3: - * - Card properties are stored in blocks as a QHash. + * - Card properties are stored in blocks as a QVariantHash. * - Sets can include a element. * - Supports user preferences via ICardPreferenceProvider (e.g., skipping rebalanced cards). * - Related cards support persistent relations and multiple attach types (e.g., transform). @@ -70,9 +70,9 @@ private: /** * @brief Loads a generic block from a element. * @param xml The open QXmlStreamReader positioned at a element. - * @return A QHash mapping property names to values. + * @return A QVariantHash mapping property names to values. */ - QHash loadCardPropertiesFromXml(QXmlStreamReader &xml); + QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml); /** * @brief Load all elements from the XML stream. diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp index ee5b1329b..5df3680a0 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp @@ -27,14 +27,14 @@ void PrintingInfo::ensurePropertiesLoaded() const void PrintingInfo::setProperty(const QString &_name, const QString &_value) { ensurePropertiesLoaded(); - if (propertiesCache.value(_name) == _value) { + if (propertiesCache.value(_name).toString() == _value) { return; } propertiesCache.insert(_name, _value); setProperties(propertiesCache); } -void PrintingInfo::setProperties(const QHash &_props) +void PrintingInfo::setProperties(const QVariantHash &_props) { ensurePropertiesLoaded(); propertiesCache = _props; @@ -48,10 +48,10 @@ void PrintingInfo::setProperties(const QHash &_props) */ QString PrintingInfo::getUuid() const { - return getPropertiesHash().value("uuid"); + return getPropertiesHash().value("uuid").toString(); } QString PrintingInfo::getFlavorName() const { - return getPropertiesHash().value("flavorName"); -} + return getPropertiesHash().value("flavorName").toString(); +} \ No newline at end of file diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index f7ca90aad..adec2010c 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -70,11 +70,11 @@ private: CardSetPtr set; ///< The set this variation belongs to. // Properties are stored as a pre-serialized blob (cheap to load) and the - // QHash is materialized on first query. This avoids constructing - // thousands of QStrings per card at database-load time. - mutable QByteArray propertiesBlob; ///< Serialized properties (load form). - mutable QHash propertiesCache; ///< Materialized properties (query form). - mutable bool propertiesLoaded = false; ///< Whether propertiesCache is valid. + // QVariantHash is materialized on first query. This avoids constructing + // thousands of QVariants per card at database-load time. + mutable QByteArray propertiesBlob; ///< Serialized properties (load form). + mutable QVariantHash propertiesCache; ///< Materialized properties (query form). + mutable bool propertiesLoaded = false; ///< Whether propertiesCache is valid. mutable QSharedPointer propertiesMutex = QSharedPointer::create(); ///< Guards lazy materialization. @@ -101,7 +101,7 @@ public: return getPropertiesHash().keys(); } - [[nodiscard]] const QHash &getPropertiesHash() const + [[nodiscard]] const QVariantHash &getPropertiesHash() const { ensurePropertiesLoaded(); return propertiesCache; @@ -115,7 +115,7 @@ public: */ [[nodiscard]] QString getProperty(const QString &propertyName) const { - return getPropertiesHash().value(propertyName); + return getPropertiesHash().value(propertyName).toString(); } /** @@ -127,12 +127,12 @@ public: * @param _value The string value to assign. */ void setProperty(const QString &_name, const QString &_value); - void setProperties(const QHash &_props); + void setProperties(const QVariantHash &_props); /** * @brief Stores the pre-serialized properties blob and marks the materialized * cache as invalid. Used by the binary cache reader to avoid building a - * QHash at load time. + * QVariantHash at load time. * @param _blob The serialized properties (as written by the cache writer). */ void setPropertiesBlob(QByteArray _blob) const diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 3d7b7b555..4eeb6ecc4 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -18,7 +18,7 @@ static const QList kSingletonCounts = {{1, "legal"}, {0, "banned"} SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, - const QHash &_properties, + const QVariantHash &_properties, const PrintingInfo &_printingInfo) : name(_name), text(_text), properties(_properties), printingInfo(_printingInfo) { @@ -135,7 +135,7 @@ static void sortAndReduceColors(QString &colors) CardInfoPtr OracleImporter::addCard(QString name, const QString &text, bool isToken, - QHash properties, + QVariantHash properties, const QList &relatedCards, const PrintingInfo &printingInfo) { @@ -152,7 +152,7 @@ CardInfoPtr OracleImporter::addCard(QString name, } // Remove {} around mana costs, except if it's split cost - QString manacost = properties.value("manacost"); + QString manacost = properties.value("manacost").toString(); if (!manacost.isEmpty()) { QStringList symbols = manacost.split("}"); QString formattedCardCost; @@ -169,12 +169,12 @@ CardInfoPtr OracleImporter::addCard(QString name, } // fix colors - QString allColors = properties.value("colors"); + QString allColors = properties.value("colors").toString(); if (allColors.size() > 1) { sortAndReduceColors(allColors); properties.insert("colors", allColors); } - QString allColorIdent = properties.value("coloridentity"); + QString allColorIdent = properties.value("coloridentity").toString(); if (allColorIdent.size() > 1) { sortAndReduceColors(allColorIdent); properties.insert("coloridentity", allColorIdent); @@ -182,15 +182,16 @@ CardInfoPtr OracleImporter::addCard(QString name, // DETECT CARD POSITIONING INFO - bool landscapeOrientation = properties.value("maintype") == "Battle" || properties.value("layout") == "split" || - properties.value("layout") == "planar"; + bool landscapeOrientation = properties.value("maintype").toString() == "Battle" || + properties.value("layout").toString() == "split" || + properties.value("layout").toString() == "planar"; // cards that enter the field tapped bool cipt = parseCipt(name, text) || landscapeOrientation; // table row int tableRow = 1; - QString mainCardType = properties.value("maintype"); + QString mainCardType = properties.value("maintype").toString(); if (mainCardType == "Land") { tableRow = 0; } else if (mainCardType == "Sorcery" || mainCardType == "Instant") { @@ -200,11 +201,11 @@ CardInfoPtr OracleImporter::addCard(QString name, } // card side - QString side = properties.value("side") == "b" ? "back" : "front"; + QString side = properties.value("side").toString() == "b" ? "back" : "front"; properties.insert("side", side); // upsideDown (flip cards) - QString layout = properties.value("layout"); + QString layout = properties.value("layout").toString(); bool upsideDown = layout == "flip" && side == "back"; // insert the card and its properties @@ -278,7 +279,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList } // card properties - QHash properties; + QVariantHash properties; for (auto i = cardProperties.cbegin(), end = cardProperties.cend(); i != end; ++i) { QString mtgjsonProperty = i.key(); QString xmlPropertyName = i.value(); @@ -290,7 +291,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList // per-set properties PrintingInfo printingInfo = PrintingInfo(currentSet); - QHash printingProps; + QVariantHash printingProps; for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) { QString mtgjsonProperty = i.key(); QString xmlPropertyName = i.value(); @@ -430,7 +431,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList QList, QString>> partsAndNames = splitCards.values(); for (auto [splitCardParts, name] : partsAndNames) { QString text; - QHash properties; + QVariantHash properties; PrintingInfo printingInfo; for (const SplitCardPart &tmp : splitCardParts) { @@ -443,11 +444,11 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList properties = tmp.getProperties(); printingInfo = tmp.getPrintingInfo(); } else { - const QHash &tmpProps = tmp.getProperties(); + const QVariantHash &tmpProps = tmp.getProperties(); for (auto i = tmpProps.cbegin(), end = tmpProps.cend(); i != end; ++i) { QString prop = i.key(); - QString originalPropertyValue = properties.value(prop); - QString thisCardPropertyValue = i.value(); + QString originalPropertyValue = properties.value(prop).toString(); + QString thisCardPropertyValue = i.value().toString(); if (!thisCardPropertyValue.isEmpty() && originalPropertyValue != thisCardPropertyValue) { if (originalPropertyValue.isEmpty()) { // don't create //es if one field is empty properties.insert(prop, thisCardPropertyValue); diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index 99644f9ce..5bf352594 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -95,7 +95,7 @@ class SplitCardPart public: SplitCardPart(const QString &_name, const QString &_text, - const QHash &_properties, + const QVariantHash &_properties, const PrintingInfo &_printingInfo); inline const QString &getName() const { @@ -105,7 +105,7 @@ public: { return text; } - inline const QHash &getProperties() const + inline const QVariantHash &getProperties() const { return properties; } @@ -117,7 +117,7 @@ public: private: QString name; QString text; - QHash properties; + QVariantHash properties; PrintingInfo printingInfo; }; @@ -142,7 +142,7 @@ private: CardInfoPtr addCard(QString name, const QString &text, bool isToken, - QHash properties, + QVariantHash properties, const QList &relatedCards, const PrintingInfo &printingInfo); signals: