Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_damagetext.qc
Go to the documentation of this file.
1#include "sv_damagetext.qh"
2
3AUTOCVAR(sv_damagetext, int, 2, "\\\"0\\\" = disable damage text, \\\"1\\\" = show damage text to spectators, \\\"2\\\" = also to the attacker, \\\"3\\\" = show damage text to all players");
4
5REGISTER_MUTATOR(damagetext, true);
6
7#define SV_DAMAGETEXT_DISABLED() (autocvar_sv_damagetext <= 0)
8#define SV_DAMAGETEXT_SPECTATORS_ONLY() (autocvar_sv_damagetext >= 1)
9#define SV_DAMAGETEXT_PLAYERS() (autocvar_sv_damagetext >= 2)
10#define SV_DAMAGETEXT_ALL() (autocvar_sv_damagetext >= 3)
11
17
18const int BITS_PER_INT = 24;
19const int DENT_ATTACKERS_SIZE = 11; // ceil(255 / BITS_PER_INT)
20// where 255 is the max number of clients that the engine can support
22
23bool write_damagetext(entity this, entity client, int sf)
24{
25 entity attacker = this.realowner;
26 entity hit = this.enemy;
27 int flags = this.dent_net_flags;
28 int deathtype = this.dent_net_deathtype;
29 float health = this.dent_net_health;
30 float armor = this.dent_net_armor;
31 float potential_damage = this.dent_net_potential;
32 if (!(
34 (SV_DAMAGETEXT_PLAYERS() && client == attacker) ||
35 (SV_DAMAGETEXT_SPECTATORS_ONLY() && IS_SPEC(client) && client.enemy == attacker) ||
37 )) return false;
38
39 WriteHeader(MSG_ENTITY, damagetext);
41 WriteInt24_t(MSG_ENTITY, deathtype);
43
44 // we need to send a few decimal places to minimize errors when accumulating damage
45 // sending them multiplied saves bandwidth compared to using WriteCoord,
46 // however if the multiplied damage would be too much for (signed) short, we send an int24
49 if (!(flags & DTFLAG_NO_ARMOR))
50 {
53 }
55 {
56 if (flags & DTFLAG_BIG_POTENTIAL) WriteInt24_t(MSG_ENTITY, potential_damage * DAMAGETEXT_PRECISION_MULTIPLIER);
58 }
59 return true;
60}
61
62MUTATOR_HOOKFUNCTION(damagetext, PlayerDamaged) {
63 if (SV_DAMAGETEXT_DISABLED()) return;
64 entity attacker = M_ARGV(0, entity);
65 entity hit = M_ARGV(1, entity); if (hit == attacker) return;
66 float health = M_ARGV(2, float);
67 float armor = M_ARGV(3, float);
68 int deathtype = M_ARGV(5, int);
69 float potential_damage = M_ARGV(6, float);
70 if (potential_damage == 0)
71 return;
72 if (MUTATOR_IS_ENABLED(mutator_instagib) && DEATH_WEAPONOF(deathtype) == WEP_VAPORIZER)
73 return;
74
75 static entity net_text_prev;
76 static float net_damagetext_prev_time;
77
78 bool multiple = (net_damagetext_prev_time == time && net_text_prev && !wasfreed(net_text_prev)
79 && net_text_prev.realowner == attacker && net_text_prev.enemy == hit
80 && net_text_prev.dent_net_deathtype == deathtype);
81
82 if (multiple)
83 {
84 // damage of multiple projectiles hitting player at the same time, e.g. shotgun
85 // is accumulated on the same damagetext entity
86 health += net_text_prev.dent_net_health;
87 armor += net_text_prev.dent_net_armor;
88 potential_damage += net_text_prev.dent_net_potential;
89 }
90
91 int flags = 0;
92 if (SAME_TEAM(hit, attacker)) flags |= DTFLAG_SAMETEAM;
95 if (potential_damage >= DAMAGETEXT_SHORT_LIMIT) flags |= DTFLAG_BIG_POTENTIAL;
96 if (!armor) flags |= DTFLAG_NO_ARMOR;
97 if (almost_equals_eps(armor + health, potential_damage, 5)) flags |= DTFLAG_NO_POTENTIAL;
98
99 if (multiple)
100 {
101 net_text_prev.dent_net_flags = flags;
102 net_text_prev.dent_net_health = health;
103 net_text_prev.dent_net_armor = armor;
104 net_text_prev.dent_net_potential = potential_damage;
105 return;
106 }
107 else if (attacker)
108 {
109 int attacker_id = etof(attacker) - 1;
110 int idx = floor(attacker_id / BITS_PER_INT);
111 int bit = attacker_id % BITS_PER_INT;
112 if (!(hit.dent_attackers[idx] & BIT(bit)))
113 {
114 // player is hit for the first time after respawn by this attacker
115 hit.dent_attackers[idx] |= BIT(bit);
116 flags |= DTFLAG_STOP_ACCUMULATION; // forcedly stop client-side damage accumulation
117 }
118 }
119
120 entity net_text = new_pure(net_damagetext);
121 net_text.realowner = attacker;
122 net_text.enemy = hit;
123 net_text.dent_net_flags = flags;
124 net_text.dent_net_deathtype = deathtype;
125 net_text.dent_net_health = health;
126 net_text.dent_net_armor = armor;
127 net_text.dent_net_potential = potential_damage;
128
129 net_text_prev = net_text;
130 net_damagetext_prev_time = time;
131
132 setthink(net_text, SUB_Remove);
133 net_text.nextthink = (time > 10) ? (time + 0.5) : 10; // allow a buffer from start time for clients to load in
134
135 Net_LinkEntity(net_text, false, 0, write_damagetext);
136}
137
139{
140 entity player = M_ARGV(0, entity);
141 int player_id = etof(player) - 1;
142 int idx = floor(player_id / BITS_PER_INT);
143 int bit = player_id % BITS_PER_INT;
144 // remove from attacker list of all players
146 it.dent_attackers[idx] &= ~BIT(bit);
147 });
148}
149
150MUTATOR_HOOKFUNCTION(damagetext, PlayerSpawn)
151{
152 entity player = M_ARGV(0, entity);
153 for (int i = 0; i < DENT_ATTACKERS_SIZE; ++i)
154 player.dent_attackers[i] = 0;
155}
#define MUTATOR_IS_ENABLED(this)
Definition base.qh:193
#define REGISTER_MUTATOR(...)
Definition base.qh:295
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_PLAYER(s)
Definition player.qh:243
float flags
float time
#define AUTOCVAR(...)
Definition cvar.qh:161
const int DTFLAG_BIG_HEALTH
Definition damagetext.qh:7
const int DTFLAG_NO_POTENTIAL
Definition damagetext.qh:11
const int DTFLAG_STOP_ACCUMULATION
Definition damagetext.qh:12
const int DTFLAG_BIG_ARMOR
Definition damagetext.qh:8
const int DTFLAG_SAMETEAM
Definition damagetext.qh:6
const int DTFLAG_BIG_POTENTIAL
Definition damagetext.qh:9
#define DAMAGETEXT_SHORT_LIMIT
Definition damagetext.qh:4
#define DAMAGETEXT_PRECISION_MULTIPLIER
Definition damagetext.qh:3
const int DTFLAG_NO_ARMOR
Definition damagetext.qh:10
#define DEATH_WEAPONOF(t)
Definition all.qh:45
void SUB_Remove(entity this)
Remove entity.
Definition defer.qh:13
#define ClientDisconnect
Definition _all.inc:242
const int MSG_ENTITY
Definition net.qh:115
#define WriteHeader(to, id)
Definition net.qh:221
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:123
ERASEABLE float almost_equals_eps(float a, float b, float times_eps)
Definition math.qh:226
void WriteShort(float data, float dest, float desto)
void WriteByte(float data, float dest, float desto)
float floor(float f)
#define etof(e)
Definition misc.qh:25
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
float health
Legacy fields for the resources. To be removed.
Definition resources.qh:9
#define setthink(e, f)
entity enemy
Definition sv_ctf.qh:153
#define SV_DAMAGETEXT_DISABLED()
float dent_net_potential
int dent_net_flags
float dent_net_health
float dent_net_armor
#define SV_DAMAGETEXT_PLAYERS()
const int BITS_PER_INT
bool write_damagetext(entity this, entity client, int sf)
#define SV_DAMAGETEXT_ALL()
const int DENT_ATTACKERS_SIZE
int dent_attackers[DENT_ATTACKERS_SIZE]
float dent_net_deathtype
#define SV_DAMAGETEXT_SPECTATORS_ONLY()
#define SAME_TEAM(a, b)
Definition teams.qh:241
entity realowner
#define IS_OBSERVER(v)
Definition utils.qh:11
#define IS_SPEC(v)
Definition utils.qh:10
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50