Xonotic
ent_cs.qc
Go to the documentation of this file.
1 #include "ent_cs.qh"
2 
3 #if defined(CSQC)
4  #include <common/gamemodes/_mod.qh>
6 #elif defined(MENUQC)
7 #elif defined(SVQC)
8  #include <common/gamemodes/_mod.qh>
11 #endif
12 
13 REGISTRY(EntCSProps, BITS(16) - 1)
14 REGISTER_REGISTRY(EntCSProps)
15 REGISTRY_SORT(EntCSProps)
16 REGISTRY_CHECK(EntCSProps)
17 
18 REGISTRY_DEFINE_GET(EntCSProps, NULL)
19 STATIC_INIT(EntCSProps_renumber) { FOREACH(EntCSProps, true, it.m_id = i); }
20 
21 // these entcs_props ids need to be referenced directly
25 STATIC_INIT(EntCSProps_setglobalids)
26 {
27  FOREACH(EntCSProps, true, {
28  if (it.registered_id == "ENTCS_PROP_ENTNUM") ENTCS_PROP_ENTNUM_id = it.m_id;
29  if (it.registered_id == "ENTCS_PROP_ORIGIN") ENTCS_PROP_ORIGIN_id = it.m_id;
30  if (it.registered_id == "ENTCS_PROP_HEALTH") ENTCS_PROP_HEALTH_id = it.m_id;
31  });
32 }
33 
34 #ifdef SVQC
35 // Force an origin update, for player sounds
36 void entcs_force_origin(entity player)
37 {
38  CS(player).entcs.m_forceupdate = BIT(ENTCS_PROP_ORIGIN_id);
39 }
40 #endif
41 
42 .bool m_public;
43 .bool(entity ent, entity player) m_check;
44 .void(entity ent, entity player) m_set;
45 .void(int chan, entity ent) m_send;
46 .void(entity ent) m_receive;
47 
48 #ifdef SVQC
49 #define _ENTCS_PROP(id, ispublic, checkprop, checkprop_pl, setprop, svsend, clreceive) \
50  void id##_set(entity ent, entity player) { setprop(ent.(checkprop), player.(checkprop_pl)); } \
51  void id##_send(int chan, entity ent) { LAMBDA(svsend); } \
52  REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
53  this.m_public = ispublic; \
54  this.m_check = id##_check; \
55  this.m_set = id##_set; \
56  this.m_send = id##_send; \
57  }
58 
59 #define ENTCS_PROP(id, ispublic, checkprop, checkprop_pl, setprop, svsend, clreceive) \
60  bool id##_check(entity ent, entity player) { return (ent.(checkprop) != player.(checkprop_pl)); } \
61  _ENTCS_PROP(id, ispublic, checkprop, checkprop_pl, setprop, svsend, clreceive)
62 
63 #define ENTCS_PROP_CODED(id, ispublic, checkprop, checkprop_pl, setprop, decfactor, svsend, clreceive) \
64  bool id##_check(entity ent, entity player) { \
65  return (floor(ent.(checkprop)) / decfactor != floor(player.(checkprop_pl)) / decfactor); \
66  } \
67  _ENTCS_PROP(id, ispublic, checkprop, checkprop_pl, setprop, svsend, clreceive)
68 
69 #elif defined(CSQC)
70 #define ENTCS_PROP(id, ispublic, checkprop, checkprop_pl, setprop, svsend, clreceive) \
71  void id##_receive(entity ent) { LAMBDA(clreceive); } \
72  REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
73  this.m_public = ispublic; \
74  this.m_receive = id##_receive; \
75  }
76 
77 #define ENTCS_PROP_CODED(id, ispublic, checkprop, checkprop_pl, setprop, decfactor, svsend, clreceive) \
78  ENTCS_PROP(id, ispublic, checkprop, checkprop_pl, setprop, svsend, clreceive)
79 #endif
80 
81 #ifdef SVQC
82 #define ENTCS_PROP_RESOURCE(id, ispublic, checkprop, setprop, decfactor, svsend, clreceive) \
83  bool id##_check(entity ent, entity player) { \
84  return (floor(GetResource(ent, checkprop) / decfactor) != floor(GetResource(player, checkprop) / decfactor)); \
85  } \
86  void id##_set(entity ent, entity player) { SetResourceExplicit(ent, checkprop, GetResource(player, checkprop)); } \
87  void id##_send(int chan, entity ent) { LAMBDA(svsend); } \
88  REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
89  this.m_public = ispublic; \
90  this.m_check = id##_check; \
91  this.m_set = id##_set; \
92  this.m_send = id##_send; \
93  }
94 #elif defined(CSQC)
95 #define ENTCS_PROP_RESOURCE(id, ispublic, checkprop, setprop, decfactor, svsend, clreceive) \
96  void id##_receive(entity ent) { LAMBDA(clreceive); } \
97  REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
98  this.m_public = ispublic; \
99  this.m_receive = id##_receive; \
100  }
101 #endif
102 
103 #define ENTCS_SET_NORMAL(var, x) MACRO_BEGIN \
104  var = x; \
105 MACRO_END
106 
108 #define ENTCS_SET_MUTABLE_STRING(var, x) MACRO_BEGIN \
109  strcpy(var, x); \
110 MACRO_END
111 
112 ENTCS_PROP(ENTNUM, false, sv_entnum, sv_entnum, ENTCS_SET_NORMAL, {}, {}) /* sentinel */
113 
115  { WriteVector(chan, ent.origin); },
116  { ent.has_sv_origin = true; vector v = ReadVector(); setorigin(ent, v); })
117 
118 #define DEC_FACTOR (360 / 32)
119 ENTCS_PROP_CODED(ANGLES, false, angles_y, angles_y, ENTCS_SET_NORMAL, DEC_FACTOR,
120  { WriteByte(chan, ent.angles.y / DEC_FACTOR); },
121  { vector v = '0 0 0'; v.y = ReadByte() * DEC_FACTOR; ent.angles = v; })
122 #undef DEC_FACTOR
123 
124 // FIXME: use a better scale?
125 #define DEC_FACTOR 10
127  { WriteByte(chan, bound(0, GetResource(ent, RES_HEALTH) / DEC_FACTOR, 255)); },
128  { ent.healthvalue = ReadByte() * DEC_FACTOR; })
129 
130 ENTCS_PROP_RESOURCE(ARMOR, false, RES_ARMOR, ENTCS_SET_NORMAL, DEC_FACTOR,
131  { WriteByte(chan, bound(0, GetResource(ent, RES_ARMOR) / DEC_FACTOR, 255)); },
132  { SetResourceExplicit(ent, RES_ARMOR, ReadByte() * DEC_FACTOR); })
133 #undef DEC_FACTOR
134 
136  { WriteString(chan, ent.netname); },
137  { strcpy(ent.netname, ReadString()); })
138 
140  { WriteString(chan, ent.model); },
141  { strcpy(ent.model, ReadString()); })
142 
144  { WriteByte(chan, ent.skin); },
145  { ent.skin = ReadByte(); })
146 
148  { WriteByte(chan, ent.clientcolors); },
149  { ent.colormap = ReadByte(); })
150 
152  { WriteShort(chan, ent.frags); },
153  { ent.frags = ReadShort(); })
154 
155 // use sv_solid to avoid changing solidity state of entcs entities
157  { WriteByte(chan, ent.sv_solid); },
158  { ent.sv_solid = ReadByte(); })
159 
160 #ifdef SVQC
161 
162  int ENTCS_PUBLICMASK = 0, ENTCS_PRIVATEMASK = 0;
163  STATIC_INIT(ENTCS_PUBLICMASK)
164  {
165  FOREACH(EntCSProps, true,
166  {
167  if (it.m_public)
168  ENTCS_PUBLICMASK |= BIT(it.m_id);
169  else
170  ENTCS_PRIVATEMASK |= BIT(it.m_id);
171  });
172  }
173 
174  void entcs_update_players(entity player)
175  {
176  FOREACH_CLIENT(it != player && IS_PLAYER(it),
177  {
178  CS(it).entcs.SendFlags |= ENTCS_PRIVATEMASK;
179  });
180  }
181 
182  bool _entcs_send(entity this, entity to, int sf, int chan)
183  {
184  entity player = this.owner;
185  sf |= BIT(ENTCS_PROP_ENTNUM_id); // assume private
186  do {
187  if (IS_PLAYER(player))
188  {
189  if (radar_showenemies) break;
190  if (SAME_TEAM(to, player)) break;
191  if (!(IS_PLAYER(to) || INGAME(to))) break;
192  }
193  sf &= ENTCS_PUBLICMASK; // no private updates
194  } while (0);
195 
196  sf |= this.m_forceupdate;
197  this.m_forceupdate = 0;
198  if (chan == MSG_ENTITY)
199  WriteHeader(chan, ENT_CLIENT_ENTCS);
200  else
201  WriteHeader(chan, CLIENT_ENTCS);
202  WriteByte(chan, etof(player) - 1);
203  WriteShort(chan, sf);
204  FOREACH(EntCSProps, sf & BIT(it.m_id),
205  {
206  it.m_send(chan, this);
207  });
208  return true;
209  }
210 
211  bool entcs_send(entity this, entity to, int sf)
212  {
213  return _entcs_send(this, to, sf, MSG_ENTITY);
214  }
215 
216  void entcs_think(entity this)
217  {
218  this.nextthink = time + 0.033333333333; // TODO: increase this to like 0.15 once the client can do smoothing
219  entity player = this.owner;
220  FOREACH(EntCSProps, it.m_check(this, player),
221  {
222  it.m_set(this, player);
223  this.SendFlags |= BIT(it.m_id);
224  });
225 
227  {
228  // health is set to special values after the game ends, ignore any change
229  this.SendFlags &= ~BIT(ENTCS_PROP_HEALTH_id);
230  }
231 
232  // always send origin of players even if they stand still otherwise
233  // if a teammate isn't in my pvs and his health (or view angle or name
234  // etc...) changes then his tag disappears
235  if (IS_PLAYER(this.owner))
236  this.SendFlags |= BIT(ENTCS_PROP_ORIGIN_id);
237 
238  // not needed, origin is just data to be sent
239  //setorigin(this, this.origin); // relink
240  }
241 
242  void entcs_attach(entity player)
243  {
244  entity e = CS(player).entcs = new_pure(entcs_sender);
245  e.owner = player;
246  setthink(e, entcs_think);
247  e.nextthink = time;
248  Net_LinkEntity(e, false, 0, entcs_send);
249  // NOTE: the following code block has been disabled as a workaround for https://gitlab.com/xonotic/xonotic-data.pk3dir/-/issues/1824
250 #if 0
251  if (!IS_REAL_CLIENT(player)) return;
252  FOREACH_CLIENT(true, {
253  assert(CS(it).entcs);
254  _entcs_send(CS(it).entcs, msg_entity = player, BITS(23), MSG_ONE);
255  });
256 #endif
257  }
258 
259  void entcs_detach(entity player)
260  {
261  if (!CS(player).entcs) return;
262  delete(CS(player).entcs);
263  CS(player).entcs = NULL;
264  }
265 
266 #endif
267 
268 #ifdef CSQC
269 
270  void Ent_RemoveEntCS(entity this)
271  {
272  int n = this.sv_entnum;
273  entity e = entcs_receiver(n);
274  entcs_receiver(n, NULL);
275  strfree(e.netname);
276  strfree(e.model);
277  if (e != this) delete(e);
278  }
279 
280  void entcs_think(entity this)
281  {
283  if (e == NULL)
284  {
285  // player model is NOT in client's PVS
286  InterpolateOrigin_Do(this);
287  this.has_origin = this.has_sv_origin;
288  return;
289  }
290  this.has_origin = true;
291  // when a player model is in client's PVS we use its origin directly
292  // (entcs networked origin is overriden)
293  this.origin = e.origin;
295  setorigin(this, this.origin);
296  // `cl_forceplayermodels 1` sounds will be wrong until the player has been in the PVS, but so be it
297  if (this.model != e.model)
298  {
299  strcpy(this.model, e.model);
300  }
301  }
302 
303  bool ReadEntcs(entity this)
304  {
305  int n = ReadByte();
306  entity e = entcs_receiver(n);
307  if (e == NULL)
308  {
309  if (!this)
310  // initial = temp
311  e = new_pure(ENT_CLIENT_ENTCS);
312  else
313  // initial = linked
314  e = this;
315  setthink(e, entcs_think);
316  entcs_receiver(n, e);
317  }
318  else if (e != this && this)
319  {
320  // upgrade to linked
321  delete(e);
322  e = this;
323  setthink(e, entcs_think);
324  entcs_receiver(n, e);
325  }
326 
328  e.sv_entnum = n;
329  int sf = ReadShort();
330  e.has_sv_origin = false;
331  e.m_entcs_private = boolean(sf & BIT(ENTCS_PROP_ENTNUM_id));
332  FOREACH(EntCSProps, sf & BIT(it.m_id),
333  {
334  it.m_receive(e);
335  });
336  e.iflags |= IFLAG_ORIGIN;
338  getthink(e)(e);
339  return true;
340  }
341 
342  NET_HANDLE(ENT_CLIENT_ENTCS, bool isnew)
343  {
344  if (isnew)
345  {
346  make_pure(this);
347  this.entremove = Ent_RemoveEntCS;
348  }
349  return ReadEntcs(this);
350  }
351 
352  NET_HANDLE(CLIENT_ENTCS, bool isnew)
353  {
354  return ReadEntcs(NULL);
355  }
356 
357 #endif
#define INGAME(it)
Definition: sv_rules.qh:20
#define assert(expr,...)
Definition: log.qh:8
void InterpolateOrigin_Note(entity this)
Definition: interpolate.qc:37
void entcs_detach(entity this)
skin
Definition: ent_cs.qc:143
void InterpolateOrigin_Undo(entity this)
snap origin to iorigin2 (actual origin)
Definition: interpolate.qc:159
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
Definition: cl_resources.qc:15
entity CSQCModel_server2csqc(int i)
Definition: cl_model.qc:314
#define getthink(e)
#define ReadString
void entcs_attach(entity this)
ENTCS_PROP_RESOURCE(ARMOR, false, RES_ARMOR, ENTCS_SET_NORMAL, DEC_FACTOR, { WriteByte(chan, bound(0, GetResource(ent, RES_ARMOR)/DEC_FACTOR, 255));}, { SetResourceExplicit(ent, RES_ARMOR, ReadByte() *DEC_FACTOR);}) ENTCS_PROP(NAME
Header file that describes the resource system.
int ENTCS_PROP_ENTNUM_id
Definition: ent_cs.qc:22
entity() spawn
int sv_entnum
Definition: main.qh:155
ClientState CS(Client this)
Definition: state.qh:47
ENTCS_PROP(ENTNUM, false, sv_entnum, sv_entnum, ENTCS_SET_NORMAL, {}, {}) ENTCS_PROP(ORIGIN
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
#define REGISTRY_CHECK(id)
Definition: registry.qh:175
netname
Definition: ent_cs.qc:135
#define NET_HANDLE(id, param)
Definition: net.qh:12
entity to
Definition: self.qh:96
origin
Definition: ent_cs.qc:114
#define DEC_FACTOR
Definition: ent_cs.qc:118
entity owner
Definition: main.qh:73
string model
Definition: csprogsdefs.qc:108
#define REGISTRY_SORT(...)
Definition: registry.qh:128
int ENTCS_PROP_ORIGIN_id
Definition: ent_cs.qc:23
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
#define strcpy(this, s)
Definition: string.qh:49
#define REGISTER_REGISTRY(id)
Definition: registry.qh:212
RES_HEALTH
Definition: ent_cs.qc:126
int sv_solid
Definition: ent_cs.qh:20
#define REGISTRY_DEFINE_GET(id, null)
Definition: registry.qh:40
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition: bits.qh:8
entity msg_entity
Definition: progsdefs.qc:63
STATIC_INIT(EntCSProps_renumber)
Definition: ent_cs.qc:19
#define REGISTRY(id, max)
Declare a new registry.
Definition: registry.qh:26
#define NULL
Definition: post.qh:17
frags
Definition: ent_cs.qc:151
#define ENTCS_SET_NORMAL(var, x)
Definition: ent_cs.qc:103
ENTCS_PROP_CODED(ANGLES, false, angles_y, angles_y, ENTCS_SET_NORMAL, DEC_FACTOR, { WriteByte(chan, ent.angles.y/DEC_FACTOR);}, { vector v='0 0 0';v.y=ReadByte() *DEC_FACTOR;ent.angles=v;}) ENTCS_PROP_RESOURCE(HEALTH
#define make_pure(e)
Definition: oo.qh:12
#define SAME_TEAM(a, b)
Definition: teams.qh:239
float nextthink
Definition: csprogsdefs.qc:121
#define MODEL(name, path)
Definition: all.qh:8
vector(float skel, float bonenum) _skel_get_boneabs_hidden
const int IFLAG_ORIGIN
Definition: interpolate.qh:36
vector v
Definition: ent_cs.qc:116
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
#define ENTCS_SET_MUTABLE_STRING(var, x)
the engine player name strings are mutable!
Definition: ent_cs.qc:108
float clientcolors
void InterpolateOrigin_Reset(entity this)
Definition: interpolate.qc:32
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
setorigin(ent, v)
bool m_public
Definition: ent_cs.qc:42
#define setthink(e, f)
bool intermission_running
Definition: intermission.qh:9
#define strfree(this)
Definition: string.qh:56
if(IS_DEAD(this))
Definition: impulse.qc:92
float time
Definition: csprogsdefs.qc:16
int ENTCS_PROP_HEALTH_id
Definition: ent_cs.qc:24
#define FOREACH(list, cond, body)
Definition: iter.qh:19
#define boolean(value)
Definition: bool.qh:9
void InterpolateOrigin_Do(entity this)
set origin based on iorigin1 (old pos), iorigin2 (desired pos), and time
Definition: interpolate.qc:129
#define IS_PLAYER(v)
Definition: utils.qh:9
#define BITS(n)
Definition: bits.qh:9
float solid
Definition: csprogsdefs.qc:99