From 745e94f332a033231d6e0163e157c0feba0c221c Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Mon, 7 Sep 2026 02:26:52 +0200 Subject: [PATCH] [Replay] Skip damage animations when skipping backward in replays (#7249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukas BrĂ¼bach --- cockatrice/src/game/board/counter_state.cpp | 4 ++-- cockatrice/src/game/board/counter_state.h | 17 +++++++++++++++-- .../src/game/player/event_processing_options.h | 3 ++- .../src/game/player/player_event_handler.cpp | 7 ++++--- .../src/game/player/player_event_handler.h | 2 +- .../game_graphics/board/abstract_counter.cpp | 6 +++--- .../src/game_graphics/board/abstract_counter.h | 3 ++- .../player/player_graphics_item.cpp | 4 ++-- .../src/game_graphics/player/player_target.cpp | 7 ++++++- .../src/game_graphics/player/player_target.h | 2 +- .../interface/widgets/replay/replay_manager.cpp | 2 ++ 11 files changed, 40 insertions(+), 17 deletions(-) diff --git a/cockatrice/src/game/board/counter_state.cpp b/cockatrice/src/game/board/counter_state.cpp index 6da18b662..0970e4272 100644 --- a/cockatrice/src/game/board/counter_state.cpp +++ b/cockatrice/src/game/board/counter_state.cpp @@ -13,12 +13,12 @@ CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent); } -void CounterState::setValue(int newValue) +void CounterState::setValue(int newValue, bool skipDamageAnimation) { if (newValue == value) { return; } int old = value; value = newValue; - emit valueChanged(old, newValue); + emit valueChanged(old, newValue, skipDamageAnimation); } \ No newline at end of file diff --git a/cockatrice/src/game/board/counter_state.h b/cockatrice/src/game/board/counter_state.h index 0f2f16b55..4c7b34473 100644 --- a/cockatrice/src/game/board/counter_state.h +++ b/cockatrice/src/game/board/counter_state.h @@ -35,10 +35,23 @@ public: return value; } - void setValue(int newValue); + /** + * @brief Set the counter value. + * @param newValue The new value. + * @param skipDamageAnimation When true, valueChanged is emitted with skipDamageAnimation=true, letting views + * suppress damage-related feedback (e.g. battlefield shimmer, life counter flash) for values set during replay + * rewinds. + */ + void setValue(int newValue, bool skipDamageAnimation = false); signals: - void valueChanged(int oldValue, int newValue); + /** + * @brief Emitted whenever the value changes. + * @param oldValue The previous value. + * @param newValue The new value. + * @param skipDamageAnimation True when the change should not trigger damage/life-change feedback in views. + */ + void valueChanged(int oldValue, int newValue, bool skipDamageAnimation); private: int id; diff --git a/cockatrice/src/game/player/event_processing_options.h b/cockatrice/src/game/player/event_processing_options.h index 4c7663789..06238d77e 100644 --- a/cockatrice/src/game/player/event_processing_options.h +++ b/cockatrice/src/game/player/event_processing_options.h @@ -13,7 +13,8 @@ enum EventProcessingOption { SKIP_REVEAL_WINDOW = 0x0001, - SKIP_TAP_ANIMATION = 0x0002 + SKIP_TAP_ANIMATION = 0x0002, + SKIP_DAMAGE_ANIMATION = 0x0004 }; // Wrap it in a QFlags typedef diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index bc48298f7..277b8b1d4 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -262,14 +262,15 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event) player->addCounter(event.counter_info()); } -void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event) +void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options) { CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr); if (!ctr) { return; } int oldValue = ctr->getValue(); - ctr->setValue(event.value()); + const bool skipDamageAnimation = options.testFlag(SKIP_DAMAGE_ANIMATION); + ctr->setValue(event.value(), skipDamageAnimation); emit logSetCounter(player, ctr->getName(), event.value(), oldValue); } @@ -625,7 +626,7 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type, eventCreateCounter(event.GetExtension(Event_CreateCounter::ext)); break; case GameEvent::SET_COUNTER: - eventSetCounter(event.GetExtension(Event_SetCounter::ext)); + eventSetCounter(event.GetExtension(Event_SetCounter::ext), options); break; case GameEvent::DEL_COUNTER: eventDelCounter(event.GetExtension(Event_DelCounter::ext)); diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index 48ad85e88..300cacd08 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -153,7 +153,7 @@ public: void eventCreateCounter(const Event_CreateCounter &event); /// Set a player-level counter value. - void eventSetCounter(const Event_SetCounter &event); + void eventSetCounter(const Event_SetCounter &event, EventProcessingOptions options); /// Delete a player-level counter. void eventDelCounter(const Event_DelCounter &event); diff --git a/cockatrice/src/game_graphics/board/abstract_counter.cpp b/cockatrice/src/game_graphics/board/abstract_counter.cpp index e63117e13..4ba04804f 100644 --- a/cockatrice/src/game_graphics/board/abstract_counter.cpp +++ b/cockatrice/src/game_graphics/board/abstract_counter.cpp @@ -29,9 +29,9 @@ AbstractCounter::AbstractCounter(CounterState *state, { setAcceptHoverEvents(true); - connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) { + connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) { value = newValue; - onValueChanged(oldValue, newValue); + onValueChanged(oldValue, newValue, skipDamageAnimation); update(); }); @@ -230,7 +230,7 @@ void AbstractCounterDialog::changeValue(int diff) setTextValue(QString::number(curValue)); } -void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/) +void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/, bool /*skipDamageAnimation*/) { // Default: no feedback. Subclasses such as PlayerCounter override this to // flash the counter on meaningful changes (life gain/loss). diff --git a/cockatrice/src/game_graphics/board/abstract_counter.h b/cockatrice/src/game_graphics/board/abstract_counter.h index 9ddcc6d58..67b5b4074 100644 --- a/cockatrice/src/game_graphics/board/abstract_counter.h +++ b/cockatrice/src/game_graphics/board/abstract_counter.h @@ -39,8 +39,9 @@ protected: * @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash). * * Called whenever the counter's value changes, before the item repaints. + * @param skipDamageAnimation True when damage-related feedback should be suppressed (replay rewinds). */ - virtual void onValueChanged(int oldValue, int newValue); + virtual void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation); void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; diff --git a/cockatrice/src/game_graphics/player/player_graphics_item.cpp b/cockatrice/src/game_graphics/player/player_graphics_item.cpp index b3c25ffc1..122ab83be 100644 --- a/cockatrice/src/game_graphics/player/player_graphics_item.cpp +++ b/cockatrice/src/game_graphics/player/player_graphics_item.cpp @@ -252,8 +252,8 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state) AbstractCounter *widget; if (state->getName() == "life") { widget = playerTarget->addCounter(state); - connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) { - if (newValue < oldValue) { + connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) { + if (newValue < oldValue && !skipDamageAnimation) { tableZoneGraphicsItem->triggerDamageShimmer(); } }); diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp index 910ee9c17..63c060b02 100644 --- a/cockatrice/src/game_graphics/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -69,7 +69,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* } } -void PlayerCounter::onValueChanged(int oldValue, int newValue) +void PlayerCounter::onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) { flashDelta = newValue - oldValue; if (flashDelta == 0) { @@ -81,6 +81,11 @@ void PlayerCounter::onValueChanged(int oldValue, int newValue) return; } + if (skipDamageAnimation) { + flashAlpha = 0.0; + return; + } + flashAlpha = 1.0; flashClock.start(); if (scene()) { diff --git a/cockatrice/src/game_graphics/player/player_target.h b/cockatrice/src/game_graphics/player/player_target.h index af0e9c8b7..1d06c6274 100644 --- a/cockatrice/src/game_graphics/player/player_target.h +++ b/cockatrice/src/game_graphics/player/player_target.h @@ -21,7 +21,7 @@ class PlayerCounter : public AbstractCounter, public IAnimatedItem { Q_OBJECT protected: - void onValueChanged(int oldValue, int newValue) override; + void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) override; private: static constexpr qreal flashDurationMs = 450.0; diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp index a2c1e0ff0..c51b96b6c 100644 --- a/cockatrice/src/interface/widgets/replay/replay_manager.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_manager.cpp @@ -142,8 +142,10 @@ void ReplayManager::processNewEvents(PlaybackMode playbackMode) } // backwards skip => always skip tap animation + // backwards skip => always skip damage animation (battlefield shimmer / life counter flash) if (playbackMode == BACKWARD_SKIP) { options |= SKIP_TAP_ANIMATION; + options |= SKIP_DAMAGE_ANIMATION; } emit eventReplayed(replay->event_list(currentEvent), options);