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 maxsize, 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)"};
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"};
{ev_entity, "nodrawtoclient"},
{ev_entity, "tag_entity"},
{ev_entity, "viewmodelforclient"},
+ {ev_float, "SendFlags"},
{ev_float, "Version"},
{ev_float, "alpha"},
{ev_float, "ammo_cells1"},
}
// 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);
if(client->sv_demo_file != NULL)
{
- void *csqcbuf;
- fs_offset_t csqclen;
- int csqccrc;
int i;
char buf[NET_MAXMESSAGE];
sizebuf_t sb;
- csqcbuf = FS_LoadFile(sv.csqc_progname, tempmempool, true, &csqclen);
- if(csqcbuf)
- {
- csqccrc = CRC_Block(csqcbuf, csqclen);
- sb.data = (void *) buf;
- sb.maxsize = sizeof(buf);
- i = 0;
- while(MakeDownloadPacket(sv.csqc_progname, csqcbuf, csqclen, csqccrc, i++, &sb, sv.protocol))
- SV_WriteDemoMessage(client, &sb, false);
- Mem_Free(csqcbuf);
- }
+ 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)
}
}
- 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
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;
dp_model_t *model;
- prvm_eval_t *val;
+ prvm_eval_t *val, *val2;
// this 2 billion unit check is actually to detect NAN origins
// (we really don't want to send those)
// 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
}
}
+ // 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;
}
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 (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, maxsize, 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, maxsize, 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, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates);
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;
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);
host_client->download_started = true;
}
+/*
+ * Compression extension negotiation:
+ *
+ * Server to client:
+ * cl_serverextension_download 2
+ *
+ * Client to server:
+ * download <filename> <list of zero or more suppported compressions in order of preference>
+ * e.g.
+ * download maps/map1.bsp lzo deflate huffman
+ *
+ * Server to client:
+ * cl_downloadbegin <compressed size> <filename> <compression method actually used>
+ * 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 <filename>\n");
+ SV_ClientPrintf("usage: download <filename> {<extensions>}*\n");
+ SV_ClientPrintf(" supported extensions: deflate\n");
return;
}
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);
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);
}
}
- 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);
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;
}
}
+/*
+================
+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);
+ }
+}
/*
================
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;
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)
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 );
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)