From 1a18a8af4786c9e9a40a9f672a98774958f2606c Mon Sep 17 00:00:00 2001 From: Shpoike Date: Fri, 19 Jun 2020 23:19:07 +0100 Subject: [PATCH] Add support for string stats (for csqc's use). --- Quake/cl_main.c | 17 +++++++++---- Quake/cl_parse.c | 16 +++++++++--- Quake/client.h | 2 ++ Quake/gl_model.c | 2 +- Quake/host.c | 7 +++--- Quake/pr_ext.c | 14 ++++++++++- Quake/server.h | 7 ++++-- Quake/sv_main.c | 64 ++++++++++++++++++++++++++++++++++++++++++------ 8 files changed, 104 insertions(+), 25 deletions(-) diff --git a/Quake/cl_main.c b/Quake/cl_main.c index cdc467ee..7098280a 100644 --- a/Quake/cl_main.c +++ b/Quake/cl_main.c @@ -88,6 +88,17 @@ void CL_ClearTrailStates(void) } } +void CL_FreeState(void) +{ + int i; + for (i = 0; i < MAX_CL_STATS; i++) + free(cl.statss[i]); + CL_ClearTrailStates(); + PR_ClearProgs(&cl.qcvm); + free(cl.static_entities); + memset (&cl, 0, sizeof(cl)); +} + /* ===================== CL_ClearState @@ -99,12 +110,8 @@ void CL_ClearState (void) if (!sv.active) Host_ClearMemory (); - CL_ClearTrailStates(); - - PR_ClearProgs(&cl.qcvm); - // wipe the entire cl structure - memset (&cl, 0, sizeof(cl)); + CL_FreeState(); SZ_Clear (&cls.message); diff --git a/Quake/cl_parse.c b/Quake/cl_parse.c index c17daa60..99f788de 100644 --- a/Quake/cl_parse.c +++ b/Quake/cl_parse.c @@ -2154,6 +2154,17 @@ static void CL_ParseStatInt(int stat, int ival) { CL_ParseStatNumeric(stat,ival,ival); } +static void CL_ParseStatString(int stat, const char *str) +{ + if (stat < 0 || stat >= MAX_CL_STATS) + { + Con_DWarning ("svc_updatestat: %i is invalid\n", stat); + return; + } + free(cl.statss[stat]); + cl.statss[stat] = strdup(str); + //hud doesn't know/care about any of these strings so don't bother invalidating anything. +} //mods and servers might not send the \n instantly. //some mods bug out and omit the \n entirely, this function helps prevent the damage from spreading too much. @@ -2651,10 +2662,7 @@ void CL_ParseServerMessage (void) if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) Host_Error ("Received svcfte_updatestatstring but extension not active"); i = MSG_ReadByte (); - if (i >= 0 && i < MAX_CL_STATS) - /*cl.statss[i] =*/ MSG_ReadString (); - else - Con_Warning ("svcfte_updatestatstring: %i is invalid\n", i); + CL_ParseStatString(i, MSG_ReadString()); break; case svcfte_updatestatfloat: if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) diff --git a/Quake/client.h b/Quake/client.h index 317d2707..919fcf05 100644 --- a/Quake/client.h +++ b/Quake/client.h @@ -164,6 +164,7 @@ typedef struct // information for local display int stats[MAX_CL_STATS]; // health, etc float statsf[MAX_CL_STATS]; + char *statss[MAX_CL_STATS]; int items; // inventory bit flags float item_gettime[32]; // cl.time of aquiring item, for blinking float faceanimtime; // use anim frame if cl.time < this @@ -394,6 +395,7 @@ void CL_ParseEffect (qboolean big); void CL_ParseTEnt (void); void CL_UpdateTEnts (void); +void CL_FreeState(void); void CL_ClearState (void); void CL_ClearTrailStates(void); diff --git a/Quake/gl_model.c b/Quake/gl_model.c index 34ab64be..32811ff6 100644 --- a/Quake/gl_model.c +++ b/Quake/gl_model.c @@ -3316,7 +3316,7 @@ void Mod_LoadAliasModel (qmodel_t *mod, void *buffer, int pvtype) for (i=0 ; inumverts ; i++) { - stverts[i].onseam = LittleLong (pinstverts[i].onseam); + stverts[i].onseam = LittleLong (pinstverts[i].onseam); //should only be 0 or ALIAS_ONSEAM. other values (particuarly 1) is a model bug and will be treated as ALIAS_ONSEAM in this implementation. stverts[i].s = LittleLong (pinstverts[i].s); stverts[i].t = LittleLong (pinstverts[i].t); } diff --git a/Quake/host.c b/Quake/host.c index 6295dba2..f39af8dd 100644 --- a/Quake/host.c +++ b/Quake/host.c @@ -589,12 +589,11 @@ void Host_ClearMemory (void) Hunk_FreeToLowMark (host_hunklevel); cls.signon = 0; PR_ClearProgs(&sv.qcvm); - PR_ClearProgs(&cl.qcvm); - free(cl.static_entities); free(sv.static_entities); //spike -- this is dynamic too, now free(sv.ambientsounds); memset (&sv, 0, sizeof(sv)); - memset (&cl, 0, sizeof(cl)); + + CL_FreeState(); } @@ -818,7 +817,7 @@ void _Host_Frame (double time) CL_AccumulateCmd (); - //Run the server+networking (client->server->client), at a different rate from everyt + //Run the server+networking (client->server->client), at a different rate from everything else if (accumtime >= host_netinterval) { float realframetime = host_frametime; diff --git a/Quake/pr_ext.c b/Quake/pr_ext.c index 8041a33c..782eb1dc 100644 --- a/Quake/pr_ext.c +++ b/Quake/pr_ext.c @@ -4529,6 +4529,18 @@ static void PF_cl_getstat_float(void) else G_FLOAT(OFS_RETURN) = cl.statsf[stnum]; } +static void PF_cl_getstat_string(void) +{ + int stnum = G_FLOAT(OFS_PARM0); + if (stnum < 0 || stnum > countof(cl.statss) || !cl.statss[stnum]) + G_INT(OFS_RETURN) = 0; + else + { + char *result = PR_GetTempString(); + q_strlcpy(result, cl.statss[stnum], STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(result); + } +} struct { @@ -5450,7 +5462,7 @@ static struct // {"drawrotsubpic", PF_NoSSQC, PF_FullCSQCOnly, 0, D("void(vector pivot, vector mins, vector maxs, string pic, vector txmin, vector txsize, vector rgb, vector alphaandangles)", "Overcomplicated draw function for over complicated people. Positions follow drawrotpic, while texture coords follow drawsubpic. Due to argument count limitations in builtins, the alpha value and angles are combined into separate fields of a vector (tip: use fteqcc's [alpha, angle] feature.")}, {"getstati", PF_NoSSQC, PF_cl_getstat_int, 330, D("#define getstati_punf(stnum) (float)(__variant)getstati(stnum)\nint(float stnum)", "Retrieves the numerical value of the given EV_INTEGER or EV_ENTITY stat. Use getstati_punf if you wish to type-pun a float stat as an int to avoid truncation issues in DP.")},// (EXT_CSQC) {"getstatf", PF_NoSSQC, PF_cl_getstat_float,331, D("#define getstatbits getstatf\nfloat(float stnum, optional float firstbit, optional float bitcount)", "Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat (converted into a float, so there are no VM dependancies).")},// (EXT_CSQC) -// {"getstats", PF_NoSSQC, PF_cl_getstat_str, 332, D("string(float stnum)", "Retrieves the value of the given EV_STRING stat, as a tempstring.\nOlder engines may use 4 consecutive integer stats, with a limit of 15 chars (yes, really. 15.), but "FULLENGINENAME" uses a separate namespace for string stats and has a much higher length limit.")}, + {"getstats", PF_NoSSQC, PF_cl_getstat_string,332, D("string(float stnum)", "Retrieves the value of the given EV_STRING stat, as a tempstring.\nString stats use a separate pool of stats from numeric ones.\n")}, // {"getplayerstat", PF_NoSSQC, PF_FullCSQCOnly, 0, D("__variant(float playernum, float statnum, float stattype)", "Retrieves a specific player's stat, matching the type specified on the server. This builtin is primarily intended for mvd playback where ALL players are known. For EV_ENTITY, world will be returned if the entity is not in the pvs, use type-punning with EV_INTEGER to get the entity number if you just want to see if its set. STAT_ITEMS should be queried as an EV_INTEGER on account of runes and items2 being packed into the upper bits.")}, {"setmodelindex", PF_sv_setmodelindex,PF_cl_setmodelindex,333, D("void(entity e, float mdlindex)", "Sets a model by precache index instead of by name. Otherwise identical to setmodel.")},// // {"modelnameforindex",PF_modelnameforidx,PF_modelnameforidx, 334, D("string(float mdlindex)", "Retrieves the name of the model based upon a precache index. This can be used to reduce csqc network traffic by enabling model matching.")},// diff --git a/Quake/server.h b/Quake/server.h index 24380987..4431f94d 100644 --- a/Quake/server.h +++ b/Quake/server.h @@ -147,9 +147,11 @@ typedef struct client_s qboolean pextknown; unsigned int protocol_pext2; - unsigned int resendstats[MAX_CL_STATS/32]; //the stats which need to be resent. + unsigned int resendstatsnum[MAX_CL_STATS/32]; //the stats which need to be resent. + unsigned int resendstatsstr[MAX_CL_STATS/32]; //the stats which need to be resent. int oldstats_i[MAX_CL_STATS]; //previous values of stats. if these differ from the current values, reflag resendstats. float oldstats_f[MAX_CL_STATS]; //previous values of stats. if these differ from the current values, reflag resendstats. + char *oldstats_s[MAX_CL_STATS]; struct entity_num_state_s{ unsigned int num; //ascending order, there can be gaps. entity_state_t state; @@ -167,7 +169,8 @@ typedef struct client_s //reflagged state includes stats updates, entity updates, and entity removes. int sequence; //to see if its stale float timestamp; - unsigned int resendstats[MAX_CL_STATS/32]; + unsigned int resendstatsnum[MAX_CL_STATS/32]; + unsigned int resendstatsstr[MAX_CL_STATS/32]; struct { unsigned int num; diff --git a/Quake/sv_main.c b/Quake/sv_main.c index cbf533a1..ffddc672 100644 --- a/Quake/sv_main.c +++ b/Quake/sv_main.c @@ -34,7 +34,7 @@ unsigned int sv_protocol_pext2 = PEXT2_SUPPORTED_SERVER; //spike //============================================================================ -void SV_CalcStats(client_t *client, int *statsi, float *statsf) +void SV_CalcStats(client_t *client, int *statsi, float *statsf, const char **statss) { size_t i; edict_t *ent = client->edict; @@ -48,6 +48,7 @@ void SV_CalcStats(client_t *client, int *statsi, float *statsf) memset(statsi, 0, sizeof(*statsi)*MAX_CL_STATS); memset(statsf, 0, sizeof(*statsf)*MAX_CL_STATS); + memset(statss, 0, sizeof(*statss)*MAX_CL_STATS); statsf[STAT_HEALTH] = ent->v.health; // statsf[STAT_FRAGS] = ent->v.frags; //obsolete statsi[STAT_WEAPON] = SV_ModelIndex(PR_GetString(ent->v.weaponmodel)); @@ -133,6 +134,8 @@ void SV_CalcStats(client_t *client, int *statsi, float *statsf) statsf[sv.customstats[i].idx+2] = eval->vector[2]; break; case ev_string: //not supported in this build... send with svcfte_updatestatstring on change, which is annoying. + statss[sv.customstats[i].idx] = PR_GetString(eval->string); + break; case ev_void: //nothing... case ev_field: //panic! everyone panic! case ev_function: //doesn't make much sense @@ -579,6 +582,14 @@ static size_t snapshot_maxents; void SVFTE_DestroyFrames(client_t *client) { + int i; + for (i = 0; i < MAX_CL_STATS; i++) + { + if (!client->oldstats_s[i]) + continue; + free(client->oldstats_s[i]); + client->oldstats_s[i] = 0; + } if (client->previousentities) free(client->previousentities); client->previousentities = NULL; @@ -639,7 +650,10 @@ static void SVFTE_DroppedFrame(client_t *client, int sequence) frame->sequence = -1; //flag their stats for resend for (i = 0; i < MAX_CL_STATS/32; i++) - client->resendstats[i] |= frame->resendstats[i]; + { + client->resendstatsnum[i] |= frame->resendstatsnum[i]; + client->resendstatsstr[i] |= frame->resendstatsstr[i]; + } //flag their various entities as needing a resend too. for (i = 0; i < frame->numents; i++) client->pendingentities_bits[frame->ents[i].num] |= frame->ents[i].bits; @@ -676,9 +690,16 @@ static void SVFTE_WriteStats(client_t *client, sizebuf_t *msg) { int statsi[MAX_CL_STATS]; float statsf[MAX_CL_STATS]; + const char *statss[MAX_CL_STATS]; int i; struct deltaframe_s *frame; int sequence = NET_QSocketGetSequenceOut(client->netconnection); + int maxstats; + + if (client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS) + maxstats = MAX_CL_STATS; + else + maxstats = 32; frame = &client->frames[sequence&(client->numframes-1)]; @@ -686,9 +707,9 @@ static void SVFTE_WriteStats(client_t *client, sizebuf_t *msg) SVFTE_DroppedFrame(client, frame->sequence); //figure out the current values in a nice easy way (yay for copying to make arrays easier!) - SV_CalcStats(client, statsi, statsf); + SV_CalcStats(client, statsi, statsf, statss); - for (i = 0; i < MAX_CL_STATS; i++) + for (i = 0; i < maxstats; i++) { //small cleanup if (!statsi[i]) @@ -701,14 +722,28 @@ static void SVFTE_WriteStats(client_t *client, sizebuf_t *msg) { client->oldstats_i[i] = statsi[i]; client->oldstats_f[i] = statsf[i]; - client->resendstats[i/32] |= 1u<<(i&31); + client->resendstatsnum[i/32] |= 1u<<(i&31); + } + + if (statss[i] || client->oldstats_s[i]) + { + const char *os = client->oldstats_s[i]; + const char *ns = statss[i]; + if (!ns) ns=""; + if (!os) os=""; + if (strcmp(os,ns)) + { + client->resendstatsstr[i/32] |= 1u<<(i&31); + free(client->oldstats_s[i]); + client->oldstats_s[i] = strdup(ns); + } } //if its flagged then unflag it, log it, and send it - if (client->resendstats[i/32] & (1u<<(i&31))) + if (client->resendstatsnum[i/32] & (1u<<(i&31))) { - client->resendstats[i/32] &= ~(1u<<(i&31)); - frame->resendstats[i/32] |= 1u<<(i&31); + client->resendstatsnum[i/32] &= ~(1u<<(i&31)); + frame->resendstatsnum[i/32] |= 1u<<(i&31); if ((double)statsi[i] != statsf[i] && statsf[i]) { //didn't round nicely, so send as a float @@ -732,6 +767,19 @@ static void SVFTE_WriteStats(client_t *client, sizebuf_t *msg) } } } + //if its flagged then unflag it, log it, and send it + if (client->resendstatsstr[i/32] & (1u<<(i&31))) + { + client->resendstatsstr[i/32] &= ~(1u<<(i&31)); + frame->resendstatsstr[i/32] |= 1u<<(i&31); + + MSG_WriteByte(msg, svcfte_updatestatstring); + MSG_WriteByte(msg, i); + if (statss[i]) + MSG_WriteString(msg, statss[i]); + else + MSG_WriteString(msg, NULL); + } } } static void SVFTE_CalcEntityDeltas(client_t *client)