diff --git a/CMakeLists.txt b/CMakeLists.txt index 22f01611..3529bd8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE USE_PR2) target_compile_definitions(${PROJECT_NAME} PRIVATE MVD_PEXT1_SERVERSIDEWEAPON) target_compile_definitions(${PROJECT_NAME} PRIVATE MVD_PEXT1_SERVERSIDEWEAPON2) target_compile_definitions(${PROJECT_NAME} PRIVATE FTE_PEXT2_VOICECHAT) +target_compile_definitions(${PROJECT_NAME} PRIVATE FTE_PEXT_ACCURATETIMINGS=0x40) include (TestBigEndian) TEST_BIG_ENDIAN(IS_BIG_ENDIAN) diff --git a/src/pr2_cmds.c b/src/pr2_cmds.c index 11119189..69953b84 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) { @@ -167,61 +170,64 @@ void PR2_CheckEmptyString(char *s) PR2_RunError("Bad string"); } -void PF2_precache_sound(char *s) +int PF2_precache_sound(char *s) { int i; - if (sv.state != ss_loading) - PR2_RunError("PF_Precache_*: Precache can only be done in spawn " - "functions"); PR2_CheckEmptyString(s); for (i = 0; i < MAX_SOUNDS; i++) { if (!sv.sound_precache[i]) { + if (sv.state != ss_loading) + { + PR2_RunError("PF_Precache_*: Precache can only add new sounds in spawn " + "functions"); + } sv.sound_precache[i] = s; - return; + return i; } if (!strcmp(sv.sound_precache[i], s)) - return; + return i; } PR2_RunError ("PF_precache_sound: overflow"); + + return 0; } -void PF2_precache_model(char *s) +int PF2_precache_model(char *s) { int i; - if (sv.state != ss_loading) - PR2_RunError("PF_Precache_*: Precache can only be done in spawn " - "functions"); - PR2_CheckEmptyString(s); for (i = 0; i < MAX_MODELS; i++) { if (!sv.model_precache[i]) { + if (sv.state != ss_loading) + { + PR2_RunError("PF_Precache_*: Precache can only add new models in spawn " + "functions"); + } sv.model_precache[i] = s; - return; + return i; } if (!strcmp(sv.model_precache[i], s)) - return; + return i; } PR2_RunError ("PF_precache_model: overflow"); + + return 0; } intptr_t PF2_precache_vwep_model(char *s) { int i; - if (sv.state != ss_loading) - PR2_RunError("PF_Precache_*: Precache can only be done in spawn " - "functions"); - PR2_CheckEmptyString(s); // the strings are transferred via the stufftext mechanism, hence the stringency @@ -231,9 +237,16 @@ intptr_t PF2_precache_vwep_model(char *s) for (i = 0; i < MAX_VWEP_MODELS; i++) { if (!sv.vw_model_name[i]) { + if (sv.state != ss_loading) + { + PR2_RunError("PF_Precache_*: Precache can only add new vweps in spawn " + "functions"); + } sv.vw_model_name[i] = s; return i; } + if (!strcmp(sv.vw_model_name[i], s)) + return i; } PR2_RunError ("PF_precache_vwep_model: overflow"); return 0; @@ -1219,9 +1232,7 @@ sizebuf_t *WriteDest2(int dest) return &sv.multicast; case MSG_CSQC: - // Should return a reference to the CSQC message buffer managed in sv_ents.c - PR2_RunError("PF_Write_*: MSG_CSQC not implemented yet."); - return NULL; + return csqcmsgbuffer; default: PR2_RunError ("WriteDest: bad destination"); @@ -2003,7 +2014,29 @@ intptr_t PF2_FS_GetFileList(char *path, char *ext, #ifdef FTE_PEXT_CSQC intptr_t EXT_SetSendNeeded(intptr_t *args) { - PR2_RunError("SetSendNeeded not implemented yet."); + 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 @@ -2578,11 +2611,9 @@ intptr_t PR2_GameSystemCalls(intptr_t *args) { ED_Free(VME(1)); return 0; case G_PRECACHE_SOUND: - PF2_precache_sound(VMA(1)); - return 0; + return PF2_precache_sound(VMA(1)); case G_PRECACHE_MODEL: - PF2_precache_model(VMA(1)); - return 0; + return PF2_precache_model(VMA(1)); case G_LIGHTSTYLE: PF2_lightstyle(args[1], VMA(2)); return 0; diff --git a/src/server.h b/src/server.h index cc2f3f5f..697270d6 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 93e71031..c2d32829 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 @@ -820,6 +823,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; @@ -840,6 +851,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 @@ -863,6 +1231,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; @@ -969,6 +1340,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 @@ -1048,6 +1432,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 5fa5b098..cef41683 100644 --- a/src/sv_main.c +++ b/src/sv_main.c @@ -498,6 +498,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); @@ -3625,6 +3639,9 @@ void SV_InitLocal (void) #ifdef MVD_PEXT1_SERVERSIDEWEAPON2 svs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON2; #endif +#ifdef MVD_PEXT1_EZCSQC + svs.mvdprotocolextension1 |= MVD_PEXT1_EZCSQC; +#endif Info_SetValueForStarKey (svs.info, "*version", SERVER_NAME " " SERVER_VERSION, MAX_SERVERINFO_STRING); Info_SetValueForStarKey (svs.info, "*z_ext", va("%i", SERVER_EXTENSIONS), MAX_SERVERINFO_STRING); 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;