From c08dc780a72def7a796fe4c831342955512e44a8 Mon Sep 17 00:00:00 2001 From: RickyRister <42636155+RickyRister@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:12:10 -0700 Subject: [PATCH] [Replay] Implement option to skip empty sections (#7069) * add settings * [Replay] Implement option to skip empty sections * correct starting offset --- .../widgets/replay/replay_manager.cpp | 56 +++++++++++++++++++ .../interface/widgets/replay/replay_manager.h | 3 + .../replay/replay_quick_settings_widget.cpp | 13 +++++ .../replay/replay_quick_settings_widget.h | 6 ++ .../widgets/replay/replay_widget.cpp | 4 ++ .../interface_interface_settings_provider.h | 1 + .../settings/interface_settings.cpp | 10 ++++ .../settings/interface_settings.h | 2 + 8 files changed, 95 insertions(+) diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp index c6e7ff1bb..a2c1e0ff0 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_manager.cpp @@ -3,9 +3,11 @@ #include "../../../client/settings/cache_settings.h" #include +#include #include static constexpr int TIMER_INTERVAL_MS = 200; +static constexpr int EMPTY_SECTION_MARGIN_MS = 500; static QList createReplayTimeline(const GameReplay *replay) { @@ -119,6 +121,10 @@ void ReplayManager::replayTimerTimeout() processNewEvents(NORMAL_PLAYBACK); timeChanged(currentVisualTime); + + if (skipEmptySections) { + handleSkipEmptySection(); + } } /** @brief Processes all unprocessed events up to the current time. */ @@ -149,6 +155,51 @@ void ReplayManager::processNewEvents(PlaybackMode playbackMode) } } +static bool hasMeaningfulEvent(const GameEventContainer &cont) +{ + const int eventListSize = cont.event_list_size(); + for (int i = 0; i < eventListSize; ++i) { + const GameEvent &event = cont.event_list(i); + const auto eventType = static_cast(getPbExtension(event)); + + if (eventType != GameEvent::PLAYER_PROPERTIES_CHANGED) { + return true; + } + } + + return false; +} + +void ReplayManager::handleSkipEmptySection() +{ + if (currentEvent == replayTimeline.size()) { + return; + } + + // find most recent meaningful event + int prevEvent = std::max(0, currentEvent - 1); + for (; prevEvent > 0 && !hasMeaningfulEvent(replay->event_list(prevEvent)); --prevEvent) { + } + + int prevEventTime = replayTimeline.value(prevEvent); + if (currentVisualTime - prevEventTime <= EMPTY_SECTION_MARGIN_MS) { + return; + } + + // find next earliest meaningful event + int nextEvent = currentEvent; + for (; nextEvent < replayTimeline.size() - 1 && !hasMeaningfulEvent(replay->event_list(nextEvent)); ++nextEvent) { + } + + int nextEventTime = replayTimeline.value(nextEvent); + if (nextEventTime - currentVisualTime <= EMPTY_SECTION_MARGIN_MS) { + return; + } + + // skip forward if we're not within margin of either event + skipToTime(nextEventTime - EMPTY_SECTION_MARGIN_MS, false); +} + void ReplayManager::setTimeScaleFactor(qreal _timeScaleFactor) { timeScaleFactor = _timeScaleFactor; @@ -156,6 +207,11 @@ void ReplayManager::setTimeScaleFactor(qreal _timeScaleFactor) replayTimer->setInterval(interval); } +void ReplayManager::setSkipEmptySections(bool value) +{ + skipEmptySections = value; +} + void ReplayManager::startReplay() { replayTimer->start(); diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.h b/cockatrice/src/interface/widgets/replay/replay_manager.h index 16d3591ba..81e66824d 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.h +++ b/cockatrice/src/interface/widgets/replay/replay_manager.h @@ -31,6 +31,7 @@ class ReplayManager : public QObject QTimer *rewindBufferingTimer; qreal timeScaleFactor = 1.0; + bool skipEmptySections = false; 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 @@ -41,6 +42,7 @@ class ReplayManager : public QObject void handleBackwardsSkip(bool doRewindBuffering); void processRewind(); void processNewEvents(PlaybackMode playbackMode); + void handleSkipEmptySection(); private slots: void replayTimerTimeout(); @@ -63,6 +65,7 @@ public: } void setTimeScaleFactor(qreal _timeScaleFactor); + void setSkipEmptySections(bool value); public slots: void startReplay(); diff --git a/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.cpp index 08113d2cd..5d58705d2 100644 --- a/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.cpp @@ -18,12 +18,17 @@ ReplayQuickSettingsWidget::ReplayQuickSettingsWidget(QWidget *parent) : Settings connect(&fastForwardSpeedBox, qOverload(&QDoubleSpinBox::valueChanged), this, &ReplayQuickSettingsWidget::actUpdateFastForwardSpeed); + skipEmptyCheckBox.setChecked(SettingsCache::instance().userInterface().getSkipEmptySections()); + connect(&skipEmptyCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &ReplayQuickSettingsWidget::actUpdateSkipEmptySections); + // putting it all together auto *widget = new QWidget; auto *grid = new QGridLayout(widget); grid->setContentsMargins(0, 0, 0, 0); grid->addWidget(&fastForwardSpeedLabel, 0, 0, 1, 1); grid->addWidget(&fastForwardSpeedBox, 0, 1, 1, 1); + grid->addWidget(&skipEmptyCheckBox, 1, 0, 1, 2); this->addSettingsWidget(widget); @@ -36,6 +41,8 @@ void ReplayQuickSettingsWidget::retranslateUi() { fastForwardSpeedLabel.setText(tr("Fast forward speed:")); fastForwardSpeedBox.setSuffix("x"); + + skipEmptyCheckBox.setText(tr("Skip empty sections")); } void ReplayQuickSettingsWidget::actUpdateFastForwardSpeed(qreal value) @@ -43,3 +50,9 @@ void ReplayQuickSettingsWidget::actUpdateFastForwardSpeed(qreal value) SettingsCache::instance().userInterface().setFastForwardSpeed(value); emit fastForwardSpeedChanged(value); } + +void ReplayQuickSettingsWidget::actUpdateSkipEmptySections(QT_STATE_CHANGED_T value) +{ + SettingsCache::instance().userInterface().setSkipEmptySections(value); + emit skipEmptySectionsChanged(value); +} diff --git a/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.h b/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.h index b88a8b4e3..a337ea0a6 100644 --- a/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_quick_settings_widget.h @@ -3,7 +3,9 @@ #include "../../interface/widgets/quick_settings/settings_button_widget.h" +#include #include +#include class ReplayQuickSettingsWidget : public SettingsButtonWidget { @@ -16,13 +18,17 @@ public: signals: void fastForwardSpeedChanged(qreal speed); + void skipEmptySectionsChanged(bool skip); private: QLabel fastForwardSpeedLabel; QDoubleSpinBox fastForwardSpeedBox; + QCheckBox skipEmptyCheckBox; + private slots: void actUpdateFastForwardSpeed(qreal value); + void actUpdateSkipEmptySections(QT_STATE_CHANGED_T value); }; #endif // COCKATRICE_REPLAY_QUICK_SETTINGS_WIDGET_H diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_widget.cpp index fc0110ff1..6c85d950e 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_widget.cpp @@ -66,6 +66,10 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) settingsWidget->setFixedSize(QSize(32, 32)); connect(settingsWidget, &ReplayQuickSettingsWidget::fastForwardSpeedChanged, this, [this] { updateTimeScaleFactor(replayFastForwardButton->isChecked()); }); + connect(settingsWidget, &ReplayQuickSettingsWidget::skipEmptySectionsChanged, replayManager, + &ReplayManager::setSkipEmptySections); + + replayManager->setSkipEmptySections(SettingsCache::instance().userInterface().getSkipEmptySections()); // putting everything together auto replayControlLayout = new QHBoxLayout; diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h index 07e60b28f..ab2caa0d7 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h @@ -31,6 +31,7 @@ public: [[nodiscard]] virtual int getMinPlayersForMultiColumnLayout() const = 0; [[nodiscard]] virtual int getRewindBufferingMs() const = 0; [[nodiscard]] virtual qreal getFastForwardSpeed() const = 0; + [[nodiscard]] virtual bool getSkipEmptySections() const = 0; [[nodiscard]] virtual bool getLeftJustified() const = 0; [[nodiscard]] virtual int getZoneViewGroupByIndex() const = 0; [[nodiscard]] virtual int getZoneViewSortByIndex() const = 0; diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp index 0fa56ee33..29c57c57e 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp @@ -120,6 +120,11 @@ qreal InterfaceSettings::getFastForwardSpeed() const return getValue("fastForwardSpeed", "replay", QString(), 10).toReal(); } +bool InterfaceSettings::getSkipEmptySections() const +{ + return getValue("skipEmptySections", "replay", QString(), false).toBool(); +} + bool InterfaceSettings::getLeftJustified() const { return getValue("leftJustified", QString(), QString(), false).toBool(); @@ -279,6 +284,11 @@ void InterfaceSettings::setFastForwardSpeed(qreal _value) setValue(_value, "fastForwardSpeed", "replay"); } +void InterfaceSettings::setSkipEmptySections(bool _value) +{ + setValue(_value, "skipEmptySections", "replay"); +} + void InterfaceSettings::setLeftJustified(bool _leftJustified) { setValue(_leftJustified, "leftJustified"); diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.h b/libcockatrice_settings/libcockatrice/settings/interface_settings.h index 7ef367cb9..982976310 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.h @@ -34,6 +34,7 @@ public: [[nodiscard]] int getMinPlayersForMultiColumnLayout() const override; [[nodiscard]] int getRewindBufferingMs() const override; [[nodiscard]] qreal getFastForwardSpeed() const override; + [[nodiscard]] bool getSkipEmptySections() const override; [[nodiscard]] bool getLeftJustified() const override; [[nodiscard]] int getZoneViewGroupByIndex() const override; [[nodiscard]] int getZoneViewSortByIndex() const override; @@ -65,6 +66,7 @@ public: void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); void setRewindBufferingMs(int _rewindBufferingMs); void setFastForwardSpeed(qreal _value); + void setSkipEmptySections(bool _value); void setLeftJustified(bool _leftJustified); void setZoneViewGroupByIndex(int _zoneViewGroupByIndex); void setZoneViewSortByIndex(int _zoneViewSortByIndex);