From dafd1fc5f9c86aa80c6582796021c79b35741b67 Mon Sep 17 00:00:00 2001 From: Daniel Svensson Date: Wed, 20 Nov 2024 17:43:06 +0100 Subject: [PATCH] PROTOCOL: Support CSQC. Forward port of Reki's initial implementation. --- src/pr2_cmds.c | 53 +++++++ src/progs.h | 4 + src/server.h | 33 +++++ src/sv_ents.c | 393 ++++++++++++++++++++++++++++++++++++++++++++++++- src/sv_main.c | 14 ++ src/sv_user.c | 57 +++++++ 6 files changed, 553 insertions(+), 1 deletion(-) diff --git a/src/pr2_cmds.c b/src/pr2_cmds.c index 0b2293b5..027c4dba 100644 --- a/src/pr2_cmds.c +++ b/src/pr2_cmds.c @@ -39,6 +39,9 @@ const char *pr2_ent_data_ptr; vm_t *sv_vm = NULL; extern gameData_t gamedata; +#ifdef FTE_PEXT_CSQC +extern sizebuf_t *csqcmsgbuffer; +#endif static int PASSFLOAT(float f) { @@ -57,6 +60,9 @@ static float GETFLOAT(int i) #endif typedef intptr_t (*ext_syscall_t)(intptr_t *arg); +#ifdef FTE_PEXT_CSQC +static intptr_t EXT_SetSendNeeded(intptr_t *args); +#endif static intptr_t EXT_MapExtFieldPtr(intptr_t *args); static intptr_t EXT_SetExtFieldPtr(intptr_t *args); static intptr_t EXT_GetExtFieldPtr(intptr_t *args); @@ -69,6 +75,9 @@ struct {"MapExtFieldPtr", EXT_MapExtFieldPtr}, {"SetExtFieldPtr", EXT_SetExtFieldPtr}, {"GetExtFieldPtr", EXT_GetExtFieldPtr}, +#ifdef FTE_PEXT_CSQC + {"setsendneeded", EXT_SetSendNeeded}, +#endif }; ext_syscall_t ext_syscall_tbl[256]; @@ -1183,6 +1192,9 @@ MESSAGE WRITING #define MSG_ALL 2 // reliable to all #define MSG_INIT 3 // write to the init string #define MSG_MULTICAST 4 // for multicast() +#ifdef FTE_PEXT_CSQC +#define MSG_CSQC 5 // for csqc +#endif sizebuf_t *WriteDest2(int dest) @@ -1219,6 +1231,9 @@ sizebuf_t *WriteDest2(int dest) case MSG_MULTICAST: return &sv.multicast; + case MSG_CSQC: + return csqcmsgbuffer; + default: PR2_RunError ("WriteDest: bad destination"); break; @@ -1996,6 +2011,36 @@ intptr_t PF2_FS_GetFileList(char *path, char *ext, return numfiles; } +#ifdef FTE_PEXT_CSQC +intptr_t EXT_SetSendNeeded(intptr_t *args) +{ + unsigned int subject = args[1]; + unsigned int fl = args[2]; + unsigned int to = args[3]; + + if (!to) + { //broadcast + for (to = 0; to < MAX_CLIENTS; to++) + { + svs.clients[to].csqcentitysendflags[subject] |= fl; + } + } + else + { + to--; + if (to >= MAX_CLIENTS) + { + ; //some kind of error. + } + else + { + svs.clients[to].csqcentitysendflags[subject] |= fl; + } + } + return 0; +} +#endif + // To prevent mods from hardcoding field offsets which would cause engine incompatibilities. static uint32_t GetExtFieldCookie(void) { @@ -2079,6 +2124,14 @@ static intptr_t EXT_MapExtFieldPtr(intptr_t *args) { return offsetof(ext_entvars_t, colourmod) | GetExtFieldCookie(); } + if (!strcmp(key, "SendEntity")) + { + return offsetof(ext_entvars_t, sendentity) | GetExtFieldCookie(); + } + if (!strcmp(key, "pvsflags")) + { + return offsetof(ext_entvars_t, pvsflags) | GetExtFieldCookie(); + } } return 0; diff --git a/src/progs.h b/src/progs.h index 17928f7d..69c0c875 100644 --- a/src/progs.h +++ b/src/progs.h @@ -67,6 +67,10 @@ typedef struct { float alpha; // 0 = opaque, 1 = opaque, 0 < x < 1 translucent float colourmod[3]; // r,g,b [0.0 .. 1.0], > 1 overbright +#ifdef FTE_PEXT_CSQC + int sendentity; // Trigger GAME_CSQCSEND indirection + float pvsflags; // CSQC pvsflags +#endif } ext_entvars_t; typedef struct edict_s diff --git a/src/server.h b/src/server.h index 42e91e31..7a474127 100644 --- a/src/server.h +++ b/src/server.h @@ -130,6 +130,7 @@ typedef struct int static_entity_count; #ifdef FTE_PEXT_CSQC unsigned int csqcchecksum; + unsigned short csqcsendstates[MAX_EDICTS]; #endif } server_t; @@ -187,6 +188,24 @@ typedef struct #define MAX_WEAPONSWITCH_OPTIONS 10 #endif +#ifdef FTE_PEXT_CSQC +// code adapted from Darkplaces +#define SCOPE_WANTREMOVE 1 // Set if a remove has been scheduled. +#define SCOPE_WANTUPDATE 2 // Set if an update has been scheduled. +#define SCOPE_WANTSEND (SCOPE_WANTREMOVE | SCOPE_WANTUPDATE) +#define SCOPE_EXISTED_ONCE 4 // Set if the entity once existed. All these get resent on a full loss. +#define SCOPE_ASSUMED_EXISTING 8 // Set if the entity is currently assumed existing and therefore needs removes. + +#define NUM_CSQCENTITIES_PER_FRAME 256 +typedef struct csqcentityframedb_s +{ + int framenum; + int num; + unsigned short entno[NUM_CSQCENTITIES_PER_FRAME]; + int sendflags[NUM_CSQCENTITIES_PER_FRAME]; +} csqcentityframedb_t; +#endif + typedef struct client_s { sv_client_state_t state; @@ -350,6 +369,16 @@ typedef struct client_s #ifdef FTE_PEXT_CSQC qbool csqcactive; + int csqc_framenum; + int csqc_latestverified; + int csqcnumedicts; + unsigned char csqcentityscope[MAX_EDICTS]; + unsigned int csqcentitysendflags[MAX_EDICTS]; + +#define NUM_CSQCENTITYDB_FRAMES UPDATE_MASK//256 + csqcentityframedb_t csqcentityframehistory[NUM_CSQCENTITYDB_FRAMES]; + int csqcentityframehistory_next; + int csqcentityframe_lastreset; #endif //===== NETWORK ============ @@ -922,6 +951,10 @@ void SV_KickClient(client_t* client, const char* reason); // void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder); void SV_SetVisibleEntitiesForBot (client_t* client); +#ifdef FTE_PEXT_CSQC +int SV_EmitCSQCUpdate(client_t *client, sizebuf_t *msg, int maxsize, int entlist_size, const unsigned short *entlist); +void EntityFrameCSQC_LostFrame(client_t *client, int framenum); +#endif // // sv_nchan.c diff --git a/src/sv_ents.c b/src/sv_ents.c index 63eb8d03..a199b767 100644 --- a/src/sv_ents.c +++ b/src/sv_ents.c @@ -25,6 +25,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //============================================================================= +#ifdef FTE_PEXT_CSQC +#define PVSF_IGNOREPVS 3 +#endif + // because there can be a lot of nails, there is a special // network protocol for them #define MAX_NAILS 32 @@ -109,7 +113,6 @@ static void SV_EmitNailUpdate (sizebuf_t *msg, qbool recorder) //============================================================================= - /* ================== SV_WriteDelta @@ -827,6 +830,14 @@ qbool SV_EntityVisibleToClient (client_t* client, int e, byte* pvs) return false; } +#ifdef FTE_PEXT_COLOURMOD + // if pvsflags are set to ignore all + if (((int)ent->xv.pvsflags & PVSF_IGNOREPVS) == PVSF_IGNOREPVS) + { + return true; + } +#endif + // ignore ents without visible models if (!ent->v->modelindex || !*PR_GetEntityString(ent->v->model)) return false; @@ -847,6 +858,363 @@ qbool SV_EntityVisibleToClient (client_t* client, int e, byte* pvs) return true; } +#ifdef FTE_PEXT_CSQC +int EntityFrameCSQC_AllocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + client->csqcentityframehistory_next += 1; + client->csqcentityframehistory_next %= NUM_CSQCENTITYDB_FRAMES; + client->csqcentityframehistory[ringfirst].framenum = framenum; + client->csqcentityframehistory[ringfirst].num = 0; + return ringfirst; +} + +void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + if (framenum == client->csqcentityframehistory[ringlast].framenum) + { + client->csqcentityframehistory[ringlast].framenum = -1; + client->csqcentityframehistory[ringlast].num = 0; + client->csqcentityframehistory_next = ringlast; + } + else + { + Con_Printf("Trying to dealloc the wrong entity frame\n"); + } +} + +sizebuf_t *csqcmsgbuffer; +int SV_EmitCSQCUpdate(client_t *client, sizebuf_t *msg, int maxsize, int entlist_size, const unsigned short *entlist) +{ + int num, number, end, sendflags; + const unsigned short *entnum; + edict_t *ed; + + client->csqc_framenum = client->netchan.incoming_sequence; + int dbframe = EntityFrameCSQC_AllocFrame(client, client->csqc_framenum); + csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; + if (client->csqcentityframe_lastreset < 0) + { + client->csqcentityframe_lastreset = client->csqc_framenum; + } + + csqcmsgbuffer = msg; + int sectionstarted = false; + + maxsize -= 24; + if (msg->cursize + 32 >= maxsize) + { + return false; + } + + // blind check to make sure we can't miss any potential csqc ents + if (client->csqcnumedicts < sv.num_edicts) + { + client->csqcnumedicts = sv.num_edicts; + } + + number = 1; + for (num = 0, entnum = entlist; num < entlist_size; num++, entnum++) + { + // cleanup old ents + end = *entnum; + for (; number < end; number++) + { + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) + { + client->csqcentityscope[number] |= SCOPE_WANTREMOVE; + } + client->csqcentitysendflags[number] = 0xFFFFFF; + } + + ed = EDICT_NUM(number);//sv.edicts + number; + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + if (ed->xv.sendentity) + { + client->csqcentityscope[number] |= SCOPE_WANTUPDATE; + } + else + { + if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) + { + client->csqcentityscope[number] |= SCOPE_WANTREMOVE; + } + client->csqcentitysendflags[number] = 0xFFFFFF; + } + number++; + } + end = client->csqcnumedicts; + for (; number < end; number++) + { + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) + { + client->csqcentityscope[number] |= SCOPE_WANTREMOVE; + } + client->csqcentitysendflags[number] = 0xFFFFFF; + } + + + end = client->csqcnumedicts; + + for (number = 1; number < end; number++) + { + if (!(client->csqcentityscope[number] & SCOPE_WANTSEND)) + { + continue; + } + + if (db->num >= NUM_CSQCENTITIES_PER_FRAME) + { + break; + } + ed = EDICT_NUM(number);//sv.edicts + number; + if (client->csqcentityscope[number] & SCOPE_WANTREMOVE) // Also implies ASSUMED_EXISTING. + { + // A removal. SendFlags have no power here. + // write a remove message + // first write the message identifier if needed + if (!sectionstarted) + { + sectionstarted = 1; + MSG_WriteByte(msg, svc_fte_csqcentities); + } + // write the remove message + { + MSG_WriteShort(msg, (unsigned short)number | 0x8000); + client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING); + client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again + db->entno[db->num] = number; + db->sendflags[db->num] = -1; + db->num += 1; + } + if (msg->cursize + 17 >= maxsize) + { + break; + } + } + else + { + // save the cursize value in case we overflow and have to rollback + int oldcursize = msg->cursize; + int oldsectionstarted = sectionstarted; + + // An update. + sendflags = client->csqcentitysendflags[number]; + + // If it's a new entity, always assume sendflags 0xFFFFFF. + if (!(client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)) + { + sendflags = 0xFFFFFF; + } + + // Nothing to send? FINE. + if (!sendflags) + { + continue; + } + + if (!sectionstarted) + { + MSG_WriteByte(msg, svc_fte_csqcentities); + sectionstarted = 1; + } + + MSG_WriteShort(msg, (unsigned short)number); + msg->allowoverflow = true; + + if (!PR2_SendEntity(ed, client->edict, sendflags)) + { + msg->cursize = oldcursize; + sectionstarted = oldsectionstarted; + msg->allowoverflow = false; + continue; + } + + msg->allowoverflow = false; + + if (msg->cursize + 4 <= maxsize) + { + // an update has been successfully written + client->csqcentitysendflags[number] = 0; + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + client->csqcentityscope[number] |= SCOPE_EXISTED_ONCE | SCOPE_ASSUMED_EXISTING; + db->entno[db->num] = number; + db->sendflags[db->num] = sendflags; + db->num += 1; + + if (msg->cursize + 17 >= maxsize) + { + break; + } + continue; + } + + // update was too big for this packet - rollback the buffer to its + // state before the writes occurred, we'll try again next frame + msg->cursize = oldcursize; + msg->overflowed = false; + } + } + + if (sectionstarted) + { + // write index 0 to end the update (0 is never used by real entities) + MSG_WriteShort(msg, 0); + } + else + { + sectionstarted = 1; + MSG_WriteByte(msg, svc_fte_csqcentities); + } + + if (db->num == 0) + { + EntityFrameCSQC_DeallocFrame(client, client->csqc_framenum); + } + + return sectionstarted; +} + +int SV_PrepareEntity_CSQC(edict_t *ent, entity_state_t *cs, int enumber) +{ + return ent->xv.sendentity != 0; +} + +void EntityFrameCSQC_LostFrame(client_t *client, int framenum) +{ + // marks a frame as lost + int i, j; + qbool valid; + int ringfirst, ringlast; + static int recoversendflags[MAX_EDICTS]; // client only + csqcentityframedb_t *d; + + if (client->csqcentityframe_lastreset < 0) + { + return; + } + if (framenum < client->csqcentityframe_lastreset) + { + return; // no action required, as we resent that data anyway + } + + // is our frame out of history? + ringfirst = client->csqcentityframehistory_next; // oldest entry + ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + + valid = false; + + for (j = 0; j < NUM_CSQCENTITYDB_FRAMES; ++j) + { + d = &client->csqcentityframehistory[(ringfirst + j) % NUM_CSQCENTITYDB_FRAMES]; + if (d->framenum < 0) + { + continue; + } + if (d->framenum == framenum) + { + break; + } + if (d->framenum < framenum) + { + valid = true; + } + } + if (j == NUM_CSQCENTITYDB_FRAMES) + { + if (valid) // got beaten, i.e. there is a frame < framenum + { + // a non-csqc frame got lost... great + return; + } + + // a too old frame got lost... sorry, cannot handle this + Con_DPrintf("CSQC entity DB: lost a frame too early to do any handling (resending ALL)...\n"); + Con_DPrintf("Lost frame = %d\n", framenum); + Con_DPrintf("Entity DB = %d to %d\n", client->csqcentityframehistory[ringfirst].framenum, client->csqcentityframehistory[ringlast].framenum); + //EntityFrameCSQC_LostAllFrames(client); + client->csqcentityframe_lastreset = -1; + return; + } + + // so j is the frame that got lost + // ringlast is the frame that we have to go to + ringfirst = (ringfirst + j) % NUM_CSQCENTITYDB_FRAMES; + if (ringlast < ringfirst) + { + ringlast += NUM_CSQCENTITYDB_FRAMES; + } + + memset(recoversendflags, 0, sizeof(recoversendflags)); + + for (j = ringfirst; j <= ringlast; ++j) + { + d = &client->csqcentityframehistory[j % NUM_CSQCENTITYDB_FRAMES]; + if (d->framenum < 0) + { + // deleted frame + } + else if (d->framenum < framenum) + { + // a frame in the past... should never happen + Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n"); + } + else if (d->framenum == framenum) + { + // handling the actually lost frame now + for (i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if (sf < 0) // remove + { + recoversendflags[ent] |= -1; // all bits, including sign + } + else if (sf > 0) + { + recoversendflags[ent] |= sf; + } + } + } + else + { + // handling the frames that followed it now + for (i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if (sf < 0) // remove + { + recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN) + break; // no flags left to remove... + } + if (sf > 0) + { + recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later + } + } + } + } + + for (i = 0; i < client->csqcnumedicts; ++i) + { + if (recoversendflags[i] < 0) + { + client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE. + } + else + { + client->csqcentitysendflags[i] |= recoversendflags[i]; + } + } +} +#endif + + /* ============= SV_WriteEntitiesToClient @@ -870,6 +1238,9 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder) int hideent; unsigned int client_flag = (1 << (client - svs.clients)); edict_t *clent = client->edict; +#ifdef FTE_PEXT_CSQC + int numcsqcsendstates = 0; +#endif float distances[MAX_PACKETENTITIES_POSSIBLE] = { 0 }; float distance; @@ -976,6 +1347,19 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder) continue; } +#ifdef FTE_PEXT_CSQC + if (clent && client->netchan.incoming_sequence > 5) { + if (client->csqcactive && !recorder) + { + if (SV_PrepareEntity_CSQC(ent, state, e)) + { + sv.csqcsendstates[numcsqcsendstates++] = e; + continue; + } + } + } +#endif + if (SV_AddNailUpdate (ent)) continue; // added to the special update list @@ -1055,6 +1439,13 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder) // now add the specialized nail update SV_EmitNailUpdate (msg, recorder); +#ifdef FTE_PEXT_CSQC + if (client->csqcactive && !recorder) + { + SV_EmitCSQCUpdate(client, msg, msg->maxsize - (client->netchan.message.cursize + 30), numcsqcsendstates, sv.csqcsendstates); + } +#endif + // Translate NQ progs' EF_MUZZLEFLASH to svc_muzzleflash if (pr_nqprogs) { diff --git a/src/sv_main.c b/src/sv_main.c index 2a3a00f9..00ebf3ab 100644 --- a/src/sv_main.c +++ b/src/sv_main.c @@ -495,6 +495,20 @@ void SV_FullClientUpdate (client_t *client, sizebuf_t *buf) char info[MAX_EXT_INFO_STRING]; int i; +#ifdef FTE_PEXT_CSQC + // Reki: resend all CSQC ents, for reasons. previously the initial CSQC ent states were being dropped for some delta reasons I think. + if (client->csqcactive) + { + for (i = 1; i < MAX_EDICTS; i++) + { + if (client->csqcentityscope[i] & SCOPE_WANTSEND) + { + client->csqcentitysendflags[i] = 0xFFFFFF; + } + } + } +#endif + i = client - svs.clients; //Sys_Printf("SV_FullClientUpdate: Updated frags for client %d\n", i); diff --git a/src/sv_user.c b/src/sv_user.c index 3616c944..3a4e9316 100644 --- a/src/sv_user.c +++ b/src/sv_user.c @@ -3087,7 +3087,18 @@ void SV_Voice_UnmuteAll_f(void) #ifdef FTE_PEXT_CSQC void SV_EnableClientsCSQC(void) { + size_t e; + sv_client->csqcactive = true; + + //if the csqc has just restarted, its probably going to want us to resend all csqc ents from scratch because of all the setup it might do. + for (e = 1; e < MAX_EDICTS; e++) + { + if (sv_client->csqcentityscope[e] & SCOPE_WANTSEND) + { + sv_client->csqcentitysendflags[e] = 0xFFFFFF; + } + } } void SV_DisableClientsCSQC(void) @@ -3527,6 +3538,40 @@ void SV_PreRunCmd(void) memset(playertouch, 0, sizeof(playertouch)); } +#ifdef FTE_PEXT_CSQC +/* +=========== +CSQC Stuff, for now just SimpleProjectiles +=========== +*/ +qbool SV_FrameLost(int framenum) +{ + if (framenum <= sv_client->csqc_framenum) + { + EntityFrameCSQC_LostFrame(sv_client, framenum); + return true; + } + + return false; +} + +static void SV_FrameAck(int framenum) +{ + /* + int i; + // scan for packets made obsolete by this ack and delete them + for (i = 0; i < ENTITYFRAME5_MAXPACKETLOGS; i++) + { + if (d->packetlog[i].packetnumber <= framenum) + { + d->packetlog[i].packetnumber = 0; + } + } + */ +} +#endif + + /* =========== SV_RunCmd @@ -4497,6 +4542,18 @@ void SV_ExecuteClientMessage (client_t *cl) seq_hash = cl->netchan.incoming_sequence; +#ifdef FTE_PEXT_CSQC + for (i = cl->csqc_latestverified + 1; i < cl->netchan.incoming_acknowledged; i++) + { + if (!SV_FrameLost(i)) + { + break; + } + } + SV_FrameAck(cl->netchan.incoming_acknowledged); + cl->csqc_latestverified = cl->netchan.incoming_acknowledged; +#endif + // mark time so clients will know how much to predict // other players cl->localtime = sv.time;