X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=sv_main.c;h=0fcde551b9f25be19878fdad09eb01be3fd14511;hb=df96a719a2e77a0fc8175c003bf261b0016cefae;hp=8897544c6bfa5737500abb3dedd680c4d2f6f924;hpb=8b37a3a674013f693250d761388cbf39265a0d42;p=xonotic%2Fdarkplaces.git diff --git a/sv_main.c b/sv_main.c index 8897544c..0fcde551 100644 --- a/sv_main.c +++ b/sv_main.c @@ -22,13 +22,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "libcurl.h" -void SV_VM_Init(); -void SV_VM_Setup(); +extern void SV_Phys_Init (void); +static void SV_SaveEntFile_f(void); +static void SV_StartDownload_f(void); +static void SV_Download_f(void); +static void SV_VM_Setup(); -void VM_AutoSentStats_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_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); @@ -39,11 +40,19 @@ cvar_t sv_maxrate = {CVAR_SAVE | CVAR_NOTIFY, "sv_maxrate", "10000", "upper limi 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)"}; @@ -61,6 +70,40 @@ cvar_t sv_gameplayfix_droptofloorstartsolid = {0, "sv_gameplayfix_droptofloorsta cvar_t sv_progs = {0, "sv_progs", "progs.dat", "selects which quakec progs.dat file to run" }; +cvar_t pr_checkextension = {CVAR_READONLY, "pr_checkextension", "1", "indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions)"}; +cvar_t nomonsters = {0, "nomonsters", "0", "unused cvar in quake, can be used by mods"}; +cvar_t gamecfg = {0, "gamecfg", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch1 = {0, "scratch1", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch2 = {0,"scratch2", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch3 = {0, "scratch3", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch4 = {0, "scratch4", "0", "unused cvar in quake, can be used by mods"}; +cvar_t savedgamecfg = {CVAR_SAVE, "savedgamecfg", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved3 = {CVAR_SAVE, "saved3", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved4 = {CVAR_SAVE, "saved4", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t nehx00 = {0, "nehx00", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx01 = {0, "nehx01", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx02 = {0, "nehx02", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx03 = {0, "nehx03", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx04 = {0, "nehx04", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx05 = {0, "nehx05", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx06 = {0, "nehx06", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx07 = {0, "nehx07", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx08 = {0, "nehx08", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx09 = {0, "nehx09", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx10 = {0, "nehx10", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx11 = {0, "nehx11", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx12 = {0, "nehx12", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx13 = {0, "nehx13", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx14 = {0, "nehx14", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx15 = {0, "nehx15", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx16 = {0, "nehx16", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx17 = {0, "nehx17", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx18 = {0, "nehx18", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx19 = {0, "nehx19", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t cutscene = {0, "cutscene", "1", "enables cutscenes in nehahra, can be used by other mods"}; + // TODO: move these cvars here extern cvar_t sv_clmovement_enable; extern cvar_t sv_clmovement_minping; @@ -72,13 +115,130 @@ server_static_t svs; mempool_t *sv_mempool = NULL; +extern cvar_t slowmo; +extern float scr_centertime_off; + +// 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" +}; + +#define REQFIELDS (sizeof(reqfields) / sizeof(prvm_required_field_t)) + +prvm_required_field_t reqfields[] = +{ + {ev_entity, "cursor_trace_ent"}, + {ev_entity, "drawonlytoclient"}, + {ev_entity, "exteriormodeltoclient"}, + {ev_entity, "nodrawtoclient"}, + {ev_entity, "tag_entity"}, + {ev_entity, "viewmodelforclient"}, + {ev_float, "Version"}, + {ev_float, "alpha"}, + {ev_float, "ammo_cells1"}, + {ev_float, "ammo_lava_nails"}, + {ev_float, "ammo_multi_rockets"}, + {ev_float, "ammo_nails1"}, + {ev_float, "ammo_plasma"}, + {ev_float, "ammo_rockets1"}, + {ev_float, "ammo_shells1"}, + {ev_float, "button3"}, + {ev_float, "button4"}, + {ev_float, "button5"}, + {ev_float, "button6"}, + {ev_float, "button7"}, + {ev_float, "button8"}, + {ev_float, "button9"}, + {ev_float, "button10"}, + {ev_float, "button11"}, + {ev_float, "button12"}, + {ev_float, "button13"}, + {ev_float, "button14"}, + {ev_float, "button15"}, + {ev_float, "button16"}, + {ev_float, "buttonchat"}, + {ev_float, "buttonuse"}, + {ev_float, "clientcolors"}, + {ev_float, "cursor_active"}, + {ev_float, "disableclientprediction"}, + {ev_float, "fullbright"}, + {ev_float, "glow_color"}, + {ev_float, "glow_size"}, + {ev_float, "glow_trail"}, + {ev_float, "gravity"}, + {ev_float, "idealpitch"}, + {ev_float, "items2"}, + {ev_float, "light_lev"}, + {ev_float, "modelflags"}, + {ev_float, "pflags"}, + {ev_float, "ping"}, + {ev_float, "pitch_speed"}, + {ev_float, "pmodel"}, + {ev_float, "renderamt"}, // HalfLife support + {ev_float, "rendermode"}, // HalfLife support + {ev_float, "scale"}, + {ev_float, "style"}, + {ev_float, "tag_index"}, + {ev_float, "viewzoom"}, + {ev_function, "SendEntity"}, + {ev_function, "contentstransition"}, // DRESK - Support for Entity Contents Transition Event + {ev_function, "customizeentityforclient"}, + {ev_string, "netaddress"}, + {ev_string, "playermodel"}, + {ev_string, "playerskin"}, + {ev_vector, "color"}, + {ev_vector, "colormod"}, + {ev_vector, "cursor_screen"}, + {ev_vector, "cursor_trace_endpos"}, + {ev_vector, "cursor_trace_start"}, + {ev_vector, "movement"}, + {ev_vector, "punchvector"}, +}; + + + //============================================================================ -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"); +} /* =============== @@ -97,6 +257,7 @@ void SV_Init (void) 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); @@ -110,6 +271,9 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_accelerate); Cvar_RegisterVariable (&sv_airaccelerate); Cvar_RegisterVariable (&sv_wateraccelerate); + Cvar_RegisterVariable (&sv_jumpvelocity); + Cvar_RegisterVariable (&sv_airaccel_qw); + Cvar_RegisterVariable (&sv_airaccel_sideways_friction); Cvar_RegisterVariable (&sv_clmovement_enable); Cvar_RegisterVariable (&sv_clmovement_minping); Cvar_RegisterVariable (&sv_clmovement_minping_disabletime); @@ -119,6 +283,12 @@ void SV_Init (void) 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); @@ -138,11 +308,49 @@ void SV_Init (void) 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(); + Cvar_RegisterVariable (&pr_checkextension); + Cvar_RegisterVariable (&nomonsters); + Cvar_RegisterVariable (&gamecfg); + Cvar_RegisterVariable (&scratch1); + Cvar_RegisterVariable (&scratch2); + Cvar_RegisterVariable (&scratch3); + Cvar_RegisterVariable (&scratch4); + Cvar_RegisterVariable (&savedgamecfg); + Cvar_RegisterVariable (&saved1); + Cvar_RegisterVariable (&saved2); + Cvar_RegisterVariable (&saved3); + Cvar_RegisterVariable (&saved4); + // LordHavoc: Nehahra uses these to pass data around cutscene demos + if (gamemode == GAME_NEHAHRA) + { + Cvar_RegisterVariable (&nehx00); + Cvar_RegisterVariable (&nehx01); + Cvar_RegisterVariable (&nehx02); + Cvar_RegisterVariable (&nehx03); + Cvar_RegisterVariable (&nehx04); + Cvar_RegisterVariable (&nehx05); + Cvar_RegisterVariable (&nehx06); + Cvar_RegisterVariable (&nehx07); + Cvar_RegisterVariable (&nehx08); + Cvar_RegisterVariable (&nehx09); + Cvar_RegisterVariable (&nehx10); + Cvar_RegisterVariable (&nehx11); + Cvar_RegisterVariable (&nehx12); + Cvar_RegisterVariable (&nehx13); + Cvar_RegisterVariable (&nehx14); + Cvar_RegisterVariable (&nehx15); + Cvar_RegisterVariable (&nehx16); + Cvar_RegisterVariable (&nehx17); + Cvar_RegisterVariable (&nehx18); + Cvar_RegisterVariable (&nehx19); + } + Cvar_RegisterVariable (&cutscene); // for Nehahra but useful to other mods as well + SV_Phys_Init(); - SV_World_Init(); sv_mempool = Mem_AllocPool("server", 0, NULL); } @@ -189,6 +397,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(); } /* @@ -226,6 +435,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(); } /* @@ -305,6 +515,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(); } /* @@ -347,6 +558,9 @@ void SV_SendServerinfo (client_t *client) if (client->entitydatabase5) EntityFrame5_FreeDatabase(client->entitydatabase5); + memset(client->stats, 0, sizeof(client->stats)); + memset(client->statsdeltabits, 0, sizeof(client->statsdeltabits)); + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE) { if (sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) @@ -357,6 +571,9 @@ void SV_SendServerinfo (client_t *client) client->entitydatabase5 = EntityFrame5_AllocDatabase(sv_mempool); } + // reset csqc entity versions + memset(client->csqcentityversion, 0, sizeof(client->csqcentityversion)); + 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); @@ -367,14 +584,14 @@ void SV_SendServerinfo (client_t *client) { 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); - //[515]: init stufftext string (it is sent before svc_serverinfo) - val = PRVM_GETGLOBALFIELDVALUE(PRVM_ED_FindGlobalOffset("SV_InitCmd")); MSG_WriteByte (&client->netconnection->message, svc_stufftext); 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); @@ -388,6 +605,15 @@ void SV_SendServerinfo (client_t *client) 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); MSG_WriteLong (&client->netconnection->message, Protocol_NumberForEnum(sv.protocol)); MSG_WriteByte (&client->netconnection->message, svs.maxclients); @@ -419,15 +645,17 @@ void SV_SendServerinfo (client_t *client) MSG_WriteByte (&client->netconnection->message, svc_signonnum); MSG_WriteByte (&client->netconnection->message, 1); - { - client_t *save; - save = host_client; - host_client = client; - Curl_SendRequirements(); - host_client = save; - } - 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; } /* @@ -446,9 +674,6 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection) client = svs.clients + clientnum; - if(netconnection)//[515]: bots don't play with csqc =) - EntityFrameCSQC_InitClientVersions(clientnum, false); - // set up the client_t if (sv.loadgame) memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms)); @@ -464,6 +689,9 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection) 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 @@ -503,17 +731,6 @@ FRAME UPDATES =============================================================================== */ -/* -================== -SV_ClearDatagram - -================== -*/ -void SV_ClearDatagram (void) -{ - SZ_Clear (&sv.datagram); -} - /* ============================================================================= @@ -525,23 +742,34 @@ crosses a waterline. ============================================================================= */ -int sv_writeentitiestoclient_pvsbytes; -unsigned char sv_writeentitiestoclient_pvs[MAX_MAP_LEAFS/8]; - -static int numsendentities; -static entity_state_t sendentities[MAX_EDICTS]; -static entity_state_t *sendentitiesindex[MAX_EDICTS]; - -qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int e) +static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int enumber) { int i; + unsigned int tagentity; unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius; unsigned int customizeentityforclient; float f; - vec3_t cullmins, cullmaxs; + vec3_t cullmins, cullmaxs, netcenter; model_t *model; prvm_eval_t *val; + // see if the customizeentityforclient extension is used by this entity + customizeentityforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.customizeentityforclient)->function; + if (customizeentityforclient) + { + prog->globals.server->self = enumber; + prog->globals.server->other = sv.writeentitiestoclient_cliententitynumber; + PRVM_ExecuteProgram(customizeentityforclient, "customizeentityforclient: NULL function"); + // customizeentityforclient can return false to reject the entity + if (!PRVM_G_FLOAT(OFS_RETURN)) + return false; + } + + // this 2 billion unit check is actually to detect NAN origins + // (we really don't want to send those) + if (!(VectorLength2(ent->fields.server->origin) < 2000000000.0*2000000000.0)) + return false; + // EF_NODRAW prevents sending for any reason except for your own // client, so we must keep all clients in this superset effects = (unsigned)ent->fields.server->effects; @@ -550,24 +778,26 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int // LordHavoc: this could kill tags attached to an invisible entity, I // just hope we never have to support that case i = (int)ent->fields.server->modelindex; - modelindex = (i >= 1 && i < MAX_MODELS && *PRVM_GetString(ent->fields.server->model)) ? i : 0; + modelindex = (i >= 1 && i < MAX_MODELS && ent->fields.server->model && *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; + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict) + flags |= RENDER_VIEWMODEL; - 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) { @@ -616,142 +846,83 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int specialvisibilityradius = max(specialvisibilityradius, 100); } - // early culling checks - // (final culling is done by SV_MarkWriteEntityStateToClient) - customizeentityforclient = PRVM_GETEDICTFIELDVALUE(ent, eval_customizeentityforclient)->function; - if (!customizeentityforclient) + // don't send uninteresting entities + if (enumber != sv.writeentitiestoclient_cliententitynumber) { - if (e > svs.maxclients && (!modelindex && !specialvisibilityradius)) + if (!modelindex && !specialvisibilityradius) return false; - // this 2 billion unit check is actually to detect NAN origins - // (we really don't want to send those) - if (VectorLength2(ent->fields.server->origin) > 2000000000.0*2000000000.0) + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.nodrawtoclient)->edict == sv.writeentitiestoclient_cliententitynumber) + return false; + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.drawonlytoclient)->edict && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.drawonlytoclient)->edict != sv.writeentitiestoclient_cliententitynumber) + return false; + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict != sv.writeentitiestoclient_cliententitynumber) + return false; + if (flags & RENDER_VIEWMODEL && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict != sv.writeentitiestoclient_cliententitynumber) + return false; + if (effects & EF_NODRAW) return false; } - - *cs = defaultstate; - cs->active = true; - cs->number = e; - VectorCopy(ent->fields.server->origin, cs->origin); - VectorCopy(ent->fields.server->angles, cs->angles); - cs->flags = flags; - cs->effects = effects; - cs->colormap = (unsigned)ent->fields.server->colormap; - 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->customizeentityforclient = customizeentityforclient; - cs->tagentity = PRVM_GETEDICTFIELDVALUE(ent, eval_tag_entity)->edict; - cs->tagindex = (unsigned char)PRVM_GETEDICTFIELDVALUE(ent, eval_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); - if (val->vector[0] || val->vector[1] || val->vector[2]) - { - i = (int)(val->vector[0] * 32.0f);cs->colormod[0] = bound(0, i, 255); - i = (int)(val->vector[1] * 32.0f);cs->colormod[1] = bound(0, i, 255); - i = (int)(val->vector[2] * 32.0f);cs->colormod[2] = bound(0, i, 255); - } - - cs->modelindex = modelindex; - - cs->alpha = 255; - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_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); - if (f) - { - i = (int)f; - cs->alpha = (unsigned char)bound(0, i, 255); - } - - cs->scale = 16; - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_scale)->_float * 16.0f); - if (f) - { - i = (int)f; - cs->scale = (unsigned char)bound(0, i, 255); - } - - cs->glowcolor = 254; - f = (PRVM_GETEDICTFIELDVALUE(ent, eval_glow_color)->_float); - if (f) - cs->glowcolor = (int)f; - - if (PRVM_GETEDICTFIELDVALUE(ent, eval_fullbright)->_float) - cs->effects |= EF_FULLBRIGHT; - - if (ent->fields.server->movetype == MOVETYPE_STEP) - cs->flags |= RENDER_STEP; - if ((cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767) - cs->flags |= RENDER_LOWPRECISION; - if (ent->fields.server->colormap >= 1024) - cs->flags |= RENDER_COLORMAPPED; - if (cs->viewmodelforclient) - cs->flags |= RENDER_VIEWMODEL; // show relative to the view - - cs->light[0] = light[0]; - cs->light[1] = light[1]; - cs->light[2] = light[2]; - cs->light[3] = light[3]; - cs->lightstyle = lightstyle; - cs->lightpflags = lightpflags; - - cs->specialvisibilityradius = specialvisibilityradius; + // don't send child if parent was rejected + // FIXME: it would be better to force the parent to send... + tagentity = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)->edict; + if (tagentity && !SV_BuildEntityState(NULL, PRVM_EDICT_NUM(tagentity), tagentity)) + return false; // 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])) { - float scale = cs->scale * (1.0f / 16.0f); - if (cs->angles[0] || cs->angles[2]) // pitch and roll + float scale = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_color)->_float; + if (scale) + scale *= (1.0f / 16.0f); + else + scale = 1; + if (ent->fields.server->angles[0] || ent->fields.server->angles[2]) // pitch and roll { - VectorMA(cs->origin, scale, model->rotatedmins, cullmins); - VectorMA(cs->origin, scale, model->rotatedmaxs, cullmaxs); + VectorMA(ent->fields.server->origin, scale, model->rotatedmins, cullmins); + VectorMA(ent->fields.server->origin, scale, model->rotatedmaxs, cullmaxs); } - else if (cs->angles[1]) + else if (ent->fields.server->angles[1]) { - VectorMA(cs->origin, scale, model->yawmins, cullmins); - VectorMA(cs->origin, scale, model->yawmaxs, cullmaxs); + VectorMA(ent->fields.server->origin, scale, model->yawmins, cullmins); + VectorMA(ent->fields.server->origin, scale, model->yawmaxs, cullmaxs); } else { - VectorMA(cs->origin, scale, model->normalmins, cullmins); - VectorMA(cs->origin, scale, model->normalmaxs, cullmaxs); + VectorMA(ent->fields.server->origin, scale, model->normalmins, cullmins); + VectorMA(ent->fields.server->origin, scale, model->normalmaxs, cullmaxs); } } else { // if there is no model (or it could not be loaded), use the physics box - VectorAdd(cs->origin, ent->fields.server->mins, cullmins); - VectorAdd(cs->origin, ent->fields.server->maxs, cullmaxs); + VectorAdd(ent->fields.server->origin, ent->fields.server->mins, cullmins); + VectorAdd(ent->fields.server->origin, ent->fields.server->maxs, cullmaxs); } if (specialvisibilityradius) { - cullmins[0] = min(cullmins[0], cs->origin[0] - specialvisibilityradius); - cullmins[1] = min(cullmins[1], cs->origin[1] - specialvisibilityradius); - cullmins[2] = min(cullmins[2], cs->origin[2] - specialvisibilityradius); - cullmaxs[0] = max(cullmaxs[0], cs->origin[0] + specialvisibilityradius); - cullmaxs[1] = max(cullmaxs[1], cs->origin[1] + specialvisibilityradius); - cullmaxs[2] = max(cullmaxs[2], cs->origin[2] + specialvisibilityradius); + cullmins[0] = min(cullmins[0], ent->fields.server->origin[0] - specialvisibilityradius); + cullmins[1] = min(cullmins[1], ent->fields.server->origin[1] - specialvisibilityradius); + cullmins[2] = min(cullmins[2], ent->fields.server->origin[2] - specialvisibilityradius); + cullmaxs[0] = max(cullmaxs[0], ent->fields.server->origin[0] + specialvisibilityradius); + cullmaxs[1] = max(cullmaxs[1], ent->fields.server->origin[1] + specialvisibilityradius); + cullmaxs[2] = max(cullmaxs[2], ent->fields.server->origin[2] + specialvisibilityradius); } + + // calculate center of bbox for network prioritization purposes + VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, 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) { @@ -761,119 +932,36 @@ qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int } } - return true; -} - -void SV_PrepareEntitiesForSending(void) -{ - int e; - prvm_edict_t *ent; - // send all entities that touch the pvs - numsendentities = 0; - sendentitiesindex[0] = NULL; - memset(sendentitiesindex, 0, prog->num_edicts * sizeof(entity_state_t *)); - for (e = 1, ent = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ent = PRVM_NEXT_EDICT(ent)) + if (enumber != sv.writeentitiestoclient_cliententitynumber && !(effects & EF_NODEPTHTEST) && !(flags & RENDER_VIEWMODEL) && !tagentity) { - if (!ent->priv.server->free && SV_PrepareEntityForSending(ent, sendentities + numsendentities, e)) - { - sendentitiesindex[e] = sendentities + numsendentities; - numsendentities++; - } - } -} - -static int sententitiesmark = 0; -static int sententities[MAX_EDICTS]; -static int sententitiesconsideration[MAX_EDICTS]; -static int sv_writeentitiestoclient_culled_pvs; -static int sv_writeentitiestoclient_culled_trace; -static int sv_writeentitiestoclient_visibleentities; -static int sv_writeentitiestoclient_totalentities; -//static entity_frame_t sv_writeentitiestoclient_entityframe; -static int sv_writeentitiestoclient_clentnum; -static vec3_t sv_writeentitiestoclient_testeye; -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; - sv_writeentitiestoclient_totalentities++; - - if (s->customizeentityforclient) - { - prog->globals.server->self = s->number; - prog->globals.server->other = sv_writeentitiestoclient_clentnum; - PRVM_ExecuteProgram(s->customizeentityforclient, "customizeentityforclient: NULL function"); - if(!PRVM_G_FLOAT(OFS_RETURN) || !SV_PrepareEntityForSending(PRVM_EDICT_NUM(s->number), s, s->number)) - return; - } - - // never reject player - if (s->number != sv_writeentitiestoclient_clentnum) - { - // check various rejection conditions - if (s->nodrawtoclient == sv_writeentitiestoclient_clentnum) - return; - if (s->drawonlytoclient && s->drawonlytoclient != sv_writeentitiestoclient_clentnum) - return; - if (s->effects & EF_NODRAW) - return; - // LordHavoc: only send entities with a model or important effects - if (!s->modelindex && s->specialvisibilityradius == 0) - return; - - // viewmodels don't have visibility checking - if (s->viewmodelforclient) - { - if (s->viewmodelforclient != sv_writeentitiestoclient_clentnum) - return; - } - else if (s->tagentity) + qboolean isbmodel = (model = sv.models[modelindex]) != NULL && model->name[0] == '*'; + if (!isbmodel || !sv_cullentities_nevercullbmodels.integer) { - // tag attached entities simply check their parent - if (!sendentitiesindex[s->tagentity]) - return; - SV_MarkWriteEntityStateToClient(sendentitiesindex[s->tagentity]); - if (sententities[s->tagentity] != sententitiesmark) - return; - } - // 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))) - { - // entity has survived every check so far, check if visible - ed = PRVM_EDICT_NUM(s->number); + // cull based on visibility // if not touching a visible leaf - if (sv_cullentities_pvs.integer && sv_writeentitiestoclient_pvsbytes) + if (sv_cullentities_pvs.integer && sv.writeentitiestoclient_pvsbytes) { - if (ed->priv.server->pvs_numclusters < 0) + if (ent->priv.server->pvs_numclusters < 0) { // entity too big for clusters list - if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv_writeentitiestoclient_pvs, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, cullmins, cullmaxs)) { - sv_writeentitiestoclient_culled_pvs++; - return; + sv.writeentitiestoclient_stats_culled_pvs++; + return false; } } else { int i; // check cached clusters list - for (i = 0;i < ed->priv.server->pvs_numclusters;i++) - if (CHECKPVSBIT(sv_writeentitiestoclient_pvs, ed->priv.server->pvs_clusterlist[i])) + for (i = 0;i < ent->priv.server->pvs_numclusters;i++) + if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ent->priv.server->pvs_clusterlist[i])) break; - if (i == ed->priv.server->pvs_numclusters) + if (i == ent->priv.server->pvs_numclusters) { - sv_writeentitiestoclient_culled_pvs++; - return; + sv.writeentitiestoclient_stats_culled_pvs++; + return false; } } } @@ -881,110 +969,194 @@ 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 = 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, cullmins, 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... { - // 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; + if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, cullmins, 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; } - if (realtime > sv_writeentitiestoclient_client->visibletime[s->number]) + while(0); + + if(visible) + svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[enumber] = realtime + sv_cullentities_trace_delay.value; + else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[enumber]) { - sv_writeentitiestoclient_culled_trace++; - return; + sv.writeentitiestoclient_stats_culled_trace++; + return false; } } } } - // this just marks it for sending - // FIXME: it would be more efficient to send here, but the entity - // compressor isn't that flexible - sv_writeentitiestoclient_visibleentities++; - sententities[s->number] = sententitiesmark; -} + // if the caller was just checking... return true + if (!cs) + return true; + + *cs = defaultstate; + cs->active = true; + cs->number = enumber; + VectorCopy(netcenter, cs->netcenter); + VectorCopy(ent->fields.server->origin, cs->origin); + VectorCopy(ent->fields.server->angles, cs->angles); + cs->flags = flags; + cs->effects = effects; + cs->colormap = (unsigned)ent->fields.server->colormap; + cs->modelindex = modelindex; + cs->skin = (unsigned)ent->fields.server->skin; + cs->frame = (unsigned)ent->fields.server->frame; + 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_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); + i = (int)(val->vector[1] * 32.0f);cs->colormod[1] = bound(0, i, 255); + i = (int)(val->vector[2] * 32.0f);cs->colormod[2] = bound(0, i, 255); + } -entity_state_t sendstates[MAX_EDICTS]; -extern int csqc_clent; + cs->modelindex = modelindex; -void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int *stats) + cs->alpha = 255; + 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_EDICTFIELDVALUE(ent, prog->fieldoffsets.renderamt)->_float); + if (f) + { + i = (int)f; + cs->alpha = (unsigned char)bound(0, i, 255); + } + + cs->scale = 16; + f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale)->_float * 16.0f); + if (f) + { + i = (int)f; + cs->scale = (unsigned char)bound(0, i, 255); + } + + cs->glowcolor = 254; + f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_color)->_float); + if (f) + cs->glowcolor = (int)f; + + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.fullbright)->_float) + cs->effects |= EF_FULLBRIGHT; + + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.modelflags); + if (val && val->_float) + cs->effects |= ((unsigned int)val->_float & 0xff) << 24; + + if (ent->fields.server->movetype == MOVETYPE_STEP) + cs->flags |= RENDER_STEP; + if (cs->number != sv.writeentitiestoclient_cliententitynumber && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767) + cs->flags |= RENDER_LOWPRECISION; + if (ent->fields.server->colormap >= 1024) + cs->flags |= RENDER_COLORMAPPED; + if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_trail)->edict && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_trail)->edict == sv.writeentitiestoclient_cliententitynumber) + cs->flags |= RENDER_EXTERIORMODEL; + + cs->light[0] = light[0]; + cs->light[1] = light[1]; + cs->light[2] = light[2]; + cs->light[3] = light[3]; + cs->lightstyle = lightstyle; + cs->lightpflags = lightpflags; + + return true; +} + +static void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg) { - int i, numsendstates; - entity_state_t *s; + int i; + int numsendstates; + prvm_edict_t *ent; // if there isn't enough space to accomplish anything, skip it if (msg->cursize + 25 > msg->maxsize) return; - sv_writeentitiestoclient_client = client; + sv.writeentitiestoclient_msg = msg; + sv.writeentitiestoclient_clientnumber = client - svs.clients; - sv_writeentitiestoclient_culled_pvs = 0; - sv_writeentitiestoclient_culled_trace = 0; - sv_writeentitiestoclient_visibleentities = 0; - sv_writeentitiestoclient_totalentities = 0; + sv.writeentitiestoclient_stats_culled_pvs = 0; + sv.writeentitiestoclient_stats_culled_trace = 0; + sv.writeentitiestoclient_stats_visibleentities = 0; + sv.writeentitiestoclient_stats_totalentities = 0; // find the client's PVS // the real place being tested from - VectorAdd(clent->fields.server->origin, clent->fields.server->view_ofs, sv_writeentitiestoclient_testeye); - sv_writeentitiestoclient_pvsbytes = 0; + VectorAdd(clent->fields.server->origin, clent->fields.server->view_ofs, sv.writeentitiestoclient_testeye); + sv.writeentitiestoclient_pvsbytes = 0; 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)); + 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 - - sententitiesmark++; - - for (i = 0;i < numsendentities;i++) - SV_MarkWriteEntityStateToClient(sendentities + i); + sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes + // send all entities that touch the pvs numsendstates = 0; - for (i = 0;i < numsendentities;i++) - { - if (sententities[sendentities[i].number] == sententitiesmark) - { - s = &sendstates[numsendstates++]; - *s = sendentities[i]; - if (s->exteriormodelforclient && s->exteriormodelforclient == sv_writeentitiestoclient_clentnum) - s->flags |= RENDER_EXTERIORMODEL; - } - } + for (i = 1, ent = PRVM_NEXT_EDICT(prog->edicts);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free && SV_BuildEntityState(sv.writeentitiestoclient_sendstates + numsendstates, ent, i)) + numsendstates++; 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_totalentities, sv_writeentitiestoclient_visibleentities, sv_writeentitiestoclient_culled_pvs + sv_writeentitiestoclient_culled_trace, sv_writeentitiestoclient_culled_pvs, sv_writeentitiestoclient_culled_trace); + 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, sendstates); + EntityFrameCSQC_WriteFrame(msg, numsendstates, sv.writeentitiestoclient_sendstates); if (client->entitydatabase5) - EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sendstates, client - svs.clients + 1, stats, client->movesequence); + EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence); else if (client->entitydatabase4) - EntityFrame4_WriteFrame(msg, client->entitydatabase4, numsendstates, sendstates); + { + EntityFrame4_WriteFrame(msg, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates); + Protocol_WriteStatsReliable(); + } else if (client->entitydatabase) - EntityFrame_WriteFrame(msg, client->entitydatabase, numsendstates, sendstates, client - svs.clients + 1); + { + EntityFrame_WriteFrame(msg, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1); + Protocol_WriteStatsReliable(); + } else - EntityFrameQuake_WriteFrame(msg, numsendstates, sendstates); + { + EntityFrameQuake_WriteFrame(msg, numsendstates, sv.writeentitiestoclient_sendstates); + Protocol_WriteStatsReliable(); + } } /* @@ -993,7 +1165,7 @@ SV_CleanupEnts ============= */ -void SV_CleanupEnts (void) +static void SV_CleanupEnts (void) { int e; prvm_edict_t *ent; @@ -1019,6 +1191,7 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t vec3_t punchvector; int viewzoom; const char *s; + float *statsf = (float *)stats; // // send a damage message @@ -1042,24 +1215,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 @@ -1072,7 +1256,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; @@ -1118,6 +1302,25 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t //stats[STAT_SECRETS] = prog->globals.server->found_secrets; //stats[STAT_MONSTERS] = prog->globals.server->killed_monsters; + // movement settings for prediction + statsf[STAT_MOVEVARS_TICRATE] = sys_ticrate.value; + statsf[STAT_MOVEVARS_TIMESCALE] = slowmo.value; + statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; + statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; + statsf[STAT_MOVEVARS_MAXSPEED] = sv_maxspeed.value; + statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_maxspeed.value; // FIXME: QW has a separate cvar for this + statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; + statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value >= 0 ? sv_airaccelerate.value : sv_accelerate.value; + statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value >= 0 ? sv_wateraccelerate.value : sv_accelerate.value; + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.gravity); + statsf[STAT_MOVEVARS_ENTGRAVITY] = val ? val->_float : 1.0f; + statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value; + statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; + statsf[STAT_MOVEVARS_MAXAIRSPEED] = sv_maxairspeed.value; + statsf[STAT_MOVEVARS_STEPHEIGHT] = sv_stepheight.value; + statsf[STAT_MOVEVARS_AIRACCEL_QW] = sv_airaccel_qw.value; + statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_airaccel_sideways_friction.value; + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) { if (stats[STAT_VIEWHEIGHT] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT; @@ -1229,44 +1432,104 @@ 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); +} + +static 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; + if (!client->unreliablemsg_splitpoints) + return; + // always accept the first one if it's within 1400 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) + { + numsegments = 1; + msg->maxsize = 1400; + } + else + 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]; + // 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) + 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 ======================= */ -static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; // FIXME? -void SV_SendClientDatagram (client_t *client) +static void SV_SendClientDatagram (client_t *client) { - int rate, maxrate, maxsize, maxsize2, downloadsize; + int clientrate, maxrate, maxsize, maxsize2, downloadsize; sizebuf_t msg; int stats[MAX_CL_STATS]; + unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; + + // 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 + // for good singleplayer, send huge packets and never limit frequency + clientrate = 1000000000; maxsize = sizeof(sv_sendclientdatagram_buf); maxsize2 = sizeof(sv_sendclientdatagram_buf); } else if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4) { - // no rate limiting support on older protocols because dp protocols - // 1-4 kick the client off if they overflow, and quake protocol shows - // less than the full entity set if rate limited + // no packet size limit support on older protocols because DP1-4 kick + // the client off if they overflow, and quake protocol shows less than + // the full entity set if rate limited maxsize = 1400; maxsize2 = 1400; } else { - // 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); - + // DP5 and later protocols support packet size limiting which is a + // better method than limiting packet frequency as QW does + // // 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)(rate * sys_ticrate.value); - maxsize = bound(100, rate, 1400); + maxsize = (int)(clientrate * sys_ticrate.value); + maxsize = bound(100, maxsize, 1400); maxsize2 = 1400; } @@ -1279,24 +1542,37 @@ void SV_SendClientDatagram (client_t *client) msg.maxsize = maxsize; msg.cursize = 0; - if (host_client->spawned) + // 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)) + { + // send the datagram + //NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate); + return; + } + else if (host_client->spawned) { MSG_WriteByte (&msg, svc_time); MSG_WriteFloat (&msg, sv.time); // 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) { @@ -1333,7 +1609,7 @@ void SV_SendClientDatagram (client_t *client) } // send the datagram - NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol); + NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate); } /* @@ -1341,7 +1617,7 @@ void SV_SendClientDatagram (client_t *client) SV_UpdateToReliableMessages ======================= */ -void SV_UpdateToReliableMessages (void) +static void SV_UpdateToReliableMessages (void) { int i, j; client_t *client; @@ -1366,7 +1642,7 @@ 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^%i changed name to %s\n", host_client->old_name, STRING_COLOR_DEFAULT, 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); @@ -1376,7 +1652,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) { @@ -1388,23 +1664,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 @@ -1434,11 +1710,13 @@ SV_SendClientMessages */ void SV_SendClientMessages (void) { - int i, prepared = false; + int i; if (sv.protocol == PROTOCOL_QUAKEWORLD) Sys_Error("SV_SendClientMessages: no quakeworld support\n"); + SV_FlushBroadcastMessages(); + // update frags, names, etc SV_UpdateToReliableMessages(); @@ -1456,12 +1734,6 @@ void SV_SendClientMessages (void) continue; } - if (!prepared) - { - prepared = true; - // only prepare entities once per frame - SV_PrepareEntitiesForSending(); - } SV_SendClientDatagram (host_client); } @@ -1469,13 +1741,13 @@ void SV_SendClientMessages (void) SV_CleanupEnts(); } -void SV_StartDownload_f(void) +static void SV_StartDownload_f(void) { if (host_client->download_file) host_client->download_started = true; } -void SV_Download_f(void) +static void SV_Download_f(void) { const char *whichpack, *whichpack2, *extension; @@ -1513,6 +1785,7 @@ void SV_Download_f(void) } 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) @@ -1547,9 +1820,28 @@ void SV_Download_f(void) } } + 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) { - extension = FS_FileExtension(host_client->download_name); 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); @@ -1694,13 +1986,92 @@ int SV_SoundIndex(const char *s, int precachemode) return 0; } +/* +================ +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_Simple(&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 ================ */ -void SV_CreateBaseline (void) +static void SV_CreateBaseline (void) { int i, entnum, large; prvm_edict_t *svent; @@ -1793,49 +2164,6 @@ void SV_SaveSpawnparms (void) host_client->spawn_parms[j] = (&prog->globals.server->parm1)[j]; } } -/* -void SV_IncreaseEdicts(void) -{ - int i; - prvm_edict_t *ent; - int oldmax_edicts = prog->max_edicts; - void *oldedictsengineprivate = prog->edictprivate; - void *oldedictsfields = prog->edictsfields; - void *oldmoved_edicts = sv.moved_edicts; - - if (prog->max_edicts >= MAX_EDICTS) - return; - - // links don't survive the transition, so unlink everything - for (i = 0, ent = prog->edicts;i < prog->max_edicts;i++, ent++) - { - if (!ent->priv.server->free) - SV_UnlinkEdict(prog->edicts + i); - memset(&ent->priv.server->areagrid, 0, sizeof(ent->priv.server->areagrid)); - } - SV_ClearWorld(); - - prog->max_edicts = min(prog->max_edicts + 256, MAX_EDICTS); - prog->edictprivate = PR_Alloc(prog->max_edicts * sizeof(edict_engineprivate_t)); - prog->edictsfields = PR_Alloc(prog->max_edicts * prog->edict_size); - sv.moved_edicts = PR_Alloc(prog->max_edicts * sizeof(prvm_edict_t *)); - - memcpy(prog->edictprivate, oldedictsengineprivate, oldmax_edicts * sizeof(edict_engineprivate_t)); - memcpy(prog->edictsfields, oldedictsfields, oldmax_edicts * prog->edict_size); - - for (i = 0, ent = prog->edicts;i < prog->max_edicts;i++, ent++) - { - ent->priv.vp = (unsigned char*) prog->edictprivate + i * prog->edictprivate_size; - ent->fields.server = (void *)((unsigned char *)prog->edictsfields + i * prog->edict_size); - // link every entity except world - if (!ent->priv.server->free) - SV_LinkEdict(ent, false); - } - - PR_Free(oldedictsengineprivate); - PR_Free(oldedictsfields); - PR_Free(oldmoved_edicts); -}*/ /* ================ @@ -1844,7 +2172,6 @@ SV_SpawnServer This is called at the start of each level ================ */ -extern float scr_centertime_off; void SV_SpawnServer (const char *server) { @@ -1943,30 +2270,6 @@ void SV_SpawnServer (const char *server) // load progs to get entity field count //PR_LoadProgs ( sv_progs.string ); - // allocate server memory - /*// start out with just enough room for clients and a reasonable estimate of entities - prog->max_edicts = max(svs.maxclients + 1, 512); - prog->max_edicts = min(prog->max_edicts, MAX_EDICTS); - - // prvm_edict_t structures (hidden from progs) - prog->edicts = PR_Alloc(MAX_EDICTS * sizeof(prvm_edict_t)); - // engine private structures (hidden from progs) - prog->edictprivate = PR_Alloc(prog->max_edicts * sizeof(edict_engineprivate_t)); - // progs fields, often accessed by server - prog->edictsfields = PR_Alloc(prog->max_edicts * prog->edict_size);*/ - // used by PushMove to move back pushed entities - sv.moved_edicts = (prvm_edict_t **)PRVM_Alloc(prog->max_edicts * sizeof(prvm_edict_t *)); - /*for (i = 0;i < prog->max_edicts;i++) - { - ent = prog->edicts + i; - ent->priv.vp = (unsigned char*) prog->edictprivate + i * prog->edictprivate_size; - ent->fields.server = (void *)((unsigned char *)prog->edictsfields + i * prog->edict_size); - }*/ - - // reset client csqc entity versions right away. - for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) - EntityFrameCSQC_InitClientVersions(i, true); - sv.datagram.maxsize = sizeof(sv.datagram_buf); sv.datagram.cursize = 0; sv.datagram.data = sv.datagram_buf; @@ -1986,7 +2289,7 @@ 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; @@ -1999,7 +2302,9 @@ void SV_SpawnServer (const char *server) // // 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])); @@ -2115,38 +2420,33 @@ void SV_SpawnServer (const char *server) ///////////////////////////////////////////////////// // SV VM stuff -void SV_VM_CB_BeginIncreaseEdicts(void) +static void SV_VM_CB_BeginIncreaseEdicts(void) { int i; prvm_edict_t *ent; - PRVM_Free( sv.moved_edicts ); - sv.moved_edicts = (prvm_edict_t **)PRVM_Alloc(prog->max_edicts * sizeof(prvm_edict_t *)); - // links don't survive the transition, so unlink everything 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) +static 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) +static void SV_VM_CB_InitEdict(prvm_edict_t *e) { // LordHavoc: for consistency set these here int num = PRVM_NUM_FOR_EDICT(e) - 1; @@ -2163,19 +2463,32 @@ 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); + // Assign netaddress (IP Address, etc) + if(prog->fieldoffsets.netaddress >= 0) + { // Valid Field; Process + if(svs.clients[num].netconnection != NULL) + {// Valid Address; Assign + // Acquire Readable Address + LHNETADDRESS_ToString(&svs.clients[num].netconnection->peeraddress, svs.clients[num].netaddress, sizeof(svs.clients[num].netaddress), false); + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.netaddress)->string = PRVM_SetEngineString(svs.clients[num].netaddress); + } + else + // Invalid / Bot + PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.netaddress)->string = PRVM_SetEngineString("null/botclient"); + } } } -void SV_VM_CB_FreeEdict(prvm_edict_t *ed) +static 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; @@ -2189,7 +2502,7 @@ void SV_VM_CB_FreeEdict(prvm_edict_t *ed) ed->fields.server->solid = 0; } -void SV_VM_CB_CountEdicts(void) +static void SV_VM_CB_CountEdicts(void) { int i; prvm_edict_t *ent; @@ -2217,7 +2530,7 @@ void SV_VM_CB_CountEdicts(void) Con_Printf("step :%3i\n", step); } -qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent) +static qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent) { // remove things from different skill levels or deathmatch if (gamemode != GAME_TRANSFUSION) //Transfusion does this in QC @@ -2239,315 +2552,12 @@ qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent) return true; } -cvar_t pr_checkextension = {CVAR_READONLY, "pr_checkextension", "1", "indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions)"}; -cvar_t nomonsters = {0, "nomonsters", "0", "unused cvar in quake, can be used by mods"}; -cvar_t gamecfg = {0, "gamecfg", "0", "unused cvar in quake, can be used by mods"}; -cvar_t scratch1 = {0, "scratch1", "0", "unused cvar in quake, can be used by mods"}; -cvar_t scratch2 = {0,"scratch2", "0", "unused cvar in quake, can be used by mods"}; -cvar_t scratch3 = {0, "scratch3", "0", "unused cvar in quake, can be used by mods"}; -cvar_t scratch4 = {0, "scratch4", "0", "unused cvar in quake, can be used by mods"}; -cvar_t savedgamecfg = {CVAR_SAVE, "savedgamecfg", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; -cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; -cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; -cvar_t saved3 = {CVAR_SAVE, "saved3", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; -cvar_t saved4 = {CVAR_SAVE, "saved4", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; -cvar_t nehx00 = {0, "nehx00", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx01 = {0, "nehx01", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx02 = {0, "nehx02", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx03 = {0, "nehx03", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx04 = {0, "nehx04", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx05 = {0, "nehx05", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx06 = {0, "nehx06", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx07 = {0, "nehx07", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx08 = {0, "nehx08", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx09 = {0, "nehx09", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx10 = {0, "nehx10", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx11 = {0, "nehx11", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx12 = {0, "nehx12", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx13 = {0, "nehx13", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx14 = {0, "nehx14", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx15 = {0, "nehx15", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx16 = {0, "nehx16", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx17 = {0, "nehx17", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx18 = {0, "nehx18", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t nehx19 = {0, "nehx19", "0", "nehahra data storage cvar (used in singleplayer)"}; -cvar_t cutscene = {0, "cutscene", "1", "enables cutscenes in nehahra, can be used by other mods"}; - -void SV_VM_Init(void) -{ - Cvar_RegisterVariable (&pr_checkextension); - Cvar_RegisterVariable (&nomonsters); - Cvar_RegisterVariable (&gamecfg); - Cvar_RegisterVariable (&scratch1); - Cvar_RegisterVariable (&scratch2); - Cvar_RegisterVariable (&scratch3); - Cvar_RegisterVariable (&scratch4); - Cvar_RegisterVariable (&savedgamecfg); - Cvar_RegisterVariable (&saved1); - Cvar_RegisterVariable (&saved2); - Cvar_RegisterVariable (&saved3); - Cvar_RegisterVariable (&saved4); - // LordHavoc: Nehahra uses these to pass data around cutscene demos - if (gamemode == GAME_NEHAHRA) - { - Cvar_RegisterVariable (&nehx00); - Cvar_RegisterVariable (&nehx01); - Cvar_RegisterVariable (&nehx02); - Cvar_RegisterVariable (&nehx03); - Cvar_RegisterVariable (&nehx04); - Cvar_RegisterVariable (&nehx05); - Cvar_RegisterVariable (&nehx06); - Cvar_RegisterVariable (&nehx07); - Cvar_RegisterVariable (&nehx08); - Cvar_RegisterVariable (&nehx09); - Cvar_RegisterVariable (&nehx10); - Cvar_RegisterVariable (&nehx11); - Cvar_RegisterVariable (&nehx12); - Cvar_RegisterVariable (&nehx13); - Cvar_RegisterVariable (&nehx14); - Cvar_RegisterVariable (&nehx15); - Cvar_RegisterVariable (&nehx16); - Cvar_RegisterVariable (&nehx17); - Cvar_RegisterVariable (&nehx18); - Cvar_RegisterVariable (&nehx19); - } - 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; -// DRESK - Support for Entity Contents Transition Event -int eval_contentstransition; - -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; - -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"); - // DRESK - Support for Entity Contents Transition Event - eval_contentstransition = PRVM_ED_FindFieldOffset("contentstransition"); - - // 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"); - 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[] = -{ - {ev_entity, "cursor_trace_ent"}, - {ev_entity, "drawonlytoclient"}, - {ev_entity, "exteriormodeltoclient"}, - {ev_entity, "nodrawtoclient"}, - {ev_entity, "tag_entity"}, - {ev_entity, "viewmodelforclient"}, - {ev_float, "alpha"}, - {ev_float, "ammo_cells1"}, - {ev_float, "ammo_lava_nails"}, - {ev_float, "ammo_multi_rockets"}, - {ev_float, "ammo_nails1"}, - {ev_float, "ammo_plasma"}, - {ev_float, "ammo_rockets1"}, - {ev_float, "ammo_shells1"}, - {ev_float, "button3"}, - {ev_float, "button4"}, - {ev_float, "button5"}, - {ev_float, "button6"}, - {ev_float, "button7"}, - {ev_float, "button8"}, - {ev_float, "button9"}, - {ev_float, "button10"}, - {ev_float, "button11"}, - {ev_float, "button12"}, - {ev_float, "button13"}, - {ev_float, "button14"}, - {ev_float, "button15"}, - {ev_float, "button16"}, - {ev_float, "buttonchat"}, - {ev_float, "buttonuse"}, - {ev_float, "clientcolors"}, - {ev_float, "cursor_active"}, - {ev_float, "fullbright"}, - {ev_float, "glow_color"}, - {ev_float, "glow_size"}, - {ev_float, "glow_trail"}, - {ev_float, "gravity"}, - {ev_float, "idealpitch"}, - {ev_float, "items2"}, - {ev_float, "light_lev"}, - {ev_float, "pflags"}, - {ev_float, "ping"}, - {ev_float, "pitch_speed"}, - {ev_float, "pmodel"}, - {ev_float, "renderamt"}, // HalfLife support - {ev_float, "rendermode"}, // HalfLife support - {ev_float, "scale"}, - {ev_float, "style"}, - {ev_float, "tag_index"}, - {ev_float, "Version"}, - {ev_float, "viewzoom"}, - {ev_vector, "color"}, - {ev_vector, "colormod"}, - {ev_vector, "cursor_screen"}, - {ev_vector, "cursor_trace_endpos"}, - {ev_vector, "cursor_trace_start"}, - {ev_vector, "movement"}, - {ev_vector, "punchvector"}, - {ev_string, "playermodel"}, - {ev_string, "playerskin"}, - {ev_function, "SendEntity"}, - {ev_function, "customizeentityforclient"}, - // DRESK - Support for Entity Contents Transition Event - {ev_function, "contentstransition"}, -}; - -void SV_VM_Setup(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; - unsigned char *csprogsdata; - fs_offset_t csprogsdatasize; + size_t csprogsdatasize; PRVM_Begin; PRVM_InitProg( PRVM_SERVERPROG ); @@ -2576,11 +2586,38 @@ 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 - EntityFrameCSQC_ClearVersions();//[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 PRVM_End; @@ -2600,7 +2637,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)