}
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()
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 "casings.qc"
#include "damageeffects.qc"
#include "gibs.qc"
+#include "globalsound.qc"
#include "lightningarc.qc"
#include "modeleffects.qc"
--- /dev/null
+#include "globalsound.qh"
+
+#include "../common/ent_cs.qh"
+
+#ifdef IMPLEMENTATION
+ #include "../../animdecide.qh"
+
+ #ifdef SVQC
+ #include "../../../server/cl_player.qh"
+ #endif
+
+ REGISTER_NET_TEMP(globalsound)
+ REGISTER_NET_TEMP(playersound)
+
+ #ifdef SVQC
+ /**
+ * @param from the source entity, its position is sent
+ * @param gs the global sound def
+ * @param r a random number in 0..1
+ */
+ void globalsound(int channel, entity from, entity gs, float r, int chan, float vol, float atten)
+ {
+ if (channel == MSG_ONE && !IS_REAL_CLIENT(msg_entity)) return;
+ WriteHeader(channel, globalsound);
+ WriteByte(channel, gs.m_id);
+ WriteByte(channel, r * 255);
+ WriteByte(channel, etof(from));
+ WriteByte(channel, fabs(chan));
+ WriteByte(channel, floor(vol * 255));
+ WriteByte(channel, floor(atten * 64));
+ vector o = from.origin + 0.5 * (from.mins + from.maxs);
+ WriteCoord(channel, o.x);
+ WriteCoord(channel, o.y);
+ WriteCoord(channel, o.z);
+ }
+
+ /**
+ * @param from the source entity, its position is sent
+ * @param ps the player sound def
+ * @param r a random number in 0..1
+ */
+ void playersound(int channel, entity from, entity ps, float r, int chan, float vol, float atten)
+ {
+ if (channel == MSG_ONE && !IS_REAL_CLIENT(msg_entity)) return;
+ WriteHeader(channel, playersound);
+ WriteByte(channel, ps.m_id);
+ WriteByte(channel, r * 255);
+ WriteByte(channel, etof(from));
+ 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);
+ WriteCoord(channel, o.z);
+ }
+ #endif
+
+ string GlobalSound_sample(string pair, float r);
+
+ #ifdef CSQC
+
+ NET_HANDLE(globalsound, bool isnew)
+ {
+ entity gs = GlobalSounds_from(ReadByte());
+ float r = ReadByte() / 255;
+ string sample = GlobalSound_sample(gs.m_globalsoundstr, r);
+ int who = ReadByte();
+ int chan = ReadByte();
+ float vol = ReadByte() / 255;
+ float atten = ReadByte() / 64;
+ vector o;
+ o.x = ReadCoord();
+ o.y = ReadCoord();
+ o.z = ReadCoord();
+ if (who == player_currententnum)
+ {
+ // client knows better, play at current position to unlag
+ entity e = findfloat(world, entnum, who);
+ sound7(e, chan, sample, vol, atten, 0, 0);
+ }
+ else
+ {
+ entity e = new(globalsound);
+ e.origin = o;
+ sound8(e, o, chan, sample, vol, atten, 0, 0);
+ remove(e); // debug with: e.think = SUB_Remove; e.nextthink = time + 1;
+ }
+ return true;
+ }
+
+ NET_HANDLE(playersound, bool isnew)
+ {
+ entity ps = PlayerSounds_from(ReadByte());
+ float r = ReadByte() / 255;
+ int who = ReadByte();
+ entity e = entcs_receiver[who - 1];
+ UpdatePlayerSounds(e);
+ string s = e.(ps.m_playersoundfld);
+ string sample = GlobalSound_sample(s, r);
+ int chan = ReadByte();
+ float vol = ReadByte() / 255;
+ float atten = ReadByte() / 64;
+ vector o;
+ o.x = ReadCoord();
+ o.y = ReadCoord();
+ o.z = ReadCoord();
+ if (e)
+ {
+ // 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);
+ remove(e); // debug with: e.think = SUB_Remove; e.nextthink = time + 1;
+ }
+ return true;
+ }
+
+ #endif
+
+ string GlobalSound_sample(string pair, float r)
+ {
+ int n;
+ {
+ string s = cdr(pair);
+ if (s) n = stof(s);
+ else n = 0;
+ }
+ string sample = car(pair);
+ if (n > 0) sample = sprintf("%s%d.wav", sample, floor(r * n + 1)); // randomization
+ else sample = sprintf("%s.wav", sample);
+ return sample;
+ }
+
+ void PrecacheGlobalSound(string sample)
+ {
+ int n;
+ {
+ string s = cdr(sample);
+ if (s) n = stof(s);
+ else n = 0;
+ }
+ sample = car(sample);
+ if (n > 0)
+ {
+ for (int i = 1; i <= n; ++i)
+ precache_sound(sprintf("%s%d.wav", sample, i));
+ }
+ else
+ {
+ precache_sound(sprintf("%s.wav", sample));
+ }
+ }
+
+ entity GetVoiceMessage(string type)
+ {
+ FOREACH(PlayerSounds, it.m_playersoundstr == type && it.instanceOfVoiceMessage == true, LAMBDA(return it));
+ return NULL;
+ }
+
+ entity GetPlayerSound(string type)
+ {
+ FOREACH(PlayerSounds, it.m_playersoundstr == type && it.instanceOfVoiceMessage == false, LAMBDA(return it));
+ return NULL;
+ }
+
+ string allvoicesamples;
+ STATIC_INIT(allvoicesamples)
+ {
+ FOREACH(PlayerSounds, it.instanceOfVoiceMessage, LAMBDA(
+ allvoicesamples = strcat(allvoicesamples, " ", it.m_playersoundstr)
+ ));
+ allvoicesamples = strzone(substring(allvoicesamples, 1, -1));
+ }
+
+ .string _GetPlayerSoundSampleField(string type, bool voice)
+ {
+ GetPlayerSoundSampleField_notFound = false;
+ entity e = voice ? GetVoiceMessage(type) : GetPlayerSound(type);
+ if (e) return e.m_playersoundfld;
+ GetPlayerSoundSampleField_notFound = true;
+ return playersound_taunt.m_playersoundfld;
+ }
+
+ .string GetVoiceMessageSampleField(string type)
+ {
+ return _GetPlayerSoundSampleField(type, true);
+ }
+
+ void PrecachePlayerSounds(string f)
+ {
+ int fh = fopen(f, FILE_READ);
+ if (fh < 0)
+ {
+ LOG_WARNINGF("Player sound file not found: %s\n", f);
+ return;
+ }
+ for (string s; (s = fgets(fh)); )
+ {
+ int n = tokenize_console(s);
+ if (n != 3)
+ {
+ if (n != 0) LOG_WARNINGF("Invalid sound info line: %s\n", s);
+ continue;
+ }
+ string file = argv(1);
+ string variants = argv(2);
+ PrecacheGlobalSound(strcat(file, " ", variants));
+ }
+ fclose(fh);
+ }
+
+ #ifdef CSQC
+
+ .string GetPlayerSoundSampleField(string type)
+ {
+ return _GetPlayerSoundSampleField(type, false);
+ }
+
+ void ClearPlayerSounds(entity this)
+ {
+ FOREACH(PlayerSounds, true, LAMBDA(
+ .string fld = it.m_playersoundfld;
+ if (this.(fld))
+ {
+ strunzone(this.(fld));
+ this.(fld) = string_null;
+ }
+ ));
+ }
+
+ bool LoadPlayerSounds(entity this, string f, bool strict)
+ {
+ int fh = fopen(f, FILE_READ);
+ if (fh < 0)
+ {
+ if (strict) LOG_WARNINGF("Player sound file not found: %s\n", f);
+ return false;
+ }
+ for (string s; (s = fgets(fh)); )
+ {
+ int n = tokenize_console(s);
+ if (n != 3)
+ {
+ if (n != 0) LOG_WARNINGF("Invalid sound info line: %s\n", s);
+ continue;
+ }
+ string key = argv(0);
+ var.string field = GetPlayerSoundSampleField(key);
+ if (GetPlayerSoundSampleField_notFound) field = GetVoiceMessageSampleField(key);
+ if (GetPlayerSoundSampleField_notFound)
+ {
+ LOG_TRACEF("Invalid sound info field: %s\n", key);
+ continue;
+ }
+ string file = argv(1);
+ string variants = argv(2);
+ if (this.(field)) strunzone(this.(field));
+ this.(field) = strzone(strcat(file, " ", variants));
+ }
+ fclose(fh);
+ return true;
+ }
+
+ .string model_for_playersound;
+ .int skin_for_playersound;
+
+ bool autocvar_g_debug_defaultsounds;
+
+ void UpdatePlayerSounds(entity this)
+ {
+ 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);
+ if (this.model == "null" || autocvar_g_debug_defaultsounds) return;
+ if (LoadPlayerSounds(this, get_model_datafilename(this.model, this.skin, "sounds"), false)) return;
+ LoadPlayerSounds(this, get_model_datafilename(this.model, 0, "sounds"), true);
+ }
+
+ #endif
+
+ #ifdef SVQC
+
+ void _GlobalSound(entity gs, entity ps, string sample, int chan, int voicetype, bool fake)
+ {
+ SELFPARAM();
+ if (gs == NULL && ps == NULL && sample == "") return;
+ float r = random();
+ if (sample != "") sample = GlobalSound_sample(sample, r);
+ switch (voicetype)
+ {
+ case VOICETYPE_LASTATTACKER_ONLY:
+ case VOICETYPE_LASTATTACKER:
+ {
+ if (!fake)
+ {
+ if (!this.pusher) break;
+ msg_entity = this.pusher;
+ if (IS_REAL_CLIENT(msg_entity))
+ {
+ float atten = (msg_entity.cvar_cl_voice_directional == 1) ? ATTEN_MIN : ATTEN_NONE;
+ if (gs) globalsound(MSG_ONE, this, gs, r, chan, VOL_BASEVOICE, atten);
+ else if (ps) playersound(MSG_ONE, this, ps, r, chan, VOL_BASEVOICE, atten);
+ else soundto(MSG_ONE, this, chan, sample, VOL_BASEVOICE, atten);
+ }
+ }
+ if (voicetype == VOICETYPE_LASTATTACKER_ONLY) break;
+ msg_entity = this;
+ if (IS_REAL_CLIENT(msg_entity))
+ {
+ if (gs) globalsound(MSG_ONE, this, gs, r, chan, VOL_BASE, ATTEN_NONE);
+ else if (ps) playersound(MSG_ONE, this, ps, r, chan, VOL_BASE, ATTEN_NONE);
+ else soundto(MSG_ONE, this, chan, sample, VOL_BASE, ATTEN_NONE);
+ }
+ break;
+ }
+ case VOICETYPE_TEAMRADIO:
+ {
+ #define X() \
+ do \
+ { \
+ float atten = (msg_entity.cvar_cl_voice_directional == 1) ? ATTEN_MIN : ATTEN_NONE; \
+ if (gs) globalsound(MSG_ONE, this, gs, r, chan, VOL_BASEVOICE, atten); \
+ else if (ps) playersound(MSG_ONE, this, ps, r, chan, VOL_BASEVOICE, atten); \
+ else soundto(MSG_ONE, this, chan, sample, VOL_BASEVOICE, atten); \
+ } \
+ while (0)
+
+ if (fake) { msg_entity = this; X(); }
+ else
+ {
+ FOR_EACH_REALCLIENT(msg_entity)
+ {
+ if (!teamplay || msg_entity.team == this.team) X();
+ }
+ }
+ #undef X
+ break;
+ }
+ case VOICETYPE_AUTOTAUNT:
+ case VOICETYPE_TAUNT:
+ {
+ if (voicetype == VOICETYPE_AUTOTAUNT) if (!sv_autotaunt) { break; }else {}
+ else if (IS_PLAYER(this) && this.deadflag == DEAD_NO) animdecide_setaction(this, ANIMACTION_TAUNT,
+ true);
+ if (!sv_taunt) break;
+ if (autocvar_sv_gentle) break;
+ float tauntrand = 0;
+ if (voicetype == VOICETYPE_AUTOTAUNT) tauntrand = random();
+ #define X() \
+ do \
+ { \
+ if (voicetype != VOICETYPE_AUTOTAUNT || tauntrand < msg_entity.cvar_cl_autotaunt) \
+ { \
+ float atten = (msg_entity.cvar_cl_voice_directional >= 1) \
+ ? bound(ATTEN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, \
+ ATTEN_MAX) \
+ : ATTEN_NONE; \
+ if (gs) globalsound(MSG_ONE, this, gs, r, chan, VOL_BASEVOICE, atten); \
+ else if (ps) playersound(MSG_ONE, this, ps, r, chan, VOL_BASEVOICE, atten); \
+ else soundto(MSG_ONE, this, chan, sample, VOL_BASEVOICE, atten); \
+ } \
+ } \
+ while (0)
+ if (fake)
+ {
+ msg_entity = this;
+ X();
+ }
+ else
+ {
+ FOR_EACH_REALCLIENT(msg_entity)
+ {
+ X();
+ }
+ }
+ #undef X
+ break;
+ }
+ case VOICETYPE_PLAYERSOUND:
+ {
+ msg_entity = this;
+ if (fake)
+ {
+ if (gs) globalsound(MSG_ONE, this, gs, r, chan, VOL_BASE, ATTEN_NORM);
+ else if (ps) playersound(MSG_ONE, this, ps, r, chan, VOL_BASE, ATTEN_NORM);
+ else soundto(MSG_ONE, this, chan, sample, VOL_BASE, ATTEN_NORM);
+ }
+ else
+ {
+ if (gs) globalsound(MSG_ALL, this, gs, r, chan, VOL_BASE, ATTEN_NORM);
+ else if (ps) playersound(MSG_ALL, this, ps, r, chan, VOL_BASE, ATTEN_NORM);
+ else _sound(this, chan, sample, VOL_BASE, ATTEN_NORM);
+ }
+ break;
+ }
+ default:
+ {
+ backtrace("Invalid voice type!");
+ break;
+ }
+ }
+ }
+
+ #endif
+#endif
--- /dev/null
+#ifndef GLOBALSOUND_H
+#define GLOBALSOUND_H
+
+// player sounds, voice messages
+
+.string m_playersoundstr;
+..string m_playersoundfld;
+
+REGISTRY(PlayerSounds, BITS(8) - 1)
+#define PlayerSounds_from(i) _PlayerSounds_from(i, NULL)
+#define REGISTER_PLAYERSOUND(id) \
+ .string _playersound_##id; \
+ REGISTER(PlayerSounds, playersound, id, m_id, new(PlayerSound)) \
+ { \
+ make_pure(this); \
+ this.m_playersoundstr = #id; \
+ this.m_playersoundfld = _playersound_##id; \
+ }
+REGISTER_REGISTRY(PlayerSounds)
+REGISTRY_SORT(PlayerSounds, 0)
+STATIC_INIT(PlayerSounds_renumber)
+{
+ FOREACH(PlayerSounds, true, LAMBDA(it.m_id = i));
+}
+REGISTRY_CHECK(PlayerSounds)
+
+// TODO implement fall and falling
+
+REGISTER_PLAYERSOUND(death)
+REGISTER_PLAYERSOUND(drown)
+REGISTER_PLAYERSOUND(fall)
+REGISTER_PLAYERSOUND(falling)
+REGISTER_PLAYERSOUND(gasp)
+REGISTER_PLAYERSOUND(jump)
+REGISTER_PLAYERSOUND(pain100)
+REGISTER_PLAYERSOUND(pain25)
+REGISTER_PLAYERSOUND(pain50)
+REGISTER_PLAYERSOUND(pain75)
+
+.bool instanceOfVoiceMessage;
+.int m_playersoundvt;
+#define REGISTER_VOICEMSG(id, vt) \
+ .string _playersound_##id; \
+ REGISTER(PlayerSounds, playersound, id, m_id, new(VoiceMessage)) \
+ { \
+ make_pure(this); \
+ this.instanceOfVoiceMessage = true; \
+ this.m_playersoundstr = #id; \
+ this.m_playersoundfld = _playersound_##id; \
+ this.m_playersoundvt = vt; \
+ }
+
+const int VOICETYPE_PLAYERSOUND = 10;
+const int VOICETYPE_TEAMRADIO = 11;
+const int VOICETYPE_LASTATTACKER = 12;
+const int VOICETYPE_LASTATTACKER_ONLY = 13;
+const int VOICETYPE_AUTOTAUNT = 14;
+const int VOICETYPE_TAUNT = 15;
+
+REGISTER_VOICEMSG(attack, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(attackinfive, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(coverme, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(defend, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(freelance, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(incoming, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(meet, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(needhelp, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(seenflag, VOICETYPE_TEAMRADIO)
+REGISTER_VOICEMSG(taunt, VOICETYPE_TAUNT)
+REGISTER_VOICEMSG(teamshoot, VOICETYPE_LASTATTACKER)
+
+// reserved sound names for the future (some models lack sounds for them):
+// _VOICEMSG(flagcarriertakingdamage)
+// _VOICEMSG(getflag)
+// reserved sound names for the future (ALL models lack sounds for them):
+// _VOICEMSG(affirmative)
+// _VOICEMSG(attacking)
+// _VOICEMSG(defending)
+// _VOICEMSG(roaming)
+// _VOICEMSG(onmyway)
+// _VOICEMSG(droppedflag)
+// _VOICEMSG(negative)
+// _VOICEMSG(seenenemy)
+
+.string m_globalsoundstr;
+REGISTRY(GlobalSounds, BITS(8) - 1)
+#define GlobalSounds_from(i) _GlobalSounds_from(i, NULL)
+#define REGISTER_GLOBALSOUND(id, str) \
+ REGISTER(GlobalSounds, GS, id, m_id, new(GlobalSound)) \
+ { \
+ make_pure(this); \
+ this.m_globalsoundstr = str; \
+ }
+REGISTER_REGISTRY(GlobalSounds)
+REGISTRY_SORT(GlobalSounds, 0)
+STATIC_INIT(GlobalSounds_renumber)
+{
+ FOREACH(GlobalSounds, true, LAMBDA(it.m_id = i));
+}
+REGISTRY_CHECK(GlobalSounds)
+void PrecacheGlobalSound(string samplestring);
+PRECACHE(GlobalSounds)
+{
+ FOREACH(GlobalSounds, true, LAMBDA(PrecacheGlobalSound(it.m_globalsoundstr)));
+}
+
+REGISTER_GLOBALSOUND(STEP, "misc/footstep0 6")
+REGISTER_GLOBALSOUND(STEP_METAL, "misc/metalfootstep0 6")
+REGISTER_GLOBALSOUND(FALL, "misc/hitground 4")
+REGISTER_GLOBALSOUND(FALL_METAL, "misc/metalhitground 4")
+
+bool GetPlayerSoundSampleField_notFound;
+void PrecachePlayerSounds(string f);
+#ifdef CSQC
+ .string GetVoiceMessageSampleField(string type);
+ .string GetPlayerSoundSampleField(string type);
+ void ClearPlayerSounds(entity this);
+ float LoadPlayerSounds(entity this, string f, bool strict);
+ void UpdatePlayerSounds(entity this);
+#endif
+
+#ifdef SVQC
+
+ void _GlobalSound(entity gs, entity ps, string sample, float chan, float voicetype, bool fake);
+ #define GlobalSound(def, chan, voicetype) _GlobalSound(def, NULL, string_null, chan, voicetype, false)
+ #define GlobalSound_string(def, chan, voicetype) _GlobalSound(NULL, NULL, def, chan, voicetype, false)
+ #define PlayerSound(def, chan, voicetype) _GlobalSound(NULL, def, string_null, chan, voicetype, false)
+ #define VoiceMessage(def, msg) \
+ do \
+ { \
+ entity VM = def; \
+ int voicetype = VM.m_playersoundvt; \
+ bool ownteam = (voicetype == VOICETYPE_TEAMRADIO); \
+ int flood = Say(this, ownteam, world, msg, true); \
+ bool fake; \
+ if (IS_SPEC(this) || IS_OBSERVER(this) || flood < 0) fake = true; \
+ else if (flood > 0) fake = false; \
+ else break; \
+ _GlobalSound(NULL, VM, string_null, CH_VOICE, voicetype, fake); \
+ } \
+ while (0)
+
+#endif
+
+#endif
--- /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
if(delaytoo)
if(time < self.msound_delay)
return; // too early
- _GlobalSound(self.(samplefield), chan, VOICETYPE_PLAYERSOUND, false);
+ GlobalSound_string(self.(samplefield), chan, VOICETYPE_PLAYERSOUND);
self.msound_delay = time + sound_delay;
}
void PM_check_hitground()
{SELFPARAM();
#ifdef SVQC
- if (IS_ONGROUND(self))
- if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
- if (self.wasFlying)
- {
- self.wasFlying = 0;
- if (self.waterlevel < WATERLEVEL_SWIMMING)
- if (time >= self.ladder_time)
- if (!self.hook)
- {
- self.nextstep = time + 0.3 + random() * 0.1;
- trace_dphitq3surfaceflags = 0;
- tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
- if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
- {
- if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
- GlobalSound(GS_FALL_METAL, CH_PLAYER, VOICETYPE_PLAYERSOUND);
- else
- GlobalSound(GS_FALL, CH_PLAYER, VOICETYPE_PLAYERSOUND);
- }
- }
- }
+ if (!IS_PLAYER(this)) return; // no fall sounds for observers thank you very much
+ if (!IS_ONGROUND(this)) return;
+ if (!this.wasFlying) return;
+ this.wasFlying = false;
+ if (this.waterlevel >= WATERLEVEL_SWIMMING) return;
+ if (time < this.ladder_time) return;
+ if (this.hook) return;
+ this.nextstep = time + 0.3 + random() * 0.1;
+ trace_dphitq3surfaceflags = 0;
+ tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
+ if ((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)) return;
+ entity fall = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS) ? GS_FALL_METAL : GS_FALL;
+ GlobalSound(fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
#endif
}
#ifndef SOUND_H
#define SOUND_H
+// negative = SVQC autochannels
+// positive = one per entity
+
const int CH_INFO = 0;
-const int CH_TRIGGER = -3;
const int CH_WEAPON_A = -1;
+const int CH_WEAPON_B = -1;
const int CH_WEAPON_SINGLE = 1;
const int CH_VOICE = -2;
-const int CH_BGM_SINGLE = 8;
-const int CH_AMBIENT = -9;
+// const int CH_VOICE_SINGLE = 2;
+const int CH_TRIGGER = -3;
const int CH_TRIGGER_SINGLE = 3;
const int CH_SHOTS = -4;
const int CH_SHOTS_SINGLE = 4;
-const int CH_WEAPON_B = -1;
+// const int CH_TUBA = -5;
+const int CH_TUBA_SINGLE = 5;
const int CH_PAIN = -6;
const int CH_PAIN_SINGLE = 6;
const int CH_PLAYER = -7;
const int CH_PLAYER_SINGLE = 7;
-const int CH_TUBA_SINGLE = 5;
+// const int CH_BGM_SINGLE = -8;
+const int CH_BGM_SINGLE = 8;
+const int CH_AMBIENT = -9;
+// const int CH_AMBIENT_SINGLE = 9;
const float ATTEN_NONE = 0;
const float ATTEN_MIN = 0.015625;
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));
}
}
#ifdef CSQC
#define REGISTER_NET_LINKED(id) \
- [[accumulate]] NET_HANDLE(id, bool) \
+ [[accumulate]] NET_HANDLE(id, bool isnew) \
{ \
this = self; \
this.sourceLocFile = __FILE__; \
this.sourceLocLine = __LINE__; \
+ if (!this) isnew = true; \
} \
REGISTER(LinkedEntities, NET, id, m_id, new(net_linked_packet)) \
{ \
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;
float autocvar_sv_gameplayfix_gravityunaffectedbyticrate;
bool autocvar_sv_gameplayfix_upwardvelocityclearsongroundflag;
float autocvar_g_trueaim_minrange;
-bool autocvar_g_debug_defaultsounds;
float autocvar_g_grab_range;
int autocvar_g_max_info_autoscreenshot;
bool autocvar_physics_ode;
#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"
precache_model(modelname);
_setmodel(e, modelname);
player_setupanimsformodel();
- UpdatePlayerSounds(e);
}
/*
if(chmdl || oldskin != player.skin) // model or skin has changed
{
player.species = player_getspecies(player); // update species
- UpdatePlayerSounds(player); // update skin sounds
}
if(!teamplay)
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)));
if(self.weaponorder_byimpulse)
strunzone(self.weaponorder_byimpulse);
- ClearPlayerSounds(self);
-
if(self.personal)
remove(self.personal);
return ret;
}
-
-int GetVoiceMessageVoiceType(string type)
-{
- if (type == "taunt") return VOICETYPE_TAUNT;
- if (type == "teamshoot") return VOICETYPE_LASTATTACKER;
- return VOICETYPE_TEAMRADIO;
-}
-
-.string GetVoiceMessageSampleField(string type)
-{
- GetPlayerSoundSampleField_notFound = false;
- switch (type)
- {
-#define X(m) case #m: return playersound_##m;
- ALLVOICEMSGS(X)
-#undef X
- }
- GetPlayerSoundSampleField_notFound = true;
- return playersound_taunt;
-}
-
-.string GetPlayerSoundSampleField(string type)
-{
- GetPlayerSoundSampleField_notFound = false;
- switch (type)
- {
-#define X(m) case #m: return playersound_##m;
- ALLPLAYERSOUNDS(X)
-#undef X
- }
- GetPlayerSoundSampleField_notFound = true;
- return playersound_taunt;
-}
-
-void PrecacheGlobalSound(string sample)
-{
- int n;
- {
- string s = cdr(sample);
- if (s) n = stof(s);
- else n = 0;
- }
- sample = car(sample);
- if (n > 0)
- {
- for (int i = 1; i <= n; ++i)
- precache_sound(sprintf("%s%d.wav", sample, i));
- }
- else
- {
- precache_sound(sprintf("%s.wav", sample));
- }
-}
-
-string allvoicesamples;
-
-void PrecachePlayerSounds(string f)
-{
- int fh = fopen(f, FILE_READ);
- if (fh < 0)
- {
- LOG_WARNINGF("Player sound file not found: %s\n", f);
- return;
- }
- for (string s; (s = fgets(fh)); )
- {
- int n = tokenize_console(s);
- if (n != 3)
- {
- if (n != 0) LOG_WARNINGF("Invalid sound info line: %s\n", s);
- continue;
- }
- string file = argv(1);
- string variants = argv(2);
- PrecacheGlobalSound(strcat(file, " ", variants));
- }
- fclose(fh);
-
- if (!allvoicesamples)
- {
-#define X(m) allvoicesamples = strcat(allvoicesamples, " ", #m);
- ALLVOICEMSGS(X)
-#undef X
- allvoicesamples = strzone(substring(allvoicesamples, 1, -1));
- }
-}
-
-void ClearPlayerSounds(entity this)
-{
-#define X(m) if (this.playersound_##m) { strunzone(this.playersound_##m); this.playersound_##m = string_null; }
- ALLPLAYERSOUNDS(X)
- ALLVOICEMSGS(X)
-#undef X
-}
-
-bool LoadPlayerSounds(string f, bool strict)
-{
- SELFPARAM();
- int fh = fopen(f, FILE_READ);
- if (fh < 0)
- {
- if (strict) LOG_WARNINGF("Player sound file not found: %s\n", f);
- return false;
- }
- for (string s; (s = fgets(fh)); )
- {
- int n = tokenize_console(s);
- if (n != 3)
- {
- if (n != 0) LOG_WARNINGF("Invalid sound info line: %s\n", s);
- continue;
- }
- string key = argv(0);
- var .string field = GetPlayerSoundSampleField(key);
- if (GetPlayerSoundSampleField_notFound) field = GetVoiceMessageSampleField(key);
- if (GetPlayerSoundSampleField_notFound)
- {
- LOG_TRACEF("Invalid sound info field: %s\n", key);
- continue;
- }
- string file = argv(1);
- string variants = argv(2);
- if (self.(field)) strunzone(self.(field));
- self.(field) = strzone(strcat(file, " ", variants));
- }
- fclose(fh);
- return true;
-}
-
-.int modelindex_for_playersound;
-.int skin_for_playersound;
-
-void UpdatePlayerSounds(entity this)
-{
- if (this.modelindex == this.modelindex_for_playersound && this.skin == this.skin_for_playersound) return;
- this.modelindex_for_playersound = this.modelindex;
- this.skin_for_playersound = this.skin;
- ClearPlayerSounds(this);
- LoadPlayerSounds("sound/player/default.sounds", true);
- if (autocvar_g_debug_defaultsounds) return;
- if (!LoadPlayerSounds(get_model_datafilename(this.model, this.skin, "sounds"), false))
- LoadPlayerSounds(get_model_datafilename(this.model, 0, "sounds"), true);
-}
-
-void _GlobalSound(string sample, int chan, int voicetype, bool fake)
-{
- SELFPARAM();
- if (sample == "") return;
- int n;
- {
- string s = cdr(sample);
- if (s) n = stof(s);
- else n = 0;
- }
- sample = car(sample);
- if (n > 0) sample = sprintf("%s%d.wav", sample, floor(random() * n + 1)); // randomization
- else sample = sprintf("%s.wav", sample);
- switch (voicetype)
- {
- case VOICETYPE_LASTATTACKER_ONLY:
- case VOICETYPE_LASTATTACKER:
- {
- if (!fake)
- {
- if (!this.pusher) break;
- msg_entity = this.pusher;
- if (IS_REAL_CLIENT(msg_entity))
- {
- float atten = (msg_entity.cvar_cl_voice_directional == 1) ? ATTEN_MIN : ATTEN_NONE;
- soundto(MSG_ONE, this, chan, sample, VOL_BASEVOICE, atten);
- }
- }
- if (voicetype == VOICETYPE_LASTATTACKER_ONLY) break;
- msg_entity = this;
- if (IS_REAL_CLIENT(msg_entity)) soundto(MSG_ONE, this, chan, sample, VOL_BASE, ATTEN_NONE);
- break;
- }
- case VOICETYPE_TEAMRADIO:
- {
- #define X() \
- do \
- { \
- float atten = (msg_entity.cvar_cl_voice_directional == 1) ? ATTEN_MIN : ATTEN_NONE; \
- soundto(MSG_ONE, this, chan, sample, VOL_BASEVOICE, atten); \
- } \
- while (0)
-
- if (fake) { msg_entity = this; X(); }
- else
- {
- FOR_EACH_REALCLIENT(msg_entity)
- {
- if (!teamplay || msg_entity.team == this.team) X();
- }
- }
- #undef X
- break;
- }
- case VOICETYPE_AUTOTAUNT:
- case VOICETYPE_TAUNT:
- {
- if (voicetype == VOICETYPE_AUTOTAUNT) if (!sv_autotaunt) { break; }else {}
- else if (IS_PLAYER(this) && this.deadflag == DEAD_NO) animdecide_setaction(this, ANIMACTION_TAUNT, true);
- if (!sv_taunt) break;
- if (autocvar_sv_gentle) break;
- float tauntrand = 0;
- if (voicetype == VOICETYPE_AUTOTAUNT) tauntrand = random();
- #define X() \
- do \
- { \
- if (voicetype != VOICETYPE_AUTOTAUNT || tauntrand < msg_entity.cvar_cl_autotaunt) \
- { \
- float atten = (msg_entity.cvar_cl_voice_directional >= 1) \
- ? bound(ATTEN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTEN_MAX) \
- : ATTEN_NONE; \
- soundto(MSG_ONE, this, chan, sample, VOL_BASEVOICE, atten); \
- } \
- } \
- while (0)
- if (fake)
- {
- msg_entity = this;
- X();
- }
- else
- {
- FOR_EACH_REALCLIENT(msg_entity)
- {
- X();
- }
- }
- #undef X
- break;
- }
- case VOICETYPE_PLAYERSOUND:
- {
- if (fake)
- {
- msg_entity = this;
- soundto(MSG_ONE, this, chan, sample, VOL_BASE, ATTEN_NORM);
- }
- else
- {
- _sound(this, chan, sample, VOL_BASE, ATTEN_NORM);
- }
- break;
- }
- default:
- {
- backtrace("Invalid voice type!");
- break;
- }
- }
-}
-
-void PlayerSound(.string samplefield, int chan, float voicetype)
-{
- SELFPARAM();
- _GlobalSound(this.(samplefield), chan, voicetype, false);
-}
-
-void VoiceMessage(string type, string msg)
-{
- SELFPARAM();
- var .string sample = GetVoiceMessageSampleField(type);
- if (GetPlayerSoundSampleField_notFound)
- {
- sprint(this, sprintf("Invalid voice. Use one of: %s\n", allvoicesamples));
- return;
- }
- int voicetype = GetVoiceMessageVoiceType(type);
- bool ownteam = (voicetype == VOICETYPE_TEAMRADIO);
- int flood = Say(this, ownteam, world, msg, true);
- bool fake;
- if (IS_SPEC(this) || IS_OBSERVER(this) || flood < 0) fake = true;
- else if (flood > 0) fake = false;
- else return;
- _GlobalSound(this.(sample), CH_VOICE, voicetype, fake);
-}
.float muted;
int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
-// player sounds, voice messages
-// TODO implemented fall and falling
-#define ALLPLAYERSOUNDS(X) \
- X(death) \
- X(drown) \
- X(fall) \
- X(falling) \
- X(gasp) \
- X(jump) \
- X(pain100) \
- X(pain25) \
- X(pain50) \
- X(pain75)
-
-#define ALLVOICEMSGS(X) \
- X(attack) \
- X(attackinfive) \
- X(coverme) \
- X(defend) \
- X(freelance) \
- X(incoming) \
- X(meet) \
- X(needhelp) \
- X(seenflag) \
- X(taunt) \
- X(teamshoot)
-
-// reserved sound names for the future (some models lack sounds for them):
-// _VOICEMSG(flagcarriertakingdamage)
-// _VOICEMSG(getflag)
-// reserved sound names for the future (ALL models lack sounds for them):
-// _VOICEMSG(affirmative)
-// _VOICEMSG(attacking)
-// _VOICEMSG(defending)
-// _VOICEMSG(roaming)
-// _VOICEMSG(onmyway)
-// _VOICEMSG(droppedflag)
-// _VOICEMSG(negative)
-// _VOICEMSG(seenenemy)
-
-#define X(m) .string playersound_##m;
-ALLPLAYERSOUNDS(X)
-ALLVOICEMSGS(X)
-#undef X
-
-bool GetPlayerSoundSampleField_notFound;
-float GetVoiceMessageVoiceType(string type);
-.string GetVoiceMessageSampleField(string type);
-.string GetPlayerSoundSampleField(string type);
-void PrecacheGlobalSound(string samplestring);
-void PrecachePlayerSounds(string f);
-void ClearPlayerSounds(entity this);
-float LoadPlayerSounds(string f, bool strict);
-void UpdatePlayerSounds(entity this);
-#define FakeGlobalSound(sample, chan, voicetype) _GlobalSound(sample, chan, voicetype, true)
-void _GlobalSound(string sample, float chan, float voicetype, bool fake);
-#define GlobalSound(def, chan, voicetype) _GlobalSound((def).m_globalsoundstr, chan, voicetype, false)
-void PlayerSound(.string samplefield, float chan, float voicetype);
-void VoiceMessage(string type, string msg);
-
-.string m_globalsoundstr;
-REGISTRY(GlobalSounds, BITS(8) - 1)
-#define GlobalSounds_from(i) _GlobalSounds_from(i, NULL)
-#define REGISTER_GLOBALSOUND(id, str) \
- REGISTER(GlobalSounds, GS, id, m_id, new(GlobalSound)) \
- { \
- make_pure(this); \
- this.m_globalsoundstr = str; \
- }
-REGISTER_REGISTRY(GlobalSounds)
-REGISTRY_SORT(GlobalSounds)
-REGISTRY_CHECK(GlobalSounds)
-PRECACHE(GlobalSounds)
-{
- FOREACH(GlobalSounds, true, LAMBDA(PrecacheGlobalSound(it.m_globalsoundstr)));
-}
-
-REGISTER_GLOBALSOUND(STEP, "misc/footstep0 6")
-REGISTER_GLOBALSOUND(STEP_METAL, "misc/metalfootstep0 6")
-REGISTER_GLOBALSOUND(FALL, "misc/hitground 4")
-REGISTER_GLOBALSOUND(FALL_METAL, "misc/metalhitground 4")
-
#endif
{
if (argv(1) != "")
{
- if (argc >= 3) VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
- else VoiceMessage(argv(1), "");
+ entity e = GetVoiceMessage(argv(1));
+ if (!e)
+ {
+ sprint(this, sprintf("Invalid voice. Use one of: %s\n", allvoicesamples));
+ return;
+ }
+ if (argc >= 3) VoiceMessage(e, substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
+ else VoiceMessage(e, "");
return;
}
float next_pingtime;
-const float VOICETYPE_PLAYERSOUND = 10;
-const float VOICETYPE_TEAMRADIO = 11;
-const float VOICETYPE_LASTATTACKER = 12;
-const float VOICETYPE_LASTATTACKER_ONLY = 13;
-const float VOICETYPE_AUTOTAUNT = 14;
-const float VOICETYPE_TAUNT = 15;
-
// autotaunt system
.float cvar_cl_autotaunt;
.float cvar_cl_voice_directional;
+++ /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
warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
}
-void PrecachePlayerSounds(string f);
void precache_playermodel(string m)
{
float globhandle, i, n;
#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"