From 71ee2aa331e2de555c50b0e1e7396de21e802f3c Mon Sep 17 00:00:00 2001 From: "Dr. Jaska" Date: Sun, 5 Nov 2023 17:50:33 +0000 Subject: [PATCH] Refactor damagetext to use a new linked list instead of proximity search and fix 2D and 3D damagetexts not accumulating --- qcsrc/client/main.qh | 2 + qcsrc/client/view.qc | 1 + .../mutator/damagetext/cl_damagetext.qc | 117 +++++++----------- .../mutator/damagetext/cl_damagetext.qh | 30 +++++ .../mutator/damagetext/sv_damagetext.qc | 1 - xonotic-client.cfg | 3 +- 6 files changed, 76 insertions(+), 78 deletions(-) diff --git a/qcsrc/client/main.qh b/qcsrc/client/main.qh index 288a303cc..7929a450e 100644 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@ -78,12 +78,14 @@ entity teamslots[17]; // 17 teams (including "spectator team") IntrusiveList g_drawables; IntrusiveList g_drawables_2d; +IntrusiveList g_damagetext; IntrusiveList g_radarlinks; IntrusiveList g_radaricons; STATIC_INIT(main) { g_drawables = IL_NEW(); g_drawables_2d = IL_NEW(); + g_damagetext = IL_NEW(); g_radarlinks = IL_NEW(); g_radaricons = IL_NEW(); } diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index c9de0d156..a9496ea15 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -1703,6 +1703,7 @@ void CSQC_UpdateView(entity this, float w, float h) // draw 2D entities IL_EACH(g_drawables_2d, it.draw2d, it.draw2d(it)); + IL_EACH(g_damagetext, it.draw2d, it.draw2d(it)); Draw_ShowNames_All(); #if ENABLE_DEBUGDRAW Debug_Draw(); diff --git a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc index e2208fb34..9a23879ae 100644 --- a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc @@ -1,45 +1,5 @@ #include "cl_damagetext.qh" -// no translatable cvar description please -AUTOCVAR_SAVE(cl_damagetext, bool, true, "Draw damage dealt where you hit the enemy"); -AUTOCVAR_SAVE(cl_damagetext_format, string, "-{total}", "How to format the damage text. {health}, {armor}, {total}, {potential}: full damage not capped to target's health, {potential_health}: health damage not capped to target's health"); -AUTOCVAR_SAVE(cl_damagetext_format_verbose, bool, false, "{health} shows {potential_health} too when they differ; {total} shows {potential} too when they differ"); -AUTOCVAR_SAVE(cl_damagetext_format_hide_redundant, bool, false, "hide {armor} if 0; hide {potential} and {potential_health} when same as actual"); -STATIC_INIT(DamageText_LegacyFormat) { - // damagetext used to be off by default and the cvar got saved in people's configs along with the old format - // enable damagetext while updating the format for a one time effect - if (strstrofs(autocvar_cl_damagetext_format, "{", 0) < 0) { - localcmd("\nseta cl_damagetext 1\n"); - localcmd("\nseta cl_damagetext_format -{total}\n"); - }; -} -AUTOCVAR_SAVE(cl_damagetext_color, vector, '1 1 0', "Damage text color"); -AUTOCVAR_SAVE(cl_damagetext_color_per_weapon, bool, false, "Damage text uses weapon color"); -AUTOCVAR_SAVE(cl_damagetext_size_min, float, 10, "Damage text font size for small damage"); -AUTOCVAR_SAVE(cl_damagetext_size_min_damage, float, 25, "How much damage is considered small"); -AUTOCVAR_SAVE(cl_damagetext_size_max, float, 16, "Damage text font size for large damage"); -AUTOCVAR_SAVE(cl_damagetext_size_max_damage, float, 140, "How much damage is considered large"); -AUTOCVAR_SAVE(cl_damagetext_alpha_start, float, 1, "Damage text initial alpha"); -AUTOCVAR_SAVE(cl_damagetext_alpha_lifetime, float, 3, "Damage text lifetime in seconds"); -AUTOCVAR_SAVE(cl_damagetext_velocity_screen, vector, '0 0 0', "Damage text move direction (screen coordinates)"); -AUTOCVAR_SAVE(cl_damagetext_velocity_world, vector, '0 0 20', "Damage text move direction (world coordinates relative to player's view)"); -AUTOCVAR_SAVE(cl_damagetext_offset_screen, vector, '0 -45 0', "Damage text offset (screen coordinates)"); -AUTOCVAR_SAVE(cl_damagetext_offset_world, vector, '0 0 0', "Damage text offset (world coordinates relative to player's view)"); -AUTOCVAR_SAVE(cl_damagetext_accumulate_range, float, 30, "Damage text spawned within this range is accumulated"); -AUTOCVAR_SAVE(cl_damagetext_accumulate_alpha_rel, float, 0.65, "Only update existing damage text when it's above this much percentage (0 to 1) of the starting alpha"); -AUTOCVAR_SAVE(cl_damagetext_friendlyfire, int, 1, "0: never show for friendly fire, 1: when more than 0 damage, 2: always"); -AUTOCVAR_SAVE(cl_damagetext_friendlyfire_color, vector, '1 0 0', "Damage text color for friendlyfire"); - -AUTOCVAR_SAVE(cl_damagetext_2d, bool, true, "Show damagetext in 2D coordinates if the enemy's location is not known"); -AUTOCVAR_SAVE(cl_damagetext_2d_pos, vector, '0.47 0.53 0', "2D damage text initial position (X and Y between 0 and 1)"); -AUTOCVAR_SAVE(cl_damagetext_2d_alpha_start, float, 1, "2D damage text initial alpha"); -AUTOCVAR_SAVE(cl_damagetext_2d_alpha_lifetime, float, 1.3, "2D damage text lifetime (alpha fading) in seconds"); -AUTOCVAR_SAVE(cl_damagetext_2d_size_lifetime, float, 3, "2D damage text lifetime (size shrinking) in seconds"); -AUTOCVAR_SAVE(cl_damagetext_2d_velocity, vector, '-25 0 0', "2D damage text move direction (screen coordinates)"); -AUTOCVAR_SAVE(cl_damagetext_2d_overlap_offset, vector, '0 -15 0', "Offset 2D damage text by this much to prevent overlapping (screen coordinates)"); -AUTOCVAR_SAVE(cl_damagetext_2d_close_range, float, 125, "Always use 2D damagetext for hits closer that this"); -AUTOCVAR_SAVE(cl_damagetext_2d_out_of_view, bool, true, "Always use 2D damagetext for hits that occurred off-screen"); - CLASS(DamageText, Object) ATTRIB(DamageText, m_color, vector, autocvar_cl_damagetext_color); ATTRIB(DamageText, m_color_friendlyfire, vector, autocvar_cl_damagetext_friendlyfire_color); @@ -57,7 +17,6 @@ CLASS(DamageText, Object) ATTRIB(DamageText, text, string, string_null); ATTRIB(DamageText, m_screen_coords, bool, false); - STATIC_ATTRIB(DamageText, screen_first, DamageText, NULL); STATIC_ATTRIB(DamageText, screen_count, int, 0); void DamageText_draw2d(DamageText this) { @@ -66,6 +25,7 @@ CLASS(DamageText, Object) float size = this.m_size - since_hit * this.m_shrink_rate * this.m_size; float alpha_ = this.alpha - since_hit * this.fade_rate; if (alpha_ <= 0 || size <= 0) { + IL_REMOVE(g_damagetext, this); delete(this); return; } @@ -82,6 +42,24 @@ CLASS(DamageText, Object) } screen_pos.y += size / 2; + // strip trailing spaces + string dtext = this.text; + int j = strlen(dtext) - 1; + while (substring(dtext, j, 1) == " " && j >= 0) + --j; + if (j < strlen(dtext) - 1) + dtext = substring(dtext, 0, j + 1); + + // strip leading spaces + j = 0; + while (substring(dtext, j, 1) == " " && j < strlen(dtext)) + ++j; + if (j > 0) + dtext = substring(dtext, j, strlen(dtext) - j); + + // Center damage text + screen_pos.x -= stringwidth(dtext, true, hud_fontsize * 2) * 0.5; + if (screen_pos.z >= 0) { screen_pos.z = 0; vector rgb; @@ -104,18 +82,19 @@ CLASS(DamageText, Object) } ATTRIB(DamageText, draw2d, void(DamageText), DamageText_draw2d); - void DamageText_update(DamageText this, vector _origin, int _health, int _armor, int _potential_damage, int _deathtype) { + void DamageText_update(DamageText this, vector _origin, bool screen_coords, int _health, int _armor, int _potential_damage, int _deathtype) { this.m_healthdamage = _health; this.m_armordamage = _armor; this.m_potential_damage = _potential_damage; this.m_deathtype = _deathtype; + this.hit_time = time; setorigin(this, _origin); + this.m_screen_coords = screen_coords; if (this.m_screen_coords) { this.alpha = autocvar_cl_damagetext_2d_alpha_start; } else { this.alpha = autocvar_cl_damagetext_alpha_start; } - this.hit_time = time; int health = rint(this.m_healthdamage / DAMAGETEXT_PRECISION_MULTIPLIER); int armor = rint(this.m_armordamage / DAMAGETEXT_PRECISION_MULTIPLIER); @@ -179,23 +158,22 @@ CLASS(DamageText, Object) this.m_friendlyfire = _friendlyfire; this.m_screen_coords = _screen_coords; if (_screen_coords) { - this.fade_rate = 1 / autocvar_cl_damagetext_2d_alpha_lifetime; - this.m_shrink_rate = 1 / autocvar_cl_damagetext_2d_size_lifetime; + if (autocvar_cl_damagetext_2d_alpha_lifetime) + this.fade_rate = 1 / autocvar_cl_damagetext_2d_alpha_lifetime; + if (autocvar_cl_damagetext_2d_size_lifetime) + this.m_shrink_rate = 1 / autocvar_cl_damagetext_2d_size_lifetime; } else { - this.fade_rate = 1 / autocvar_cl_damagetext_alpha_lifetime; + if (autocvar_cl_damagetext_alpha_lifetime) + this.fade_rate = 1 / autocvar_cl_damagetext_alpha_lifetime; this.m_shrink_rate = 0; } - DamageText_update(this, _origin, _health, _armor, _potential_damage, _deathtype); - IL_PUSH(g_drawables_2d, this); + DamageText_update(this, _origin, _screen_coords, _health, _armor, _potential_damage, _deathtype); + IL_PUSH(g_damagetext, this); } DESTRUCTOR(DamageText) { strfree(this.text); - if (this == DamageText_screen_first) { - // start from 0 offset again, hopefully, others (if any) will have faded away by now - DamageText_screen_first = NULL; - DamageText_screen_count = 0; - } + --DamageText_screen_count; } ENDCLASS(DamageText) @@ -240,35 +218,24 @@ NET_HANDLE(damagetext, bool isNew) if (can_use_3d && !prefer_2d) { // world coords - // 1 as min because shotgun sends damagetext per pellet (see https://gitlab.com/xonotic/xonotic-data.pk3dir/issues/1994). - for (entity e = findradius(entcs.origin, max(autocvar_cl_damagetext_accumulate_range, 1)); e; e = e.chain) { - if (e.instanceOfDamageText - && !e.m_screen_coords // we're using origin for both world coords and screen coords so avoid mismatches - && e.m_group == server_entity_index - && current_alpha(e) > autocvar_cl_damagetext_accumulate_alpha_rel * autocvar_cl_damagetext_alpha_start) { - DamageText_update(e, entcs.origin, e.m_healthdamage + health, e.m_armordamage + armor, e.m_potential_damage + potential_damage, deathtype); + IL_EACH(g_damagetext, it.m_group == server_entity_index, { + if (current_alpha(it) > autocvar_cl_damagetext_accumulate_alpha_rel * autocvar_cl_damagetext_alpha_start) { + DamageText_update(it, entcs.origin, false, it.m_healthdamage + health, it.m_armordamage + armor, it.m_potential_damage + potential_damage, deathtype); return; } - } - make_impure(NEW(DamageText, server_entity_index, entcs.origin, false, health, armor, potential_damage, deathtype, friendlyfire)); + }); + ++DamageText_screen_count; //3D DamageTexts can later be changed into 2D, increment this just in case + NEW(DamageText, server_entity_index, entcs.origin, false, health, armor, potential_damage, deathtype, friendlyfire); } else if (autocvar_cl_damagetext_2d && spectatee_status != -1) { // screen coords only vector screen_pos = vec2(vid_conwidth * autocvar_cl_damagetext_2d_pos.x, vid_conheight * autocvar_cl_damagetext_2d_pos.y); - IL_EACH(g_drawables_2d, it.instanceOfDamageText && it.m_screen_coords && it.m_group == server_entity_index, { - DamageText_update(it, screen_pos, it.m_healthdamage + health, it.m_armordamage + armor, it.m_potential_damage + potential_damage, deathtype); + IL_EACH(g_damagetext, it.m_group == server_entity_index, { + DamageText_update(it, screen_pos, true, it.m_healthdamage + health, it.m_armordamage + armor, it.m_potential_damage + potential_damage, deathtype); return; }); - // when hitting multiple enemies, dmgtext would overlap - if (DamageText_screen_first == NULL) { - DamageText dt = NEW(DamageText, server_entity_index, screen_pos, true, health, armor, potential_damage, deathtype, friendlyfire); - make_impure(dt); - DamageText_screen_first = dt; - DamageText_screen_count = 1; - } else { - screen_pos += autocvar_cl_damagetext_2d_overlap_offset * DamageText_screen_count; - DamageText_screen_count++; - make_impure(NEW(DamageText, server_entity_index, screen_pos, true, health, armor, potential_damage, deathtype, friendlyfire)); - } + // offset when hitting multiple enemies, dmgtext would overlap + screen_pos += autocvar_cl_damagetext_2d_overlap_offset * DamageText_screen_count++; + NEW(DamageText, server_entity_index, screen_pos, true, health, armor, potential_damage, deathtype, friendlyfire); } } diff --git a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh index 6f70f09be..e76b4119a 100644 --- a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh +++ b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh @@ -1 +1,31 @@ #pragma once + +bool autocvar_cl_damagetext; +string autocvar_cl_damagetext_format; +bool autocvar_cl_damagetext_format_verbose; +bool autocvar_cl_damagetext_format_hide_redundant; +vector autocvar_cl_damagetext_color; +bool autocvar_cl_damagetext_color_per_weapon; +float autocvar_cl_damagetext_size_min; +float autocvar_cl_damagetext_size_min_damage; +float autocvar_cl_damagetext_size_max; +float autocvar_cl_damagetext_size_max_damage; +float autocvar_cl_damagetext_alpha_start; +float autocvar_cl_damagetext_alpha_lifetime; +vector autocvar_cl_damagetext_velocity_world; +vector autocvar_cl_damagetext_offset_world; +float autocvar_cl_damagetext_accumulate_alpha_rel; +int autocvar_cl_damagetext_friendlyfire; +vector autocvar_cl_damagetext_friendlyfire_color; + +bool autocvar_cl_damagetext_2d; +vector autocvar_cl_damagetext_2d_pos; +float autocvar_cl_damagetext_2d_alpha_start; +float autocvar_cl_damagetext_2d_alpha_lifetime; +float autocvar_cl_damagetext_2d_size_lifetime; +vector autocvar_cl_damagetext_2d_velocity; +vector autocvar_cl_damagetext_2d_overlap_offset; +float autocvar_cl_damagetext_2d_close_range; +bool autocvar_cl_damagetext_2d_out_of_view; +vector autocvar_cl_damagetext_velocity_screen; +vector autocvar_cl_damagetext_offset_screen; diff --git a/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc index c9ded88f0..8bf951680 100644 --- a/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc @@ -63,7 +63,6 @@ MUTATOR_HOOKFUNCTION(damagetext, PlayerDamaged) { int deathtype = M_ARGV(5, int); float potential_damage = M_ARGV(6, float); if(DEATH_WEAPONOF(deathtype) == WEP_VAPORIZER) return; - if(deathtype == DEATH_FIRE.m_id || deathtype == DEATH_BUFF.m_id) return; // TODO: exclude damage over time and thorn effects int flags = 0; if (SAME_TEAM(hit, attacker)) flags |= DTFLAG_SAMETEAM; diff --git a/xonotic-client.cfg b/xonotic-client.cfg index af952d0ce..9e9c48f5f 100644 --- a/xonotic-client.cfg +++ b/xonotic-client.cfg @@ -459,8 +459,7 @@ seta cl_damagetext_alpha_lifetime "3" "Damage text lifetime in seconds" seta cl_damagetext_velocity_screen "0 0 0" "Damage text move direction (screen coordinates)" seta cl_damagetext_velocity_world "0 0 20" "Damage text move direction (world coordinates relative to player's view)" seta cl_damagetext_offset_screen "0 -45 0" "Damage text offset (screen coordinates)" -seta cl_damagetext_offset_world "0 0 0" "Damage text offset (world coordinates relative to player's view)" -seta cl_damagetext_accumulate_range "30" "Damage text spawned within this range is accumulated" +seta cl_damagetext_offset_world "0 25 0" "Damage text offset (world coordinates relative to player's view)" seta cl_damagetext_accumulate_alpha_rel "0.65" "Only update existing damage text when it's above this much percentage (0 to 1) of the starting alpha" seta cl_damagetext_friendlyfire "1" "0: never show for friendly fire, 1: when more than 0 damage, 2: always" seta cl_damagetext_friendlyfire_color "1 0 0" "Damage text color for friendlyfire" -- 2.39.2