Skip to content

Comments

Show damage flash in spectator#255

Merged
GooberRF merged 4 commits intoGooberRF:masterfrom
nickalreadyinuse:spectator-damage-flash
Feb 24, 2026
Merged

Show damage flash in spectator#255
GooberRF merged 4 commits intoGooberRF:masterfrom
nickalreadyinuse:spectator-damage-flash

Conversation

@nickalreadyinuse
Copy link

No description provided.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a configurable “damage screen flash” effect while spectating another player, including persistence via Alpine settings and a new console toggle.

Changes:

  • Track spectated player health/armor deltas and trigger a red screen flash on decreases.
  • Add cl_damageflash_spectator console command and persist the setting in alpine_settings.ini.
  • Document the feature in the changelog.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
game_patch/misc/player.cpp Implements spectator damage flash detection in players_do_frame and adds a toggle command.
game_patch/misc/alpine_settings.h Adds spectate_damage_screen_flash setting to AlpineGameSettings.
game_patch/misc/alpine_settings.cpp Loads/saves SpectateDamageScreenFlash from/to the settings file.
docs/CHANGELOG.md Adds an entry describing the spectator damage flash and its console toggle.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 506 to 510
else if (target_entity && g_spectate_last_health >= 0.0f) {
if (target_entity->life < g_spectate_last_health
|| target_entity->armor < g_spectate_last_armor) {
rf::local_screen_flash(rf::local_player, 255, 0, 0, 128);
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g_spectate_last_health uses -1.0f as a sentinel and the update path is gated by g_spectate_last_health >= 0.0f. Entity::life can legitimately be <= 0 (and even negative) for dead players, so after the target dies this condition will prevent the baseline from ever updating again (and the flash may stop working until changing targets). Also, if target_entity is null when the target is first set, the baseline is left at -1.0f and will never initialize later. Use a dedicated “initialized” flag/std::optional (or similar) and always refresh the baseline whenever target_entity becomes available; only trigger the flash when you have a valid previous sample.

Copilot uses AI. Check for mistakes.
Comment on lines 498 to 510
// Spectator damage flash: detect health or armor decreases on the spectated player
if (target && g_alpine_game_config.spectate_damage_screen_flash) {
rf::Entity* target_entity = rf::entity_from_handle(target->entity_handle);
if (target != g_spectate_last_target) {
g_spectate_last_target = target;
g_spectate_last_health = target_entity ? target_entity->life : -1.0f;
g_spectate_last_armor = target_entity ? target_entity->armor : -1.0f;
}
else if (target_entity && g_spectate_last_health >= 0.0f) {
if (target_entity->life < g_spectate_last_health
|| target_entity->armor < g_spectate_last_armor) {
rf::local_screen_flash(rf::local_player, 255, 0, 0, 128);
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the baseline (g_spectate_last_health / g_spectate_last_armor) is only updated inside the spectate_damage_screen_flash enabled block, turning the setting off while spectating will freeze the baseline. When the user re-enables it, any damage that happened while it was off can cause an immediate false-positive flash due to stale values. Consider continuing to update the baseline even when the effect is disabled (but skip the flash), or reset/reinitialize the baseline when toggling the setting.

Suggested change
// Spectator damage flash: detect health or armor decreases on the spectated player
if (target && g_alpine_game_config.spectate_damage_screen_flash) {
rf::Entity* target_entity = rf::entity_from_handle(target->entity_handle);
if (target != g_spectate_last_target) {
g_spectate_last_target = target;
g_spectate_last_health = target_entity ? target_entity->life : -1.0f;
g_spectate_last_armor = target_entity ? target_entity->armor : -1.0f;
}
else if (target_entity && g_spectate_last_health >= 0.0f) {
if (target_entity->life < g_spectate_last_health
|| target_entity->armor < g_spectate_last_armor) {
rf::local_screen_flash(rf::local_player, 255, 0, 0, 128);
}
// Spectator damage flash: detect health or armor decreases on the spectated player.
// Baselines are updated even when the flash effect is disabled to avoid stale state.
if (target) {
rf::Entity* target_entity = rf::entity_from_handle(target->entity_handle);
if (target != g_spectate_last_target) {
// New spectate target: initialize baselines.
g_spectate_last_target = target;
g_spectate_last_health = target_entity ? target_entity->life : -1.0f;
g_spectate_last_armor = target_entity ? target_entity->armor : -1.0f;
}
else if (target_entity && g_spectate_last_health >= 0.0f) {
// Existing target: detect damage based on current baselines.
bool took_damage =
(target_entity->life < g_spectate_last_health) ||
(target_entity->armor < g_spectate_last_armor);
if (took_damage && g_alpine_game_config.spectate_damage_screen_flash) {
rf::local_screen_flash(rf::local_player, 255, 0, 0, 128);
}
// Always refresh baselines so they stay current even when the flash is disabled.

Copilot uses AI. Check for mistakes.
@is-this-c
Copy link

is-this-c commented Feb 22, 2026

It seems hacky. Servers already tracks spectators. A damage_flash packet should be suitable.

@GooberRF
Copy link
Owner

It seems hacky. Servers already tracks spectators. A damage_flash packet should be suitable.

Having this as a packet doesn't make sense to me - damage flash is strictly a clientside graphical feature, having the client determine when to apply it makes sense. This implementation seems like the most lightweight and reasonable way to do this without introducing unnecessary complexity.

I do think it would be a good idea to collapse g_spectate_last_target, g_spectate_last_health, g_spectate_last_armor, and any additional data points we may need about the spectatee in the future into a "SpectateeData" structure (name TBD) rather than separate globals, though.

@is-this-c
Copy link

This implementation seems like the most lightweight and reasonable way to do this without introducing unnecessary complexity.

Why not hook obj_update instead?

@GooberRF
Copy link
Owner

Why not hook obj_update instead?

Either the existing hook or hooking obj_update could work. I'm not entirely convinced of which seems "more correct", though.

Perhaps doing it in obj_update would be a better fit though, since that is where the local player's damage flash gets triggered when they take damage?

@is-this-c
Copy link

Perhaps doing it in obj_update would be a better fit though, since that is where the local player's damage flash gets triggered when they take damage?

Yeah and it avoids g_spectate_last_health etc.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

GooberRF and others added 2 commits February 24, 2026 00:39
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@GooberRF GooberRF merged commit 09d9509 into GooberRF:master Feb 24, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants