From: Dr. Jaska Date: Thu, 28 Dec 2023 19:21:12 +0000 (+0000) Subject: DamageText code quality changes and new DT lifetime and accumulation lifetime cvars X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=df1b8ac95e18b7f00fa01d2f081bf6a0772ff2e9;p=xonotic%2Fxonotic-data.pk3dir.git DamageText code quality changes and new DT lifetime and accumulation lifetime cvars --- diff --git a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc index 9a23879ae..010c1b581 100644 --- a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc @@ -1,241 +1,326 @@ #include "cl_damagetext.qh" CLASS(DamageText, Object) - ATTRIB(DamageText, m_color, vector, autocvar_cl_damagetext_color); - ATTRIB(DamageText, m_color_friendlyfire, vector, autocvar_cl_damagetext_friendlyfire_color); - ATTRIB(DamageText, m_size, float, autocvar_cl_damagetext_size_min); - ATTRIB(DamageText, alpha, float, autocvar_cl_damagetext_alpha_start); - ATTRIB(DamageText, fade_rate, float, 0); - ATTRIB(DamageText, m_shrink_rate, float, 0); - ATTRIB(DamageText, m_group, int, 0); - ATTRIB(DamageText, m_friendlyfire, bool, false); - ATTRIB(DamageText, m_healthdamage, int, 0); - ATTRIB(DamageText, m_armordamage, int, 0); - ATTRIB(DamageText, m_potential_damage, int, 0); - ATTRIB(DamageText, m_deathtype, int, 0); - ATTRIB(DamageText, hit_time, float, 0); - ATTRIB(DamageText, text, string, string_null); - ATTRIB(DamageText, m_screen_coords, bool, false); - - STATIC_ATTRIB(DamageText, screen_count, int, 0); - - void DamageText_draw2d(DamageText this) { - float since_hit = time - this.hit_time; - // can't use `dt = hit_time - prev_update_time` because shrinking wouldn't be linear - 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; - } - - vector screen_pos; - if (this.m_screen_coords) { - screen_pos = this.origin + since_hit * autocvar_cl_damagetext_2d_velocity; - } else { - vector forward, right, up; - MAKE_VECTORS(view_angles, forward, right, up); - vector world_offset = since_hit * autocvar_cl_damagetext_velocity_world + autocvar_cl_damagetext_offset_world; - vector world_pos = this.origin + world_offset.x * forward + world_offset.y * right + world_offset.z * up; - screen_pos = project_3d_to_2d(world_pos) + since_hit * autocvar_cl_damagetext_velocity_screen + autocvar_cl_damagetext_offset_screen; - } - 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; - if (this.m_friendlyfire) { - rgb = this.m_color_friendlyfire; - } else { - rgb = this.m_color; - } - if (autocvar_cl_damagetext_color_per_weapon) { - Weapon w = DEATH_WEAPONOF(this.m_deathtype); - if (w != WEP_Null) rgb = w.wpcolor; - } - - vector drawfontscale_save = drawfontscale; - drawfontscale = (size / autocvar_cl_damagetext_size_max) * '1 1 0'; - screen_pos.y -= drawfontscale.x * size / 2; - drawcolorcodedstring2_builtin(screen_pos, this.text, autocvar_cl_damagetext_size_max * '1 1 0', rgb, alpha_, DRAWFLAG_NORMAL); - drawfontscale = drawfontscale_save; - } - } - ATTRIB(DamageText, draw2d, void(DamageText), DamageText_draw2d); - - 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; - } - - int health = rint(this.m_healthdamage / DAMAGETEXT_PRECISION_MULTIPLIER); - int armor = rint(this.m_armordamage / DAMAGETEXT_PRECISION_MULTIPLIER); - int total = rint((this.m_healthdamage + this.m_armordamage) / DAMAGETEXT_PRECISION_MULTIPLIER); - int potential = rint(this.m_potential_damage / DAMAGETEXT_PRECISION_MULTIPLIER); - int potential_health = rint((this.m_potential_damage - this.m_armordamage) / DAMAGETEXT_PRECISION_MULTIPLIER); - - bool redundant = almost_equals_eps(this.m_healthdamage + this.m_armordamage, this.m_potential_damage, 5); - - string s = autocvar_cl_damagetext_format; - s = strreplace("{armor}", ( - (this.m_armordamage == 0 && autocvar_cl_damagetext_format_hide_redundant) - ? "" - : sprintf("%d", armor) - ), s); - s = strreplace("{potential}", ( - (redundant && autocvar_cl_damagetext_format_hide_redundant) - ? "" - : sprintf("%d", potential) - ), s); - s = strreplace("{potential_health}", ( - (redundant && autocvar_cl_damagetext_format_hide_redundant) - ? "" - : sprintf("%d", potential_health) - ), s); - - s = strreplace("{health}", ( - (health == potential_health || !autocvar_cl_damagetext_format_verbose) - ? sprintf("%d", health) - : sprintf("%d (%d)", health, potential_health) - ), s); - s = strreplace("{total}", ( - (total == potential || !autocvar_cl_damagetext_format_verbose) - ? sprintf("%d", total) - : sprintf("%d (%d)", total, potential) - ), s); - - // futureproofing: remove any remaining (unknown) format strings in case we add new ones in the future - // so players can use them on new servers and still have working damagetext on old ones - while (true) { - int opening_pos = strstrofs(s, "{", 0); - if (opening_pos == -1) break; - int closing_pos = strstrofs(s, "}", opening_pos); - if (closing_pos == -1 || closing_pos <= opening_pos) break; - s = strcat( - substring(s, 0, opening_pos), - substring_range(s, closing_pos + 1, strlen(s)) - ); - } - - strcpy(this.text, s); - - this.m_size = map_bound_ranges(potential, - autocvar_cl_damagetext_size_min_damage, autocvar_cl_damagetext_size_max_damage, - autocvar_cl_damagetext_size_min, autocvar_cl_damagetext_size_max); - } - - CONSTRUCTOR(DamageText, int _group, vector _origin, bool _screen_coords, int _health, int _armor, int _potential_damage, int _deathtype, bool _friendlyfire) { - CONSTRUCT(DamageText); - this.m_group = _group; - this.m_friendlyfire = _friendlyfire; - this.m_screen_coords = _screen_coords; - if (_screen_coords) { - 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 { - if (autocvar_cl_damagetext_alpha_lifetime) - this.fade_rate = 1 / autocvar_cl_damagetext_alpha_lifetime; - this.m_shrink_rate = 0; - } - DamageText_update(this, _origin, _screen_coords, _health, _armor, _potential_damage, _deathtype); - IL_PUSH(g_damagetext, this); - } - - DESTRUCTOR(DamageText) { - strfree(this.text); - --DamageText_screen_count; - } + ATTRIB(DamageText, m_color, vector, autocvar_cl_damagetext_color); + ATTRIB(DamageText, m_color_friendlyfire, vector, autocvar_cl_damagetext_friendlyfire_color); + ATTRIB(DamageText, m_size, float, autocvar_cl_damagetext_size_min); + ATTRIB(DamageText, alpha, float, autocvar_cl_damagetext_alpha_start); + ATTRIB(DamageText, fade_rate, float, 0); + ATTRIB(DamageText, m_shrink_rate, float, 0); + ATTRIB(DamageText, m_group, int, 0); + ATTRIB(DamageText, m_friendlyfire, bool, false); + ATTRIB(DamageText, m_healthdamage, int, 0); + ATTRIB(DamageText, m_armordamage, int, 0); + ATTRIB(DamageText, m_potential_damage, int, 0); + ATTRIB(DamageText, m_deathtype, int, 0); + ATTRIB(DamageText, hit_time, float, 0); + ATTRIB(DamageText, text, string, string_null); + ATTRIB(DamageText, m_screen_coords, bool, false); + + STATIC_ATTRIB(DamageText, screen_count, int, 0); + + void DamageText_draw2d(DamageText this) + { + float since_hit = time - this.hit_time; + // can't use `dt = hit_time - prev_update_time` because shrinking wouldn't be linear + float size = this.m_size - since_hit * this.m_shrink_rate * this.m_size; + float alpha_ = this.alpha - since_hit * this.fade_rate; + bool haslifetime = (autocvar_cl_damagetext_lifetime < 0 // negative ignores lifetime + || (since_hit < autocvar_cl_damagetext_lifetime)); + if (alpha_ <= 0 || size <= 0 || !haslifetime) + { + IL_REMOVE(g_damagetext, this); + delete(this); + return; + } + + vector screen_pos; + if (this.m_screen_coords) + screen_pos = this.origin + since_hit * autocvar_cl_damagetext_2d_velocity; + else + { + vector forward, right, up; + MAKE_VECTORS(view_angles, forward, right, up); + + vector world_offset = since_hit * autocvar_cl_damagetext_velocity_world + + autocvar_cl_damagetext_offset_world; + vector world_pos = this.origin + + world_offset.x * forward + + world_offset.y * right + + world_offset.z * up; + + screen_pos = project_3d_to_2d(world_pos) + + (since_hit * autocvar_cl_damagetext_velocity_screen) + + autocvar_cl_damagetext_offset_screen; + } + 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; + if (this.m_friendlyfire) + rgb = this.m_color_friendlyfire; + else + rgb = this.m_color; + + if (autocvar_cl_damagetext_color_per_weapon) + { + Weapon w = DEATH_WEAPONOF(this.m_deathtype); + if (w != WEP_Null) rgb = w.wpcolor; + } + + vector drawfontscale_save = drawfontscale; + drawfontscale = (size / autocvar_cl_damagetext_size_max) * '1 1 0'; + screen_pos.y -= drawfontscale.x * size / 2; + drawcolorcodedstring2_builtin(screen_pos, this.text, + autocvar_cl_damagetext_size_max * '1 1 0', + rgb, alpha_, DRAWFLAG_NORMAL); + drawfontscale = drawfontscale_save; + } + } + ATTRIB(DamageText, draw2d, void(DamageText), DamageText_draw2d); + + 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; + + int health = rint(this.m_healthdamage + / DAMAGETEXT_PRECISION_MULTIPLIER); + int armor = rint(this.m_armordamage + / DAMAGETEXT_PRECISION_MULTIPLIER); + int total = rint((this.m_healthdamage + this.m_armordamage) + / DAMAGETEXT_PRECISION_MULTIPLIER); + int potential = rint(this.m_potential_damage + / DAMAGETEXT_PRECISION_MULTIPLIER); + int potential_health = rint((this.m_potential_damage - this.m_armordamage) + / DAMAGETEXT_PRECISION_MULTIPLIER); + + bool redundant = almost_equals_eps(this.m_healthdamage + this.m_armordamage, + this.m_potential_damage, 5); + + string s = autocvar_cl_damagetext_format; + s = strreplace("{armor}", ( + (this.m_armordamage == 0 && autocvar_cl_damagetext_format_hide_redundant) + ? "" + : sprintf("%d", armor) + ), s); + s = strreplace("{potential}", ( + (redundant && autocvar_cl_damagetext_format_hide_redundant) + ? "" + : sprintf("%d", potential) + ), s); + s = strreplace("{potential_health}", ( + (redundant && autocvar_cl_damagetext_format_hide_redundant) + ? "" + : sprintf("%d", potential_health) + ), s); + + s = strreplace("{health}", ( + (health == potential_health || !autocvar_cl_damagetext_format_verbose) + ? sprintf("%d", health) + : sprintf("%d (%d)", health, potential_health) + ), s); + s = strreplace("{total}", ( + (total == potential || !autocvar_cl_damagetext_format_verbose) + ? sprintf("%d", total) + : sprintf("%d (%d)", total, potential) + ), s); + + // futureproofing: remove any remaining (unknown) format strings + // in case we add new ones in the future so players can use them + // on new servers and still have working damagetext on old ones + while (true) + { + int opening_pos = strstrofs(s, "{", 0); + if (opening_pos == -1) break; + int closing_pos = strstrofs(s, "}", opening_pos); + if (closing_pos == -1 || closing_pos <= opening_pos) break; + s = strcat( + substring(s, 0, opening_pos), + substring_range(s, closing_pos + 1, strlen(s)) + ); + } + + strcpy(this.text, s); + + this.m_size = map_bound_ranges(potential, + autocvar_cl_damagetext_size_min_damage, autocvar_cl_damagetext_size_max_damage, + autocvar_cl_damagetext_size_min, autocvar_cl_damagetext_size_max); + } + + CONSTRUCTOR(DamageText, int _group, vector _origin, bool _screen_coords, + int _health, int _armor, int _potential_damage, int _deathtype, bool _friendlyfire) + { + CONSTRUCT(DamageText); + this.m_group = _group; + this.m_friendlyfire = _friendlyfire; + this.m_screen_coords = _screen_coords; + if (_screen_coords) + { + 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 + { + if (autocvar_cl_damagetext_alpha_lifetime) + this.fade_rate = 1 / autocvar_cl_damagetext_alpha_lifetime; + this.m_shrink_rate = 0; + } + DamageText_update(this, _origin, _screen_coords, + _health, _armor, _potential_damage, _deathtype); + IL_PUSH(g_damagetext, this); + } + + DESTRUCTOR(DamageText) + { + strfree(this.text); + --DamageText_screen_count; + } ENDCLASS(DamageText) -float current_alpha(entity damage_text) { - // alpha doesn't change - actual alpha is always calculated from the initial value - return damage_text.alpha - (time - damage_text.hit_time) * damage_text.fade_rate; +float current_alpha(entity damage_text) +{ + // alpha doesn't change - actual alpha is always calculated from the initial value + return damage_text.alpha - (time - damage_text.hit_time) * damage_text.fade_rate; } NET_HANDLE(damagetext, bool isNew) { - make_pure(this); - int server_entity_index = ReadByte(); - int deathtype = ReadInt24_t(); - int flags = ReadByte(); - bool friendlyfire = flags & DTFLAG_SAMETEAM; - - int health, armor, potential_damage; - if (flags & DTFLAG_BIG_HEALTH) health = ReadInt24_t(); - else health = ReadShort(); - if (flags & DTFLAG_NO_ARMOR) armor = 0; - else if (flags & DTFLAG_BIG_ARMOR) armor = ReadInt24_t(); - else armor = ReadShort(); - if (flags & DTFLAG_NO_POTENTIAL) potential_damage = health + armor; - else if (flags & DTFLAG_BIG_POTENTIAL) potential_damage = ReadInt24_t(); - else potential_damage = ReadShort(); - - return = true; - if (!isNew) return; - if (autocvar_cl_damagetext == 0) return; - if (friendlyfire) { - if (autocvar_cl_damagetext_friendlyfire == 0) return; - if (autocvar_cl_damagetext_friendlyfire == 1 && health == 0 && armor == 0) return; - } - - int client_entity_index = server_entity_index - 1; - entity entcs = entcs_receiver(client_entity_index); - - bool can_use_3d = entcs && entcs.has_origin; - bool too_close = vdist(entcs.origin - view_origin, <, autocvar_cl_damagetext_2d_close_range); - bool prefer_in_view = autocvar_cl_damagetext_2d_out_of_view && !projected_on_screen(project_3d_to_2d(entcs.origin)); - bool prefer_2d = spectatee_status != -1 && autocvar_cl_damagetext_2d && (too_close || prefer_in_view); - - if (can_use_3d && !prefer_2d) { - // world coords - 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; - } - }); - ++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_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; - }); - - // 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); - } + make_pure(this); + int server_entity_index = ReadByte(); + int deathtype = ReadInt24_t(); + int flags = ReadByte(); + bool friendlyfire = flags & DTFLAG_SAMETEAM; + + int health, armor, potential_damage; + if (flags & DTFLAG_BIG_HEALTH) health = ReadInt24_t(); + else health = ReadShort(); + if (flags & DTFLAG_NO_ARMOR) armor = 0; + else if (flags & DTFLAG_BIG_ARMOR) armor = ReadInt24_t(); + else armor = ReadShort(); + if (flags & DTFLAG_NO_POTENTIAL) potential_damage = health + armor; + else if (flags & DTFLAG_BIG_POTENTIAL) potential_damage = ReadInt24_t(); + else potential_damage = ReadShort(); + + return = true; + if (!isNew) return; + if (autocvar_cl_damagetext == 0) return; + if (friendlyfire) + { + if (autocvar_cl_damagetext_friendlyfire == 0) + return; + + if (autocvar_cl_damagetext_friendlyfire == 1 + && health == 0 && armor == 0) + return; + } + + int client_entity_index = server_entity_index - 1; + entity entcs = entcs_receiver(client_entity_index); + + bool can_use_3d = entcs && entcs.has_origin; + bool too_close = vdist(entcs.origin - view_origin, <, autocvar_cl_damagetext_2d_close_range); + bool prefer_in_view = autocvar_cl_damagetext_2d_out_of_view && !projected_on_screen(project_3d_to_2d(entcs.origin)); + bool prefer_2d = spectatee_status != -1 && autocvar_cl_damagetext_2d && (too_close || prefer_in_view); + + vector position; + bool is2d; + entity entDT = NULL; // which DT to update + + // check if this entity already has a DamageText for it + IL_EACH(g_damagetext, it.m_group == server_entity_index, { + // if the time window where damage accumulates closes, + // disown the parent entity from this DamageText + // and (likely) give the entity a new DT afterwards + // this should only cancel damage accumulation for this DT + if (autocvar_cl_damagetext_accumulate_lifetime > 0 // negative never disowns + && (time - it.hit_time > autocvar_cl_damagetext_accumulate_lifetime)) + { + it.m_group = 0; + break; + } + else + { + health += it.m_healthdamage; + armor += it.m_armordamage; + potential_damage += it.m_potential_damage; + + entDT = it; + break; + } + }); + + if (can_use_3d && !prefer_2d) + { + // world coords + is2d = false; + position = entcs.origin; + + float threshold = autocvar_cl_damagetext_accumulate_alpha_rel + * autocvar_cl_damagetext_alpha_start; + + if (entDT && current_alpha(entDT) > threshold) + goto updateDT; + + // 3D DamageTexts can later be changed into 2D, + // increment this here just in case + ++DamageText_screen_count; + goto spawnnewDT; + } + else if (autocvar_cl_damagetext_2d && spectatee_status != -1) + { + // screen coords only + is2d = true; + position = vec2(vid_conwidth * autocvar_cl_damagetext_2d_pos.x, + vid_conheight * autocvar_cl_damagetext_2d_pos.y); + + if (entDT) + goto updateDT; + + // offset when hitting multiple enemies, dmgtext would overlap + position += autocvar_cl_damagetext_2d_overlap_offset * DamageText_screen_count++; + goto spawnnewDT; + } + return; + +LABEL(updateDT) + DamageText_update(entDT, position, is2d, health, armor, potential_damage, deathtype); + return; + +LABEL(spawnnewDT) + NEW(DamageText, server_entity_index, position, is2d, + health, armor, potential_damage, deathtype, friendlyfire); + return; } diff --git a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh index e76b4119a..f845a97cd 100644 --- a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh +++ b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh @@ -29,3 +29,14 @@ 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; + +// TODO: remove the value init and sort them above properly after next (0.9) release +// or the release after it to support the old-stable release +// this can't be done now as players would lack these from their configs then +#if 0 +float autocvar_cl_damagetext_lifetime = -1; +float autocvar_cl_damagetext_accumulate_lifetime = -1; +#else +AUTOCVAR_SAVE(cl_damagetext_lifetime, float, -1, "Damage text lifetime, edit this if you wish for damage text to disappear before it fades out"); +AUTOCVAR_SAVE(cl_damagetext_accumulate_lifetime, float, -1, "Only update existing damage text when it is younger than this many seconds, negative always updates"); +#endif diff --git a/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc index 8bf951680..c244d170d 100644 --- a/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc @@ -17,72 +17,72 @@ REGISTER_MUTATOR(damagetext, true); bool write_damagetext(entity this, entity client, int sf) { - entity attacker = this.realowner; - entity hit = this.enemy; - int flags = this.dent_net_flags; - int deathtype = this.dent_net_deathtype; - float health = this.dent_net_health; - float armor = this.dent_net_armor; - float potential_damage = this.dent_net_potential; - if (!( - (SV_DAMAGETEXT_ALL()) || - (SV_DAMAGETEXT_PLAYERS() && client == attacker) || - (SV_DAMAGETEXT_SPECTATORS_ONLY() && IS_SPEC(client) && client.enemy == attacker) || - (SV_DAMAGETEXT_SPECTATORS_ONLY() && IS_OBSERVER(client)) - )) return false; + entity attacker = this.realowner; + entity hit = this.enemy; + int flags = this.dent_net_flags; + int deathtype = this.dent_net_deathtype; + float health = this.dent_net_health; + float armor = this.dent_net_armor; + float potential_damage = this.dent_net_potential; + if (!( + (SV_DAMAGETEXT_ALL()) || + (SV_DAMAGETEXT_PLAYERS() && client == attacker) || + (SV_DAMAGETEXT_SPECTATORS_ONLY() && IS_SPEC(client) && client.enemy == attacker) || + (SV_DAMAGETEXT_SPECTATORS_ONLY() && IS_OBSERVER(client)) + )) return false; - WriteHeader(MSG_ENTITY, damagetext); - WriteByte(MSG_ENTITY, etof(hit)); - WriteInt24_t(MSG_ENTITY, deathtype); - WriteByte(MSG_ENTITY, flags); + WriteHeader(MSG_ENTITY, damagetext); + WriteByte(MSG_ENTITY, etof(hit)); + WriteInt24_t(MSG_ENTITY, deathtype); + WriteByte(MSG_ENTITY, flags); - // we need to send a few decimal places to minimize errors when accumulating damage - // sending them multiplied saves bandwidth compared to using WriteCoord, - // however if the multiplied damage would be too much for (signed) short, we send an int24 - if (flags & DTFLAG_BIG_HEALTH) WriteInt24_t(MSG_ENTITY, health * DAMAGETEXT_PRECISION_MULTIPLIER); - else WriteShort(MSG_ENTITY, health * DAMAGETEXT_PRECISION_MULTIPLIER); - if (!(flags & DTFLAG_NO_ARMOR)) - { - if (flags & DTFLAG_BIG_ARMOR) WriteInt24_t(MSG_ENTITY, armor * DAMAGETEXT_PRECISION_MULTIPLIER); - else WriteShort(MSG_ENTITY, armor * DAMAGETEXT_PRECISION_MULTIPLIER); - } - if (!(flags & DTFLAG_NO_POTENTIAL)) - { - if (flags & DTFLAG_BIG_POTENTIAL) WriteInt24_t(MSG_ENTITY, potential_damage * DAMAGETEXT_PRECISION_MULTIPLIER); - else WriteShort(MSG_ENTITY, potential_damage * DAMAGETEXT_PRECISION_MULTIPLIER); - } - return true; + // we need to send a few decimal places to minimize errors when accumulating damage + // sending them multiplied saves bandwidth compared to using WriteCoord, + // however if the multiplied damage would be too much for (signed) short, we send an int24 + if (flags & DTFLAG_BIG_HEALTH) WriteInt24_t(MSG_ENTITY, health * DAMAGETEXT_PRECISION_MULTIPLIER); + else WriteShort(MSG_ENTITY, health * DAMAGETEXT_PRECISION_MULTIPLIER); + if (!(flags & DTFLAG_NO_ARMOR)) + { + if (flags & DTFLAG_BIG_ARMOR) WriteInt24_t(MSG_ENTITY, armor * DAMAGETEXT_PRECISION_MULTIPLIER); + else WriteShort(MSG_ENTITY, armor * DAMAGETEXT_PRECISION_MULTIPLIER); + } + if (!(flags & DTFLAG_NO_POTENTIAL)) + { + if (flags & DTFLAG_BIG_POTENTIAL) WriteInt24_t(MSG_ENTITY, potential_damage * DAMAGETEXT_PRECISION_MULTIPLIER); + else WriteShort(MSG_ENTITY, potential_damage * DAMAGETEXT_PRECISION_MULTIPLIER); + } + return true; } MUTATOR_HOOKFUNCTION(damagetext, PlayerDamaged) { - if (SV_DAMAGETEXT_DISABLED()) return; - entity attacker = M_ARGV(0, entity); - entity hit = M_ARGV(1, entity); if (hit == attacker) return; - float health = M_ARGV(2, float); - float armor = M_ARGV(3, float); - int deathtype = M_ARGV(5, int); - float potential_damage = M_ARGV(6, float); - if(DEATH_WEAPONOF(deathtype) == WEP_VAPORIZER) return; + if (SV_DAMAGETEXT_DISABLED()) return; + entity attacker = M_ARGV(0, entity); + entity hit = M_ARGV(1, entity); if (hit == attacker) return; + float health = M_ARGV(2, float); + float armor = M_ARGV(3, float); + int deathtype = M_ARGV(5, int); + float potential_damage = M_ARGV(6, float); + if(DEATH_WEAPONOF(deathtype) == WEP_VAPORIZER) return; - int flags = 0; - if (SAME_TEAM(hit, attacker)) flags |= DTFLAG_SAMETEAM; - if (health >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_HEALTH; - if (armor >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_ARMOR; - if (potential_damage >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_POTENTIAL; - if (!armor) flags |= DTFLAG_NO_ARMOR; - if (almost_equals_eps(armor + health, potential_damage, 5)) flags |= DTFLAG_NO_POTENTIAL; + int flags = 0; + if (SAME_TEAM(hit, attacker)) flags |= DTFLAG_SAMETEAM; + if (health >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_HEALTH; + if (armor >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_ARMOR; + if (potential_damage >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_POTENTIAL; + if (!armor) flags |= DTFLAG_NO_ARMOR; + if (almost_equals_eps(armor + health, potential_damage, 5)) flags |= DTFLAG_NO_POTENTIAL; - entity net_text = new_pure(net_damagetext); - net_text.realowner = attacker; - net_text.enemy = hit; - net_text.dent_net_flags = flags; - net_text.dent_net_deathtype = deathtype; - net_text.dent_net_health = health; - net_text.dent_net_armor = armor; - net_text.dent_net_potential = potential_damage; + entity net_text = new_pure(net_damagetext); + net_text.realowner = attacker; + net_text.enemy = hit; + net_text.dent_net_flags = flags; + net_text.dent_net_deathtype = deathtype; + net_text.dent_net_health = health; + net_text.dent_net_armor = armor; + net_text.dent_net_potential = potential_damage; - setthink(net_text, SUB_Remove); - net_text.nextthink = (time > 10) ? (time + 0.5) : 10; // allow a buffer from start time for clients to load in + setthink(net_text, SUB_Remove); + net_text.nextthink = (time > 10) ? (time + 0.5) : 10; // allow a buffer from start time for clients to load in - Net_LinkEntity(net_text, false, 0, write_damagetext); + Net_LinkEntity(net_text, false, 0, write_damagetext); } diff --git a/xonotic-client.cfg b/xonotic-client.cfg index 9e9c48f5f..d1b449f47 100644 --- a/xonotic-client.cfg +++ b/xonotic-client.cfg @@ -456,11 +456,13 @@ seta cl_damagetext_size_max 16 "Damage text font size for large damage" seta cl_damagetext_size_max_damage 140 "How much damage is considered large" seta cl_damagetext_alpha_start "1" "Damage text initial alpha" seta cl_damagetext_alpha_lifetime "3" "Damage text lifetime in seconds" +seta cl_damagetext_lifetime "-1" "Damage text lifetime, edit this if you wish for damage text to disappear before it fades out" 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 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_accumulate_lifetime "-1" "Only update existing damage text when it is younger than this many seconds, negative always updates" 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"