[Replay] Skip damage animations when skipping backward in replays (#7249)

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-09-07 02:26:52 +02:00 • committed by GitHub
parent b01e107908
commit 745e94f332
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 40 additions and 17 deletions

View file

@ -13,12 +13,12 @@ CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent); convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
} }
void CounterState::setValue(int newValue) void CounterState::setValue(int newValue, bool skipDamageAnimation)
{ {
if (newValue == value) { if (newValue == value) {
return; return;
} }
int old = value; int old = value;
value = newValue; value = newValue;
emit valueChanged(old, newValue); emit valueChanged(old, newValue, skipDamageAnimation);
} }

View file

@ -35,10 +35,23 @@ public:
return value; 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: 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: private:
int id; int id;

View file

@ -13,7 +13,8 @@
enum EventProcessingOption enum EventProcessingOption
{ {
SKIP_REVEAL_WINDOW = 0x0001, SKIP_REVEAL_WINDOW = 0x0001,
SKIP_TAP_ANIMATION = 0x0002 SKIP_TAP_ANIMATION = 0x0002,
SKIP_DAMAGE_ANIMATION = 0x0004
}; };
// Wrap it in a QFlags typedef // Wrap it in a QFlags typedef

View file

@ -262,14 +262,15 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event)
player->addCounter(event.counter_info()); 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); CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
if (!ctr) { if (!ctr) {
return; return;
} }
int oldValue = ctr->getValue(); 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); emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
} }
@ -625,7 +626,7 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
eventCreateCounter(event.GetExtension(Event_CreateCounter::ext)); eventCreateCounter(event.GetExtension(Event_CreateCounter::ext));
break; break;
case GameEvent::SET_COUNTER: case GameEvent::SET_COUNTER:
eventSetCounter(event.GetExtension(Event_SetCounter::ext)); eventSetCounter(event.GetExtension(Event_SetCounter::ext), options);
break; break;
case GameEvent::DEL_COUNTER: case GameEvent::DEL_COUNTER:
eventDelCounter(event.GetExtension(Event_DelCounter::ext)); eventDelCounter(event.GetExtension(Event_DelCounter::ext));

View file

@ -153,7 +153,7 @@ public:
void eventCreateCounter(const Event_CreateCounter &event); void eventCreateCounter(const Event_CreateCounter &event);
/// Set a player-level counter value. /// 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. /// Delete a player-level counter.
void eventDelCounter(const Event_DelCounter &event); void eventDelCounter(const Event_DelCounter &event);

View file

@ -29,9 +29,9 @@ AbstractCounter::AbstractCounter(CounterState *state,
{ {
setAcceptHoverEvents(true); 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; value = newValue;
onValueChanged(oldValue, newValue); onValueChanged(oldValue, newValue, skipDamageAnimation);
update(); update();
}); });
@ -230,7 +230,7 @@ void AbstractCounterDialog::changeValue(int diff)
setTextValue(QString::number(curValue)); 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 // Default: no feedback. Subclasses such as PlayerCounter override this to
// flash the counter on meaningful changes (life gain/loss). // flash the counter on meaningful changes (life gain/loss).

View file

@ -39,8 +39,9 @@ protected:
* @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash). * @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. * 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 mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;

View file

@ -252,8 +252,8 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
AbstractCounter *widget; AbstractCounter *widget;
if (state->getName() == "life") { if (state->getName() == "life") {
widget = playerTarget->addCounter(state); widget = playerTarget->addCounter(state);
connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) { connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue, bool skipDamageAnimation) {
if (newValue < oldValue) { if (newValue < oldValue && !skipDamageAnimation) {
tableZoneGraphicsItem->triggerDamageShimmer(); tableZoneGraphicsItem->triggerDamageShimmer();
} }
}); });

View file

@ -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; flashDelta = newValue - oldValue;
if (flashDelta == 0) { if (flashDelta == 0) {
@ -81,6 +81,11 @@ void PlayerCounter::onValueChanged(int oldValue, int newValue)
return; return;
} }
if (skipDamageAnimation) {
flashAlpha = 0.0;
return;
}
flashAlpha = 1.0; flashAlpha = 1.0;
flashClock.start(); flashClock.start();
if (scene()) { if (scene()) {

View file

@ -21,7 +21,7 @@ class PlayerCounter : public AbstractCounter, public IAnimatedItem
{ {
Q_OBJECT Q_OBJECT
protected: protected:
void onValueChanged(int oldValue, int newValue) override; void onValueChanged(int oldValue, int newValue, bool skipDamageAnimation) override;
private: private:
static constexpr qreal flashDurationMs = 450.0; static constexpr qreal flashDurationMs = 450.0;

View file

@ -142,8 +142,10 @@ void ReplayManager::processNewEvents(PlaybackMode playbackMode)
} }
// backwards skip => always skip tap animation // backwards skip => always skip tap animation
// backwards skip => always skip damage animation (battlefield shimmer / life counter flash)
if (playbackMode == BACKWARD_SKIP) { if (playbackMode == BACKWARD_SKIP) {
options |= SKIP_TAP_ANIMATION; options |= SKIP_TAP_ANIMATION;
options |= SKIP_DAMAGE_ANIMATION;
} }
emit eventReplayed(replay->event_list(currentEvent), options); emit eventReplayed(replay->event_list(currentEvent), options);