#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);
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)"};
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" };
+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;
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);
+void SV_AreaStats_f(void)
+{
+ World_PrintAreaStats(&sv.world, "server");
+}
/*
===============
*/
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_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);
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);
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();
+ 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);
}
MSG_WriteChar (&sv.datagram, (int)bound(-128, dir[i]*16, 127));
MSG_WriteByte (&sv.datagram, count);
MSG_WriteByte (&sv.datagram, color);
+ SV_FlushBroadcastMessages();
}
/*
MSG_WriteByte (&sv.datagram, framecount);
MSG_WriteByte (&sv.datagram, framerate);
}
+ SV_FlushBroadcastMessages();
}
/*
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();
}
/*
==============================================================================
*/
-static const char *SV_InitCmd; //[515]: svprogs able to send cmd to client on connect
-extern qboolean csqc_loaded;
/*
================
SV_SendServerinfo
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)
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);
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);
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;
}
/*
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));
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
===============================================================================
*/
-/*
-==================
-SV_ClearDatagram
-
-==================
-*/
-void SV_ClearDatagram (void)
-{
- SZ_Clear (&sv.datagram);
-}
-
/*
=============================================================================
=============================================================================
*/
-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;
// 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)
{
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)
{
}
}
- 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))
+ qboolean isbmodel = (model = sv.models[modelindex]) != NULL && model->name[0] == '*';
+ if (!isbmodel || !sv_cullentities_nevercullbmodels.integer)
{
- 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)
- {
- // 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;
}
}
}
// 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);
+ }
+
+ cs->modelindex = modelindex;
+
+ 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);
+ }
-entity_state_t sendstates[MAX_EDICTS];
-extern int csqc_clent;
+ 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);
+ }
-void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int *stats)
+ 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));
-
- csqc_clent = sv_writeentitiestoclient_clentnum = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes
+ sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv.writeentitiestoclient_testeye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs));
- 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();
+ }
}
/*
=============
*/
-void SV_CleanupEnts (void)
+static void SV_CleanupEnts (void)
{
int e;
prvm_edict_t *ent;
vec3_t punchvector;
int viewzoom;
const char *s;
+ float *statsf = (float *)stats;
//
// send a damage message
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
}
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;
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;
+ // 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;
}
}
+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;
+ 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 = bound(NET_MINRATE, sv_maxrate.integer, NET_MAXRATE);
- 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)(client->rate * sys_ticrate.value);
- maxsize = bound(100, rate, 1400);
+ maxsize = (int)(clientrate * sys_ticrate.value);
+ maxsize = bound(100, maxsize, 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;
- 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);
+
+ msg.maxsize = maxsize;
- // 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);
+ // 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);
+ NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate);
}
/*
SV_UpdateToReliableMessages
=======================
*/
-void SV_UpdateToReliableMessages (void)
+static void SV_UpdateToReliableMessages (void)
{
int i, j;
client_t *client;
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);
// 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)
{
}
// 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
*/
void SV_SendClientMessages (void)
{
- int i, prepared = false;
+ int i;
if (sv.protocol == PROTOCOL_QUAKEWORLD)
Sys_Error("SV_SendClientMessages: no quakeworld support\n");
-// update frags, names, etc
- SV_UpdateToReliableMessages();
+ SV_FlushBroadcastMessages();
+
+// update frags, names, etc
+ SV_UpdateToReliableMessages();
+
+// build individual updates
+ for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
+ {
+ if (!host_client->active)
+ continue;
+ if (!host_client->netconnection)
+ continue;
+
+ if (host_client->netconnection->message.overflowed)
+ {
+ SV_DropClient (true); // if the message couldn't send, kick off
+ continue;
+ }
+
+ SV_SendClientDatagram (host_client);
+ }
+
+// clear muzzle flashes
+ SV_CleanupEnts();
+}
+
+static void SV_StartDownload_f(void)
+{
+ if (host_client->download_file)
+ host_client->download_started = true;
+}
+
+static void SV_Download_f(void)
+{
+ const char *whichpack, *whichpack2, *extension;
+
+ if (Cmd_Argc() != 2)
+ {
+ SV_ClientPrintf("usage: download <filename>\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;
+ }
+ }
-// build individual updates
- for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
+ if (!sv_allowdownloads_config.integer)
{
- if (!host_client->active)
- continue;
- if (!host_client->netconnection)
- continue;
+ 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 (host_client->netconnection->message.overflowed)
+ if (!sv_allowdownloads_dlcache.integer)
+ {
+ if (!strncasecmp(host_client->download_name, "dlcache/", 8))
{
- SV_DropClient (true); // if the message couldn't send, kick off
- continue;
+ 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 (!prepared)
+ if (!sv_allowdownloads_archive.integer)
+ {
+ if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
{
- prepared = true;
- // only prepare entities once per frame
- SV_PrepareEntitiesForSending();
+ 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;
}
- SV_SendClientDatagram (host_client);
}
-// clear muzzle flashes
- SV_CleanupEnts();
-}
+ 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
+}
/*
==============================================================================
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;
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);
-}*/
/*
================
This is called at the start of each level
================
*/
-extern float scr_centertime_off;
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;
// 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;
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;
//
// 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]));
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;
/////////////////////////////////////////////////////
// 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;
// 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;
ed->fields.server->solid = 0;
}
-void SV_VM_CB_CountEdicts(void)
+static void SV_VM_CB_CountEdicts(void)
{
int i;
prvm_edict_t *ent;
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
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;
-
-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[] =
-{
- {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"},
-};
-
-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;
+ 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;
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;
+
+ // 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)
PRVM_Begin;
PRVM_SetProg( PRVM_SERVERPROG );
- *prog->time = (float) sv.time;
+ prog->globals.server->time = (float) sv.time;
}
void SV_VM_End(void)