4 AUTOCVAR_SAVE(cl_damagetext,
bool,
true,
"Draw damage dealt where you hit the enemy");
5 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");
6 AUTOCVAR_SAVE(cl_damagetext_format_verbose,
bool,
false,
"{health} shows {potential_health} too when they differ; {total} shows {potential} too when they differ");
7 AUTOCVAR_SAVE(cl_damagetext_format_hide_redundant,
bool,
false,
"hide {armor} if 0; hide {potential} and {potential_health} when same as actual");
11 if (
strstrofs(autocvar_cl_damagetext_format,
"{", 0) < 0) {
12 localcmd(
"\nseta cl_damagetext 1\n");
13 localcmd(
"\nseta cl_damagetext_format -{total}\n");
17 AUTOCVAR_SAVE(cl_damagetext_color_per_weapon,
bool,
false,
"Damage text uses weapon color");
18 AUTOCVAR_SAVE(cl_damagetext_size_min,
float, 10,
"Damage text font size for small damage");
19 AUTOCVAR_SAVE(cl_damagetext_size_min_damage,
float, 25,
"How much damage is considered small");
20 AUTOCVAR_SAVE(cl_damagetext_size_max,
float, 16,
"Damage text font size for large damage");
21 AUTOCVAR_SAVE(cl_damagetext_size_max_damage,
float, 140,
"How much damage is considered large");
22 AUTOCVAR_SAVE(cl_damagetext_alpha_start,
float, 1,
"Damage text initial alpha");
23 AUTOCVAR_SAVE(cl_damagetext_alpha_lifetime,
float, 3,
"Damage text lifetime in seconds");
24 AUTOCVAR_SAVE(cl_damagetext_velocity_screen,
vector,
'0 0 0',
"Damage text move direction (screen coordinates)");
25 AUTOCVAR_SAVE(cl_damagetext_velocity_world,
vector,
'0 0 20',
"Damage text move direction (world coordinates relative to player's view)");
26 AUTOCVAR_SAVE(cl_damagetext_offset_screen,
vector,
'0 -45 0',
"Damage text offset (screen coordinates)");
27 AUTOCVAR_SAVE(cl_damagetext_offset_world,
vector,
'0 0 0',
"Damage text offset (world coordinates relative to player's view)");
28 AUTOCVAR_SAVE(cl_damagetext_accumulate_range,
float, 30,
"Damage text spawned within this range is accumulated");
29 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");
30 AUTOCVAR_SAVE(cl_damagetext_friendlyfire,
int, 1,
"0: never show for friendly fire, 1: when more than 0 damage, 2: always");
31 AUTOCVAR_SAVE(cl_damagetext_friendlyfire_color,
vector,
'1 0 0',
"Damage text color for friendlyfire");
33 AUTOCVAR_SAVE(cl_damagetext_2d,
bool,
true,
"Show damagetext in 2D coordinates if the enemy's location is not known");
34 AUTOCVAR_SAVE(cl_damagetext_2d_pos,
vector,
'0.47 0.53 0',
"2D damage text initial position (X and Y between 0 and 1)");
35 AUTOCVAR_SAVE(cl_damagetext_2d_alpha_start,
float, 1,
"2D damage text initial alpha");
36 AUTOCVAR_SAVE(cl_damagetext_2d_alpha_lifetime,
float, 1.3,
"2D damage text lifetime (alpha fading) in seconds");
37 AUTOCVAR_SAVE(cl_damagetext_2d_size_lifetime,
float, 3,
"2D damage text lifetime (size shrinking) in seconds");
38 AUTOCVAR_SAVE(cl_damagetext_2d_velocity,
vector,
'-25 0 0',
"2D damage text move direction (screen coordinates)");
39 AUTOCVAR_SAVE(cl_damagetext_2d_overlap_offset,
vector,
'0 -15 0',
"Offset 2D damage text by this much to prevent overlapping (screen coordinates)");
40 AUTOCVAR_SAVE(cl_damagetext_2d_close_range,
float, 125,
"Always use 2D damagetext for hits closer that this");
41 AUTOCVAR_SAVE(cl_damagetext_2d_out_of_view,
bool,
true,
"Always use 2D damagetext for hits that occurred off-screen");
45 ATTRIB(DamageText, m_color_friendlyfire, vector, autocvar_cl_damagetext_friendlyfire_color);
46 ATTRIB(DamageText, m_size,
float, autocvar_cl_damagetext_size_min);
47 ATTRIB(DamageText,
alpha,
float, autocvar_cl_damagetext_alpha_start);
49 ATTRIB(DamageText, m_shrink_rate,
float, 0);
52 ATTRIB(DamageText, m_healthdamage,
int, 0);
53 ATTRIB(DamageText, m_armordamage,
int, 0);
54 ATTRIB(DamageText, m_potential_damage,
int, 0);
55 ATTRIB(DamageText, m_deathtype,
int, 0);
56 ATTRIB(DamageText, hit_time,
float, 0);
58 ATTRIB(DamageText, m_screen_coords,
bool, false);
63 void DamageText_draw2d(DamageText
this) {
64 float since_hit =
time - this.hit_time;
66 float size = this.m_size - since_hit * this.m_shrink_rate * this.m_size;
67 float alpha_ = this.alpha - since_hit * this.
fade_rate;
68 if (alpha_ <= 0 || size <= 0) {
74 if (this.m_screen_coords) {
75 screen_pos = this.
origin + since_hit * autocvar_cl_damagetext_2d_velocity;
77 vector forward, right, up;
79 vector world_offset = since_hit * autocvar_cl_damagetext_velocity_world + autocvar_cl_damagetext_offset_world;
80 vector world_pos = this.
origin + world_offset.x * forward + world_offset.y * right + world_offset.z * up;
81 screen_pos =
project_3d_to_2d(world_pos) + since_hit * autocvar_cl_damagetext_velocity_screen + autocvar_cl_damagetext_offset_screen;
83 screen_pos.y += size / 2;
85 if (screen_pos.z >= 0) {
88 if (this.m_friendlyfire) {
89 rgb = this.m_color_friendlyfire;
93 if (autocvar_cl_damagetext_color_per_weapon) {
95 if (w != WEP_Null) rgb = w.
wpcolor;
99 drawfontscale = (size / autocvar_cl_damagetext_size_max) *
'1 1 0';
101 drawcolorcodedstring2_builtin(screen_pos, this.text, autocvar_cl_damagetext_size_max *
'1 1 0', rgb, alpha_,
DRAWFLAG_NORMAL);
108 this.m_healthdamage = _health;
109 this.m_armordamage = _armor;
110 this.m_potential_damage = _potential_damage;
111 this.m_deathtype = _deathtype;
113 if (this.m_screen_coords) {
114 this.
alpha = autocvar_cl_damagetext_2d_alpha_start;
116 this.
alpha = autocvar_cl_damagetext_alpha_start;
118 this.hit_time =
time;
126 bool redundant =
almost_equals_eps(this.m_healthdamage + this.m_armordamage, this.m_potential_damage, 5);
128 string s = autocvar_cl_damagetext_format;
129 s = strreplace(
"{armor}", (
130 (this.m_armordamage == 0 && autocvar_cl_damagetext_format_hide_redundant)
132 : sprintf(
"%d", armor)
134 s = strreplace(
"{potential}", (
135 (redundant && autocvar_cl_damagetext_format_hide_redundant)
137 : sprintf(
"%d", potential)
139 s = strreplace(
"{potential_health}", (
140 (redundant && autocvar_cl_damagetext_format_hide_redundant)
142 : sprintf(
"%d", potential_health)
145 s = strreplace(
"{health}", (
146 (health == potential_health || !autocvar_cl_damagetext_format_verbose)
147 ? sprintf(
"%d", health)
148 : sprintf(
"%d (%d)", health, potential_health)
150 s = strreplace(
"{total}", (
151 (total == potential || !autocvar_cl_damagetext_format_verbose)
152 ? sprintf(
"%d", total)
153 : sprintf(
"%d (%d)", total, potential)
160 if (opening_pos == -1)
break;
161 int closing_pos =
strstrofs(s,
"}", opening_pos);
162 if (closing_pos == -1 || closing_pos <= opening_pos)
break;
172 autocvar_cl_damagetext_size_min_damage, autocvar_cl_damagetext_size_max_damage,
173 autocvar_cl_damagetext_size_min, autocvar_cl_damagetext_size_max);
176 CONSTRUCTOR(
DamageText,
int _group,
vector _origin,
bool _screen_coords,
int _health,
int _armor,
int _potential_damage,
int _deathtype,
bool _friendlyfire) {
178 this.m_group = _group;
179 this.m_friendlyfire = _friendlyfire;
180 this.m_screen_coords = _screen_coords;
181 if (_screen_coords) {
182 this.
fade_rate = 1 / autocvar_cl_damagetext_2d_alpha_lifetime;
183 this.m_shrink_rate = 1 / autocvar_cl_damagetext_2d_size_lifetime;
185 this.
fade_rate = 1 / autocvar_cl_damagetext_alpha_lifetime;
186 this.m_shrink_rate = 0;
188 DamageText_update(
this, _origin, _health, _armor, _potential_damage, _deathtype);
194 if (
this == DamageText_screen_first) {
196 DamageText_screen_first =
NULL;
197 DamageText_screen_count = 0;
204 return damage_text.alpha - (
time - damage_text.hit_time) * damage_text.fade_rate;
210 int server_entity_index = ReadByte();
211 int deathtype = ReadInt24_t();
212 int flags = ReadByte();
215 int health, armor, potential_damage;
217 else health = ReadShort();
220 else armor = ReadShort();
223 else potential_damage = ReadShort();
227 if (autocvar_cl_damagetext == 0)
return;
229 if (autocvar_cl_damagetext_friendlyfire == 0)
return;
230 if (autocvar_cl_damagetext_friendlyfire == 1 && health == 0 && armor == 0)
return;
233 int client_entity_index = server_entity_index - 1;
234 entity entcs = entcs_receiver(client_entity_index);
236 bool can_use_3d = entcs && entcs.has_origin;
237 bool too_close =
vdist(entcs.origin -
view_origin, <, autocvar_cl_damagetext_2d_close_range);
239 bool prefer_2d =
spectatee_status != -1 && autocvar_cl_damagetext_2d && (too_close || prefer_in_view);
241 if (can_use_3d && !prefer_2d) {
244 for (
entity e = findradius(entcs.origin,
max(autocvar_cl_damagetext_accumulate_range, 1)); e; e = e.chain) {
245 if (e.instanceOfDamageText
246 && !e.m_screen_coords
247 && e.m_group == server_entity_index
248 &&
current_alpha(e) > autocvar_cl_damagetext_accumulate_alpha_rel * autocvar_cl_damagetext_alpha_start) {
249 DamageText_update(e, entcs.origin, e.m_healthdamage + health, e.m_armordamage + armor, e.m_potential_damage + potential_damage, deathtype);
253 make_impure(
NEW(
DamageText, server_entity_index, entcs.origin,
false, health, armor, potential_damage, deathtype, friendlyfire));
257 IL_EACH(
g_drawables_2d, it.instanceOfDamageText && it.m_screen_coords && it.m_group == server_entity_index, {
258 DamageText_update(it, screen_pos, it.m_healthdamage + health, it.m_armordamage + armor, it.m_potential_damage + potential_damage, deathtype);
263 if (DamageText_screen_first ==
NULL) {
264 DamageText dt =
NEW(
DamageText, server_entity_index, screen_pos,
true, health, armor, potential_damage, deathtype, friendlyfire);
266 DamageText_screen_first = dt;
267 DamageText_screen_count = 1;
269 screen_pos += autocvar_cl_damagetext_2d_overlap_offset * DamageText_screen_count;
270 DamageText_screen_count++;
271 make_impure(
NEW(
DamageText, server_entity_index, screen_pos,
true, health, armor, potential_damage, deathtype, friendlyfire));
#define DESTRUCTOR(cname)
#define IL_EACH(this, cond, body)
NET_HANDLE(damagetext, bool isNew)
void DamageText_update(DamageText this, vector _origin, int _health, int _armor, int _potential_damage, int _deathtype)
vector project_3d_to_2d(vector vec)
float(entity, float) PlayerPhysplug
bool projected_on_screen(vector screen_pos)
vector wpcolor
M: color : waypointsprite color.
const int DTFLAG_NO_POTENTIAL
#define CONSTRUCT(cname,...)
const int DTFLAG_NO_ARMOR
STATIC_INIT(DamageText_LegacyFormat)
#define DAMAGETEXT_PRECISION_MULTIPLIER
#define DEATH_WEAPONOF(t)
ERASEABLE string substring_range(string s, float b, float e)
ERASEABLE float map_bound_ranges(float value, float src_min, float src_max, float dest_min, float dest_max)
Same as map_ranges except that values outside the source range are clamped to min or max...
const int DTFLAG_BIG_POTENTIAL
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
float current_alpha(entity damage_text)
const float DRAWFLAG_NORMAL
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define STATIC_ATTRIB(cname, name, type, val)
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
const int DTFLAG_SAMETEAM
AUTOCVAR_SAVE(cl_damagetext, bool, true, "Draw damage dealt where you hit the enemy")
const int DTFLAG_BIG_HEALTH
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
ERASEABLE float almost_equals_eps(float a, float b, float times_eps)
#define CONSTRUCTOR(cname,...)
const int DTFLAG_BIG_ARMOR
IntrusiveList g_drawables_2d