From 9e0582042285d472c0223e67b0db3615e767e53c Mon Sep 17 00:00:00 2001 From: terencehill Date: Wed, 14 Feb 2024 01:23:49 +0100 Subject: [PATCH] HUD hit indicator: implement a 2d directional hit indicator around the screen center when a player hits you * for now only 1 indicator at a time * starts to fade out as soon as it appears * the higher the damage, the bigger the indicator size * damage is accumulated * hidden when the attacker is not visible * disabled if chase_active is enabled * shown while spectating a player too * implemented as an extension of damagetext (requires sv_damagetext >= 2) Extra: added cl_damagetext_received (disabled by default) that allows displaying damage text when you receive damage too --- qcsrc/client/view.qc | 47 +++++++++++++++++++ qcsrc/client/view.qh | 9 ++++ .../mutator/damagetext/cl_damagetext.qc | 21 ++++++++- .../mutator/damagetext/cl_damagetext.qh | 1 + .../mutator/damagetext/sv_damagetext.qc | 3 +- .../mutator/waypoints/waypointsprites.qc | 6 +-- .../mutator/waypoints/waypointsprites.qh | 2 +- qcsrc/common/turrets/cl_turrets.qc | 2 +- xonotic-client.cfg | 7 +++ 9 files changed, 90 insertions(+), 8 deletions(-) diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 689f4b4ce..900114588 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -753,6 +753,51 @@ void View_EventChase(entity this) } } +void HitIndicatorUpdate(float dmg, entity attacker) +{ + if (time < HitIndicator_time + 1 && HitIndicator_attacker == attacker) + HitIndicator_damage += dmg; + else + HitIndicator_damage = dmg; + HitIndicator_attacker = attacker; + HitIndicator_time = time; +} + +void HitIndicatorShow() +{ + if (!autocvar_cl_hit_indicator || autocvar_cl_hit_indicator_size <= 0 + || autocvar_chase_active || time > HitIndicator_time + autocvar_cl_hit_indicator_fade_time) + { + return; + } + + entity attacker = HitIndicator_attacker; + if (!attacker) + return; + + // hide indicator if attacker is not visible (check ported from shownames.qc) + traceline(view_origin, attacker.origin, MOVE_NOMONSTERS, attacker); + if (trace_fraction < 1 && (trace_networkentity != attacker.sv_entnum && trace_ent.entnum != attacker.sv_entnum)) + return; + + vector org = project_3d_to_2d(attacker.origin); + vector scr_center = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight; + float radius = autocvar_cl_hit_indicator_radius * min(vid_conwidth, vid_conheight); + vector ofs = normalize(org - scr_center - org.z * eZ) * radius; + float ang = atan2(-ofs.x, -ofs.y); + if (org.z < 0) + { + ang += M_PI; + ofs = -ofs; + } + org = scr_center + ofs; + float alpha = autocvar_cl_hit_indicator_fade_alpha; + alpha *= 1 - (time - HitIndicator_time) / autocvar_cl_hit_indicator_fade_time; + float size = autocvar_cl_hit_indicator_size; + size = map_bound_ranges(HitIndicator_damage, 30, 90, size, size * 2); + drawspritearrow(org, ang, '1 0 0', alpha, size, true); +} + vector damage_blurpostprocess, content_blurpostprocess; void UpdateDamage() @@ -907,6 +952,8 @@ void HUD_Draw(entity this) } } + HitIndicatorShow(); + // crosshair goes VERY LAST UpdateDamage(); HUD_Crosshair(this); diff --git a/qcsrc/client/view.qh b/qcsrc/client/view.qh index cd33ebfb6..0a54db106 100644 --- a/qcsrc/client/view.qh +++ b/qcsrc/client/view.qh @@ -94,6 +94,15 @@ int autocvar_cl_nade_timer; bool autocvar_r_drawviewmodel; vector autocvar_cl_gunoffset; +bool autocvar_cl_hit_indicator = 1; +float autocvar_cl_hit_indicator_fade_alpha = 0.8; +float autocvar_cl_hit_indicator_fade_time = 1.5; +float autocvar_cl_hit_indicator_radius = 0.15; +float autocvar_cl_hit_indicator_size = 1.1; +entity HitIndicator_attacker; +float HitIndicator_damage; +int HitIndicator_time; + void calc_followmodel_ofs(entity view); vector project_3d_to_2d(vector vec); diff --git a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc index 8383626fc..a4db479ec 100644 --- a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qc @@ -219,6 +219,7 @@ float current_alpha(entity damage_text) NET_HANDLE(damagetext, bool isNew) { make_pure(this); + int attacker_server_entity_index = ReadByte(); int server_entity_index = ReadByte(); int deathtype = ReadInt24_t(); int flags = ReadByte(); @@ -236,6 +237,24 @@ NET_HANDLE(damagetext, bool isNew) return = true; if (!isNew) return; + + int attacker_client_entity_index = attacker_server_entity_index - 1; + entity attacker_entcs = entcs_receiver(attacker_client_entity_index); + + int client_entity_index = server_entity_index - 1; + entity entcs = entcs_receiver(client_entity_index); + + if (attacker_entcs && client_entity_index == current_player) + { + if (autocvar_cl_hit_indicator && autocvar_cl_hit_indicator_size > 0 && !autocvar_chase_active) + { + float total = rint((health + armor) / DAMAGETEXT_PRECISION_MULTIPLIER); + HitIndicatorUpdate(total, attacker_entcs); + } + if (!autocvar_cl_damagetext_received) + return; + } + if (autocvar_cl_damagetext == 0) return; if (friendlyfire) { @@ -247,8 +266,6 @@ NET_HANDLE(damagetext, bool isNew) 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); diff --git a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh index f845a97cd..0db741515 100644 --- a/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh +++ b/qcsrc/common/mutators/mutator/damagetext/cl_damagetext.qh @@ -17,6 +17,7 @@ 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_received; bool autocvar_cl_damagetext_2d; vector autocvar_cl_damagetext_2d_pos; diff --git a/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc b/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc index c244d170d..d72bc973a 100644 --- a/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc +++ b/qcsrc/common/mutators/mutator/damagetext/sv_damagetext.qc @@ -26,12 +26,13 @@ bool write_damagetext(entity this, entity client, int sf) float potential_damage = this.dent_net_potential; if (!( (SV_DAMAGETEXT_ALL()) || - (SV_DAMAGETEXT_PLAYERS() && client == attacker) || + (SV_DAMAGETEXT_PLAYERS() && (client == attacker || client == hit || client.enemy == hit)) || (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(attacker)); WriteByte(MSG_ENTITY, etof(hit)); WriteInt24_t(MSG_ENTITY, deathtype); WriteByte(MSG_ENTITY, flags); diff --git a/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc b/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc index f19fc059e..c371778be 100644 --- a/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc +++ b/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc @@ -337,10 +337,10 @@ void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, fl } // returns location of sprite text -vector drawspritearrow(vector o, float ang, vector rgb, float a, float t) +vector drawspritearrow(vector o, float ang, vector rgb, float a, float t, bool no_border) { float size = 9.0 * t; - float border = 1.5 * t; + float border = no_border ? 0 : 1.5 * t; float margin = 4.0 * t; float borderDiag = border * M_SQRT2; @@ -659,7 +659,7 @@ void Draw_WaypointSprite(entity this) SetResourceExplicit(this, RES_HEALTH, -1); } - o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t); + o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t, false); string pic = ""; bool is_text = true; diff --git a/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qh b/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qh index 2274da336..bc0241790 100644 --- a/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qh +++ b/qcsrc/common/mutators/mutator/waypoints/waypointsprites.qh @@ -102,7 +102,7 @@ void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, f void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float height, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f); // returns location of sprite text -vector drawspritearrow(vector o, float ang, vector rgb, float a, float t); +vector drawspritearrow(vector o, float ang, vector rgb, float a, float t, bool no_border); // returns location of sprite healthbar vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str); diff --git a/qcsrc/common/turrets/cl_turrets.qc b/qcsrc/common/turrets/cl_turrets.qc index 219b23318..e480dd49c 100644 --- a/qcsrc/common/turrets/cl_turrets.qc +++ b/qcsrc/common/turrets/cl_turrets.qc @@ -181,7 +181,7 @@ void turret_draw2d(entity this) if(draw_healthbar || draw_text) // make sure it's actually being drawn { - o = drawspritearrow(o, M_PI, rgb, a, SPRITE_ARROW_SCALE * t); + o = drawspritearrow(o, M_PI, rgb, a, SPRITE_ARROW_SCALE * t, false); } if(draw_text) { diff --git a/xonotic-client.cfg b/xonotic-client.cfg index d1b449f47..3815bddda 100644 --- a/xonotic-client.cfg +++ b/xonotic-client.cfg @@ -201,6 +201,12 @@ seta cl_autotaunt 0 "automatically taunt enemies when fragging them" seta cl_voice_directional 1 "0 = all voices are non-directional, 1 = all voices are directional, 2 = only taunts are directional" seta cl_voice_directional_taunt_attenuation 0.5 "this defines the distance from which taunts can be heard" +seta cl_hit_indicator 1 "show a 2d directional indicator around the screen center when a player hits you" +seta cl_hit_indicator_fade_time 1.5 "how long hit indicator takes to fade away" +seta cl_hit_indicator_fade_alpha 0.8 "initial hit indicator alpha" +seta cl_hit_indicator_radius 0.15 "show the directional indicator at this percentage of the screen from the center" +seta cl_hit_indicator_size 1.1 "hit indicator size" + seta cl_hitsound 1 "play a hit notifier sound when you have hit an enemy, 1: same pitch 2: decrease pitch with more damage 3: increase pitch with more damage" set cl_hitsound_antispam_time 0.05 "don't play the hitsound more often than this" seta cl_hitsound_min_pitch 0.75 "minimum pitch of hit sound" @@ -465,6 +471,7 @@ seta cl_damagetext_accumulate_alpha_rel "0.65" "Only update existing damage text 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" +seta cl_damagetext_received "0" "Draw damage received too" seta cl_damagetext_2d 1 "Show damagetext in 2D coordinates if the enemy's location is not known" seta cl_damagetext_2d_pos "0.47 0.53 0" "2D damage text initial position (X and Y between 0 and 1)" -- 2.39.2