X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;ds=sidebyside;f=sv_main.c;h=75d43359ef19e3c295f5b74cb541c6f27c6b06c4;hb=68df12c14ca723e8cca3d0c0ce21a11cdb12fe2c;hp=853d57d9ebd055bcee60610517ea8f0d7ad332f6;hpb=cd524e2892cf91764996d5e7cd45ac5191708ef5;p=xonotic%2Fdarkplaces.git diff --git a/sv_main.c b/sv_main.c index 853d57d9..75d43359 100644 --- a/sv_main.c +++ b/sv_main.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "sv_demo.h" #include "libcurl.h" +#include "csprogs.h" static void SV_SaveEntFile_f(void); static void SV_StartDownload_f(void); @@ -30,7 +31,6 @@ static void SV_VM_Setup(); void VM_CustomStats_Clear (void); void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); -void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int numstates, const entity_state_t *states); cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"}; cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"}; @@ -65,13 +65,15 @@ cvar_t sv_cullentities_pvs = {0, "sv_cullentities_pvs", "1", "fast but loose cul cvar_t sv_cullentities_stats = {0, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; cvar_t sv_cullentities_trace = {0, "sv_cullentities_trace", "0", "somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless"}; cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +cvar_t sv_cullentities_trace_delay_players = {0, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"}; cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"}; cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "1", "number of samples to test for entity culling"}; cvar_t sv_cullentities_trace_samples_extra = {0, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"}; +cvar_t sv_cullentities_trace_samples_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"}; cvar_t sv_debugmove = {CVAR_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"}; cvar_t sv_echobprint = {CVAR_SAVE, "sv_echobprint", "1", "prints gamecode bprint() calls to server console"}; -cvar_t sv_edgefriction = {0, "edgefriction", "2", "how much you slow down when nearing a ledge you might fall off"}; +cvar_t sv_edgefriction = {0, "edgefriction", "1", "how much you slow down when nearing a ledge you might fall off, multiplier of sv_friction (Quake used 2, QuakeWorld used 1 due to a bug in physics code)"}; cvar_t sv_entpatch = {0, "sv_entpatch", "1", "enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps)"}; cvar_t sv_fixedframeratesingleplayer = {0, "sv_fixedframeratesingleplayer", "1", "allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate)"}; cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"}; @@ -212,6 +214,7 @@ prvm_required_field_t reqfields[] = {ev_entity, "nodrawtoclient"}, {ev_entity, "tag_entity"}, {ev_entity, "viewmodelforclient"}, + {ev_float, "SendFlags"}, {ev_float, "Version"}, {ev_float, "alpha"}, {ev_float, "ammo_cells1"}, @@ -337,10 +340,12 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_cullentities_stats); Cvar_RegisterVariable (&sv_cullentities_trace); Cvar_RegisterVariable (&sv_cullentities_trace_delay); + Cvar_RegisterVariable (&sv_cullentities_trace_delay_players); Cvar_RegisterVariable (&sv_cullentities_trace_enlarge); Cvar_RegisterVariable (&sv_cullentities_trace_prediction); Cvar_RegisterVariable (&sv_cullentities_trace_samples); Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra); + Cvar_RegisterVariable (&sv_cullentities_trace_samples_players); Cvar_RegisterVariable (&sv_debugmove); Cvar_RegisterVariable (&sv_echobprint); Cvar_RegisterVariable (&sv_edgefriction); @@ -728,13 +733,47 @@ void SV_SendServerinfo (client_t *client) } // reset csqc entity versions - memset(client->csqcentityversion, 0, sizeof(client->csqcentityversion)); + for (i = 0;i < prog->max_edicts;i++) + { + client->csqcentityscope[i] = 0; + client->csqcentitysendflags[i] = 0xFFFFFF; + client->csqcentityglobalhistory[i] = 0; + } + for (i = 0;i < NUM_CSQCENTITYDB_FRAMES;i++) + { + client->csqcentityframehistory[i].num = 0; + client->csqcentityframehistory[i].framenum = -1; + } + client->csqcnumedicts = 0; + client->csqcentityframehistory_next = 0; SZ_Clear (&client->netconnection->message); MSG_WriteByte (&client->netconnection->message, svc_print); dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)", gamename, buildstring, prog->filecrc); MSG_WriteString (&client->netconnection->message,message); + SV_StopDemoRecording(client); // to split up demos into different files + if(sv_autodemo_perclient.integer && client->netconnection) + { + char demofile[MAX_OSPATH]; + char levelname[MAX_QPATH]; + char ipaddress[MAX_QPATH]; + size_t i; + + // start a new demo file + strlcpy(levelname, FS_FileWithoutPath(sv.worldmodel->name), sizeof(levelname)); + if (strrchr(levelname, '.')) + *(strrchr(levelname, '.')) = 0; + + LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); + for(i = 0; ipaddress[i]; ++i) + if(!isalnum(ipaddress[i])) + ipaddress[i] = '-'; + dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), levelname, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); + + SV_StartDemoRecording(client, demofile, -1); + } + //[515]: init csprogs according to version of svprogs, check the crc, etc. if (sv.csqc_progname[0]) { @@ -746,6 +785,20 @@ void SV_SendServerinfo (client_t *client) MSG_WriteString (&client->netconnection->message, va("csqc_progsize %i\n", sv.csqc_progsize)); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", sv.csqc_progcrc)); + + if(client->sv_demo_file != NULL) + { + int i; + char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + + sb.data = (unsigned char *) buf; + sb.maxsize = sizeof(buf); + i = 0; + while(MakeDownloadPacket(sv.csqc_progname, sv.csqc_progdata, sv.csqc_progsize, sv.csqc_progcrc, i++, &sb, sv.protocol)) + SV_WriteDemoMessage(client, &sb, false); + } + //[515]: init stufftext string (it is sent before svc_serverinfo) val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.SV_InitCmd); if (val) @@ -755,10 +808,12 @@ void SV_SendServerinfo (client_t *client) } } - if (sv_allowdownloads.integer) + //if (sv_allowdownloads.integer) + // always send the info that the server supports the protocol, even if downloads are forbidden + // only because of that, the CSQC exception can work { MSG_WriteByte (&client->netconnection->message, svc_stufftext); - MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 1\n"); + MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 2\n"); } // send at this time so it's guaranteed to get executed at the right time @@ -815,28 +870,6 @@ void SV_SendServerinfo (client_t *client) client->num_pings = 0; #endif client->ping = 0; - - SV_StopDemoRecording(client); // to split up demos into different files - if(sv_autodemo_perclient.integer && client->netconnection) - { - char demofile[MAX_OSPATH]; - char levelname[MAX_QPATH]; - char ipaddress[MAX_QPATH]; - size_t i; - - // start a new demo file - strlcpy(levelname, FS_FileWithoutPath(sv.worldmodel->name), sizeof(levelname)); - if (strrchr(levelname, '.')) - *(strrchr(levelname, '.')) = 0; - - LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); - for(i = 0; ipaddress[i]; ++i) - if(!isalnum(ipaddress[i])) - ipaddress[i] = '-'; - dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), levelname, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); - - SV_StartDemoRecording(client, demofile, -1); - } } /* @@ -926,12 +959,14 @@ crosses a waterline. static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber) { int i; + unsigned int sendflags; + unsigned int version; unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius; unsigned int customizeentityforclient; float f; vec3_t cullmins, cullmaxs; - model_t *model; - prvm_eval_t *val; + dp_model_t *model; + prvm_eval_t *val, *val2; // this 2 billion unit check is actually to detect NAN origins // (we really don't want to send those) @@ -1108,7 +1143,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c // calculate the visible box of this entity (don't use the physics box // as that is often smaller than a model, and would not count // specialvisibilityradius) - if ((model = sv.models[modelindex])) + if ((model = sv.models[modelindex]) && (model->type != mod_null)) { float scale = cs->scale * (1.0f / 16.0f); if (cs->angles[0] || cs->angles[2]) // pitch and roll @@ -1164,6 +1199,30 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c } } + // we need to do some csqc entity upkeep here + // get self.SendFlags and clear them + // (to let the QC know that they've been read) + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendEntity); + if (val->function) + { + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendFlags); + sendflags = (unsigned int)val->_float; + val->_float = 0; + // legacy self.Version system + val2 = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.Version); + if (val2->_float) + { + version = (unsigned int)val2->_float; + if (sv.csqcentityversion[enumber] != version) + sendflags = 0xFFFFFF; + sv.csqcentityversion[enumber] = version; + } + // move sendflags into the per-client sendflags + if (sendflags) + for (i = 0;i < svs.maxclients;i++) + svs.clients[i].csqcentitysendflags[enumber] |= sendflags; + } + return true; } @@ -1188,7 +1247,7 @@ void SV_PrepareEntitiesForSending(void) void SV_MarkWriteEntityStateToClient(entity_state_t *s) { int isbmodel; - model_t *model; + dp_model_t *model; prvm_edict_t *ed; if (sv.sententitiesconsideration[s->number] == sv.sententitiesmark) return; @@ -1272,48 +1331,62 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s) // or not seen by random tracelines if (sv_cullentities_trace.integer && !isbmodel && sv.worldmodel->brush.TraceLineOfSight) { - int samples = s->specialvisibilityradius ? sv_cullentities_trace_samples_extra.integer : sv_cullentities_trace_samples.integer; + int samples = + s->number <= svs.maxclients + ? sv_cullentities_trace_samples_players.integer + : + s->specialvisibilityradius + ? sv_cullentities_trace_samples_extra.integer + : sv_cullentities_trace_samples.integer; float enlarge = sv_cullentities_trace_enlarge.value; qboolean visible = TRUE; - do + if(samples > 0) { - if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) - break; // directly visible from the server's view - - if(sv_cullentities_trace_prediction.integer) + do { - vec3_t predeye; - - // get player velocity - float predtime = bound(0, host_client->ping, 0.2); // / 2 - // sorry, no wallhacking by high ping please, and at 200ms - // ping a FPS is annoying to play anyway and a player is - // likely to have changed his direction - VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye); - if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there... - { - if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) - break; // directly visible from the predicted view - } - else + if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + break; // directly visible from the server's view + + if(sv_cullentities_trace_prediction.integer) { - //Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n"); + vec3_t predeye; + + // get player velocity + float predtime = bound(0, host_client->ping, 0.2); // / 2 + // sorry, no wallhacking by high ping please, and at 200ms + // ping a FPS is annoying to play anyway and a player is + // likely to have changed his direction + VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye); + if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there... + { + if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + break; // directly visible from the predicted view + } + else + { + //Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n"); + } } - } - - // when we get here, we can't see the entity - visible = false; - } - while(0); - if(visible) - svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = realtime + sv_cullentities_trace_delay.value; - else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number]) - { - sv.writeentitiestoclient_stats_culled_trace++; - return; + // when we get here, we can't see the entity + visible = false; + } + while(0); + + if(visible) + svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = + realtime + ( + s->number <= svs.maxclients + ? sv_cullentities_trace_delay_players.value + : sv_cullentities_trace_delay.value + ); + else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number]) + { + sv.writeentitiestoclient_stats_culled_trace++; + return; + } } } } @@ -1326,14 +1399,15 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s) sv.sententities[s->number] = sv.sententitiesmark; } -void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg) +void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int maxsize) { + qboolean need_empty = false; int i, numsendstates; entity_state_t *s; prvm_edict_t *camera; // if there isn't enough space to accomplish anything, skip it - if (msg->cursize + 25 > msg->maxsize) + if (msg->cursize + 25 > maxsize) return; sv.writeentitiestoclient_msg = msg; @@ -1374,23 +1448,26 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t * if (sv_cullentities_stats.integer) Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace); - EntityFrameCSQC_WriteFrame(msg, numsendstates, sv.writeentitiestoclient_sendstates); + if(client->entitydatabase5) + need_empty = EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, client->entitydatabase5->latestframenum + 1); + else + EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, 0); if (client->entitydatabase5) - EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence); + EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence, need_empty); else if (client->entitydatabase4) { - EntityFrame4_WriteFrame(msg, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates); + EntityFrame4_WriteFrame(msg, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates); Protocol_WriteStatsReliable(); } else if (client->entitydatabase) { - EntityFrame_WriteFrame(msg, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1); + EntityFrame_WriteFrame(msg, maxsize, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1); Protocol_WriteStatsReliable(); } else { - EntityFrameQuake_WriteFrame(msg, numsendstates, sv.writeentitiestoclient_sendstates); + EntityFrameQuake_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates); Protocol_WriteStatsReliable(); } } @@ -1531,8 +1608,8 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t stats[STAT_CELLS] = (int)ent->fields.server->ammo_cells; stats[STAT_ACTIVEWEAPON] = (int)ent->fields.server->weapon; stats[STAT_VIEWZOOM] = viewzoom; - stats[STAT_TOTALSECRETS] = prog->globals.server->total_secrets; - stats[STAT_TOTALMONSTERS] = prog->globals.server->total_monsters; + stats[STAT_TOTALSECRETS] = (int)prog->globals.server->total_secrets; + stats[STAT_TOTALMONSTERS] = (int)prog->globals.server->total_monsters; // the QC bumps these itself by sending svc_'s, so we have to keep them // zero or they'll be corrected by the engine //stats[STAT_SECRETS] = prog->globals.server->found_secrets; @@ -1694,23 +1771,24 @@ void SV_FlushBroadcastMessages(void) SZ_Clear(&sv.datagram); } -static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg) +static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int maxsize) { // scan the splitpoints to find out how many we can fit in int numsegments, j, split; if (!client->unreliablemsg_splitpoints) return; - // always accept the first one if it's within 1400 bytes, this ensures + // always accept the first one if it's within 1024 bytes, this ensures // that very big datagrams which are over the rate limit still get // through, just to keep it working - if (msg->cursize + client->unreliablemsg_splitpoint[0] > msg->maxsize && msg->maxsize < 1400) + j = msg->cursize + client->unreliablemsg_splitpoint[0]; + if (maxsize < 1024 && j > maxsize && j <= 1024) { numsegments = 1; - msg->maxsize = 1400; + maxsize = 1024; } else for (numsegments = 0;numsegments < client->unreliablemsg_splitpoints;numsegments++) - if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > msg->maxsize) + if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > maxsize) break; if (numsegments > 0) { @@ -1719,7 +1797,7 @@ static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg) // note this discards ones that were accepted by the segments scan but // can not fit, such as a really huge first one that will never ever // fit in a packet... - if (msg->cursize + split <= msg->maxsize) + if (msg->cursize + split <= maxsize) SZ_Write(msg, client->unreliablemsg.data, split); // remove the part we sent, keeping any remaining data client->unreliablemsg.cursize -= split; @@ -1744,40 +1822,47 @@ static void SV_SendClientDatagram (client_t *client) int stats[MAX_CL_STATS]; unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; + // obey rate limit by limiting packet frequency if the packet size + // limiting fails + // (usually this is caused by reliable messages) + if (!NetConn_CanSend(client->netconnection)) + return; + // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates maxrate = max(NET_MINRATE, sv_maxrate.integer); if (sv_maxrate.integer != maxrate) Cvar_SetValueQuick(&sv_maxrate, maxrate); + // clientrate determines the 'cleartime' of a packet // (how long to wait before sending another, based on this packet's size) clientrate = bound(NET_MINRATE, client->rate, maxrate); - if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer) - { - // for good singleplayer, send huge packets - maxsize = sizeof(sv_sendclientdatagram_buf); - maxsize2 = sizeof(sv_sendclientdatagram_buf); - // never limit frequency in singleplayer - clientrate = 1000000000; - } - else if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_QUAKEWORLD) + switch (sv.protocol) { + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + case PROTOCOL_QUAKEWORLD: // no packet size limit support on Quake protocols because it just // causes missing entities/effects // packets are simply sent less often to obey the rate limit maxsize = 1024; maxsize2 = 1024; - } - else if (sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4) - { + break; + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + case PROTOCOL_DARKPLACES4: // no packet size limit support on DP1-4 protocols because they kick // the client off if they overflow, and miss effects // packets are simply sent less often to obey the rate limit maxsize = sizeof(sv_sendclientdatagram_buf); maxsize2 = sizeof(sv_sendclientdatagram_buf); - } - else - { + break; + default: // DP5 and later protocols support packet size limiting which is a // better method than limiting packet frequency as QW does // @@ -1790,13 +1875,17 @@ static void SV_SendClientDatagram (client_t *client) // mods that use csqc (they are likely to use less bandwidth anyway) if (sv.csqc_progsize > 0) maxsize = maxsize2; + break; } - // obey rate limit by limiting packet frequency if the packet size - // limiting fails - // (usually this is caused by reliable messages) - if (!NetConn_CanSend(client->netconnection)) - return; + if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer) + { + // for good singleplayer, send huge packets + maxsize = sizeof(sv_sendclientdatagram_buf); + maxsize2 = sizeof(sv_sendclientdatagram_buf); + // never limit frequency in singleplayer + clientrate = 1000000000; + } // while downloading, limit entity updates to half the packet // (any leftover space will be used for downloading) @@ -1804,7 +1893,7 @@ static void SV_SendClientDatagram (client_t *client) maxsize /= 2; msg.data = sv_sendclientdatagram_buf; - msg.maxsize = maxsize; + msg.maxsize = sizeof(sv_sendclientdatagram_buf); msg.cursize = 0; msg.allowoverflow = false; @@ -1823,30 +1912,24 @@ static void SV_SendClientDatagram (client_t *client) // add as many queued unreliable messages (effects) as we can fit // limit effects to half of the remaining space - msg.maxsize -= (msg.maxsize - msg.cursize) / 2; if (client->unreliablemsg.cursize) - SV_WriteUnreliableMessages (client, &msg); - - msg.maxsize = maxsize; + SV_WriteUnreliableMessages (client, &msg, (msg.cursize + maxsize) / 2); // now write as many entities as we can fit, and also sends stats - SV_WriteEntitiesToClient (client, client->edict, &msg); + SV_WriteEntitiesToClient (client, client->edict, &msg, maxsize); } else if (realtime > client->keepalivetime) { // the player isn't totally in the game yet // send small keepalive messages if too much time has passed // (may also be sending downloads) - msg.maxsize = maxsize2; client->keepalivetime = realtime + 5; MSG_WriteChar (&msg, svc_nop); } - msg.maxsize = maxsize2; - // if a download is active, see if there is room to fit some download data // in this packet - downloadsize = maxsize * 2 - msg.cursize - 7; + downloadsize = min(maxsize*2,maxsize2) - msg.cursize - 7; if (host_client->download_file && host_client->download_started && downloadsize > 0) { fs_offset_t downloadstart; @@ -1909,7 +1992,7 @@ static void SV_UpdateToReliableMessages (void) if (strcmp(host_client->old_name, host_client->name)) { if (host_client->spawned) - SV_BroadcastPrintf("%s changed name to %s\n", host_client->old_name, host_client->name); + SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatename); @@ -1982,7 +2065,7 @@ static void SV_UpdateToReliableMessages (void) } for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) - if (client->netconnection) + if (client->netconnection && (client->spawned || client->clientconnectcalled)) // also send MSG_ALL to people who are past ClientConnect, but not spawned yet SZ_Write (&client->netconnection->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); SZ_Clear (&sv.reliable_datagram); @@ -2039,13 +2122,58 @@ static void SV_StartDownload_f(void) host_client->download_started = true; } +/* + * Compression extension negotiation: + * + * Server to client: + * cl_serverextension_download 2 + * + * Client to server: + * download + * e.g. + * download maps/map1.bsp lzo deflate huffman + * + * Server to client: + * cl_downloadbegin + * e.g. + * cl_downloadbegin 123456 maps/map1.bsp deflate + * + * The server may choose not to compress the file by sending no compression name, like: + * cl_downloadbegin 345678 maps/map1.bsp + * + * NOTE: the "download" command may only specify compression algorithms if + * cl_serverextension_download is 2! + * If cl_serverextension_download has a different value, the client must + * assume this extension is not supported! + */ + +static void Download_CheckExtensions(void) +{ + int i; + int argc = Cmd_Argc(); + + // first reset them all + host_client->download_deflate = false; + + for(i = 2; i < argc; ++i) + { + if(!strcmp(Cmd_Argv(i), "deflate")) + { + host_client->download_deflate = true; + break; + } + } +} + static void SV_Download_f(void) { const char *whichpack, *whichpack2, *extension; + qboolean is_csqc; // so we need to check only once - if (Cmd_Argc() != 2) + if (Cmd_Argc() < 2) { - SV_ClientPrintf("usage: download \n"); + SV_ClientPrintf("usage: download {}*\n"); + SV_ClientPrintf(" supported extensions: deflate\n"); return; } @@ -2069,13 +2197,17 @@ static void SV_Download_f(void) host_client->download_started = false; } - if (!sv_allowdownloads.integer) + is_csqc = (sv.csqc_progname[0] && strcmp(Cmd_Argv(1), sv.csqc_progname) == 0); + + if (!sv_allowdownloads.integer && !is_csqc) { SV_ClientPrintf("Downloads are disabled on this server\n"); Host_ClientCommands("\nstopdownload\n"); return; } + Download_CheckExtensions(); + strlcpy(host_client->download_name, Cmd_Argv(1), sizeof(host_client->download_name)); extension = FS_FileExtension(host_client->download_name); @@ -2083,6 +2215,30 @@ static void SV_Download_f(void) if (developer.integer >= 100) Con_Printf("Download request for %s by %s\n", host_client->download_name, host_client->name); + if(is_csqc) + { + char extensions[MAX_QPATH]; // make sure this can hold all extensions + extensions[0] = '\0'; + + if(host_client->download_deflate) + strlcat(extensions, " deflate", sizeof(extensions)); + + Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); + + if(host_client->download_deflate) + host_client->download_file = FS_FileFromData(sv.csqc_progdata_deflated, sv.csqc_progsize_deflated, true); + else + host_client->download_file = FS_FileFromData(sv.csqc_progdata, sv.csqc_progsize, true); + + // no, no space is needed between %s and %s :P + Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); + + host_client->download_expectedposition = 0; + host_client->download_started = false; + host_client->sendsignon = true; // make sure this message is sent + return; + } + if (!FS_FileExists(host_client->download_name)) { SV_ClientPrintf("Download rejected: server does not have the file \"%s\"\nYou may need to separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); @@ -2142,7 +2298,7 @@ static void SV_Download_f(void) } } - host_client->download_file = FS_Open(host_client->download_name, "rb", true, false); + host_client->download_file = FS_OpenVirtualFile(host_client->download_name, true); if (!host_client->download_file) { SV_ClientPrintf("Download rejected: server could not open the file \"%s\"\n", host_client->download_name); @@ -2159,8 +2315,31 @@ static void SV_Download_f(void) return; } + if (FS_FileSize(host_client->download_file) < 0) + { + SV_ClientPrintf("Download rejected: file \"%s\" is not a regular file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + return; + } + Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); + /* + * we can only do this if we would actually deflate on the fly + * which we do not (yet)! + { + char extensions[MAX_QPATH]; // make sure this can hold all extensions + extensions[0] = '\0'; + + if(host_client->download_deflate) + strlcat(extensions, " deflate", sizeof(extensions)); + + // no, no space is needed between %s and %s :P + Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); + } + */ Host_ClientCommands("\ncl_downloadbegin %i %s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name); host_client->download_expectedposition = 0; @@ -2439,6 +2618,51 @@ static void SV_CreateBaseline (void) } } +/* +================ +SV_Prepare_CSQC + +Load csprogs.dat and comperss it so it doesn't need to be +reloaded on request. +================ +*/ +void SV_Prepare_CSQC(void) +{ + fs_offset_t progsize; + + if(sv.csqc_progdata) + { + Con_DPrintf("Unloading old CSQC data.\n"); + Mem_Free(sv.csqc_progdata); + if(sv.csqc_progdata_deflated) + Mem_Free(sv.csqc_progdata_deflated); + } + + sv.csqc_progdata = NULL; + sv.csqc_progdata_deflated = NULL; + + Con_Print("Loading csprogs.dat\n"); + + sv.csqc_progname[0] = 0; + sv.csqc_progdata = FS_LoadFile(csqc_progname.string, sv_mempool, false, &progsize); + + if(progsize > 0) + { + size_t deflated_size; + + sv.csqc_progsize = (int)progsize; + sv.csqc_progcrc = CRC_Block(sv.csqc_progdata, progsize); + strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname)); + Con_Printf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); + + Con_Print("Compressing csprogs.dat\n"); + //unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); + sv.csqc_progdata_deflated = FS_Deflate(sv.csqc_progdata, progsize, &deflated_size, -1, sv_mempool); + sv.csqc_progsize_deflated = (int)deflated_size; + Con_Printf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize)); + Con_DPrintf("Uncompressed: %u\nCompressed: %u\n", (unsigned)sv.csqc_progsize, (unsigned)sv.csqc_progsize_deflated); + } +} /* ================ @@ -2480,7 +2704,7 @@ void SV_SpawnServer (const char *server) prvm_edict_t *ent; int i; char *entities; - model_t *worldmodel; + dp_model_t *worldmodel; char modelname[sizeof(sv.modelname)]; Con_DPrintf("SpawnServer: %s\n", server); @@ -2709,6 +2933,7 @@ void SV_SpawnServer (const char *server) // send serverinfo to all connected clients, and set up botclients coming back from a level change for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { + host_client->clientconnectcalled = false; // do NOT call ClientDisconnect if he drops before ClientConnect! if (!host_client->active) continue; if (host_client->netconnection) @@ -2801,6 +3026,9 @@ static void SV_VM_CB_InitEdict(prvm_edict_t *e) static void SV_VM_CB_FreeEdict(prvm_edict_t *ed) { + int i; + int e; + World_UnlinkEdict(ed); // unlink from world bsp ed->fields.server->model = 0; @@ -2813,6 +3041,16 @@ static void SV_VM_CB_FreeEdict(prvm_edict_t *ed) VectorClear(ed->fields.server->angles); ed->fields.server->nextthink = -1; ed->fields.server->solid = 0; + + // make sure csqc networking is aware of the removed entity + e = PRVM_NUM_FOR_EDICT(ed); + sv.csqcentityversion[e] = 0; + for (i = 0;i < svs.maxclients;i++) + { + if (svs.clients[i].csqcentityscope[e]) + svs.clients[i].csqcentityscope[e] = 1; // removed, awaiting send + svs.clients[i].csqcentitysendflags[e] = 0xFFFFFF; + } } static void SV_VM_CB_CountEdicts(void) @@ -2867,10 +3105,6 @@ static qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent) static void SV_VM_Setup(void) { - extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat - extern cvar_t csqc_progcrc; - extern cvar_t csqc_progsize; - size_t csprogsdatasize; PRVM_Begin; PRVM_InitProg( PRVM_SERVERPROG ); @@ -2944,15 +3178,7 @@ static void SV_VM_Setup(void) PRVM_End; - // see if there is a csprogs.dat installed, and if so, set the csqc_progcrc accordingly, this will be sent to connecting clients to tell them to only load a matching csprogs.dat file - sv.csqc_progname[0] = 0; - sv.csqc_progcrc = FS_CRCFile(csqc_progname.string, &csprogsdatasize); - sv.csqc_progsize = csprogsdatasize; - if (sv.csqc_progsize > 0) - { - strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname)); - Con_DPrintf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); - } + SV_Prepare_CSQC(); } void SV_VM_Begin(void)