X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=sv_main.c;h=5a09477d1f650c44c290257676fd8e3081c5a61e;hb=5c00b1379d819146fb18968fd9b5fa84bf4a770a;hp=e2700e6cdea83d2b8bc1dd1ea021717554420d06;hpb=01ba19424cec267526ca273d809516ff36fe23cb;p=xonotic%2Fdarkplaces.git diff --git a/sv_main.c b/sv_main.c index e2700e6c..5a09477d 100644 --- a/sv_main.c +++ b/sv_main.c @@ -20,14 +20,15 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // sv_main.c -- server main program #include "quakedef.h" +#include "libcurl.h" void SV_VM_Init(); void SV_VM_Setup(); -void VM_AutoSentStats_Clear (void); +void VM_CustomStats_Clear (void); void EntityFrameCSQC_ClearVersions (void); void EntityFrameCSQC_InitClientVersions (int client, qboolean clear); -void VM_SV_WriteAutoSentStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); +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); @@ -35,9 +36,22 @@ void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int numstates, const entity_sta cvar_t sv_protocolname = {0, "sv_protocolname", "DP7", "selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up)"}; cvar_t sv_ratelimitlocalplayer = {0, "sv_ratelimitlocalplayer", "0", "whether to apply rate limiting to the local player in a listen server (only useful for testing)"}; cvar_t sv_maxrate = {CVAR_SAVE | CVAR_NOTIFY, "sv_maxrate", "10000", "upper limit on client rate cvar, should reflect your network connection quality"}; +cvar_t sv_allowdownloads = {0, "sv_allowdownloads", "1", "whether to allow clients to download files from the server (does not affect http downloads)"}; +cvar_t sv_allowdownloads_inarchive = {0, "sv_allowdownloads_inarchive", "0", "whether to allow downloads from archives (pak/pk3)"}; +cvar_t sv_allowdownloads_archive = {0, "sv_allowdownloads_archive", "0", "whether to allow downloads of archives (pak/pk3)"}; +cvar_t sv_allowdownloads_config = {0, "sv_allowdownloads_config", "0", "whether to allow downloads of config files (cfg)"}; +cvar_t sv_allowdownloads_dlcache = {0, "sv_allowdownloads_dlcache", "0", "whether to allow downloads of dlcache files (dlcache/)"}; + +extern cvar_t sv_random_seed; static cvar_t sv_cullentities_pvs = {0, "sv_cullentities_pvs", "1", "fast but loose culling of hidden entities"}; // fast but loose static 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"}; // tends to get false negatives, uses a timeout to keep entities visible a short time after becoming hidden +static cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "1", "number of samples to test for entity culling"}; +static 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"}; +static cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; +static cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +static cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"}; +static cvar_t sv_cullentities_nevercullbmodels = {0, "sv_cullentities_nevercullbmodels", "0", "if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!)"}; static cvar_t sv_cullentities_stats = {0, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; static 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)"}; @@ -49,9 +63,18 @@ cvar_t sv_gameplayfix_swiminbmodels = {0, "sv_gameplayfix_swiminbmodels", "1", " cvar_t sv_gameplayfix_setmodelrealbox = {0, "sv_gameplayfix_setmodelrealbox", "1", "fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods"}; cvar_t sv_gameplayfix_blowupfallenzombies = {0, "sv_gameplayfix_blowupfallenzombies", "1", "causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them"}; cvar_t sv_gameplayfix_findradiusdistancetobox = {0, "sv_gameplayfix_findradiusdistancetobox", "1", "causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage"}; +cvar_t sv_gameplayfix_qwplayerphysics = {0, "sv_gameplayfix_qwplayerphysics", "1", "changes water jumping to make it easier to get out of water, and prevents friction on landing when bunnyhopping"}; +cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag = {0, "sv_gameplayfix_upwardvelocityclearsongroundflag", "1", "prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods"}; +cvar_t sv_gameplayfix_droptofloorstartsolid = {0, "sv_gameplayfix_droptofloorstartsolid", "1", "prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome)"}; cvar_t sv_progs = {0, "sv_progs", "progs.dat", "selects which quakec progs.dat file to run" }; +// TODO: move these cvars here +extern cvar_t sv_clmovement_enable; +extern cvar_t sv_clmovement_minping; +extern cvar_t sv_clmovement_minping_disabletime; +extern cvar_t sv_clmovement_waitforinput; + server_t sv; server_static_t svs; @@ -60,8 +83,14 @@ mempool_t *sv_mempool = NULL; //============================================================================ extern void SV_Phys_Init (void); -extern void SV_World_Init (void); static void SV_SaveEntFile_f(void); +static void SV_StartDownload_f(void); +static void SV_Download_f(void); + +void SV_AreaStats_f(void) +{ + World_PrintAreaStats(&sv.world, "server"); +} /* =============== @@ -70,20 +99,45 @@ SV_Init */ void SV_Init (void) { + // init the csqc progs cvars, since they are updated/used by the server code + // TODO: fix this since this is a quick hack to make some of [515]'s broken code run ;) [9/13/2006 Black] + 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; + Cvar_RegisterVariable (&csqc_progname); + Cvar_RegisterVariable (&csqc_progcrc); + Cvar_RegisterVariable (&csqc_progsize); + Cmd_AddCommand("sv_saveentfile", SV_SaveEntFile_f, "save map entities to .ent file (to allow external editing)"); + Cmd_AddCommand("sv_areastats", SV_AreaStats_f, "prints statistics on entity culling during collision traces"); + Cmd_AddCommand_WithClientCommand("sv_startdownload", NULL, SV_StartDownload_f, "begins sending a file to the client (network protocol use only)"); + Cmd_AddCommand_WithClientCommand("download", NULL, SV_Download_f, "downloads a specified file from the server"); Cvar_RegisterVariable (&sv_maxvelocity); Cvar_RegisterVariable (&sv_gravity); Cvar_RegisterVariable (&sv_friction); + Cvar_RegisterVariable (&sv_waterfriction); Cvar_RegisterVariable (&sv_edgefriction); Cvar_RegisterVariable (&sv_stopspeed); Cvar_RegisterVariable (&sv_maxspeed); Cvar_RegisterVariable (&sv_maxairspeed); Cvar_RegisterVariable (&sv_accelerate); + Cvar_RegisterVariable (&sv_airaccelerate); + Cvar_RegisterVariable (&sv_wateraccelerate); + Cvar_RegisterVariable (&sv_clmovement_enable); + Cvar_RegisterVariable (&sv_clmovement_minping); + Cvar_RegisterVariable (&sv_clmovement_minping_disabletime); + Cvar_RegisterVariable (&sv_clmovement_waitforinput); Cvar_RegisterVariable (&sv_idealpitchscale); Cvar_RegisterVariable (&sv_aim); Cvar_RegisterVariable (&sv_nostep); Cvar_RegisterVariable (&sv_cullentities_pvs); Cvar_RegisterVariable (&sv_cullentities_trace); + Cvar_RegisterVariable (&sv_cullentities_trace_samples); + Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra); + Cvar_RegisterVariable (&sv_cullentities_trace_enlarge); + Cvar_RegisterVariable (&sv_cullentities_trace_delay); + Cvar_RegisterVariable (&sv_cullentities_trace_prediction); + Cvar_RegisterVariable (&sv_cullentities_nevercullbmodels); Cvar_RegisterVariable (&sv_cullentities_stats); Cvar_RegisterVariable (&sv_entpatch); Cvar_RegisterVariable (&sv_gameplayfix_grenadebouncedownslopes); @@ -94,14 +148,21 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox); Cvar_RegisterVariable (&sv_gameplayfix_blowupfallenzombies); Cvar_RegisterVariable (&sv_gameplayfix_findradiusdistancetobox); + Cvar_RegisterVariable (&sv_gameplayfix_qwplayerphysics); + Cvar_RegisterVariable (&sv_gameplayfix_upwardvelocityclearsongroundflag); + Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid); Cvar_RegisterVariable (&sv_protocolname); Cvar_RegisterVariable (&sv_ratelimitlocalplayer); Cvar_RegisterVariable (&sv_maxrate); + Cvar_RegisterVariable (&sv_allowdownloads); + Cvar_RegisterVariable (&sv_allowdownloads_inarchive); + Cvar_RegisterVariable (&sv_allowdownloads_archive); + Cvar_RegisterVariable (&sv_allowdownloads_config); + Cvar_RegisterVariable (&sv_allowdownloads_dlcache); Cvar_RegisterVariable (&sv_progs); SV_VM_Init(); SV_Phys_Init(); - SV_World_Init(); sv_mempool = Mem_AllocPool("server", 0, NULL); } @@ -148,6 +209,7 @@ void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count) MSG_WriteChar (&sv.datagram, (int)bound(-128, dir[i]*16, 127)); MSG_WriteByte (&sv.datagram, count); MSG_WriteByte (&sv.datagram, color); + SV_FlushBroadcastMessages(); } /* @@ -185,6 +247,7 @@ void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, MSG_WriteByte (&sv.datagram, framecount); MSG_WriteByte (&sv.datagram, framerate); } + SV_FlushBroadcastMessages(); } /* @@ -264,6 +327,7 @@ void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int v MSG_WriteByte (&sv.datagram, sound_num); for (i = 0;i < 3;i++) MSG_WriteCoord (&sv.datagram, entity->fields.server->origin[i]+0.5*(entity->fields.server->mins[i]+entity->fields.server->maxs[i]), sv.protocol); + SV_FlushBroadcastMessages(); } /* @@ -274,8 +338,6 @@ CLIENT SPAWNING ============================================================================== */ -static const char *SV_InitCmd; //[515]: svprogs able to send cmd to client on connect -extern qboolean csqc_loaded; /* ================ SV_SendServerinfo @@ -323,15 +385,39 @@ void SV_SendServerinfo (client_t *client) dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)", gamename, buildstring, prog->filecrc); MSG_WriteString (&client->netconnection->message,message); - // FIXME: LordHavoc: this does not work on dedicated servers, needs fixing. -//[515]: init csprogs according to version of svprogs, check the crc, etc. - if(csqc_loaded && (cls.state == ca_dedicated || PRVM_NUM_FOR_EDICT(client->edict) != 1)) + //[515]: init csprogs according to version of svprogs, check the crc, etc. + if (sv.csqc_progname[0]) { + prvm_eval_t *val; + Con_DPrintf("sending csqc info to client (\"%s\" with size %i and crc %i)\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); MSG_WriteByte (&client->netconnection->message, svc_stufftext); - if(SV_InitCmd) - MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i;%s\n", csqc_progcrc.integer, SV_InitCmd)); - else - MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", csqc_progcrc.integer)); + MSG_WriteString (&client->netconnection->message, va("csqc_progname %s\n", sv.csqc_progname)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + 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)); + //[515]: init stufftext string (it is sent before svc_serverinfo) + val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.SV_InitCmd); + if (val) + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("%s\n", PRVM_GetString(val->string))); + } + } + + if (sv_allowdownloads.integer) + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 1"); + } + + // send at this time so it's guaranteed to get executed at the right time + { + client_t *save; + save = host_client; + host_client = client; + Curl_SendRequirements(); + host_client = save; } MSG_WriteByte (&client->netconnection->message, svc_serverinfo); @@ -366,6 +452,16 @@ void SV_SendServerinfo (client_t *client) MSG_WriteByte (&client->netconnection->message, 1); client->spawned = false; // need prespawn, spawn, etc + + // clear movement info until client enters the new level properly + memset(&client->cmd, 0, sizeof(client->cmd)); + client->movesequence = 0; +#ifdef NUM_PING_TIMES + for (i = 0;i < NUM_PING_TIMES;i++) + client->ping_times[i] = 0; + client->num_pings = 0; +#endif + client->ping = 0; } /* @@ -396,12 +492,15 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection) Con_DPrintf("Client %s connected\n", client->netconnection ? client->netconnection->address : "botclient"); - strcpy(client->name, "unconnected"); - strcpy(client->old_name, "unconnected"); + strlcpy(client->name, "unconnected", sizeof(client->name)); + strlcpy(client->old_name, "unconnected", sizeof(client->old_name)); client->spawned = false; client->edict = PRVM_EDICT_NUM(clientnum+1); if (client->netconnection) client->netconnection->message.allowoverflow = true; // we can catch it + // prepare the unreliable message buffer + client->unreliablemsg.data = client->unreliablemsg_data; + client->unreliablemsg.maxsize = sizeof(client->unreliablemsg_data); // updated by receiving "rate" command from client client->rate = NET_MINRATE; // no limits for local player @@ -441,17 +540,6 @@ FRAME UPDATES =============================================================================== */ -/* -================== -SV_ClearDatagram - -================== -*/ -void SV_ClearDatagram (void) -{ - SZ_Clear (&sv.datagram); -} - /* ============================================================================= @@ -491,21 +579,21 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int modelindex = (i >= 1 && i < MAX_MODELS && *PRVM_GetString(ent->fields.server->model)) ? i : 0; flags = 0; - i = (int)(PRVM_GETEDICTFIELDVALUE(ent, eval_glow_size)->_float * 0.25f); + i = (int)(PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_size)->_float * 0.25f); glowsize = (unsigned char)bound(0, i, 255); - if (PRVM_GETEDICTFIELDVALUE(ent, eval_glow_trail)->_float) + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_trail)->_float) flags |= RENDER_GLOWTRAIL; - f = PRVM_GETEDICTFIELDVALUE(ent, eval_color)->vector[0]*256; + f = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.color)->vector[0]*256; light[0] = (unsigned short)bound(0, f, 65535); - f = PRVM_GETEDICTFIELDVALUE(ent, eval_color)->vector[1]*256; + f = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.color)->vector[1]*256; light[1] = (unsigned short)bound(0, f, 65535); - f = PRVM_GETEDICTFIELDVALUE(ent, eval_color)->vector[2]*256; + f = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.color)->vector[2]*256; light[2] = (unsigned short)bound(0, f, 65535); - f = PRVM_GETEDICTFIELDVALUE(ent, eval_light_lev)->_float; + f = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.light_lev)->_float; light[3] = (unsigned short)bound(0, f, 65535); - lightstyle = (unsigned char)PRVM_GETEDICTFIELDVALUE(ent, eval_style)->_float; - lightpflags = (unsigned char)PRVM_GETEDICTFIELDVALUE(ent, eval_pflags)->_float; + lightstyle = (unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.style)->_float; + lightpflags = (unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.pflags)->_float; if (gamemode == GAME_TENEBRAE) { @@ -556,7 +644,7 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int // early culling checks // (final culling is done by SV_MarkWriteEntityStateToClient) - customizeentityforclient = PRVM_GETEDICTFIELDVALUE(ent, eval_customizeentityforclient)->function; + customizeentityforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.customizeentityforclient)->function; if (!customizeentityforclient) { if (e > svs.maxclients && (!modelindex && !specialvisibilityradius)) @@ -579,18 +667,18 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int cs->modelindex = modelindex; cs->skin = (unsigned)ent->fields.server->skin; cs->frame = (unsigned)ent->fields.server->frame; - cs->viewmodelforclient = PRVM_GETEDICTFIELDVALUE(ent, eval_viewmodelforclient)->edict; - cs->exteriormodelforclient = PRVM_GETEDICTFIELDVALUE(ent, eval_exteriormodeltoclient)->edict; - cs->nodrawtoclient = PRVM_GETEDICTFIELDVALUE(ent, eval_nodrawtoclient)->edict; - cs->drawonlytoclient = PRVM_GETEDICTFIELDVALUE(ent, eval_drawonlytoclient)->edict; + cs->viewmodelforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict; + cs->exteriormodelforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.exteriormodeltoclient)->edict; + cs->nodrawtoclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.nodrawtoclient)->edict; + cs->drawonlytoclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.drawonlytoclient)->edict; cs->customizeentityforclient = customizeentityforclient; - cs->tagentity = PRVM_GETEDICTFIELDVALUE(ent, eval_tag_entity)->edict; - cs->tagindex = (unsigned char)PRVM_GETEDICTFIELDVALUE(ent, eval_tag_index)->_float; + cs->tagentity = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)->edict; + cs->tagindex = (unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_index)->_float; cs->glowsize = glowsize; // don't need to init cs->colormod because the defaultstate did that for us //cs->colormod[0] = cs->colormod[1] = cs->colormod[2] = 32; - val = PRVM_GETEDICTFIELDVALUE(ent, eval_colormod); + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.colormod); if (val->vector[0] || val->vector[1] || val->vector[2]) { i = (int)(val->vector[0] * 32.0f);cs->colormod[0] = bound(0, i, 255); @@ -601,14 +689,14 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int cs->modelindex = modelindex; cs->alpha = 255; - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_alpha)->_float * 255.0f); + f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.alpha)->_float * 255.0f); if (f) { i = (int)f; cs->alpha = (unsigned char)bound(0, i, 255); } // halflife - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_renderamt)->_float); + f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.renderamt)->_float); if (f) { i = (int)f; @@ -616,7 +704,7 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int } cs->scale = 16; - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_scale)->_float * 16.0f); + f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale)->_float * 16.0f); if (f) { i = (int)f; @@ -624,11 +712,11 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int } cs->glowcolor = 254; - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_glow_color)->_float); + f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_color)->_float); if (f) cs->glowcolor = (int)f; - if (PRVM_GETEDICTFIELDVALUE(ent, eval_fullbright)->_float) + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.fullbright)->_float) cs->effects |= EF_FULLBRIGHT; if (ent->fields.server->movetype == MOVETYPE_STEP) @@ -686,10 +774,17 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int cullmaxs[1] = max(cullmaxs[1], cs->origin[1] + specialvisibilityradius); cullmaxs[2] = max(cullmaxs[2], cs->origin[2] + specialvisibilityradius); } + // calculate center of bbox for network prioritization purposes + VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, cs->netcenter); + // if culling box has moved, update pvs cluster links if (!VectorCompare(cullmins, ent->priv.server->cullmins) || !VectorCompare(cullmaxs, ent->priv.server->cullmaxs)) { VectorCopy(cullmins, ent->priv.server->cullmins); VectorCopy(cullmaxs, ent->priv.server->cullmaxs); + // a value of -1 for pvs_numclusters indicates that the links are not + // cached, and should be re-tested each time, this is the case if the + // culling box touches too many pvs clusters to store, or if the world + // model does not support FindBoxClusters ent->priv.server->pvs_numclusters = -1; if (sv.worldmodel && sv.worldmodel->brush.FindBoxClusters) { @@ -735,10 +830,8 @@ static client_t *sv_writeentitiestoclient_client; void SV_MarkWriteEntityStateToClient(entity_state_t *s) { int isbmodel; - vec3_t testorigin; model_t *model; prvm_edict_t *ed; - trace_t trace; if (sententitiesconsideration[s->number] == sententitiesmark) return; sententitiesconsideration[s->number] = sententitiesmark; @@ -767,6 +860,7 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s) if (!s->modelindex && s->specialvisibilityradius == 0) return; + isbmodel = (model = sv.models[s->modelindex]) != NULL && model->name[0] == '*'; // viewmodels don't have visibility checking if (s->viewmodelforclient) { @@ -784,7 +878,8 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s) } // always send world submodels in newer protocols because they don't // generate much traffic (in old protocols they hog bandwidth) - else if (!(s->effects & EF_NODEPTHTEST) && !((isbmodel = (model = sv.models[s->modelindex]) != NULL && model->name[0] == '*') && (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE))) + // but only if sv_cullentities_alwayssendbmodels is on + else if (!(s->effects & EF_NODEPTHTEST) && (!isbmodel || !sv_cullentities_nevercullbmodels.integer || sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE)) { // entity has survived every check so far, check if visible ed = PRVM_EDICT_NUM(s->number); @@ -819,36 +914,45 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s) // or not seen by random tracelines if (sv_cullentities_trace.integer && !isbmodel) { - // LordHavoc: test center first - testorigin[0] = (ed->priv.server->cullmins[0] + ed->priv.server->cullmaxs[0]) * 0.5f; - testorigin[1] = (ed->priv.server->cullmins[1] + ed->priv.server->cullmaxs[1]) * 0.5f; - testorigin[2] = (ed->priv.server->cullmins[2] + ed->priv.server->cullmaxs[2]) * 0.5f; - sv.worldmodel->TraceBox(sv.worldmodel, 0, &trace, sv_writeentitiestoclient_testeye, vec3_origin, vec3_origin, testorigin, SUPERCONTENTS_SOLID); - if (trace.fraction == 1 || BoxesOverlap(trace.endpos, trace.endpos, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) - sv_writeentitiestoclient_client->visibletime[s->number] = realtime + 1; - else + int samples = s->specialvisibilityradius ? sv_cullentities_trace_samples_extra.integer : sv_cullentities_trace_samples.integer; + float enlarge = sv_cullentities_trace_enlarge.value; + + qboolean visible = TRUE; + + do { - // LordHavoc: test random offsets, to maximize chance of detection - testorigin[0] = lhrandom(ed->priv.server->cullmins[0], ed->priv.server->cullmaxs[0]); - testorigin[1] = lhrandom(ed->priv.server->cullmins[1], ed->priv.server->cullmaxs[1]); - testorigin[2] = lhrandom(ed->priv.server->cullmins[2], ed->priv.server->cullmaxs[2]); - sv.worldmodel->TraceBox(sv.worldmodel, 0, &trace, sv_writeentitiestoclient_testeye, vec3_origin, vec3_origin, testorigin, SUPERCONTENTS_SOLID); - if (trace.fraction == 1 || BoxesOverlap(trace.endpos, trace.endpos, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) - sv_writeentitiestoclient_client->visibletime[s->number] = realtime + 1; - 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) { - if (s->specialvisibilityradius) + 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 { - // LordHavoc: test random offsets, to maximize chance of detection - testorigin[0] = lhrandom(ed->priv.server->cullmins[0], ed->priv.server->cullmaxs[0]); - testorigin[1] = lhrandom(ed->priv.server->cullmins[1], ed->priv.server->cullmaxs[1]); - testorigin[2] = lhrandom(ed->priv.server->cullmins[2], ed->priv.server->cullmaxs[2]); - sv.worldmodel->TraceBox(sv.worldmodel, 0, &trace, sv_writeentitiestoclient_testeye, vec3_origin, vec3_origin, testorigin, SUPERCONTENTS_SOLID); - if (trace.fraction == 1 || BoxesOverlap(trace.endpos, trace.endpos, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) - sv_writeentitiestoclient_client->visibletime[s->number] = realtime + 1; + //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) + sv_writeentitiestoclient_client->visibletime[s->number] = realtime + sv_cullentities_trace_delay.value; + if (realtime > sv_writeentitiestoclient_client->visibletime[s->number]) { sv_writeentitiestoclient_culled_trace++; @@ -866,9 +970,9 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s) } entity_state_t sendstates[MAX_EDICTS]; -extern int csqc_clent; +extern int csqc_clientnum; -void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int *stats) +void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg) { int i, numsendstates; entity_state_t *s; @@ -891,7 +995,8 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t * if (sv.worldmodel && sv.worldmodel->brush.FatPVS) sv_writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv_writeentitiestoclient_testeye, 8, sv_writeentitiestoclient_pvs, sizeof(sv_writeentitiestoclient_pvs)); - csqc_clent = sv_writeentitiestoclient_clentnum = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes + sv_writeentitiestoclient_clentnum = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes + csqc_clientnum = sv_writeentitiestoclient_clentnum - 1; sententitiesmark++; @@ -916,13 +1021,22 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t * EntityFrameCSQC_WriteFrame(msg, numsendstates, sendstates); if (client->entitydatabase5) - EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sendstates, client - svs.clients + 1, stats, client->movesequence); + EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sendstates, client - svs.clients + 1, client->movesequence); else if (client->entitydatabase4) + { EntityFrame4_WriteFrame(msg, client->entitydatabase4, numsendstates, sendstates); + Protocol_WriteStatsReliable(); + } else if (client->entitydatabase) + { EntityFrame_WriteFrame(msg, client->entitydatabase, numsendstates, sendstates, client - svs.clients + 1); + Protocol_WriteStatsReliable(); + } else + { EntityFrameQuake_WriteFrame(msg, numsendstates, sendstates); + Protocol_WriteStatsReliable(); + } } /* @@ -980,24 +1094,35 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t SV_SetIdealPitch (); // how much to look up / down ideally // a fixangle might get lost in a dropped packet. Oh well. - if ( ent->fields.server->fixangle ) + if(ent->fields.server->fixangle) + { + // angle fixing was requested by global thinking code... + // so store the current angles for later use + memcpy(host_client->fixangle_angles, ent->fields.server->angles, sizeof(host_client->fixangle_angles)); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + ent->fields.server->fixangle = 0; + } + + if (host_client->fixangle_angles_set) { MSG_WriteByte (msg, svc_setangle); for (i=0 ; i < 3 ; i++) - MSG_WriteAngle (msg, ent->fields.server->angles[i], sv.protocol); - ent->fields.server->fixangle = 0; + MSG_WriteAngle (msg, host_client->fixangle_angles[i], sv.protocol); + host_client->fixangle_angles_set = FALSE; } // stuff the sigil bits into the high bits of items for sbar, or else // mix in items2 - val = PRVM_GETEDICTFIELDVALUE(ent, eval_items2); + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.items2); if (gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) items = (int)ent->fields.server->items | ((int)val->_float << 23); else items = (int)ent->fields.server->items | ((int)prog->globals.server->serverflags << 28); VectorClear(punchvector); - if ((val = PRVM_GETEDICTFIELDVALUE(ent, eval_punchvector))) + if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.punchvector))) VectorCopy(val->vector, punchvector); // cache weapon model name and index in client struct to save time @@ -1010,7 +1135,7 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t } viewzoom = 255; - if ((val = PRVM_GETEDICTFIELDVALUE(ent, eval_viewzoom))) + if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewzoom))) viewzoom = (int)(val->_float * 255.0f); if (viewzoom == 0) viewzoom = 255; @@ -1049,10 +1174,10 @@ 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; // 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_TOTALSECRETS] = prog->globals.server->total_secrets; - //stats[STAT_TOTALMONSTERS] = prog->globals.server->total_monsters; //stats[STAT_SECRETS] = prog->globals.server->found_secrets; //stats[STAT_MONSTERS] = prog->globals.server->killed_monsters; @@ -1167,6 +1292,45 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t } } +void SV_FlushBroadcastMessages(void) +{ + int i; + client_t *client; + if (sv.datagram.cursize <= 0) + return; + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->spawned || !client->netconnection || client->unreliablemsg.cursize + sv.datagram.cursize > client->unreliablemsg.maxsize || client->unreliablemsg_splitpoints >= (int)(sizeof(client->unreliablemsg_splitpoint)/sizeof(client->unreliablemsg_splitpoint[0]))) + continue; + SZ_Write(&client->unreliablemsg, sv.datagram.data, sv.datagram.cursize); + client->unreliablemsg_splitpoint[client->unreliablemsg_splitpoints++] = client->unreliablemsg.cursize; + } + SZ_Clear(&sv.datagram); +} + +void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg) +{ + // scan the splitpoints to find out how many we can fit in + int numsegments, j, split; + for (numsegments = 0;numsegments < client->unreliablemsg_splitpoints;numsegments++) + if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > msg->maxsize) + break; + if (numsegments > 0) + { + // some will fit, so add the ones that will fit + split = client->unreliablemsg_splitpoint[numsegments-1]; + SZ_Write(msg, client->unreliablemsg.data, split); + // remove the part we sent, keeping any remaining data + client->unreliablemsg.cursize -= split; + if (client->unreliablemsg.cursize > 0) + memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize); + // adjust remaining splitpoints + client->unreliablemsg_splitpoints -= numsegments; + for (j = 0;j < client->unreliablemsg_splitpoints;j++) + client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split; + } +} + /* ======================= SV_SendClientDatagram @@ -1175,7 +1339,7 @@ SV_SendClientDatagram static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; // FIXME? void SV_SendClientDatagram (client_t *client) { - int rate, maxrate, maxsize, maxsize2; + int rate, maxrate, maxsize, maxsize2, downloadsize; sizebuf_t msg; int stats[MAX_CL_STATS]; @@ -1196,18 +1360,23 @@ void SV_SendClientDatagram (client_t *client) else { // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates - maxrate = bound(NET_MINRATE, sv_maxrate.integer, NET_MAXRATE); + maxrate = max(NET_MINRATE, sv_maxrate.integer); if (sv_maxrate.integer != maxrate) Cvar_SetValueQuick(&sv_maxrate, maxrate); // this rate limiting does not understand sys_ticrate 0 // (but no one should be running that on a server!) rate = bound(NET_MINRATE, client->rate, maxrate); - rate = (int)(client->rate * sys_ticrate.value); - maxsize = bound(100, rate, 1400); + rate = (int)(rate * sys_ticrate.value); + maxsize = bound(50, rate, 1400); maxsize2 = 1400; } + // while downloading, limit entity updates to half the packet + // (any leftover space will be used for downloading) + if (host_client->download_file) + maxsize /= 2; + msg.data = sv_sendclientdatagram_buf; msg.maxsize = maxsize; msg.cursize = 0; @@ -1219,26 +1388,56 @@ void SV_SendClientDatagram (client_t *client) // add the client specific data to the datagram SV_WriteClientdataToMessage (client, client->edict, &msg, stats); - VM_SV_WriteAutoSentStats (client, client->edict, &msg, stats); - SV_WriteEntitiesToClient (client, client->edict, &msg, stats); + // now update the stats[] array using any registered custom fields + VM_SV_UpdateCustomStats (client, client->edict, &msg, stats); + // set host_client->statsdeltabits + Protocol_UpdateClientStats (stats); - // expand packet size to allow effects to go over the rate limit - // (dropping them is FAR too ugly) - msg.maxsize = maxsize2; + // 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); - // copy the server datagram if there is space - // FIXME: put in delayed queue of effects to send - if (sv.datagram.cursize > 0 && msg.cursize + sv.datagram.cursize <= msg.maxsize) - SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); + msg.maxsize = maxsize; + + // now write as many entities as we can fit, and also sends stats + SV_WriteEntitiesToClient (client, client->edict, &msg); } else if (realtime > client->keepalivetime) { // the player isn't totally in the game yet // send small keepalive messages if too much time has passed + 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; + if (host_client->download_file && host_client->download_started && downloadsize > 0) + { + fs_offset_t downloadstart; + unsigned char data[1400]; + downloadstart = FS_Tell(host_client->download_file); + downloadsize = min(downloadsize, (int)sizeof(data)); + downloadsize = FS_Read(host_client->download_file, data, downloadsize); + // note this sends empty messages if at the end of the file, which is + // necessary to keep the packet loss logic working + // (the last blocks may be lost and need to be re-sent, and that will + // only occur if the client acks the empty end messages, revealing + // a gap in the download progress, causing the last blocks to be + // sent again) + MSG_WriteChar (&msg, svc_downloaddata); + MSG_WriteLong (&msg, downloadstart); + MSG_WriteShort (&msg, downloadsize); + if (downloadsize > 0) + SZ_Write (&msg, data, downloadsize); + } + // send the datagram NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol); } @@ -1274,7 +1473,7 @@ void SV_UpdateToReliableMessages (void) { if (host_client->spawned) SV_BroadcastPrintf("%s changed name to %s\n", host_client->old_name, host_client->name); - strcpy(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); MSG_WriteByte (&sv.reliable_datagram, i); @@ -1283,7 +1482,7 @@ void SV_UpdateToReliableMessages (void) // DP_SV_CLIENTCOLORS // this is always found (since it's added by the progs loader) - if ((val = PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_clientcolors))) + if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.clientcolors))) host_client->colors = (int)val->_float; if (host_client->old_colors != host_client->colors) { @@ -1295,23 +1494,23 @@ void SV_UpdateToReliableMessages (void) } // NEXUIZ_PLAYERMODEL - if( eval_playermodel ) { - model = PRVM_GetString(PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_playermodel)->string); + if( prog->fieldoffsets.playermodel >= 0 ) { + model = PRVM_GetString(PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playermodel)->string); if (model == NULL) model = ""; // always point the string back at host_client->name to keep it safe strlcpy (host_client->playermodel, model, sizeof (host_client->playermodel)); - PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_playermodel)->string = PRVM_SetEngineString(host_client->playermodel); + PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playermodel)->string = PRVM_SetEngineString(host_client->playermodel); } // NEXUIZ_PLAYERSKIN - if( eval_playerskin ) { - skin = PRVM_GetString(PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_playerskin)->string); + if( prog->fieldoffsets.playerskin >= 0 ) { + skin = PRVM_GetString(PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playerskin)->string); if (skin == NULL) skin = ""; // always point the string back at host_client->name to keep it safe strlcpy (host_client->playerskin, skin, sizeof (host_client->playerskin)); - PRVM_GETEDICTFIELDVALUE(host_client->edict, eval_playerskin)->string = PRVM_SetEngineString(host_client->playerskin); + PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playerskin)->string = PRVM_SetEngineString(host_client->playerskin); } // frags @@ -1346,6 +1545,8 @@ void SV_SendClientMessages (void) if (sv.protocol == PROTOCOL_QUAKEWORLD) Sys_Error("SV_SendClientMessages: no quakeworld support\n"); + SV_FlushBroadcastMessages(); + // update frags, names, etc SV_UpdateToReliableMessages(); @@ -1376,6 +1577,145 @@ void SV_SendClientMessages (void) SV_CleanupEnts(); } +void SV_StartDownload_f(void) +{ + if (host_client->download_file) + host_client->download_started = true; +} + +void SV_Download_f(void) +{ + const char *whichpack, *whichpack2, *extension; + + if (Cmd_Argc() != 2) + { + SV_ClientPrintf("usage: download \n"); + return; + } + + if (FS_CheckNastyPath(Cmd_Argv(1), false)) + { + SV_ClientPrintf("Download rejected: nasty filename \"%s\"\n", Cmd_Argv(1)); + return; + } + + if (host_client->download_file) + { + // at this point we'll assume the previous download should be aborted + Con_DPrintf("Download of %s aborted by %s starting a new download\n", host_client->download_name, host_client->name); + Host_ClientCommands("\nstopdownload\n"); + + // close the file and reset variables + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + + if (!sv_allowdownloads.integer) + { + SV_ClientPrintf("Downloads are disabled on this server\n"); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + strlcpy(host_client->download_name, Cmd_Argv(1), sizeof(host_client->download_name)); + extension = FS_FileExtension(host_client->download_name); + + // host_client is asking to download a specified file + if (developer.integer >= 100) + Con_Printf("Download request for %s by %s\n", host_client->download_name, host_client->name); + + 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_ClientCommands("\nstopdownload\n"); + return; + } + + // check if the user is trying to download part of registered Quake(r) + whichpack = FS_WhichPack(host_client->download_name); + whichpack2 = FS_WhichPack("gfx/pop.lmp"); + if ((whichpack && whichpack2 && !strcasecmp(whichpack, whichpack2)) || FS_IsRegisteredQuakePack(host_client->download_name)) + { + SV_ClientPrintf("Download rejected: file \"%s\" is part of registered Quake(r)\nYou must purchase Quake(r) from id Software or a retailer to get this file\nPlease go to http://www.idsoftware.com/games/quake/quake/index.php?game_section=buy\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + // check if the server has forbidden archive downloads entirely + if (!sv_allowdownloads_inarchive.integer) + { + whichpack = FS_WhichPack(host_client->download_name); + if (whichpack) + { + SV_ClientPrintf("Download rejected: file \"%s\" is in an archive (\"%s\")\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name, whichpack); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_config.integer) + { + if (!strcasecmp(extension, "cfg")) + { + SV_ClientPrintf("Download rejected: file \"%s\" is a .cfg file which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_dlcache.integer) + { + if (!strncasecmp(host_client->download_name, "dlcache/", 8)) + { + SV_ClientPrintf("Download rejected: file \"%s\" is in the dlcache/ directory which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_archive.integer) + { + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + { + SV_ClientPrintf("Download rejected: file \"%s\" is an archive\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + host_client->download_file = FS_Open(host_client->download_name, "rb", true, false); + if (!host_client->download_file) + { + SV_ClientPrintf("Download rejected: server could not open the file \"%s\"\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + if (FS_FileSize(host_client->download_file) > 1<<30) + { + SV_ClientPrintf("Download rejected: file \"%s\" is very large\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); + + Host_ClientCommands("\ncl_downloadbegin %i %s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name); + + host_client->download_expectedposition = 0; + host_client->download_started = false; + + // the rest of the download process is handled in SV_SendClientDatagram + // and other code dealing with svc_downloaddata and clc_ackdownloaddata + // + // no svc_downloaddata messages will be sent until sv_startdownload is + // sent by the client +} /* ============================================================================== @@ -1482,6 +1822,126 @@ int SV_SoundIndex(const char *s, int precachemode) return 0; } +// MUST match effectnameindex_t in client.h +static const char *standardeffectnames[EFFECT_TOTAL] = +{ + "", + "TE_GUNSHOT", + "TE_GUNSHOTQUAD", + "TE_SPIKE", + "TE_SPIKEQUAD", + "TE_SUPERSPIKE", + "TE_SUPERSPIKEQUAD", + "TE_WIZSPIKE", + "TE_KNIGHTSPIKE", + "TE_EXPLOSION", + "TE_EXPLOSIONQUAD", + "TE_TAREXPLOSION", + "TE_TELEPORT", + "TE_LAVASPLASH", + "TE_SMALLFLASH", + "TE_FLAMEJET", + "EF_FLAME", + "TE_BLOOD", + "TE_SPARK", + "TE_PLASMABURN", + "TE_TEI_G3", + "TE_TEI_SMOKE", + "TE_TEI_BIGEXPLOSION", + "TE_TEI_PLASMAHIT", + "EF_STARDUST", + "TR_ROCKET", + "TR_GRENADE", + "TR_BLOOD", + "TR_WIZSPIKE", + "TR_SLIGHTBLOOD", + "TR_KNIGHTSPIKE", + "TR_VORESPIKE", + "TR_NEHAHRASMOKE", + "TR_NEXUIZPLASMA", + "TR_GLOWTRAIL", + "SVC_PARTICLE" +}; + +/* +================ +SV_ParticleEffectIndex + +================ +*/ +int SV_ParticleEffectIndex(const char *name) +{ + int i, argc, linenumber, effectnameindex; + fs_offset_t filesize; + unsigned char *filedata; + const char *text, *textstart, *textend; + char argv[16][1024]; + if (!sv.particleeffectnamesloaded) + { + sv.particleeffectnamesloaded = true; + memset(sv.particleeffectname, 0, sizeof(sv.particleeffectname)); + for (i = 0;i < EFFECT_TOTAL;i++) + strlcpy(sv.particleeffectname[i], standardeffectnames[i], sizeof(sv.particleeffectname[i])); + filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize); + if (filedata) + { + textstart = (const char *)filedata; + textend = (const char *)filedata + filesize; + text = textstart; + for (linenumber = 1;;linenumber++) + { + argc = 0; + for (;;) + { + if (!COM_ParseToken(&text, true) || !strcmp(com_token, "\n")) + break; + if (argc < 16) + { + strlcpy(argv[argc], com_token, sizeof(argv[argc])); + argc++; + } + } + if (com_token[0] == 0) + break; // if the loop exited and it's not a \n, it's EOF + if (argc < 1) + continue; + if (!strcmp(argv[0], "effect")) + { + if (argc == 2) + { + for (effectnameindex = 1;effectnameindex < SV_MAX_PARTICLEEFFECTNAME;effectnameindex++) + { + if (sv.particleeffectname[effectnameindex][0]) + { + if (!strcmp(sv.particleeffectname[effectnameindex], argv[1])) + break; + } + else + { + strlcpy(sv.particleeffectname[effectnameindex], argv[1], sizeof(sv.particleeffectname[effectnameindex])); + break; + } + } + // if we run out of names, abort + if (effectnameindex == SV_MAX_PARTICLEEFFECTNAME) + { + Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber); + break; + } + } + } + } + Mem_Free(filedata); + } + } + // search for the name + for (effectnameindex = 1;effectnameindex < SV_MAX_PARTICLEEFFECTNAME && sv.particleeffectname[effectnameindex][0];effectnameindex++) + if (!strcmp(sv.particleeffectname[effectnameindex], name)) + return effectnameindex; + // return 0 if we couldn't find it + return 0; +} + /* ================ SV_CreateBaseline @@ -1601,7 +2061,7 @@ void SV_IncreaseEdicts(void) SV_UnlinkEdict(prog->edicts + i); memset(&ent->priv.server->areagrid, 0, sizeof(ent->priv.server->areagrid)); } - SV_ClearWorld(); + World_Clear(&sv.world); prog->max_edicts = min(prog->max_edicts + 256, MAX_EDICTS); prog->edictprivate = PR_Alloc(prog->max_edicts * sizeof(edict_engineprivate_t)); @@ -1662,6 +2122,10 @@ void SV_SpawnServer (const char *server) svs.changelevel_issued = false; // now safe to issue another + // make the map a required file for clients + Curl_ClearRequirements(); + Curl_RequireFile(modelname); + // // tell all connected clients that we are going to a new level // @@ -1701,6 +2165,12 @@ void SV_SpawnServer (const char *server) // level's data which is no longer valiud cls.signon = 0; + if(*sv_random_seed.string) + { + srand(sv_random_seed.integer); + Con_Printf("NOTE: random seed is %d; use for debugging/benchmarking only!\nUnset sv_random_seed to get real random numbers again.\n", sv_random_seed.integer); + } + SV_VM_Setup(); sv.active = true; @@ -1764,20 +2234,22 @@ void SV_SpawnServer (const char *server) prog->allowworldwrites = true; sv.paused = false; - *prog->time = sv.time = 1.0; + prog->globals.server->time = sv.time = 1.0; Mod_ClearUsed(); worldmodel->used = true; strlcpy (sv.name, server, sizeof (sv.name)); - strcpy(sv.modelname, modelname); + strlcpy(sv.modelname, modelname, sizeof(sv.modelname)); sv.worldmodel = worldmodel; sv.models[1] = sv.worldmodel; // // clear world interaction links // - SV_ClearWorld (); + VectorCopy(sv.worldmodel->normalmins, sv.world.areagrid_mins); + VectorCopy(sv.worldmodel->normalmaxs, sv.world.areagrid_maxs); + World_Clear(&sv.world); strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0])); @@ -1800,6 +2272,10 @@ void SV_SpawnServer (const char *server) ent->fields.server->modelindex = 1; // world model ent->fields.server->solid = SOLID_BSP; ent->fields.server->movetype = MOVETYPE_PUSH; + VectorCopy(sv.worldmodel->normalmins, ent->fields.server->mins); + VectorCopy(sv.worldmodel->normalmaxs, ent->fields.server->maxs); + VectorCopy(sv.worldmodel->normalmins, ent->fields.server->absmin); + VectorCopy(sv.worldmodel->normalmaxs, ent->fields.server->absmax); if (coop.value) prog->globals.server->coop = coop.integer; @@ -1901,10 +2377,10 @@ void SV_VM_CB_BeginIncreaseEdicts(void) for (i = 0, ent = prog->edicts;i < prog->max_edicts;i++, ent++) { if (!ent->priv.server->free) - SV_UnlinkEdict(prog->edicts + i); + World_UnlinkEdict(prog->edicts + i); memset(&ent->priv.server->areagrid, 0, sizeof(ent->priv.server->areagrid)); } - SV_ClearWorld(); + World_Clear(&sv.world); } void SV_VM_CB_EndIncreaseEdicts(void) @@ -1912,12 +2388,10 @@ void SV_VM_CB_EndIncreaseEdicts(void) int i; prvm_edict_t *ent; - for (i = 0, ent = prog->edicts;i < prog->max_edicts;i++, ent++) - { - // link every entity except world + // link every entity except world + for (i = 1, ent = prog->edicts;i < prog->max_edicts;i++, ent++) if (!ent->priv.server->free) SV_LinkEdict(ent, false); - } } void SV_VM_CB_InitEdict(prvm_edict_t *e) @@ -1937,19 +2411,19 @@ void SV_VM_CB_InitEdict(prvm_edict_t *e) // DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS will not immediately // reset them e->fields.server->netname = PRVM_SetEngineString(svs.clients[num].name); - if ((val = PRVM_GETEDICTFIELDVALUE(e, eval_clientcolors))) + if ((val = PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.clientcolors))) val->_float = svs.clients[num].colors; // NEXUIZ_PLAYERMODEL and NEXUIZ_PLAYERSKIN - if( eval_playermodel ) - PRVM_GETEDICTFIELDVALUE(e, eval_playermodel)->string = PRVM_SetEngineString(svs.clients[num].playermodel); - if( eval_playerskin ) - PRVM_GETEDICTFIELDVALUE(e, eval_playerskin)->string = PRVM_SetEngineString(svs.clients[num].playerskin); + if( prog->fieldoffsets.playermodel >= 0 ) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.playermodel)->string = PRVM_SetEngineString(svs.clients[num].playermodel); + if( prog->fieldoffsets.playerskin >= 0 ) + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.playerskin)->string = PRVM_SetEngineString(svs.clients[num].playerskin); } } void SV_VM_CB_FreeEdict(prvm_edict_t *ed) { - SV_UnlinkEdict (ed); // unlink from world bsp + World_UnlinkEdict(ed); // unlink from world bsp ed->fields.server->model = 0; ed->fields.server->takedamage = 0; @@ -2088,168 +2562,6 @@ void SV_VM_Init(void) Cvar_RegisterVariable (&cutscene); // for Nehahra but useful to other mods as well } -// LordHavoc: in an effort to eliminate time wasted on GetEdictFieldValue... these are defined as externs in progs.h -int eval_gravity; -int eval_button3; -int eval_button4; -int eval_button5; -int eval_button6; -int eval_button7; -int eval_button8; -int eval_button9; -int eval_button10; -int eval_button11; -int eval_button12; -int eval_button13; -int eval_button14; -int eval_button15; -int eval_button16; -int eval_buttonuse; -int eval_buttonchat; -int eval_glow_size; -int eval_glow_trail; -int eval_glow_color; -int eval_items2; -int eval_scale; -int eval_alpha; -int eval_renderamt; // HalfLife support -int eval_rendermode; // HalfLife support -int eval_fullbright; -int eval_ammo_shells1; -int eval_ammo_nails1; -int eval_ammo_lava_nails; -int eval_ammo_rockets1; -int eval_ammo_multi_rockets; -int eval_ammo_cells1; -int eval_ammo_plasma; -int eval_idealpitch; -int eval_pitch_speed; -int eval_viewmodelforclient; -int eval_nodrawtoclient; -int eval_exteriormodeltoclient; -int eval_drawonlytoclient; -int eval_ping; -int eval_movement; -int eval_pmodel; -int eval_punchvector; -int eval_viewzoom; -int eval_clientcolors; -int eval_tag_entity; -int eval_tag_index; -int eval_light_lev; -int eval_color; -int eval_style; -int eval_pflags; -int eval_cursor_active; -int eval_cursor_screen; -int eval_cursor_trace_start; -int eval_cursor_trace_endpos; -int eval_cursor_trace_ent; -int eval_colormod; -int eval_playermodel; -int eval_playerskin; -int eval_SendEntity; -int eval_Version; -int eval_customizeentityforclient; -int eval_dphitcontentsmask; - -int gval_trace_dpstartcontents; -int gval_trace_dphitcontents; -int gval_trace_dphitq3surfaceflags; -int gval_trace_dphittexturename; - -mfunction_t *SV_PlayerPhysicsQC; -mfunction_t *EndFrameQC; -//KrimZon - SERVER COMMANDS IN QUAKEC -mfunction_t *SV_ParseClientCommandQC; - -ddef_t *PRVM_ED_FindGlobal(const char *name); - -void SV_VM_FindEdictFieldOffsets(void) -{ - eval_gravity = PRVM_ED_FindFieldOffset("gravity"); - eval_button3 = PRVM_ED_FindFieldOffset("button3"); - eval_button4 = PRVM_ED_FindFieldOffset("button4"); - eval_button5 = PRVM_ED_FindFieldOffset("button5"); - eval_button6 = PRVM_ED_FindFieldOffset("button6"); - eval_button7 = PRVM_ED_FindFieldOffset("button7"); - eval_button8 = PRVM_ED_FindFieldOffset("button8"); - eval_button9 = PRVM_ED_FindFieldOffset("button9"); - eval_button10 = PRVM_ED_FindFieldOffset("button10"); - eval_button11 = PRVM_ED_FindFieldOffset("button11"); - eval_button12 = PRVM_ED_FindFieldOffset("button12"); - eval_button13 = PRVM_ED_FindFieldOffset("button13"); - eval_button14 = PRVM_ED_FindFieldOffset("button14"); - eval_button15 = PRVM_ED_FindFieldOffset("button15"); - eval_button16 = PRVM_ED_FindFieldOffset("button16"); - eval_buttonuse = PRVM_ED_FindFieldOffset("buttonuse"); - eval_buttonchat = PRVM_ED_FindFieldOffset("buttonchat"); - eval_glow_size = PRVM_ED_FindFieldOffset("glow_size"); - eval_glow_trail = PRVM_ED_FindFieldOffset("glow_trail"); - eval_glow_color = PRVM_ED_FindFieldOffset("glow_color"); - eval_items2 = PRVM_ED_FindFieldOffset("items2"); - eval_scale = PRVM_ED_FindFieldOffset("scale"); - eval_alpha = PRVM_ED_FindFieldOffset("alpha"); - eval_renderamt = PRVM_ED_FindFieldOffset("renderamt"); // HalfLife support - eval_rendermode = PRVM_ED_FindFieldOffset("rendermode"); // HalfLife support - eval_fullbright = PRVM_ED_FindFieldOffset("fullbright"); - eval_ammo_shells1 = PRVM_ED_FindFieldOffset("ammo_shells1"); - eval_ammo_nails1 = PRVM_ED_FindFieldOffset("ammo_nails1"); - eval_ammo_lava_nails = PRVM_ED_FindFieldOffset("ammo_lava_nails"); - eval_ammo_rockets1 = PRVM_ED_FindFieldOffset("ammo_rockets1"); - eval_ammo_multi_rockets = PRVM_ED_FindFieldOffset("ammo_multi_rockets"); - eval_ammo_cells1 = PRVM_ED_FindFieldOffset("ammo_cells1"); - eval_ammo_plasma = PRVM_ED_FindFieldOffset("ammo_plasma"); - eval_idealpitch = PRVM_ED_FindFieldOffset("idealpitch"); - eval_pitch_speed = PRVM_ED_FindFieldOffset("pitch_speed"); - eval_viewmodelforclient = PRVM_ED_FindFieldOffset("viewmodelforclient"); - eval_nodrawtoclient = PRVM_ED_FindFieldOffset("nodrawtoclient"); - eval_exteriormodeltoclient = PRVM_ED_FindFieldOffset("exteriormodeltoclient"); - eval_drawonlytoclient = PRVM_ED_FindFieldOffset("drawonlytoclient"); - eval_ping = PRVM_ED_FindFieldOffset("ping"); - eval_movement = PRVM_ED_FindFieldOffset("movement"); - eval_pmodel = PRVM_ED_FindFieldOffset("pmodel"); - eval_punchvector = PRVM_ED_FindFieldOffset("punchvector"); - eval_viewzoom = PRVM_ED_FindFieldOffset("viewzoom"); - eval_clientcolors = PRVM_ED_FindFieldOffset("clientcolors"); - eval_tag_entity = PRVM_ED_FindFieldOffset("tag_entity"); - eval_tag_index = PRVM_ED_FindFieldOffset("tag_index"); - eval_light_lev = PRVM_ED_FindFieldOffset("light_lev"); - eval_color = PRVM_ED_FindFieldOffset("color"); - eval_style = PRVM_ED_FindFieldOffset("style"); - eval_pflags = PRVM_ED_FindFieldOffset("pflags"); - eval_cursor_active = PRVM_ED_FindFieldOffset("cursor_active"); - eval_cursor_screen = PRVM_ED_FindFieldOffset("cursor_screen"); - eval_cursor_trace_start = PRVM_ED_FindFieldOffset("cursor_trace_start"); - eval_cursor_trace_endpos = PRVM_ED_FindFieldOffset("cursor_trace_endpos"); - eval_cursor_trace_ent = PRVM_ED_FindFieldOffset("cursor_trace_ent"); - eval_colormod = PRVM_ED_FindFieldOffset("colormod"); - eval_playermodel = PRVM_ED_FindFieldOffset("playermodel"); - eval_playerskin = PRVM_ED_FindFieldOffset("playerskin"); - eval_SendEntity = PRVM_ED_FindFieldOffset("SendEntity"); - eval_Version = PRVM_ED_FindFieldOffset("Version"); - eval_customizeentityforclient = PRVM_ED_FindFieldOffset("customizeentityforclient"); - eval_dphitcontentsmask = PRVM_ED_FindFieldOffset("dphitcontentsmask"); - - // LordHavoc: allowing QuakeC to override the player movement code - SV_PlayerPhysicsQC = PRVM_ED_FindFunction ("SV_PlayerPhysics"); - // LordHavoc: support for endframe - EndFrameQC = PRVM_ED_FindFunction ("EndFrame"); - //KrimZon - SERVER COMMANDS IN QUAKEC - SV_ParseClientCommandQC = PRVM_ED_FindFunction ("SV_ParseClientCommand"); - - //[515]: init stufftext string (it is sent before svc_serverinfo) - if(PRVM_ED_FindGlobal("SV_InitCmd") && PRVM_ED_FindGlobal("SV_InitCmd")->type & ev_string) - SV_InitCmd = PRVM_G_STRING(PRVM_ED_FindGlobal("SV_InitCmd")->ofs); - else - SV_InitCmd = NULL; - - gval_trace_dpstartcontents = PRVM_ED_FindGlobalOffset("trace_dpstartcontents"); - gval_trace_dphitcontents = PRVM_ED_FindGlobalOffset("trace_dphitcontents"); - gval_trace_dphitq3surfaceflags = PRVM_ED_FindGlobalOffset("trace_dphitq3surfaceflags"); - gval_trace_dphittexturename = PRVM_ED_FindGlobalOffset("trace_dphittexturename"); -} - #define REQFIELDS (sizeof(reqfields) / sizeof(prvm_required_field_t)) prvm_required_field_t reqfields[] = @@ -2286,6 +2598,7 @@ prvm_required_field_t reqfields[] = {ev_float, "buttonuse"}, {ev_float, "clientcolors"}, {ev_float, "cursor_active"}, + {ev_float, "disableclientprediction"}, {ev_float, "fullbright"}, {ev_float, "glow_color"}, {ev_float, "glow_size"}, @@ -2316,14 +2629,21 @@ prvm_required_field_t reqfields[] = {ev_string, "playerskin"}, {ev_function, "SendEntity"}, {ev_function, "customizeentityforclient"}, + // DRESK - Support for Entity Contents Transition Event + {ev_function, "contentstransition"}, }; 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 ); // allocate the mempools + // TODO: move the magic numbers/constants into #defines [9/13/2006 Black] prog->progs_mempool = Mem_AllocPool("Server Progs", 0, NULL); prog->builtins = vm_sv_builtins; prog->numbuiltins = vm_sv_numbuiltins; @@ -2347,13 +2667,51 @@ void SV_VM_Setup(void) prog->error_cmd = Host_Error; // TODO: add a requiredfuncs list (ask LH if this is necessary at all) - PRVM_LoadProgs( sv_progs.string, 0, NULL, REQFIELDS, reqfields ); - SV_VM_FindEdictFieldOffsets(); - - VM_AutoSentStats_Clear();//[515]: csqc + PRVM_LoadProgs( sv_progs.string, 0, NULL, REQFIELDS, reqfields, 0, NULL ); + + // some mods compiled with scrambling compilers lack certain critical + // global names and field names such as "self" and "time" and "nextthink" + // so we have to set these offsets manually, matching the entvars_t + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, angles); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, chain); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, classname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frame); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, groundentity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ideal_yaw); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, nextthink); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, think); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, yaw_speed); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, self); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, time); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_forward); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_right); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_up); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_allsolid); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_startsolid); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_fraction); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inwater); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inopen); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_endpos); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_normal); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_dist); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_ent); + // OP_STATE is always supported on server (due to entvars_t) + prog->flag |= PRVM_OP_STATE; + + VM_CustomStats_Clear();//[515]: csqc EntityFrameCSQC_ClearVersions();//[515]: csqc 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); + } } void SV_VM_Begin(void) @@ -2361,7 +2719,7 @@ void SV_VM_Begin(void) PRVM_Begin; PRVM_SetProg( PRVM_SERVERPROG ); - *prog->time = (float) sv.time; + prog->globals.server->time = (float) sv.time; } void SV_VM_End(void)