From: cloudwalk Date: Wed, 1 Jul 2020 19:39:50 +0000 (+0000) Subject: (Round 1) Break up protocol.c X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=e4ea617371d853df6889fef0dee598e1030c1268;p=xonotic%2Fdarkplaces.git (Round 1) Break up protocol.c Different entframe functions for different protocols warrant separate files for maintainability and readability purposes. git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12772 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/cl_entframe.c b/cl_entframe.c new file mode 100644 index 00000000..18ea48a2 --- /dev/null +++ b/cl_entframe.c @@ -0,0 +1,191 @@ +#include "quakedef.h" +#include "protocol.h" + +// (client) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata) +{ + int n, e; + entity_frameinfo_t *info; + + VectorCopy(eye, d->eye); + + // figure out how many entity slots are used already + if (d->numframes) + { + n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; + if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) + { + // ran out of room, dump database + EntityFrame_ClearDatabase(d); + } + } + + info = &d->frames[d->numframes]; + info->framenum = framenum; + e = -1000; + // make sure we check the newly added frame as well, but we haven't incremented numframes yet + for (n = 0;n <= d->numframes;n++) + { + if (e >= d->frames[n].framenum) + { + if (e == framenum) + Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); + else + Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); + return; + } + e = d->frames[n].framenum; + } + // if database still has frames after that... + if (d->numframes) + info->firstentity = d->frames[d->numframes - 1].endentity; + else + info->firstentity = 0; + info->endentity = info->firstentity + numentities; + d->numframes++; + + n = info->firstentity % MAX_ENTITY_DATABASE; + e = MAX_ENTITY_DATABASE - n; + if (e > numentities) + e = numentities; + memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); + if (numentities > e) + memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); +} + +// (client) reads a frame from network stream +void EntityFrame_CL_ReadFrame(void) +{ + int i, number, removed; + entity_frame_t *f, *delta; + entity_state_t *e, *old, *oldend; + entity_t *ent; + entityframe_database_t *d; + if (!cl.entitydatabase) + cl.entitydatabase = EntityFrame_AllocDatabase(cls.levelmempool); + d = cl.entitydatabase; + f = &d->framedata; + delta = &d->deltaframe; + + EntityFrame_Clear(f, NULL, -1); + + // read the frame header info + f->time = cl.mtime[0]; + number = MSG_ReadLong(&cl_message); + f->framenum = MSG_ReadLong(&cl_message); + CL_NewFrameReceived(f->framenum); + f->eye[0] = MSG_ReadFloat(&cl_message); + f->eye[1] = MSG_ReadFloat(&cl_message); + f->eye[2] = MSG_ReadFloat(&cl_message); + EntityFrame_AckFrame(d, number); + EntityFrame_FetchFrame(d, number, delta); + old = delta->entitydata; + oldend = old + delta->numentities; + // read entities until we hit the magic 0xFFFF end tag + while ((number = (unsigned short) MSG_ReadShort(&cl_message)) != 0xFFFF && !cl_message.badread) + { + if (cl_message.badread) + Host_Error("EntityFrame_Read: read error"); + removed = number & 0x8000; + number &= 0x7FFF; + if (number >= MAX_EDICTS) + Host_Error("EntityFrame_Read: number (%i) >= MAX_EDICTS (%i)", number, MAX_EDICTS); + + // seek to entity, while copying any skipped entities (assume unchanged) + while (old < oldend && old->number < number) + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + f->entitydata[f->numentities] = *old++; + f->entitydata[f->numentities++].time = cl.mtime[0]; + } + if (removed) + { + if (old < oldend && old->number == number) + old++; + else + Con_Printf("EntityFrame_Read: REMOVE on unused entity %i\n", number); + } + else + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + + // reserve this slot + e = f->entitydata + f->numentities++; + + if (old < oldend && old->number == number) + { + // delta from old entity + *e = *old++; + } + else + { + // delta from defaults + *e = defaultstate; + } + + if (cl.num_entities <= number) + { + cl.num_entities = number + 1; + if (number >= cl.max_entities) + CL_ExpandEntities(number); + } + cl.entities_active[number] = true; + e->active = ACTIVE_NETWORK; + e->time = cl.mtime[0]; + e->number = number; + EntityState_ReadFields(e, EntityState_ReadExtendBits()); + } + } + while (old < oldend) + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + f->entitydata[f->numentities] = *old++; + f->entitydata[f->numentities++].time = cl.mtime[0]; + } + EntityFrame_AddFrame_Client(d, f->eye, f->framenum, f->numentities, f->entitydata); + + memset(cl.entities_active, 0, cl.num_entities * sizeof(unsigned char)); + number = 1; + for (i = 0;i < f->numentities;i++) + { + for (;number < f->entitydata[i].number && number < cl.num_entities;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } + if (number >= cl.num_entities) + break; + // update the entity + ent = &cl.entities[number]; + ent->state_previous = ent->state_current; + ent->state_current = f->entitydata[i]; + CL_MoveLerpEntityStates(ent); + // the entity lives again... + cl.entities_active[number] = true; + number++; + } + for (;number < cl.num_entities;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } +} + + +// (client) returns the frame number of the most recent frame recieved +int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d) +{ + if (d->numframes) + return d->frames[d->numframes - 1].framenum; + else + return -1; +} \ No newline at end of file diff --git a/cl_entframe_quake.c b/cl_entframe_quake.c new file mode 100644 index 00000000..0edd34e3 --- /dev/null +++ b/cl_entframe_quake.c @@ -0,0 +1,142 @@ +#include "quakedef.h" +#include "protocol.h" + +void EntityFrameQuake_ReadEntity(int bits) +{ + int num; + entity_t *ent; + entity_state_t s; + + if (bits & U_MOREBITS) + bits |= (MSG_ReadByte(&cl_message)<<8); + if ((bits & U_EXTEND1) && cls.protocol != PROTOCOL_NEHAHRAMOVIE) + { + bits |= MSG_ReadByte(&cl_message) << 16; + if (bits & U_EXTEND2) + bits |= MSG_ReadByte(&cl_message) << 24; + } + + if (bits & U_LONGENTITY) + num = (unsigned short) MSG_ReadShort(&cl_message); + else + num = MSG_ReadByte(&cl_message); + + if (num >= MAX_EDICTS) + Host_Error("EntityFrameQuake_ReadEntity: entity number (%i) >= MAX_EDICTS (%i)", num, MAX_EDICTS); + if (num < 1) + Host_Error("EntityFrameQuake_ReadEntity: invalid entity number (%i)", num); + + if (cl.num_entities <= num) + { + cl.num_entities = num + 1; + if (num >= cl.max_entities) + CL_ExpandEntities(num); + } + + ent = cl.entities + num; + + // note: this inherits the 'active' state of the baseline chosen + // (state_baseline is always active, state_current may not be active if + // the entity was missing in the last frame) + if (bits & U_DELTA) + s = ent->state_current; + else + { + s = ent->state_baseline; + s.active = ACTIVE_NETWORK; + } + + cl.isquakeentity[num] = true; + if (cl.lastquakeentity < num) + cl.lastquakeentity = num; + s.number = num; + s.time = cl.mtime[0]; + s.flags = 0; + if (bits & U_MODEL) + { + if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + s.modelindex = (unsigned short) MSG_ReadShort(&cl_message); + else + s.modelindex = (s.modelindex & 0xFF00) | MSG_ReadByte(&cl_message); + } + if (bits & U_FRAME) s.frame = (s.frame & 0xFF00) | MSG_ReadByte(&cl_message); + if (bits & U_COLORMAP) s.colormap = MSG_ReadByte(&cl_message); + if (bits & U_SKIN) s.skin = MSG_ReadByte(&cl_message); + if (bits & U_EFFECTS) s.effects = (s.effects & 0xFF00) | MSG_ReadByte(&cl_message); + if (bits & U_ORIGIN1) s.origin[0] = MSG_ReadCoord(&cl_message, cls.protocol); + if (bits & U_ANGLE1) s.angles[0] = MSG_ReadAngle(&cl_message, cls.protocol); + if (bits & U_ORIGIN2) s.origin[1] = MSG_ReadCoord(&cl_message, cls.protocol); + if (bits & U_ANGLE2) s.angles[1] = MSG_ReadAngle(&cl_message, cls.protocol); + if (bits & U_ORIGIN3) s.origin[2] = MSG_ReadCoord(&cl_message, cls.protocol); + if (bits & U_ANGLE3) s.angles[2] = MSG_ReadAngle(&cl_message, cls.protocol); + if (bits & U_STEP) s.flags |= RENDER_STEP; + if (bits & U_ALPHA) s.alpha = MSG_ReadByte(&cl_message); + if (bits & U_SCALE) s.scale = MSG_ReadByte(&cl_message); + if (bits & U_EFFECTS2) s.effects = (s.effects & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); + if (bits & U_GLOWSIZE) s.glowsize = MSG_ReadByte(&cl_message); + if (bits & U_GLOWCOLOR) s.glowcolor = MSG_ReadByte(&cl_message); + if (bits & U_COLORMOD) {int c = MSG_ReadByte(&cl_message);s.colormod[0] = (unsigned char)(((c >> 5) & 7) * (32.0f / 7.0f));s.colormod[1] = (unsigned char)(((c >> 2) & 7) * (32.0f / 7.0f));s.colormod[2] = (unsigned char)((c & 3) * (32.0f / 3.0f));} + if (bits & U_GLOWTRAIL) s.flags |= RENDER_GLOWTRAIL; + if (bits & U_FRAME2) s.frame = (s.frame & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); + if (bits & U_MODEL2) s.modelindex = (s.modelindex & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); + if (bits & U_VIEWMODEL) s.flags |= RENDER_VIEWMODEL; + if (bits & U_EXTERIORMODEL) s.flags |= RENDER_EXTERIORMODEL; + + // LadyHavoc: to allow playback of the Nehahra movie + if (cls.protocol == PROTOCOL_NEHAHRAMOVIE && (bits & U_EXTEND1)) + { + // LadyHavoc: evil format + int i = (int)MSG_ReadFloat(&cl_message); + int j = (int)(MSG_ReadFloat(&cl_message) * 255.0f); + if (i == 2) + { + i = (int)MSG_ReadFloat(&cl_message); + if (i) + s.effects |= EF_FULLBRIGHT; + } + if (j < 0) + s.alpha = 0; + else if (j == 0 || j >= 255) + s.alpha = 255; + else + s.alpha = j; + } + + ent->state_previous = ent->state_current; + ent->state_current = s; + if (ent->state_current.active == ACTIVE_NETWORK) + { + CL_MoveLerpEntityStates(ent); + cl.entities_active[ent->state_current.number] = true; + } + + if (cl_message.badread) + Host_Error("EntityFrameQuake_ReadEntity: read error"); +} + +void EntityFrameQuake_ISeeDeadEntities(void) +{ + int num, lastentity; + if (cl.lastquakeentity == 0) + return; + lastentity = cl.lastquakeentity; + cl.lastquakeentity = 0; + for (num = 0;num <= lastentity;num++) + { + if (cl.isquakeentity[num]) + { + if (cl.entities_active[num] && cl.entities[num].state_current.time == cl.mtime[0]) + { + cl.isquakeentity[num] = true; + cl.lastquakeentity = num; + } + else + { + cl.isquakeentity[num] = false; + cl.entities_active[num] = ACTIVE_NOT; + cl.entities[num].state_current = defaultstate; + cl.entities[num].state_current.number = num; + } + } + } +} \ No newline at end of file diff --git a/darkplaces-sdl2-vs2017.vcxproj b/darkplaces-sdl2-vs2017.vcxproj index c292a782..68059433 100644 --- a/darkplaces-sdl2-vs2017.vcxproj +++ b/darkplaces-sdl2-vs2017.vcxproj @@ -201,6 +201,8 @@ + + @@ -277,6 +279,8 @@ + + diff --git a/darkplaces-sdl2-vs2019.vcxproj b/darkplaces-sdl2-vs2019.vcxproj index 3b4ea935..9f74d90a 100644 --- a/darkplaces-sdl2-vs2019.vcxproj +++ b/darkplaces-sdl2-vs2019.vcxproj @@ -202,6 +202,8 @@ + + @@ -278,6 +280,8 @@ + + diff --git a/makefile.inc b/makefile.inc index a69e8fa4..36aad5f7 100644 --- a/makefile.inc +++ b/makefile.inc @@ -82,6 +82,8 @@ OBJ_COMMON= \ cl_cmd.o \ cl_collision.o \ cl_demo.o \ + cl_entframe.o \ + cl_entframe_quake.o \ cl_input.o \ cl_main.o \ cl_parse.o \ @@ -144,6 +146,8 @@ OBJ_COMMON= \ sbar.o \ sv_ccmds.o \ sv_demo.o \ + sv_entframe_csqc.o \ + sv_entframe_quake.o \ sv_main.o \ sv_move.o \ sv_phys.o \ diff --git a/protocol.c b/protocol.c index 073828ee..6db54958 100644 --- a/protocol.c +++ b/protocol.c @@ -1,22 +1,5 @@ #include "quakedef.h" -#define ENTITYSIZEPROFILING_START(msg, num, flags) \ - int entityprofiling_startsize = msg->cursize - -#define ENTITYSIZEPROFILING_END(msg, num, flags) \ - if(developer_networkentities.integer >= 2) \ - { \ - prvm_edict_t *edict = prog->edicts + num; \ - Con_Printf("sent entity update of size %u for %d classname %s flags %d\n", (msg->cursize - entityprofiling_startsize), num, PRVM_serveredictstring(edict, classname) ? PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)) : "(no classname)", flags); \ - } - -// CSQC entity scope values. Bitflags! -#define SCOPE_WANTREMOVE 1 // Set if a remove has been scheduled. Never set together with WANTUPDATE. -#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. - // this is 88 bytes (must match entity_state_t in protocol.h) entity_state_t defaultstate = { @@ -131,494 +114,6 @@ void Protocol_Names(char *buffer, size_t buffersize) } } -void EntityFrameQuake_ReadEntity(int bits) -{ - int num; - entity_t *ent; - entity_state_t s; - - if (bits & U_MOREBITS) - bits |= (MSG_ReadByte(&cl_message)<<8); - if ((bits & U_EXTEND1) && cls.protocol != PROTOCOL_NEHAHRAMOVIE) - { - bits |= MSG_ReadByte(&cl_message) << 16; - if (bits & U_EXTEND2) - bits |= MSG_ReadByte(&cl_message) << 24; - } - - if (bits & U_LONGENTITY) - num = (unsigned short) MSG_ReadShort(&cl_message); - else - num = MSG_ReadByte(&cl_message); - - if (num >= MAX_EDICTS) - Host_Error("EntityFrameQuake_ReadEntity: entity number (%i) >= MAX_EDICTS (%i)", num, MAX_EDICTS); - if (num < 1) - Host_Error("EntityFrameQuake_ReadEntity: invalid entity number (%i)", num); - - if (cl.num_entities <= num) - { - cl.num_entities = num + 1; - if (num >= cl.max_entities) - CL_ExpandEntities(num); - } - - ent = cl.entities + num; - - // note: this inherits the 'active' state of the baseline chosen - // (state_baseline is always active, state_current may not be active if - // the entity was missing in the last frame) - if (bits & U_DELTA) - s = ent->state_current; - else - { - s = ent->state_baseline; - s.active = ACTIVE_NETWORK; - } - - cl.isquakeentity[num] = true; - if (cl.lastquakeentity < num) - cl.lastquakeentity = num; - s.number = num; - s.time = cl.mtime[0]; - s.flags = 0; - if (bits & U_MODEL) - { - if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) - s.modelindex = (unsigned short) MSG_ReadShort(&cl_message); - else - s.modelindex = (s.modelindex & 0xFF00) | MSG_ReadByte(&cl_message); - } - if (bits & U_FRAME) s.frame = (s.frame & 0xFF00) | MSG_ReadByte(&cl_message); - if (bits & U_COLORMAP) s.colormap = MSG_ReadByte(&cl_message); - if (bits & U_SKIN) s.skin = MSG_ReadByte(&cl_message); - if (bits & U_EFFECTS) s.effects = (s.effects & 0xFF00) | MSG_ReadByte(&cl_message); - if (bits & U_ORIGIN1) s.origin[0] = MSG_ReadCoord(&cl_message, cls.protocol); - if (bits & U_ANGLE1) s.angles[0] = MSG_ReadAngle(&cl_message, cls.protocol); - if (bits & U_ORIGIN2) s.origin[1] = MSG_ReadCoord(&cl_message, cls.protocol); - if (bits & U_ANGLE2) s.angles[1] = MSG_ReadAngle(&cl_message, cls.protocol); - if (bits & U_ORIGIN3) s.origin[2] = MSG_ReadCoord(&cl_message, cls.protocol); - if (bits & U_ANGLE3) s.angles[2] = MSG_ReadAngle(&cl_message, cls.protocol); - if (bits & U_STEP) s.flags |= RENDER_STEP; - if (bits & U_ALPHA) s.alpha = MSG_ReadByte(&cl_message); - if (bits & U_SCALE) s.scale = MSG_ReadByte(&cl_message); - if (bits & U_EFFECTS2) s.effects = (s.effects & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); - if (bits & U_GLOWSIZE) s.glowsize = MSG_ReadByte(&cl_message); - if (bits & U_GLOWCOLOR) s.glowcolor = MSG_ReadByte(&cl_message); - if (bits & U_COLORMOD) {int c = MSG_ReadByte(&cl_message);s.colormod[0] = (unsigned char)(((c >> 5) & 7) * (32.0f / 7.0f));s.colormod[1] = (unsigned char)(((c >> 2) & 7) * (32.0f / 7.0f));s.colormod[2] = (unsigned char)((c & 3) * (32.0f / 3.0f));} - if (bits & U_GLOWTRAIL) s.flags |= RENDER_GLOWTRAIL; - if (bits & U_FRAME2) s.frame = (s.frame & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); - if (bits & U_MODEL2) s.modelindex = (s.modelindex & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); - if (bits & U_VIEWMODEL) s.flags |= RENDER_VIEWMODEL; - if (bits & U_EXTERIORMODEL) s.flags |= RENDER_EXTERIORMODEL; - - // LadyHavoc: to allow playback of the Nehahra movie - if (cls.protocol == PROTOCOL_NEHAHRAMOVIE && (bits & U_EXTEND1)) - { - // LadyHavoc: evil format - int i = (int)MSG_ReadFloat(&cl_message); - int j = (int)(MSG_ReadFloat(&cl_message) * 255.0f); - if (i == 2) - { - i = (int)MSG_ReadFloat(&cl_message); - if (i) - s.effects |= EF_FULLBRIGHT; - } - if (j < 0) - s.alpha = 0; - else if (j == 0 || j >= 255) - s.alpha = 255; - else - s.alpha = j; - } - - ent->state_previous = ent->state_current; - ent->state_current = s; - if (ent->state_current.active == ACTIVE_NETWORK) - { - CL_MoveLerpEntityStates(ent); - cl.entities_active[ent->state_current.number] = true; - } - - if (cl_message.badread) - Host_Error("EntityFrameQuake_ReadEntity: read error"); -} - -void EntityFrameQuake_ISeeDeadEntities(void) -{ - int num, lastentity; - if (cl.lastquakeentity == 0) - return; - lastentity = cl.lastquakeentity; - cl.lastquakeentity = 0; - for (num = 0;num <= lastentity;num++) - { - if (cl.isquakeentity[num]) - { - if (cl.entities_active[num] && cl.entities[num].state_current.time == cl.mtime[0]) - { - cl.isquakeentity[num] = true; - cl.lastquakeentity = num; - } - else - { - cl.isquakeentity[num] = false; - cl.entities_active[num] = ACTIVE_NOT; - cl.entities[num].state_current = defaultstate; - cl.entities[num].state_current.number = num; - } - } - } -} - -// NOTE: this only works with DP5 protocol and upwards. For lower protocols -// (including QUAKE), no packet loss handling for CSQC is done, which makes -// CSQC basically useless. -// Always use the DP5 protocol, or a higher one, when using CSQC entities. -static void EntityFrameCSQC_LostAllFrames(client_t *client) -{ - prvm_prog_t *prog = SVVM_prog; - // mark ALL csqc entities as requiring a FULL resend! - // I know this is a bad workaround, but better than nothing. - int i, n; - prvm_edict_t *ed; - - n = client->csqcnumedicts; - for(i = 0; i < n; ++i) - { - if(client->csqcentityscope[i] & SCOPE_EXISTED_ONCE) - { - ed = prog->edicts + i; - client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND. We can't clear SCOPE_ASSUMED_EXISTING yet as this would cancel removes on a rejected send attempt. - if (!PRVM_serveredictfunction(ed, SendEntity)) // If it was ever sent to that client as a CSQC entity... - client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE. - } - } -} -void EntityFrameCSQC_LostFrame(client_t *client, int framenum) -{ - // marks a frame as lost - int i, j; - qboolean 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; - else 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; - } - else - { - // 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... - } - else 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]; - } -} -static 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; -} -static 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"); -} - -//[515]: we use only one array per-client for SendEntity feature -// TODO: add some handling for entity send priorities, to better deal with huge -// amounts of csqc networked entities -qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum) -{ - prvm_prog_t *prog = SVVM_prog; - int num, number, end, sendflags; - qboolean sectionstarted = false; - const unsigned short *n; - prvm_edict_t *ed; - client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; - int dbframe = EntityFrameCSQC_AllocFrame(client, framenum); - csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; - - if(client->csqcentityframe_lastreset < 0) - client->csqcentityframe_lastreset = framenum; - - maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!) - - // make sure there is enough room to store the svc_csqcentities byte, - // the terminator (0x0000) and at least one entity update - if (msg->cursize + 32 >= maxsize) - return false; - - if (client->csqcnumedicts < prog->num_edicts) - client->csqcnumedicts = prog->num_edicts; - - number = 1; - for (num = 0, n = numbers;num < numnumbers;num++, n++) - { - end = *n; - 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 = prog->edicts + number; - client->csqcentityscope[number] &= ~SCOPE_WANTSEND; - if (PRVM_serveredictfunction(ed, 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; - } - - // now try to emit the entity updates - // (FIXME: prioritize by distance?) - 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 = prog->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_csqcentities); - } - // write the remove message - { - ENTITYSIZEPROFILING_START(msg, number, 0); - 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; - ENTITYSIZEPROFILING_END(msg, number, 0); - } - if (msg->cursize + 17 >= maxsize) - break; - } - else - { - // save the cursize value in case we overflow and have to rollback - int oldcursize = msg->cursize; - - // An update. - sendflags = client->csqcentitysendflags[number]; - // Nothing to send? FINE. - if (!sendflags) - continue; - // If it's a new entity, always assume sendflags 0xFFFFFF. - if (!(client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)) - sendflags = 0xFFFFFF; - - // write an update - if (PRVM_serveredictfunction(ed, SendEntity)) - { - if(!sectionstarted) - MSG_WriteByte(msg, svc_csqcentities); - { - int oldcursize2 = msg->cursize; - ENTITYSIZEPROFILING_START(msg, number, sendflags); - MSG_WriteShort(msg, number); - msg->allowoverflow = true; - PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; - PRVM_G_FLOAT(OFS_PARM1) = sendflags; - PRVM_serverglobaledict(self) = number; - prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n"); - msg->allowoverflow = false; - if(!PRVM_G_FLOAT(OFS_RETURN)) - { - // Send rejected by CSQC. This means we want to remove it. - // CSQC requests we remove this one. - if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) - { - msg->cursize = oldcursize2; - msg->overflowed = false; - MSG_WriteShort(msg, (unsigned short)number | 0x8000); - client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING); - client->csqcentitysendflags[number] = 0; - db->entno[db->num] = number; - db->sendflags[db->num] = -1; - db->num += 1; - // and take note that we have begun the svc_csqcentities - // section of the packet - sectionstarted = 1; - ENTITYSIZEPROFILING_END(msg, number, 0); - if (msg->cursize + 17 >= maxsize) - break; - } - else - { - // Nothing to do. Just don't do it again. - msg->cursize = oldcursize; - msg->overflowed = false; - client->csqcentityscope[number] &= ~SCOPE_WANTSEND; - client->csqcentitysendflags[number] = 0; - } - continue; - } - else if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize) - { - // an update has been successfully written - client->csqcentitysendflags[number] = 0; - db->entno[db->num] = number; - db->sendflags[db->num] = sendflags; - db->num += 1; - client->csqcentityscope[number] &= ~SCOPE_WANTSEND; - client->csqcentityscope[number] |= SCOPE_EXISTED_ONCE | SCOPE_ASSUMED_EXISTING; - // and take note that we have begun the svc_csqcentities - // section of the packet - sectionstarted = 1; - ENTITYSIZEPROFILING_END(msg, number, sendflags); - if (msg->cursize + 17 >= maxsize) - break; - continue; - } - } - } - // self.SendEntity returned false (or does not exist) or the - // 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); - } - - if(db->num == 0) - // if no single ent got added, remove the frame from the DB again, to allow - // for a larger history - EntityFrameCSQC_DeallocFrame(client, framenum); - - return sectionstarted; -} - void Protocol_UpdateClientStats(const int *stats) { int i; @@ -697,177 +192,6 @@ void Protocol_WriteStatsReliable(void) } } - -qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states) -{ - prvm_prog_t *prog = SVVM_prog; - const entity_state_t *s; - entity_state_t baseline; - int i, bits; - sizebuf_t buf; - unsigned char data[128]; - qboolean success = false; - - // prepare the buffer - memset(&buf, 0, sizeof(buf)); - buf.data = data; - buf.maxsize = sizeof(data); - - for (i = 0;i < numstates;i++) - { - s = states[i]; - if(PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) - continue; - - // prepare the buffer - SZ_Clear(&buf); - -// send an update - bits = 0; - if (s->number >= 256) - bits |= U_LONGENTITY; - if (s->flags & RENDER_STEP) - bits |= U_STEP; - if (s->flags & RENDER_VIEWMODEL) - bits |= U_VIEWMODEL; - if (s->flags & RENDER_GLOWTRAIL) - bits |= U_GLOWTRAIL; - if (s->flags & RENDER_EXTERIORMODEL) - bits |= U_EXTERIORMODEL; - - // LadyHavoc: old stuff, but rewritten to have more exact tolerances - baseline = prog->edicts[s->number].priv.server->baseline; - if (baseline.origin[0] != s->origin[0]) - bits |= U_ORIGIN1; - if (baseline.origin[1] != s->origin[1]) - bits |= U_ORIGIN2; - if (baseline.origin[2] != s->origin[2]) - bits |= U_ORIGIN3; - if (baseline.angles[0] != s->angles[0]) - bits |= U_ANGLE1; - if (baseline.angles[1] != s->angles[1]) - bits |= U_ANGLE2; - if (baseline.angles[2] != s->angles[2]) - bits |= U_ANGLE3; - if (baseline.colormap != s->colormap) - bits |= U_COLORMAP; - if (baseline.skin != s->skin) - bits |= U_SKIN; - if (baseline.frame != s->frame) - { - bits |= U_FRAME; - if (s->frame & 0xFF00) - bits |= U_FRAME2; - } - if (baseline.effects != s->effects) - { - bits |= U_EFFECTS; - if (s->effects & 0xFF00) - bits |= U_EFFECTS2; - } - if (baseline.modelindex != s->modelindex) - { - bits |= U_MODEL; - if ((s->modelindex & 0xFF00) && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) - bits |= U_MODEL2; - } - if (baseline.alpha != s->alpha) - bits |= U_ALPHA; - if (baseline.scale != s->scale) - bits |= U_SCALE; - if (baseline.glowsize != s->glowsize) - bits |= U_GLOWSIZE; - if (baseline.glowcolor != s->glowcolor) - bits |= U_GLOWCOLOR; - if (!VectorCompare(baseline.colormod, s->colormod)) - bits |= U_COLORMOD; - - // if extensions are disabled, clear the relevant update flags - if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_NEHAHRAMOVIE) - bits &= 0x7FFF; - if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) - if (s->alpha != 255 || s->effects & EF_FULLBRIGHT) - bits |= U_EXTEND1; - - // write the message - if (bits >= 16777216) - bits |= U_EXTEND2; - if (bits >= 65536) - bits |= U_EXTEND1; - if (bits >= 256) - bits |= U_MOREBITS; - bits |= U_SIGNAL; - - { - ENTITYSIZEPROFILING_START(msg, states[i]->number, bits); - - MSG_WriteByte (&buf, bits); - if (bits & U_MOREBITS) MSG_WriteByte(&buf, bits>>8); - if (sv.protocol != PROTOCOL_NEHAHRAMOVIE) - { - if (bits & U_EXTEND1) MSG_WriteByte(&buf, bits>>16); - if (bits & U_EXTEND2) MSG_WriteByte(&buf, bits>>24); - } - if (bits & U_LONGENTITY) MSG_WriteShort(&buf, s->number); - else MSG_WriteByte(&buf, s->number); - - if (bits & U_MODEL) - { - if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) - MSG_WriteShort(&buf, s->modelindex); - else - MSG_WriteByte(&buf, s->modelindex); - } - if (bits & U_FRAME) MSG_WriteByte(&buf, s->frame); - if (bits & U_COLORMAP) MSG_WriteByte(&buf, s->colormap); - if (bits & U_SKIN) MSG_WriteByte(&buf, s->skin); - if (bits & U_EFFECTS) MSG_WriteByte(&buf, s->effects); - if (bits & U_ORIGIN1) MSG_WriteCoord(&buf, s->origin[0], sv.protocol); - if (bits & U_ANGLE1) MSG_WriteAngle(&buf, s->angles[0], sv.protocol); - if (bits & U_ORIGIN2) MSG_WriteCoord(&buf, s->origin[1], sv.protocol); - if (bits & U_ANGLE2) MSG_WriteAngle(&buf, s->angles[1], sv.protocol); - if (bits & U_ORIGIN3) MSG_WriteCoord(&buf, s->origin[2], sv.protocol); - if (bits & U_ANGLE3) MSG_WriteAngle(&buf, s->angles[2], sv.protocol); - if (bits & U_ALPHA) MSG_WriteByte(&buf, s->alpha); - if (bits & U_SCALE) MSG_WriteByte(&buf, s->scale); - if (bits & U_EFFECTS2) MSG_WriteByte(&buf, s->effects >> 8); - if (bits & U_GLOWSIZE) MSG_WriteByte(&buf, s->glowsize); - if (bits & U_GLOWCOLOR) MSG_WriteByte(&buf, s->glowcolor); - if (bits & U_COLORMOD) {int c = ((int)bound(0, s->colormod[0] * (7.0f / 32.0f), 7) << 5) | ((int)bound(0, s->colormod[1] * (7.0f / 32.0f), 7) << 2) | ((int)bound(0, s->colormod[2] * (3.0f / 32.0f), 3) << 0);MSG_WriteByte(&buf, c);} - if (bits & U_FRAME2) MSG_WriteByte(&buf, s->frame >> 8); - if (bits & U_MODEL2) MSG_WriteByte(&buf, s->modelindex >> 8); - - // the nasty protocol - if ((bits & U_EXTEND1) && sv.protocol == PROTOCOL_NEHAHRAMOVIE) - { - if (s->effects & EF_FULLBRIGHT) - { - MSG_WriteFloat(&buf, 2); // QSG protocol version - MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha - MSG_WriteFloat(&buf, 1); // fullbright - } - else - { - MSG_WriteFloat(&buf, 1); // QSG protocol version - MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha - } - } - - // if the commit is full, we're done this frame - if (msg->cursize + buf.cursize > maxsize) - { - // next frame we will continue where we left off - break; - } - // write the message to the packet - SZ_Write(msg, buf.data, buf.cursize); - success = true; - ENTITYSIZEPROFILING_END(msg, s->number, bits); - } - } - return success; -} - int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n) { unsigned int bits; @@ -1305,58 +629,6 @@ void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_fram } } -// (client) adds a entity_frame to the database, for future reference -void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata) -{ - int n, e; - entity_frameinfo_t *info; - - VectorCopy(eye, d->eye); - - // figure out how many entity slots are used already - if (d->numframes) - { - n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; - if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) - { - // ran out of room, dump database - EntityFrame_ClearDatabase(d); - } - } - - info = &d->frames[d->numframes]; - info->framenum = framenum; - e = -1000; - // make sure we check the newly added frame as well, but we haven't incremented numframes yet - for (n = 0;n <= d->numframes;n++) - { - if (e >= d->frames[n].framenum) - { - if (e == framenum) - Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); - else - Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); - return; - } - e = d->frames[n].framenum; - } - // if database still has frames after that... - if (d->numframes) - info->firstentity = d->frames[d->numframes - 1].endentity; - else - info->firstentity = 0; - info->endentity = info->firstentity + numentities; - d->numframes++; - - n = info->firstentity % MAX_ENTITY_DATABASE; - e = MAX_ENTITY_DATABASE - n; - if (e > numentities) - e = numentities; - memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); - if (numentities > e) - memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); -} - // (server) adds a entity_frame to the database, for future reference void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata) { @@ -1479,148 +751,6 @@ qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_databas return true; } -// (client) reads a frame from network stream -void EntityFrame_CL_ReadFrame(void) -{ - int i, number, removed; - entity_frame_t *f, *delta; - entity_state_t *e, *old, *oldend; - entity_t *ent; - entityframe_database_t *d; - if (!cl.entitydatabase) - cl.entitydatabase = EntityFrame_AllocDatabase(cls.levelmempool); - d = cl.entitydatabase; - f = &d->framedata; - delta = &d->deltaframe; - - EntityFrame_Clear(f, NULL, -1); - - // read the frame header info - f->time = cl.mtime[0]; - number = MSG_ReadLong(&cl_message); - f->framenum = MSG_ReadLong(&cl_message); - CL_NewFrameReceived(f->framenum); - f->eye[0] = MSG_ReadFloat(&cl_message); - f->eye[1] = MSG_ReadFloat(&cl_message); - f->eye[2] = MSG_ReadFloat(&cl_message); - EntityFrame_AckFrame(d, number); - EntityFrame_FetchFrame(d, number, delta); - old = delta->entitydata; - oldend = old + delta->numentities; - // read entities until we hit the magic 0xFFFF end tag - while ((number = (unsigned short) MSG_ReadShort(&cl_message)) != 0xFFFF && !cl_message.badread) - { - if (cl_message.badread) - Host_Error("EntityFrame_Read: read error"); - removed = number & 0x8000; - number &= 0x7FFF; - if (number >= MAX_EDICTS) - Host_Error("EntityFrame_Read: number (%i) >= MAX_EDICTS (%i)", number, MAX_EDICTS); - - // seek to entity, while copying any skipped entities (assume unchanged) - while (old < oldend && old->number < number) - { - if (f->numentities >= MAX_ENTITY_DATABASE) - Host_Error("EntityFrame_Read: entity list too big"); - f->entitydata[f->numentities] = *old++; - f->entitydata[f->numentities++].time = cl.mtime[0]; - } - if (removed) - { - if (old < oldend && old->number == number) - old++; - else - Con_Printf("EntityFrame_Read: REMOVE on unused entity %i\n", number); - } - else - { - if (f->numentities >= MAX_ENTITY_DATABASE) - Host_Error("EntityFrame_Read: entity list too big"); - - // reserve this slot - e = f->entitydata + f->numentities++; - - if (old < oldend && old->number == number) - { - // delta from old entity - *e = *old++; - } - else - { - // delta from defaults - *e = defaultstate; - } - - if (cl.num_entities <= number) - { - cl.num_entities = number + 1; - if (number >= cl.max_entities) - CL_ExpandEntities(number); - } - cl.entities_active[number] = true; - e->active = ACTIVE_NETWORK; - e->time = cl.mtime[0]; - e->number = number; - EntityState_ReadFields(e, EntityState_ReadExtendBits()); - } - } - while (old < oldend) - { - if (f->numentities >= MAX_ENTITY_DATABASE) - Host_Error("EntityFrame_Read: entity list too big"); - f->entitydata[f->numentities] = *old++; - f->entitydata[f->numentities++].time = cl.mtime[0]; - } - EntityFrame_AddFrame_Client(d, f->eye, f->framenum, f->numentities, f->entitydata); - - memset(cl.entities_active, 0, cl.num_entities * sizeof(unsigned char)); - number = 1; - for (i = 0;i < f->numentities;i++) - { - for (;number < f->entitydata[i].number && number < cl.num_entities;number++) - { - if (cl.entities_active[number]) - { - cl.entities_active[number] = false; - cl.entities[number].state_current.active = ACTIVE_NOT; - } - } - if (number >= cl.num_entities) - break; - // update the entity - ent = &cl.entities[number]; - ent->state_previous = ent->state_current; - ent->state_current = f->entitydata[i]; - CL_MoveLerpEntityStates(ent); - // the entity lives again... - cl.entities_active[number] = true; - number++; - } - for (;number < cl.num_entities;number++) - { - if (cl.entities_active[number]) - { - cl.entities_active[number] = false; - cl.entities[number].state_current.active = ACTIVE_NOT; - } - } -} - - -// (client) returns the frame number of the most recent frame recieved -int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d) -{ - if (d->numframes) - return d->frames[d->numframes - 1].framenum; - else - return -1; -} - - - - - - entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number) { if (d->maxreferenceentities <= number) diff --git a/protocol.h b/protocol.h index 8770d82b..1799ca11 100644 --- a/protocol.h +++ b/protocol.h @@ -30,6 +30,24 @@ protocolversion_t Protocol_EnumForNumber(int n); int Protocol_NumberForEnum(protocolversion_t p); void Protocol_Names(char *buffer, size_t buffersize); +#define ENTITYSIZEPROFILING_START(msg, num, flags) \ + int entityprofiling_startsize = msg->cursize + +#define ENTITYSIZEPROFILING_END(msg, num, flags) \ + if(developer_networkentities.integer >= 2) \ + { \ + prvm_edict_t *edict = prog->edicts + num; \ + Con_Printf("sent entity update of size %u for %d classname %s flags %d\n", (msg->cursize - entityprofiling_startsize), num, PRVM_serveredictstring(edict, classname) ? PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)) : "(no classname)", flags); \ + } + +// CSQC entity scope values. Bitflags! +#define SCOPE_WANTREMOVE 1 // Set if a remove has been scheduled. Never set together with WANTUPDATE. +#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. + + // model effects #define MF_ROCKET 1 // leave a trail #define MF_GRENADE 2 // leave a trail diff --git a/sv_entframe_csqc.c b/sv_entframe_csqc.c new file mode 100644 index 00000000..9144f963 --- /dev/null +++ b/sv_entframe_csqc.c @@ -0,0 +1,350 @@ +#include "quakedef.h" +#include "protocol.h" + +// NOTE: this only works with DP5 protocol and upwards. For lower protocols +// (including QUAKE), no packet loss handling for CSQC is done, which makes +// CSQC basically useless. +// Always use the DP5 protocol, or a higher one, when using CSQC entities. +static void EntityFrameCSQC_LostAllFrames(client_t *client) +{ + prvm_prog_t *prog = SVVM_prog; + // mark ALL csqc entities as requiring a FULL resend! + // I know this is a bad workaround, but better than nothing. + int i, n; + prvm_edict_t *ed; + + n = client->csqcnumedicts; + for(i = 0; i < n; ++i) + { + if(client->csqcentityscope[i] & SCOPE_EXISTED_ONCE) + { + ed = prog->edicts + i; + client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND. We can't clear SCOPE_ASSUMED_EXISTING yet as this would cancel removes on a rejected send attempt. + if (!PRVM_serveredictfunction(ed, SendEntity)) // If it was ever sent to that client as a CSQC entity... + client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE. + } + } +} +void EntityFrameCSQC_LostFrame(client_t *client, int framenum) +{ + // marks a frame as lost + int i, j; + qboolean 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; + else 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; + } + else + { + // 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... + } + else 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]; + } +} +static 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; +} +static 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"); +} + +//[515]: we use only one array per-client for SendEntity feature +// TODO: add some handling for entity send priorities, to better deal with huge +// amounts of csqc networked entities +qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum) +{ + prvm_prog_t *prog = SVVM_prog; + int num, number, end, sendflags; + qboolean sectionstarted = false; + const unsigned short *n; + prvm_edict_t *ed; + client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; + int dbframe = EntityFrameCSQC_AllocFrame(client, framenum); + csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; + + if(client->csqcentityframe_lastreset < 0) + client->csqcentityframe_lastreset = framenum; + + maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!) + + // make sure there is enough room to store the svc_csqcentities byte, + // the terminator (0x0000) and at least one entity update + if (msg->cursize + 32 >= maxsize) + return false; + + if (client->csqcnumedicts < prog->num_edicts) + client->csqcnumedicts = prog->num_edicts; + + number = 1; + for (num = 0, n = numbers;num < numnumbers;num++, n++) + { + end = *n; + 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 = prog->edicts + number; + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + if (PRVM_serveredictfunction(ed, 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; + } + + // now try to emit the entity updates + // (FIXME: prioritize by distance?) + 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 = prog->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_csqcentities); + } + // write the remove message + { + ENTITYSIZEPROFILING_START(msg, number, 0); + 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; + ENTITYSIZEPROFILING_END(msg, number, 0); + } + if (msg->cursize + 17 >= maxsize) + break; + } + else + { + // save the cursize value in case we overflow and have to rollback + int oldcursize = msg->cursize; + + // An update. + sendflags = client->csqcentitysendflags[number]; + // Nothing to send? FINE. + if (!sendflags) + continue; + // If it's a new entity, always assume sendflags 0xFFFFFF. + if (!(client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)) + sendflags = 0xFFFFFF; + + // write an update + if (PRVM_serveredictfunction(ed, SendEntity)) + { + if(!sectionstarted) + MSG_WriteByte(msg, svc_csqcentities); + { + int oldcursize2 = msg->cursize; + ENTITYSIZEPROFILING_START(msg, number, sendflags); + MSG_WriteShort(msg, number); + msg->allowoverflow = true; + PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; + PRVM_G_FLOAT(OFS_PARM1) = sendflags; + PRVM_serverglobaledict(self) = number; + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n"); + msg->allowoverflow = false; + if(!PRVM_G_FLOAT(OFS_RETURN)) + { + // Send rejected by CSQC. This means we want to remove it. + // CSQC requests we remove this one. + if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) + { + msg->cursize = oldcursize2; + msg->overflowed = false; + MSG_WriteShort(msg, (unsigned short)number | 0x8000); + client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING); + client->csqcentitysendflags[number] = 0; + db->entno[db->num] = number; + db->sendflags[db->num] = -1; + db->num += 1; + // and take note that we have begun the svc_csqcentities + // section of the packet + sectionstarted = 1; + ENTITYSIZEPROFILING_END(msg, number, 0); + if (msg->cursize + 17 >= maxsize) + break; + } + else + { + // Nothing to do. Just don't do it again. + msg->cursize = oldcursize; + msg->overflowed = false; + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + client->csqcentitysendflags[number] = 0; + } + continue; + } + else if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize) + { + // an update has been successfully written + client->csqcentitysendflags[number] = 0; + db->entno[db->num] = number; + db->sendflags[db->num] = sendflags; + db->num += 1; + client->csqcentityscope[number] &= ~SCOPE_WANTSEND; + client->csqcentityscope[number] |= SCOPE_EXISTED_ONCE | SCOPE_ASSUMED_EXISTING; + // and take note that we have begun the svc_csqcentities + // section of the packet + sectionstarted = 1; + ENTITYSIZEPROFILING_END(msg, number, sendflags); + if (msg->cursize + 17 >= maxsize) + break; + continue; + } + } + } + // self.SendEntity returned false (or does not exist) or the + // 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); + } + + if(db->num == 0) + // if no single ent got added, remove the frame from the DB again, to allow + // for a larger history + EntityFrameCSQC_DeallocFrame(client, framenum); + + return sectionstarted; +} \ No newline at end of file diff --git a/sv_entframe_quake.c b/sv_entframe_quake.c new file mode 100644 index 00000000..05d82d9c --- /dev/null +++ b/sv_entframe_quake.c @@ -0,0 +1,172 @@ +#include "quakedef.h" +#include "protocol.h" + +qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states) +{ + prvm_prog_t *prog = SVVM_prog; + const entity_state_t *s; + entity_state_t baseline; + int i, bits; + sizebuf_t buf; + unsigned char data[128]; + qboolean success = false; + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + for (i = 0;i < numstates;i++) + { + s = states[i]; + if(PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) + continue; + + // prepare the buffer + SZ_Clear(&buf); + +// send an update + bits = 0; + if (s->number >= 256) + bits |= U_LONGENTITY; + if (s->flags & RENDER_STEP) + bits |= U_STEP; + if (s->flags & RENDER_VIEWMODEL) + bits |= U_VIEWMODEL; + if (s->flags & RENDER_GLOWTRAIL) + bits |= U_GLOWTRAIL; + if (s->flags & RENDER_EXTERIORMODEL) + bits |= U_EXTERIORMODEL; + + // LadyHavoc: old stuff, but rewritten to have more exact tolerances + baseline = prog->edicts[s->number].priv.server->baseline; + if (baseline.origin[0] != s->origin[0]) + bits |= U_ORIGIN1; + if (baseline.origin[1] != s->origin[1]) + bits |= U_ORIGIN2; + if (baseline.origin[2] != s->origin[2]) + bits |= U_ORIGIN3; + if (baseline.angles[0] != s->angles[0]) + bits |= U_ANGLE1; + if (baseline.angles[1] != s->angles[1]) + bits |= U_ANGLE2; + if (baseline.angles[2] != s->angles[2]) + bits |= U_ANGLE3; + if (baseline.colormap != s->colormap) + bits |= U_COLORMAP; + if (baseline.skin != s->skin) + bits |= U_SKIN; + if (baseline.frame != s->frame) + { + bits |= U_FRAME; + if (s->frame & 0xFF00) + bits |= U_FRAME2; + } + if (baseline.effects != s->effects) + { + bits |= U_EFFECTS; + if (s->effects & 0xFF00) + bits |= U_EFFECTS2; + } + if (baseline.modelindex != s->modelindex) + { + bits |= U_MODEL; + if ((s->modelindex & 0xFF00) && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) + bits |= U_MODEL2; + } + if (baseline.alpha != s->alpha) + bits |= U_ALPHA; + if (baseline.scale != s->scale) + bits |= U_SCALE; + if (baseline.glowsize != s->glowsize) + bits |= U_GLOWSIZE; + if (baseline.glowcolor != s->glowcolor) + bits |= U_GLOWCOLOR; + if (!VectorCompare(baseline.colormod, s->colormod)) + bits |= U_COLORMOD; + + // if extensions are disabled, clear the relevant update flags + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_NEHAHRAMOVIE) + bits &= 0x7FFF; + if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) + if (s->alpha != 255 || s->effects & EF_FULLBRIGHT) + bits |= U_EXTEND1; + + // write the message + if (bits >= 16777216) + bits |= U_EXTEND2; + if (bits >= 65536) + bits |= U_EXTEND1; + if (bits >= 256) + bits |= U_MOREBITS; + bits |= U_SIGNAL; + + { + ENTITYSIZEPROFILING_START(msg, states[i]->number, bits); + + MSG_WriteByte (&buf, bits); + if (bits & U_MOREBITS) MSG_WriteByte(&buf, bits>>8); + if (sv.protocol != PROTOCOL_NEHAHRAMOVIE) + { + if (bits & U_EXTEND1) MSG_WriteByte(&buf, bits>>16); + if (bits & U_EXTEND2) MSG_WriteByte(&buf, bits>>24); + } + if (bits & U_LONGENTITY) MSG_WriteShort(&buf, s->number); + else MSG_WriteByte(&buf, s->number); + + if (bits & U_MODEL) + { + if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + MSG_WriteShort(&buf, s->modelindex); + else + MSG_WriteByte(&buf, s->modelindex); + } + if (bits & U_FRAME) MSG_WriteByte(&buf, s->frame); + if (bits & U_COLORMAP) MSG_WriteByte(&buf, s->colormap); + if (bits & U_SKIN) MSG_WriteByte(&buf, s->skin); + if (bits & U_EFFECTS) MSG_WriteByte(&buf, s->effects); + if (bits & U_ORIGIN1) MSG_WriteCoord(&buf, s->origin[0], sv.protocol); + if (bits & U_ANGLE1) MSG_WriteAngle(&buf, s->angles[0], sv.protocol); + if (bits & U_ORIGIN2) MSG_WriteCoord(&buf, s->origin[1], sv.protocol); + if (bits & U_ANGLE2) MSG_WriteAngle(&buf, s->angles[1], sv.protocol); + if (bits & U_ORIGIN3) MSG_WriteCoord(&buf, s->origin[2], sv.protocol); + if (bits & U_ANGLE3) MSG_WriteAngle(&buf, s->angles[2], sv.protocol); + if (bits & U_ALPHA) MSG_WriteByte(&buf, s->alpha); + if (bits & U_SCALE) MSG_WriteByte(&buf, s->scale); + if (bits & U_EFFECTS2) MSG_WriteByte(&buf, s->effects >> 8); + if (bits & U_GLOWSIZE) MSG_WriteByte(&buf, s->glowsize); + if (bits & U_GLOWCOLOR) MSG_WriteByte(&buf, s->glowcolor); + if (bits & U_COLORMOD) {int c = ((int)bound(0, s->colormod[0] * (7.0f / 32.0f), 7) << 5) | ((int)bound(0, s->colormod[1] * (7.0f / 32.0f), 7) << 2) | ((int)bound(0, s->colormod[2] * (3.0f / 32.0f), 3) << 0);MSG_WriteByte(&buf, c);} + if (bits & U_FRAME2) MSG_WriteByte(&buf, s->frame >> 8); + if (bits & U_MODEL2) MSG_WriteByte(&buf, s->modelindex >> 8); + + // the nasty protocol + if ((bits & U_EXTEND1) && sv.protocol == PROTOCOL_NEHAHRAMOVIE) + { + if (s->effects & EF_FULLBRIGHT) + { + MSG_WriteFloat(&buf, 2); // QSG protocol version + MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha + MSG_WriteFloat(&buf, 1); // fullbright + } + else + { + MSG_WriteFloat(&buf, 1); // QSG protocol version + MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha + } + } + + // if the commit is full, we're done this frame + if (msg->cursize + buf.cursize > maxsize) + { + // next frame we will continue where we left off + break; + } + // write the message to the packet + SZ_Write(msg, buf.data, buf.cursize); + success = true; + ENTITYSIZEPROFILING_END(msg, s->number, bits); + } + } + return success; +} \ No newline at end of file