}
for(tm = world; (tm = find(tm, classname, "entcs_receiver")); )
{
+ if (!tm.m_entcs_private) continue;
+ if (entcs_is_self(tm)) continue;
color2 = GetPlayerColor(tm.sv_entnum);
//if(color == NUM_SPECTATOR || color == color2)
draw_teamradar_player(tm.origin, tm.angles, Team_ColorRGB(color2));
// --------------------------------------------------------------------------
// BEGIN OPTIONAL CSQC FUNCTIONS
-void Ent_RemoveEntCS()
-{
- SELFPARAM();
- entcs_receiver[this.sv_entnum] = NULL;
-}
-
-NET_HANDLE(ENT_CLIENT_ENTCS, bool isnew)
-{
- make_pure(this);
- this.classname = "entcs_receiver";
- InterpolateOrigin_Undo();
- int sf = ReadByte();
-
- if(sf & BIT(0))
- this.sv_entnum = ReadByte();
- if (sf & BIT(1))
- {
- this.origin_x = ReadShort();
- this.origin_y = ReadShort();
- this.origin_z = ReadShort();
- setorigin(this, this.origin);
- }
- if (sf & BIT(2))
- {
- this.angles_y = ReadByte() * 360.0 / 256;
- this.angles_x = this.angles_z = 0;
- }
- if (sf & BIT(3))
- this.healthvalue = ReadByte() * 10;
- if (sf & BIT(4))
- this.armorvalue = ReadByte() * 10;
-
- return = true;
-
- entcs_receiver[this.sv_entnum] = this;
- this.entremove = Ent_RemoveEntCS;
- this.iflags |= IFLAG_ORIGIN;
-
- InterpolateOrigin_Note();
-}
-
void Ent_Remove();
void Ent_RemovePlayerScore()
this.enttype = t;
bool done = false;
FOREACH(LinkedEntities, it.m_id == t, LAMBDA(
- this.classname = it.netname;
+ if (bIsNewEntity) this.classname = it.netname;
if (autocvar_developer_csqcentities)
LOG_INFOF("CSQC_Ent_Update(%d) with this=%i {.entnum=%d, .enttype=%d} t=%s (%d)\n", bIsNewEntity, this, this.entnum, this.enttype, it.netname, t);
done = it.m_read(this, bIsNewEntity);
float g_balance_electro_secondary_bouncestop;
float g_trueaim_minrange;
-entity entcs_receiver[255]; // 255 is the engine limit on maxclients
-
float hud;
float view_quality;
int framecount;
}
}
-vector getplayerorigin(int pl)
+// TODO: entcs
+float getplayeralpha(int pl)
{
- entity e;
-
- e = CSQCModel_server2csqc(pl + 1);
- if(e)
- return e.origin;
-
- e = entcs_receiver[pl];
- if(e)
- return e.origin;
-
- return GETPLAYERORIGIN_ERROR;
-}
-
-float getplayeralpha(float pl)
-{
- entity e;
-
- e = CSQCModel_server2csqc(pl + 1);
- if(e)
- return e.alpha;
-
- return 1;
+ entity e = CSQCModel_server2csqc(pl + 1);
+ return (e) ? e.alpha : 1;
}
-vector getcsqcplayercolor(float pl)
+// TODO: entcs
+vector getcsqcplayercolor(int pl)
{
- entity e;
-
- e = CSQCModel_server2csqc(pl);
- if(e)
- {
- if(e.colormap > 0)
- return colormapPaletteColor(((e.colormap >= 1024) ? e.colormap : stof(getplayerkeyvalue(e.colormap - 1, "colors"))) & 0x0F, true);
- }
-
- return '1 1 1';
+ entity e = CSQCModel_server2csqc(pl);
+ return (e && e.colormap > 0) ? colormapPaletteColor(((e.colormap >= 1024) ? e.colormap : stof(getplayerkeyvalue(e.colormap - 1, "colors"))) & 0x0F, true) : '1 1 1';
}
-float getplayerisdead(float pl)
+// TODO: entcs
+bool getplayerisdead(int pl)
{
- entity e;
-
- e = CSQCModel_server2csqc(pl + 1);
- if(e)
- return e.csqcmodel_isdead;
-
- return false;
+ entity e = CSQCModel_server2csqc(pl + 1);
+ return e ? e.csqcmodel_isdead : false;
}
/** engine callback */
void DrawCircleClippedPic(vector centre, float radi, string pic, float f, vector rgb, float a, float drawflag);
-const vector GETPLAYERORIGIN_ERROR = '1123581321 2357111317 3141592653'; // way out of bounds for anything on the map
-vector getplayerorigin(int pl);
-
float getplayeralpha(float pl);
vector getcsqcplayercolor(float pl);
#include "../common/animdecide.qc"
#include "../common/effects/effectinfo.qc"
+#include "../common/ent_cs.qc"
#include "../common/mapinfo.qc"
#include "../common/movetypes/include.qc"
#include "../common/net_notice.qc"
#include "../lib/csqcmodel/cl_model.qh"
+entity shownames_ent[255];
+
// self.isactive = player is in range and coordinates/status (health and armor) are up to date
-// self.origin = player origin TODO: should maybe move this so it's the origin of the shownames tag already in SSQC for culling?
+// self.origin = player origin
// self.healthvalue
// self.armorvalue
// self.sameteam = player is on same team as local client
const float SHOWNAMES_FADESPEED = 4;
const float SHOWNAMES_FADEDELAY = 0.4;
-void Draw_ShowNames(entity ent)
+void Draw_ShowNames(entity this)
{
- if(!autocvar_hud_shownames)
- return;
-
- if(ent.sv_entnum == player_localentnum) // ent is me or person i'm spectating
- if(!(autocvar_hud_shownames_self && autocvar_chase_active))
- return;
-
- if(ent.sameteam || (!ent.sameteam && autocvar_hud_shownames_enemies))
+ if (!autocvar_hud_shownames) return;
+ if (this.sv_entnum == player_localentnum) // self or spectatee
+ if (!(autocvar_hud_shownames_self && autocvar_chase_active)) return;
+ if (!this.sameteam && !autocvar_hud_shownames_enemies) return;
+ bool hit;
+ if (!autocvar_hud_shownames_crosshairdistance && this.sameteam)
{
- ent.origin_z += autocvar_hud_shownames_offset;
-
- float hit;
- if(ent.sameteam && !autocvar_hud_shownames_crosshairdistance)
- {
- hit = 1;
- }
- else
- {
- traceline(view_origin, ent.origin, MOVE_NORMAL, ent);
- if(trace_fraction < 1 && (trace_networkentity != ent.sv_entnum && trace_ent.entnum != ent.sv_entnum))
- hit = 0;
- else
- hit = 1;
- }
-
- // handle tag fading
- float overlap = false, onscreen, crosshairdistance;
- vector o, eo;
-
- o = project_3d_to_2d(ent.origin);
-
- if(autocvar_hud_shownames_antioverlap)
+ hit = true;
+ }
+ else
+ {
+ traceline(view_origin, this.origin, MOVE_NORMAL, this);
+ hit = !(trace_fraction < 1 && (trace_networkentity != this.sv_entnum && trace_ent.entnum != this.sv_entnum));
+ }
+ // handle tag fading
+ bool overlap = false;
+ vector o = project_3d_to_2d(this.origin + eZ * autocvar_hud_shownames_offset);
+ float dist = vlen(this.origin - view_origin);
+ if (autocvar_hud_shownames_antioverlap)
+ {
+ // fade tag out if another tag that is closer to you overlaps
+ for (entity e = world; (e = find(e, classname, "shownames_tag")); )
{
- // fade tag out if another tag that is closer to you overlaps
- entity e;
- for(e = world; (e = find(e, classname, "shownames_tag")); )
+ if (e == this) continue;
+ vector eo = project_3d_to_2d(e.origin);
+ if (eo.z < 0 || eo.x < 0 || eo.y < 0 || eo.x > vid_conwidth || eo.y > vid_conheight) continue;
+ eo.z = 0;
+ if (vlen((eX * o.x + eY * o.y) - eo) < autocvar_hud_shownames_antioverlap_distance
+ && dist > vlen(e.origin - view_origin))
{
- if(e == ent)
- continue;
- eo = project_3d_to_2d(e.origin);
- if (!(eo.z < 0 || eo.x < 0 || eo.y < 0 || eo.x > vid_conwidth || eo.y > vid_conheight))
- {
- eo.z = 0;
- if(vlen((eX * o.x + eY * o.y) - eo) < autocvar_hud_shownames_antioverlap_distance && vlen(ent.origin - view_origin) > vlen(e.origin - view_origin))
- {
- overlap = true;
- break;
- }
- }
- }
- }
-
- onscreen = (o.z >= 0 && o.x >= 0 && o.y >= 0 && o.x <= vid_conwidth && o.y <= vid_conheight);
- crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
-
- if(autocvar_hud_shownames_crosshairdistance)
- {
- if(autocvar_hud_shownames_crosshairdistance > crosshairdistance)
- ent.pointtime = time;
-
- if (ent.pointtime + autocvar_hud_shownames_crosshairdistance_time <= time)
overlap = true;
- else
- overlap = (autocvar_hud_shownames_crosshairdistance_antioverlap ? overlap : false); // override what antioverlap says unless allowed by cvar.
- }
-
- if(!ent.fadedelay)
- ent.fadedelay = time + SHOWNAMES_FADEDELAY;
-
- if(!ent.sameteam && (!onscreen || !hit)) // out of view, fade out
- {
- ent.alpha = max(0, ent.alpha - SHOWNAMES_FADESPEED * frametime);
- ent.fadedelay = 0; // reset fade in delay, enemy has left the view
- }
- else if(ent.csqcmodel_isdead) // dead player, fade out slowly
- ent.alpha = max(0, ent.alpha - SHOWNAMES_FADESPEED * 0.25 * frametime);
- else if(overlap) // tag overlap detected, fade out
- ent.alpha = max(0, ent.alpha - SHOWNAMES_FADESPEED * frametime);
- else if(ent.sameteam) // fade in for team mates
- ent.alpha = min(1, ent.alpha + SHOWNAMES_FADESPEED * frametime);
- else if(time > ent.fadedelay) // fade in for enemies
- ent.alpha = min(1, ent.alpha + SHOWNAMES_FADESPEED * frametime);
-
- // multiply by player alpha
- if(!ent.sameteam || (ent.sv_entnum == player_localentnum))
- ent.alpha *= getplayeralpha(ent.sv_entnum-1);
-
- if(ent.alpha < ALPHA_MIN_VISIBLE && gametype != MAPINFO_TYPE_CTS)
- return;
-
- float dist;
- dist = vlen(ent.origin - view_origin);
-
- float a;
- a = autocvar_hud_shownames_alpha;
- a *= ent.alpha;
- if(autocvar_hud_shownames_maxdistance)
- {
- if(dist >= autocvar_hud_shownames_maxdistance)
- return;
- a *= ((autocvar_hud_shownames_maxdistance - autocvar_hud_shownames_mindistance) - max(0, dist - autocvar_hud_shownames_mindistance)) / (autocvar_hud_shownames_maxdistance - autocvar_hud_shownames_mindistance);
+ break;
+ }
}
-
- if(!a)
- return;
-
- float resize;
- resize = 1;
- if(autocvar_hud_shownames_resize) // limit resize so its never smaller than 0.5... gets unreadable
- resize = 0.5 + 0.5 * ((autocvar_hud_shownames_maxdistance - autocvar_hud_shownames_mindistance) - max(0, dist - autocvar_hud_shownames_mindistance)) / (autocvar_hud_shownames_maxdistance - autocvar_hud_shownames_mindistance);
-
- // draw the sprite image
- if(o.z >= 0)
+ }
+ bool onscreen = (o.z >= 0 && o.x >= 0 && o.y >= 0 && o.x <= vid_conwidth && o.y <= vid_conheight);
+ float crosshairdistance = sqrt(pow(o.x - vid_conwidth / 2, 2) + pow(o.y - vid_conheight / 2, 2));
+ if (autocvar_hud_shownames_crosshairdistance)
+ {
+ if (autocvar_hud_shownames_crosshairdistance > crosshairdistance) this.pointtime = time;
+ if (this.pointtime + autocvar_hud_shownames_crosshairdistance_time <= time) overlap = true;
+ else overlap = (autocvar_hud_shownames_crosshairdistance_antioverlap ? overlap : false); // override what antioverlap says unless allowed by cvar.
+ }
+ if (!this.fadedelay) this.fadedelay = time + SHOWNAMES_FADEDELAY;
+ if (this.csqcmodel_isdead) // dead player, fade out slowly
+ {
+ this.alpha = max(0, this.alpha - SHOWNAMES_FADESPEED * 0.25 * frametime);
+ }
+ else if (!this.sameteam && (!onscreen || !hit)) // out of view, fade out
+ {
+ this.alpha = max(0, this.alpha - SHOWNAMES_FADESPEED * frametime);
+ this.fadedelay = 0; // reset fade in delay, enemy has left the view
+ }
+ else if (overlap) // tag overlap detected, fade out
+ {
+ this.alpha = max(0, this.alpha - SHOWNAMES_FADESPEED * frametime);
+ }
+ else if (this.sameteam) // fade in for team mates
+ {
+ this.alpha = min(1, this.alpha + SHOWNAMES_FADESPEED * frametime);
+ }
+ else if (time > this.fadedelay) // fade in for enemies
+ {
+ this.alpha = min(1, this.alpha + SHOWNAMES_FADESPEED * frametime);
+ }
+ float a = autocvar_hud_shownames_alpha * this.alpha;
+ // multiply by player alpha
+ if (!this.sameteam || (this.sv_entnum == player_localentnum))
+ {
+ float f = getplayeralpha(this.sv_entnum - 1);
+ if (f == 0) f = 1;
+ if (f < 0) f = 0;
+ // FIXME: alpha is negative when dead, breaking death fade
+ if (!this.csqcmodel_isdead) a *= f;
+ }
+ if (a < ALPHA_MIN_VISIBLE && gametype != MAPINFO_TYPE_CTS) return;
+ if (autocvar_hud_shownames_maxdistance)
+ {
+ if (dist >= autocvar_hud_shownames_maxdistance) return;
+ float f = autocvar_hud_shownames_maxdistance - autocvar_hud_shownames_mindistance;
+ a *= (f - max(0, dist - autocvar_hud_shownames_mindistance)) / f;
+ }
+ if (!a) return;
+ float resize = 1;
+ if (autocvar_hud_shownames_resize) // limit resize so its never smaller than 0.5... gets unreadable
+ {
+ float f = autocvar_hud_shownames_maxdistance - autocvar_hud_shownames_mindistance;
+ resize = 0.5 + 0.5 * (f - max(0, dist - autocvar_hud_shownames_mindistance)) / f;
+ }
+ // draw the sprite image
+ if (o.z >= 0)
+ {
+ o.z = 0;
+ vector mySize = (eX * autocvar_hud_shownames_aspect + eY) * autocvar_hud_shownames_fontsize;
+ vector myPos = o - '0.5 0 0' * mySize.x - '0 1 0' * mySize.y;
+ // size scaling
+ mySize.x *= resize;
+ mySize.y *= resize;
+ myPos.x += 0.5 * (mySize.x / resize - mySize.x);
+ myPos.y += (mySize.y / resize - mySize.y);
+ // this is where the origin of the string
+ vector namepos = myPos;
+ float namewidth = mySize.x;
+ if (autocvar_hud_shownames_status && this.sameteam)
{
- o.z = 0;
-
- vector myPos, mySize;
- mySize = (eX * autocvar_hud_shownames_aspect + eY) * autocvar_hud_shownames_fontsize;
- myPos = o - '0.5 0 0' * mySize.x - '0 1 0' * mySize.y;
-
- // size scaling
- mySize.x *= resize;
- mySize.y *= resize;
-
- myPos.x += 0.5 * (mySize.x / resize - mySize.x);
- myPos.y += (mySize.y / resize - mySize.y);
-
- vector namepos; // this is where the origin of the string
- float namewidth;
-
- namepos = myPos;
- namewidth = mySize.x;
-
- if(autocvar_hud_shownames_status && teamplay)
+ vector v = namepos + '0 1 0' * autocvar_hud_shownames_fontsize * resize;
+ vector s = eX * 0.5 * mySize.x + eY * resize * autocvar_hud_shownames_statusbar_height;
+ if (this.healthvalue > 0)
{
- if(ent.sameteam)
- {
- if(ent.healthvalue > 0)
- {
- HUD_Panel_DrawProgressBar(namepos + '0 1 0' * autocvar_hud_shownames_fontsize * resize, eX * 0.5 * mySize.x + eY * resize * autocvar_hud_shownames_statusbar_height, "nametag_statusbar", ent.healthvalue/autocvar_hud_panel_healtharmor_maxhealth, 0, 1, '1 0 0', a, DRAWFLAG_NORMAL);
-
- if(ent.armorvalue > 0)
- HUD_Panel_DrawProgressBar(namepos + '0 1 0' * autocvar_hud_shownames_fontsize * resize + eX * 0.5 * mySize.x, eX * 0.5 * mySize.x + eY * resize * autocvar_hud_shownames_statusbar_height, "nametag_statusbar", ent.armorvalue/autocvar_hud_panel_healtharmor_maxarmor, 0, 0, '0 1 0', a, DRAWFLAG_NORMAL);
- }
- }
+ HUD_Panel_DrawProgressBar(v, s, "nametag_statusbar",
+ this.healthvalue / autocvar_hud_panel_healtharmor_maxhealth, false, 1, '1 0 0', a,
+ DRAWFLAG_NORMAL);
+ }
+ if (this.armorvalue > 0)
+ {
+ HUD_Panel_DrawProgressBar(v + eX * 0.5 * mySize.x, s, "nametag_statusbar",
+ this.armorvalue / autocvar_hud_panel_healtharmor_maxarmor, false, 0, '0 1 0', a,
+ DRAWFLAG_NORMAL);
}
-
- string s;
- s = GetPlayerName(ent.sv_entnum-1);
- if((autocvar_hud_shownames_decolorize == 1 && teamplay) || autocvar_hud_shownames_decolorize == 2)
- s = playername(s, GetPlayerColor(ent.sv_entnum-1));
-
- drawfontscale = '1 1 0' * resize;
- s = textShortenToWidth(s, namewidth, '1 1 0' * autocvar_hud_shownames_fontsize, stringwidth_colors);
-
- float width;
- width = stringwidth(s, true, '1 1 0' * autocvar_hud_shownames_fontsize);
-
- if (width != namewidth)
- namepos.x += (namewidth - width) / 2;
- drawcolorcodedstring(namepos, s, '1 1 0' * autocvar_hud_shownames_fontsize, a, DRAWFLAG_NORMAL);
- drawfontscale = '1 1 0';
}
+ string s = GetPlayerName(this.sv_entnum - 1);
+ if ((autocvar_hud_shownames_decolorize == 1 && teamplay)
+ || autocvar_hud_shownames_decolorize == 2) s = playername(s, GetPlayerColor(this.sv_entnum - 1));
+ drawfontscale = '1 1 0' * resize;
+ s = textShortenToWidth(s, namewidth, '1 1 0' * autocvar_hud_shownames_fontsize, stringwidth_colors);
+ float width = stringwidth(s, true, '1 1 0' * autocvar_hud_shownames_fontsize);
+ if (width != namewidth) namepos.x += (namewidth - width) / 2;
+ drawcolorcodedstring(namepos, s, '1 1 0' * autocvar_hud_shownames_fontsize, a, DRAWFLAG_NORMAL);
+ drawfontscale = '1 1 0';
}
}
-entity shownames_ent[255];
void Draw_ShowNames_All()
{
- int i;
- for(i = 0; i < maxclients; ++i)
+ for (int i = 0; i < maxclients; ++i)
{
- float t;
- t = GetPlayerColor(i);
- if(t == NUM_SPECTATOR)
- continue;
-
- entity e;
- e = shownames_ent[i];
- if(!e)
+ entity e = shownames_ent[i];
+ if (!e)
{
- e = new(shownames_tag);
- e.sv_entnum = i+1;
- shownames_ent[i] = e;
+ e = shownames_ent[i] = new(shownames_tag);
+ e.sv_entnum = i + 1;
}
-
- entity entcs;
- entcs = entcs_receiver[i];
- if(entcs)
+ entity entcs = entcs_receiver[i];
+ if (entcs.m_entcs_private)
{
e.healthvalue = entcs.healthvalue;
e.armorvalue = entcs.armorvalue;
- e.sameteam = 1; /* (teamplay && (t == myteam)); */
+ e.sameteam = true;
}
else
{
- e.healthvalue = 2342;
+ e.healthvalue = 0;
e.armorvalue = 0;
- e.sameteam = 0;
+ e.sameteam = false;
}
-
- setorigin(e, getplayerorigin(i));
- if(e.origin == GETPLAYERORIGIN_ERROR)
- continue;
-
- e.csqcmodel_isdead = getplayerisdead(i);
-
+ bool dead = getplayerisdead(i);
+ if (!e.csqcmodel_isdead && entcs.has_origin) setorigin(e, entcs.origin);
+ e.csqcmodel_isdead = dead;
Draw_ShowNames(e);
}
}
break;
}
- vector traceorigin = getplayerorigin(player_localentnum-1) + (eZ * getstati(STAT_VIEWHEIGHT));
+ vector traceorigin = entcs_receiver[player_localentnum - 1].origin + (eZ * getstati(STAT_VIEWHEIGHT));
vecs = decompressShotOrigin(getstati(STAT_SHOTORG));
/** Sent as a temp entity from a persistent linked entity */
REGISTER_NET_TEMP(ENT_CLIENT_INIT)
-REGISTER_NET_LINKED(ENT_CLIENT_ENTCS)
REGISTER_NET_LINKED(ENT_CLIENT_SCORES_INFO)
REGISTER_NET_LINKED(ENT_CLIENT_SCORES)
REGISTER_NET_LINKED(ENT_CLIENT_TEAMSCORES)
#include "globalsound.qh"
+
+#include "../common/ent_cs.qh"
+
#ifdef IMPLEMENTATION
#include "../../animdecide.qh"
WriteByte(channel, fabs(chan));
WriteByte(channel, floor(vol * 255));
WriteByte(channel, floor(atten * 64));
+ entcs_force_origin(from);
vector o = from.origin + 0.5 * (from.mins + from.maxs);
WriteCoord(channel, o.x);
WriteCoord(channel, o.y);
NET_HANDLE(playersound, bool isnew)
{
- entity ps;
- ps = PlayerSounds_from(ReadByte());
+ entity ps = PlayerSounds_from(ReadByte());
float r = ReadByte() / 255;
int who = ReadByte();
- entity e = findfloat(world, entnum, who);
- if (autocvar_cl_forceplayermodels)
- {
- entity me = findfloat(world, entnum, player_currententnum);
- if (me.model != "null") e = me;
- }
+ entity e = entcs_receiver[who - 1];
UpdatePlayerSounds(e);
string s = e.(ps.m_playersoundfld);
string sample = GlobalSound_sample(s, r);
o.x = ReadCoord();
o.y = ReadCoord();
o.z = ReadCoord();
- if (who == player_currententnum)
+ if (e)
{
- // client knows better, play at current position to unlag
+ // TODO: for non-visible players, origin should probably continue to be updated as long as the sound is playing
+ e.origin = o;
sound7(e, chan, sample, vol, atten, 0, 0);
}
else
{
+ LOG_WARNINGF("Missing entcs data for player %i\n", e);
+ // Can this happen?
entity e = new(playersound);
e.origin = o;
sound8(e, o, chan, sample, vol, atten, 0, 0);
return true;
}
- .int modelindex_for_playersound;
+ .string model_for_playersound;
.int skin_for_playersound;
bool autocvar_g_debug_defaultsounds;
void UpdatePlayerSounds(entity this)
{
- if (this.modelindex == this.modelindex_for_playersound && this.skin == this.skin_for_playersound) return;
- this.modelindex_for_playersound = this.modelindex;
+ if (this.model == this.model_for_playersound && this.skin == this.skin_for_playersound) return;
+ if (this.model_for_playersound) strunzone(this.model_for_playersound);
+ this.model_for_playersound = strzone(this.model);
this.skin_for_playersound = this.skin;
ClearPlayerSounds(this);
LoadPlayerSounds(this, "sound/player/default.sounds", true);
--- /dev/null
+#include "ent_cs.qh"
+
+// #define PROP(public, fld, sv, cl)
+#define ENTCS_NETPROPS(PROP) \
+ PROP(true, sv_entnum, \
+ { WriteByte(MSG_ENTITY, etof(player) - 1); }, \
+ { this.sv_entnum = ReadByte(); }) \
+ \
+ PROP(false, origin, \
+ { WriteShort(MSG_ENTITY, this.origin.x); WriteShort(MSG_ENTITY, this.origin.y); \
+ WriteShort(MSG_ENTITY, this.origin.z); }, \
+ { this.has_origin = true; vector v; v.x = ReadShort(); v.y = ReadShort(); v.z = ReadShort(); setorigin(this, v); }) \
+ \
+ PROP(false, angles_y, \
+ { WriteByte(MSG_ENTITY, this.angles.y / 360 * 256); }, \
+ { vector v = '0 0 0'; v.y = ReadByte() / 256 * 360; this.angles = v; }) \
+ \
+ PROP(false, health, \
+ { WriteByte(MSG_ENTITY, this.health / 10); /* FIXME: use a better scale? */ }, \
+ { this.healthvalue = ReadByte() * 10; }) \
+ \
+ PROP(false, armorvalue, \
+ { WriteByte(MSG_ENTITY, this.armorvalue / 10); /* FIXME: use a better scale? */ }, \
+ { this.armorvalue = ReadByte() * 10; }) \
+ \
+ PROP(true, netname, \
+ { WriteString(MSG_ENTITY, this.netname); }, \
+ { if (this.netname) strunzone(this.netname); this.netname = strzone(ReadString()); }) \
+ \
+ PROP(true, model, \
+ { WriteString(MSG_ENTITY, this.model); }, \
+ { if (this.model) strunzone(this.model); this.model = strzone(ReadString()); }) \
+ \
+ PROP(true, skin, \
+ { WriteByte(MSG_ENTITY, this.skin); }, \
+ { this.skin = ReadByte(); }) \
+ \
+ /**/
+
+#ifdef SVQC
+
+ int ENTCS_PUBLICMASK = 0;
+ STATIC_INIT(ENTCS_PUBLICMASK)
+ {
+ int i = 1;
+ #define X(public, fld, sv, cl) { if (public) ENTCS_PUBLICMASK |= BIT(i); } i += 1;
+ ENTCS_NETPROPS(X);
+ #undef X
+ if (i >= BITS(16 - 1)) LOG_FATAL("Exceeded ENTCS_NETPROPS limit");
+ }
+
+ bool entcs_customize()
+ {
+ SELFPARAM();
+ entity player = this.owner;
+ return IS_PLAYER(player) // player must be active
+ && player.deadflag == DEAD_NO // player must be alive
+ ;
+ }
+
+ bool entcs_send(entity this, entity to, int sf)
+ {
+ entity player = this.owner;
+ sf |= 1;
+ if (IS_PLAYER(to) || to.caplayer) // unless spectating,
+ {
+ bool same_team = (to == player) || (teamplay && player.team == to.team);
+ if (!same_team && !radar_showennemies) sf &= ENTCS_PUBLICMASK; // no private updates
+ }
+ sf |= this.m_forceupdate;
+ this.m_forceupdate = 0;
+ WriteHeader(MSG_ENTITY, ENT_CLIENT_ENTCS);
+ WriteShort(MSG_ENTITY, sf);
+ int i = 1;
+ #define X(public, fld, sv, cl) { if (sf & BIT(i)) sv; } i += 1;
+ ENTCS_NETPROPS(X);
+ #undef X
+ return true;
+ }
+
+ void entcs_think()
+ {
+ SELFPARAM();
+ this.nextthink = time + 0.033333333333; // TODO: increase this to like 0.15 once the client can do smoothing
+ entity o = this.owner;
+ int i = 1;
+ #define X(public, fld, sv, cl) \
+ if (o.fld != this.fld) \
+ { \
+ this.fld = o.fld; \
+ this.SendFlags |= BIT(i); \
+ } \
+ i += 1;
+ ENTCS_NETPROPS(X);
+ #undef X
+ }
+
+ void entcs_attach(entity player)
+ {
+ entity e = player.entcs = new(entcs_sender);
+ make_pure(e);
+ e.owner = player;
+ e.think = entcs_think;
+ e.nextthink = time;
+ Net_LinkEntity(e, false, 0, entcs_send);
+ e.customizeentityforclient = entcs_customize;
+ }
+
+ void entcs_detach(entity player)
+ {
+ if (!player.entcs) return;
+ remove(player.entcs);
+ player.entcs = NULL;
+ }
+
+#endif
+
+#ifdef CSQC
+
+ void Ent_RemoveEntCS()
+ {
+ SELFPARAM();
+ entcs_receiver[this.sv_entnum] = NULL;
+ }
+
+ void entcs_think()
+ {
+ SELFPARAM();
+ this.nextthink = time;
+ entity e = CSQCModel_server2csqc(this.sv_entnum + 1);
+ bool exists = !!e;
+ if (exists)
+ {
+ this.has_origin = true;
+ this.origin = e.origin;
+ // `cl_forceplayermodels 1` sounds will be wrong until the player has been in the PVS, but so be it
+ this.model = e.model;
+ }
+ }
+
+ NET_HANDLE(ENT_CLIENT_ENTCS, bool isnew)
+ {
+ if (isnew)
+ {
+ make_pure(this);
+ this.classname = "entcs_receiver";
+ this.entremove = Ent_RemoveEntCS;
+ this.think = entcs_think;
+ this.nextthink = time;
+ }
+ InterpolateOrigin_Undo();
+ int sf = ReadShort();
+ this.has_origin = false;
+ this.m_entcs_private = boolean(sf & 1);
+ int i = 1;
+ #define X(public, fld, sv, cl) { if (sf & BIT(i)) cl; } i += 1;
+ ENTCS_NETPROPS(X);
+ #undef X
+ entcs_receiver[this.sv_entnum] = this;
+ this.iflags |= IFLAG_ORIGIN;
+ InterpolateOrigin_Note();
+ return true;
+ }
+
+#endif
--- /dev/null
+#ifndef ENT_CS_H
+#define ENT_CS_H
+
+REGISTER_NET_LINKED(ENT_CLIENT_ENTCS)
+
+/** True when private information such as origin is available */
+.bool m_entcs_private;
+
+/** True when a recent origin is available */
+.bool has_origin;
+
+#ifdef SVQC
+/**
+ * The point of these entities is to avoid the problems
+ * with clientprediction.
+ * If you add SendEntity to players, the engine will not
+ * do any prediction anymore, and you'd have to write the whole
+ * prediction code in CSQC, you want that? :P
+ * Data can depend on gamemode. For now, it serves as GPS entities
+ * in onslaught... YAY ;)
+ */
+
+.entity entcs;
+
+bool entcs_customize();
+
+bool entcs_send(entity this, entity to, int sf);
+
+void entcs_think();
+
+void entcs_attach(entity e);
+
+void entcs_detach(entity e);
+
+.int m_forceupdate;
+
+/** Force an origin update, for player sounds */
+#define entcs_force_origin(e) ((e).entcs.m_forceupdate = BIT(2))
+
+#endif
+
+#ifdef CSQC
+
+entity entcs_receiver[255]; // 255 is the engine limit on maxclients
+#define entcs_is_self(e) ((e).sv_entnum + 1 == player_localentnum)
+
+#endif
+
+#endif
else
{
// use player origin so that third person display still works
- self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT));
+ self.origin = entcs_receiver[player_localnum].origin + ('0 0 1' * getstati(STAT_VIEWHEIGHT));
}
}
if (is_pure(e)) return true;
switch (s)
{
- case "entcs_sender":
- case "entcs_receiver":
// case "net_linked": // actually some real entities are linked without classname, fail
case "":
return true;
#include "anticheat.qh"
#include "cl_impulse.qh"
#include "cl_player.qh"
-#include "ent_cs.qh"
#include "ipban.qh"
#include "miscfunctions.qh"
#include "portals.qh"
#include "bot/bot.qh"
#include "bot/navigation.qh"
+#include "../common/ent_cs.qh"
#include "../common/vehicles/all.qh"
#include "../common/triggers/teleporters.qh"
else
stuffcmd(self, "set _teams_available 0\n");
- attach_entcs(self);
+ entcs_attach(self);
bot_relinkplayerlist();
bot_clientdisconnect();
- detach_entcs(self);
+ entcs_detach(self);
if(autocvar_sv_eventlog)
GameLogEcho(strcat(":part:", ftos(self.playerid)));
+++ /dev/null
-#include "ent_cs.qh"
-
-float entcs_customize()
-{
- SELFPARAM();
- entity o = self.owner;
- if(o.deadflag != DEAD_NO)
- return false;
- if (!IS_PLAYER(o))
- return false;
- if(other == o)
- return false;
- if((IS_PLAYER(other)) || other.caplayer)
- if(!teamplay || o.team != other.team)
- if (!radar_showennemies)
- return false;
- return true;
-}
-
-bool entcs_send(entity this, entity to, int sf)
-{
- WriteHeader(MSG_ENTITY, ENT_CLIENT_ENTCS);
- WriteByte(MSG_ENTITY, sf);
- if(sf & BIT(0))
- WriteByte(MSG_ENTITY, num_for_edict(self.owner) - 1);
- if(sf & BIT(1))
- {
- WriteShort(MSG_ENTITY, self.origin.x);
- WriteShort(MSG_ENTITY, self.origin.y);
- WriteShort(MSG_ENTITY, self.origin.z);
- }
- if(sf & BIT(2))
- WriteByte(MSG_ENTITY, self.angles.y * 256.0 / 360);
- if(sf & BIT(3))
- WriteByte(MSG_ENTITY, self.health / 10); // FIXME use a better scale?
- if(sf & BIT(4))
- WriteByte(MSG_ENTITY, self.armorvalue / 10); // FIXME use a better scale?
- return true;
-}
-
-void entcs_think()
-{
- SELFPARAM();
- self.nextthink = time + 0.033333333333; // increase this to like 0.15 once the client can do smoothing
- entity o = self.owner;
- if (o.origin != self.origin)
- {
- setorigin(self, o.origin);
- self.SendFlags |= BIT(1);
- }
- if (o.angles.y != self.angles.y)
- {
- self.angles = o.angles;
- self.SendFlags |= BIT(2);
- }
- if (o.health != self.health)
- {
- self.health = o.health;
- self.SendFlags |= BIT(3);
- }
- if (o.armorvalue != self.armorvalue)
- {
- self.armorvalue = o.armorvalue;
- self.SendFlags |= BIT(4);
- }
-}
-
-entity attach_entcs(entity e)
-{
- entity ent = e.entcs = new(entcs_sender);
- make_pure(ent);
- ent.owner = e;
- ent.think = entcs_think;
- ent.nextthink = time;
-
- Net_LinkEntity(ent, false, 0, entcs_send);
- ent.customizeentityforclient = entcs_customize;
-
- return ent;
-}
-
-void detach_entcs(entity e)
-{
- if (!e.entcs) return;
- remove(e.entcs);
- e.entcs = NULL;
-}
+++ /dev/null
-#ifndef ENT_CS_H
-#define ENT_CS_H
-
-/**
- * The point of these entities is to avoid the problems
- * with clientprediction.
- * If you add SendEntity to players, the engine will not
- * do any prediction anymore, and you'd have to write the whole
- * prediction code in CSQC, you want that? :P
- * Data can depend on gamemode. For now, it serves as GPS entities
- * in onslaught... YAY ;)
- */
-
-.entity entcs;
-
-float entcs_customize();
-
-bool entcs_send(entity this, entity to, int sf);
-
-void entcs_think();
-
-entity attach_entcs(entity e);
-
-void detach_entcs(entity e);
-
-#endif
#include "cl_client.qc"
#include "cl_impulse.qc"
#include "cl_player.qc"
-#include "ent_cs.qc"
#include "g_damage.qc"
#include "g_hook.qc"
// #include "g_lights.qc" // TODO: was never used
#include "../common/campaign_file.qc"
#include "../common/campaign_setup.qc"
#include "../common/effects/effectinfo.qc"
+#include "../common/ent_cs.qc"
#include "../common/mapinfo.qc"
#include "../common/minigames/minigames.qc"
#include "../common/minigames/sv_minigames.qc"