From 0e47249e6d6093c20f8dd8fb3b31493a81a7d479 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 25 May 2020 20:05:57 +1000 Subject: [PATCH] Experimental port of stats (networked variables) to QC --- qcsrc/client/hud/panel/ammo.qc | 2 +- qcsrc/client/hud/panel/healtharmor.qc | 4 +- qcsrc/client/hud/panel/weapons.qc | 2 +- qcsrc/client/view.qc | 1 - qcsrc/common/state.qc | 2 + qcsrc/common/stats.qh | 11 ++ qcsrc/common/weapons/all.qc | 12 +- qcsrc/dpdefs/csprogsdefs.qh | 16 ++ qcsrc/lib/stats.qh | 264 +++++++++++++++----------- 9 files changed, 187 insertions(+), 127 deletions(-) diff --git a/qcsrc/client/hud/panel/ammo.qc b/qcsrc/client/hud/panel/ammo.qc index 5abbbf036b..8ae792e7f0 100644 --- a/qcsrc/client/hud/panel/ammo.qc +++ b/qcsrc/client/hud/panel/ammo.qc @@ -48,7 +48,7 @@ void DrawAmmoItem(vector myPos, vector mySize, int ammoType, bool isCurrent, boo ammo = 60; } else - ammo = getstati(GetAmmoStat(ammoType)); + ammo = GetAmmoStat(ammoType); if(!isCurrent) { diff --git a/qcsrc/client/hud/panel/healtharmor.qc b/qcsrc/client/hud/panel/healtharmor.qc index b1a9311939..ecf4d20aa3 100644 --- a/qcsrc/client/hud/panel/healtharmor.qc +++ b/qcsrc/client/hud/panel/healtharmor.qc @@ -229,7 +229,7 @@ void HUD_HealthArmor() HUD_Panel_DrawProgressBar(pos + health_offset, mySize, autocvar_hud_panel_healtharmor_progressbar_health, p_health/maxhealth, is_vertical, health_baralign, autocvar_hud_progressbar_health_color, autocvar_hud_progressbar_alpha * panel_fg_alpha * pain_health_alpha, DRAWFLAG_NORMAL); } if(autocvar_hud_panel_healtharmor_text) - DrawNumIcon(pos + health_offset, mySize, health, "health", is_vertical, health_iconalign, HUD_Get_Num_Color(health, maxhealth), 1); + DrawNumIcon(pos + health_offset, mySize, floor(health), "health", is_vertical, health_iconalign, HUD_Get_Num_Color(health, maxhealth), 1); } //if(armor) @@ -276,7 +276,7 @@ void HUD_HealthArmor() } if(!autocvar_hud_panel_healtharmor_progressbar || p_armor) if(autocvar_hud_panel_healtharmor_text) - DrawNumIcon(pos + armor_offset, mySize, armor, "armor", is_vertical, armor_iconalign, HUD_Get_Num_Color(armor, maxarmor), 1); + DrawNumIcon(pos + armor_offset, mySize, floor(armor), "armor", is_vertical, armor_iconalign, HUD_Get_Num_Color(armor, maxarmor), 1); } vector cell_size = mySize; diff --git a/qcsrc/client/hud/panel/weapons.qc b/qcsrc/client/hud/panel/weapons.qc index f7444daaa3..fc8250ee7c 100644 --- a/qcsrc/client/hud/panel/weapons.qc +++ b/qcsrc/client/hud/panel/weapons.qc @@ -531,7 +531,7 @@ void HUD_Weapons() if(!infinite_ammo && autocvar_hud_panel_weapons_ammo && (it.ammo_type != RES_NONE)) { float ammo_full; - a = getstati(GetAmmoStat(it.ammo_type)); // how much ammo do we have? + a = floor(GetAmmoStat(it.ammo_type)); // how much ammo do we have? if(a > 0) { diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index 57cd15a78b..5042515ee2 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -2300,7 +2300,6 @@ void CSQC_UpdateView(entity this, float w, float h) ++framecount; - stats_get(); hud = STAT(HUD); ReplicateVars(false); diff --git a/qcsrc/common/state.qc b/qcsrc/common/state.qc index 14b22e991c..a61047549b 100644 --- a/qcsrc/common/state.qc +++ b/qcsrc/common/state.qc @@ -52,6 +52,7 @@ void ClientState_attach(entity this) entcs_attach(this); anticheat_init(this); W_HitPlotOpen(this); + Stats_new(this); } void bot_clientdisconnect(entity this); @@ -72,6 +73,7 @@ void ClientState_detach(entity this) entcs_detach(this); delete(CS(this)); this._cs = NULL; + Stats_delete(this); bot_clientdisconnect(this); diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index c77ca16be2..4489328b3a 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -19,6 +19,7 @@ const int MAX_CL_STATS = 256; // const int STAT_ITEMS = 15; // .items | .items2 << 23 | serverflags << 28 // const int STAT_VIEWHEIGHT = 16; +#if 0 #if defined(CSQC) #define g_stat_HEALTH getstati(STAT_HEALTH) #define g_stat_ARMOR getstati(STAT_ARMOR) @@ -38,6 +39,16 @@ const int MAX_CL_STATS = 256; #define stat_ITEMS items #define stat_VIEWHEIGHT view_ofs_z #endif +#endif + +REGISTER_STAT(HEALTH, int, this.health) +REGISTER_STAT(ARMOR, int, this.armorvalue) +REGISTER_STAT(SHELLS, int, this.ammo_shells) +REGISTER_STAT(NAILS, int, this.ammo_nails) +REGISTER_STAT(ROCKETS, int, this.ammo_rockets) +REGISTER_STAT(CELLS, int, this.ammo_cells) +REGISTER_STAT(ITEMS, int, this.items) +REGISTER_STAT(VIEWHEIGHT, int, this.view_ofs_z) #ifdef SVQC /// all the weapons actually spawned in the map, does not include filtered items diff --git a/qcsrc/common/weapons/all.qc b/qcsrc/common/weapons/all.qc index a8d1376d1d..04cbd6dd61 100644 --- a/qcsrc/common/weapons/all.qc +++ b/qcsrc/common/weapons/all.qc @@ -258,12 +258,12 @@ int GetAmmoStat(int ammotype) { switch (ammotype) { - case RES_SHELLS: return STAT_SHELLS; - case RES_BULLETS: return STAT_NAILS; - case RES_ROCKETS: return STAT_ROCKETS; - case RES_CELLS: return STAT_CELLS; - case RES_PLASMA: return STAT_PLASMA.m_id; - case RES_FUEL: return STAT_FUEL.m_id; + case RES_SHELLS: return STAT(SHELLS); + case RES_BULLETS: return STAT(NAILS); + case RES_ROCKETS: return STAT(ROCKETS); + case RES_CELLS: return STAT(CELLS); + case RES_PLASMA: return STAT(PLASMA); + case RES_FUEL: return STAT(FUEL); default: return -1; } } diff --git a/qcsrc/dpdefs/csprogsdefs.qh b/qcsrc/dpdefs/csprogsdefs.qh index eaea70f5e9..9756c07458 100644 --- a/qcsrc/dpdefs/csprogsdefs.qh +++ b/qcsrc/dpdefs/csprogsdefs.qh @@ -18,6 +18,14 @@ #define STAT_MOVEVARS_TICRATE _STAT_MOVEVARS_TICRATE #define STAT_MOVEVARS_TIMESCALE _STAT_MOVEVARS_TIMESCALE #define STAT_MOVEVARS_GRAVITY _STAT_MOVEVARS_GRAVITY +#define STAT_HEALTH _STAT_HEALTH +#define STAT_ARMOR _STAT_ARMOR +#define STAT_SHELLS _STAT_SHELLS +#define STAT_NAILS _STAT_NAILS +#define STAT_ROCKETS _STAT_ROCKETS +#define STAT_CELLS _STAT_CELLS +#define STAT_ITEMS _STAT_ITEMS +#define STAT_VIEWHEIGHT _STAT_VIEWHEIGHT #include "upstream/csprogsdefs.qc" @@ -37,6 +45,14 @@ #undef STAT_MOVEVARS_TICRATE #undef STAT_MOVEVARS_TIMESCALE #undef STAT_MOVEVARS_GRAVITY +#undef STAT_HEALTH +#undef STAT_ARMOR +#undef STAT_SHELLS +#undef STAT_NAILS +#undef STAT_ROCKETS +#undef STAT_CELLS +#undef STAT_ITEMS +#undef STAT_VIEWHEIGHT #define use use1 .void(entity this, entity actor, entity trigger) use; diff --git a/qcsrc/lib/stats.qh b/qcsrc/lib/stats.qh index 5a2b53b69f..3727286197 100644 --- a/qcsrc/lib/stats.qh +++ b/qcsrc/lib/stats.qh @@ -8,148 +8,77 @@ .int m_id; USING(vectori, vector); -const int STATS_ENGINE_RESERVE = 32; -// must be listed in ascending order -#define MAGIC_STATS(_, x) \ - _(x, MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, 220) \ - _(x, MOVEVARS_AIRCONTROL_PENALTY, 221) \ - _(x, MOVEVARS_AIRSPEEDLIMIT_NONQW, 222) \ - _(x, MOVEVARS_AIRSTRAFEACCEL_QW, 223) \ - _(x, MOVEVARS_AIRCONTROL_POWER, 224) \ - _(x, MOVEFLAGS, 225) \ - _(x, MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, 226) \ - _(x, MOVEVARS_WARSOWBUNNY_ACCEL, 227) \ - _(x, MOVEVARS_WARSOWBUNNY_TOPSPEED, 228) \ - _(x, MOVEVARS_WARSOWBUNNY_TURNACCEL, 229) \ - _(x, MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, 230) \ - _(x, MOVEVARS_AIRSTOPACCELERATE, 231) \ - _(x, MOVEVARS_AIRSTRAFEACCELERATE, 232) \ - _(x, MOVEVARS_MAXAIRSTRAFESPEED, 233) \ - _(x, MOVEVARS_AIRCONTROL, 234) \ - _(x, FRAGLIMIT, 235) \ - _(x, TIMELIMIT, 236) \ - _(x, MOVEVARS_WALLFRICTION, 237) \ - _(x, MOVEVARS_FRICTION, 238) \ - _(x, MOVEVARS_WATERFRICTION, 239) \ - _(x, MOVEVARS_TICRATE, 240) \ - _(x, MOVEVARS_TIMESCALE, 241) \ - _(x, MOVEVARS_GRAVITY, 242) \ - _(x, MOVEVARS_STOPSPEED, 243) \ - _(x, MOVEVARS_MAXSPEED, 244) \ - _(x, MOVEVARS_SPECTATORMAXSPEED, 245) \ - _(x, MOVEVARS_ACCELERATE, 246) \ - _(x, MOVEVARS_AIRACCELERATE, 247) \ - _(x, MOVEVARS_WATERACCELERATE, 248) \ - _(x, MOVEVARS_ENTGRAVITY, 249) \ - _(x, MOVEVARS_JUMPVELOCITY, 250) \ - _(x, MOVEVARS_EDGEFRICTION, 251) \ - _(x, MOVEVARS_MAXAIRSPEED, 252) \ - _(x, MOVEVARS_STEPHEIGHT, 253) \ - _(x, MOVEVARS_AIRACCEL_QW, 254) \ - _(x, MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, 255) \ - /**/ - -int g_magic_stats_hole = 0; - -#define MAGIC_STATS_FIX_MANUAL(it, var, id) \ - if (it.registered_id == "STAT_" #var) { --g_magic_stats_hole; it.m_id = id; } else - -#define MAGIC_STATS_FIX_AUTO(it, var, id) \ - if (it.m_id == id) { ++g_magic_stats_hole; ++it.m_id; } - -#define MAGIC_STATS_FIX(it) \ - it.m_id += g_magic_stats_hole; \ - MAGIC_STATS(MAGIC_STATS_FIX_MANUAL, it) { MAGIC_STATS(MAGIC_STATS_FIX_AUTO, it) } +REGISTRY(Stats, 256) +#define Stats_from(i) _Stats_from(i, NULL) +REGISTER_REGISTRY(Stats) +REGISTRY_SORT(Stats) +REGISTRY_CHECK(Stats) +STATIC_INIT(Stats_renumber) { FOREACH(Stats, true, it.m_id = i); } #define REGISTER_STAT(...) EVAL_REGISTER_STAT(OVERLOAD(REGISTER_STAT, __VA_ARGS__)) #define EVAL_REGISTER_STAT(...) __VA_ARGS__ + +#if defined(CSQC) + #define stats_get_int() ReadInt24_t() + #define stats_get_bool() boolean(ReadByte()) + #define stats_get_float() ReadFloat() + #define stats_get_vector() ReadVector() + #define stats_get_vectori() ReadInt72_t() + +.void(entity ent) m_receive; +#elif defined(SVQC) + #define stats_write_int(chan,id,ent) WriteInt24_t(chan, STAT(id, ent)) + #define stats_write_bool(chan,id,ent) WriteByte(chan, STAT(id, ent)) + #define stats_write_float(chan,id,ent) WriteFloat(chan, STAT(id, ent)) + #define stats_write_vector(chan,id,ent) WriteVector(chan, STAT(id, ent)) + #define stats_write_vectori(chan,id,ent) WriteInt72_t(chan, STAT(id, ent)) + +.bool(entity ent, entity player) m_check; +.void(entity ent, entity player) m_set; +.void(int chan, entity ent) m_send; +#endif + #if defined(CSQC) - /** Get all stats and store them as globals, access with `STAT(ID)` */ - void stats_get() {} #define STAT(...) EVAL_STAT(OVERLOAD(STAT, __VA_ARGS__)) #define EVAL_STAT(...) __VA_ARGS__ #define STAT_1(id) (RVALUE, _STAT(id)) #define STAT_2(id, cl) STAT_1(id) - #define getstat_int(id) getstati(id, 0, 24) - #define getstat_bool(id) boolean(getstati(id)) - #define getstat_float(id) getstatf(id) - #define getstat_vector(id) vec3(getstat_float(id + 0), getstat_float(id + 1), getstat_float(id + 2)) - #define getstat_vectori(id) vec3(getstat_int(id + 0), getstat_int(id + 1), getstat_int(id + 2)) - #define _STAT(id) g_stat_##id #define REGISTER_STAT_2(id, T) \ T _STAT(id); \ /* T CAT(_STAT(id), _prev); */ \ + void STAT_##id##_receive(entity data) { _STAT(id) = stats_get_##T(); } \ REGISTER(Stats, STAT_##id, m_id, new_pure(stat)) \ { \ - if (#T == "vector" || #T == "vectori") { \ - REGISTRY_RESERVE(Stats, m_id, STAT_##id, y); \ - REGISTRY_RESERVE(Stats, m_id, STAT_##id, z); \ - } \ - } \ - ACCUMULATE void stats_get() \ - { \ - T it = getstat_##T(STAT_##id.m_id); \ - /* if (it != CAT(_STAT(id), _prev)) \ - CAT(_STAT(id), _prev) = */ _STAT(id) = it; \ + this.m_receive = STAT_##id##_receive; \ } #define REGISTER_STAT_3(x, T, expr) REGISTER_STAT_2(x, T) #elif defined(SVQC) /** Internal use only */ entity STATS; /** Add all registered stats, access with `STAT(ID, player)` or `.type stat = _STAT(ID); player.stat` */ - void stats_add() {} #define STAT(...) EVAL_STAT(OVERLOAD_(STAT, __VA_ARGS__)) #define EVAL_STAT(...) __VA_ARGS__ #define STAT_1(id) (RVALUE, STAT_2(id, STATS)) #define STAT_2(id, cl) (cl)._STAT(id) - #define addstat_int(id, fld) addstat(id, AS_INT, fld) - #define addstat_bool(id, fld) addstat(id, AS_INT, fld) - #define addstat_float(id, fld) addstat(id, AS_FLOAT, fld) - #define addstat_vector(id, fld) MACRO_BEGIN \ - addstat_float(id + 0, fld##_x); \ - addstat_float(id + 1, fld##_y); \ - addstat_float(id + 2, fld##_z); \ - MACRO_END - #define addstat_vectori(id, fld) MACRO_BEGIN \ - addstat_int(id + 0, fld##_x); \ - addstat_int(id + 1, fld##_y); \ - addstat_int(id + 2, fld##_z); \ - MACRO_END - const int AS_STRING = 1; - const int AS_INT = 2; - const int AS_FLOAT = 8; - - .int __stat_null; STATIC_INIT(stats) { - STATS = new(stats); - // Prevent engine stats being sent - int r = STATS_ENGINE_RESERVE; - for (int i = 0, n = 256 - r; i < n; ++i) { - #define X(_, name, id) if (i == id) continue; - MAGIC_STATS(X, ); - #undef X - addstat(r + i, AS_INT, __stat_null); - } + STATS = new_pure(stats); } #define _STAT(id) stat_##id #define REGISTER_STAT_2(id, T) \ .T _STAT(id); \ + bool STAT_##id##_check(entity ent, entity player) { return STAT(id, ent) != STAT(id, player); } \ + void STAT_##id##_set(entity ent, entity player) { STAT(id, ent) = STAT(id, player); } \ + void STAT_##id##_send(int chan, entity ent) { stats_write_##T(chan, id, ent); } \ REGISTER(Stats, STAT_##id, m_id, new_pure(stat)) \ { \ - if (#T == "vector" || #T == "vectori") { \ - REGISTRY_RESERVE(Stats, m_id, STAT_##id, y); \ - REGISTRY_RESERVE(Stats, m_id, STAT_##id, z); \ - } \ - } \ - ACCUMULATE void stats_add() \ - { \ - .T fld = _STAT(id); \ - addstat_##T(STAT_##id.m_id, fld); \ + this.m_check = STAT_##id##_check; \ + this.m_set = STAT_##id##_set; \ + this.m_send = STAT_##id##_send; \ } void GlobalStats_update(entity this) {} void GlobalStats_updateglobal() {} @@ -164,17 +93,120 @@ int g_magic_stats_hole = 0; #define REGISTER_STAT_3(id, T, expr) #endif -REGISTRY(Stats, 256 - STATS_ENGINE_RESERVE) -REGISTER_REGISTRY(Stats) -REGISTRY_SORT(Stats) -REGISTRY_CHECK(Stats) -STATIC_INIT(Stats_renumber) +#ifdef GAMEQC +/** Player stats */ +.entity stats; + +REGISTER_NET_LINKED(ENT_CLIENT_STATS) + +const int Stats_groups_minor = 24; // exactly 1 byte +const int Stats_groups_major = 11; // ceil(Stats_MAX / Stats_groups_minor) + +#define G_MAJOR(id) (floor((id) / Stats_groups_minor)) +#define G_MINOR(id) ((id) % Stats_groups_minor) +#endif + +#ifdef CSQC +NET_HANDLE(ENT_CLIENT_STATS, bool isnew) +{ + make_pure(this); + const int majorBits = ReadByte(); + for (int i = 0; i < Stats_groups_major; ++i) { + if (!(majorBits & BIT(i))) { + continue; + } + const int minorBits = ReadInt24_t(); + for (int j = 0; j < Stats_groups_minor; ++j) { + if (!(minorBits & BIT(j))) { + continue; + } + const entity it = Stats_from(Stats_groups_minor * i + j); + it.m_receive(it); + } + } + return true; +} +#endif + +#ifdef SVQC +int statsminorBitsArr[Stats_groups_major]; +void Stats_Write(entity data) +{ + if (!data) { + WriteShort(MSG_ENTITY, 0); + return; + } + TC(entity, data); + + for (int i = 0; i < Stats_groups_major; ++i) + statsminorBitsArr[i] = 0; + + int majorBits = 0; + FOREACH(Stats, true, { + const bool changed = it.m_check(data, data.owner); + if (changed) { + it.m_set(data, data.owner); + int maj = G_MAJOR(it.m_id); + majorBits = BITSET(majorBits, BIT(maj), true); + statsminorBitsArr[maj] = BITSET(statsminorBitsArr[maj], BIT(G_MINOR(it.m_id)), true); + } + }); + WriteByte(MSG_ENTITY, majorBits); + + for (int i = 0; i < Stats_groups_major; ++i) + { + if (!(majorBits & BIT(i))) + continue; + + const int minorBits = statsminorBitsArr[i]; + WriteInt24_t(MSG_ENTITY, minorBits); + for (int j = 0; j < Stats_groups_minor; ++j) + { + if (!(minorBits & BIT(j))) + continue; + + const entity it = Stats_from(Stats_groups_minor * i + j); + it.m_send(MSG_ENTITY, data); + } + } +} +#endif + +#undef G_MAJOR +#undef G_MINOR + +#ifdef SVQC +bool Stats_Send(entity this, entity to, int sf) +{ + TC(entity, this); + WriteHeader(MSG_ENTITY, ENT_CLIENT_STATS); + TC(PlayerState, this.owner); + Stats_Write(this); + return true; +} + +void Stats_update(entity e) { e.stats.SendFlags = 0xFFFFFF; } +void Stats_checkupdate(entity this) { FOREACH(Stats, true, { - it.m_id = STATS_ENGINE_RESERVE + i; - MAGIC_STATS_FIX(it); + const bool changed = it.m_check(this, this.owner); + if(changed) + { + Stats_update(this.owner); + break; // no need to keep looping once we've found a stat that needs updating + } }); + + this.nextthink = time; } -#ifdef SVQC -STATIC_INIT(stats_add) { stats_add(); } + +void Stats_new(entity this) +{ + entity ent = new_pure(Stats); + setthink(ent, Stats_checkupdate); + ent.nextthink = time; + ent.drawonlytoclient = this; + Net_LinkEntity((ent.owner = this).stats = ent, false, 0, Stats_Send); +} +void Stats_delete(entity e) { delete(e.stats); } #endif -- 2.39.2