]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_main.c
new cvar: sv_status_show_qcstatus
[xonotic/darkplaces.git] / sv_main.c
index aa274005779b0661c205af161f11af3e42ee38ed..4e541af0153185e5b643431b119174e230716519 100644 (file)
--- a/sv_main.c
+++ b/sv_main.c
@@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "quakedef.h"
 #include "sv_demo.h"
 #include "libcurl.h"
+#include "csprogs.h"
 
 static void SV_SaveEntFile_f(void);
 static void SV_StartDownload_f(void);
@@ -30,7 +31,6 @@ static void SV_VM_Setup();
 
 void VM_CustomStats_Clear (void);
 void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats);
-void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, const entity_state_t *states);
 
 cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"};
 cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"};
@@ -65,13 +65,15 @@ cvar_t sv_cullentities_pvs = {0, "sv_cullentities_pvs", "1", "fast but loose cul
 cvar_t sv_cullentities_stats = {0, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"};
 cvar_t sv_cullentities_trace = {0, "sv_cullentities_trace", "0", "somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless"};
 cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"};
+cvar_t sv_cullentities_trace_delay_players = {0, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"};
 cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"};
 cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"};
 cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "1", "number of samples to test for entity culling"};
 cvar_t sv_cullentities_trace_samples_extra = {0, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"};
+cvar_t sv_cullentities_trace_samples_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"};
 cvar_t sv_debugmove = {CVAR_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"};
 cvar_t sv_echobprint = {CVAR_SAVE, "sv_echobprint", "1", "prints gamecode bprint() calls to server console"};
-cvar_t sv_edgefriction = {0, "edgefriction", "2", "how much you slow down when nearing a ledge you might fall off"};
+cvar_t sv_edgefriction = {0, "edgefriction", "1", "how much you slow down when nearing a ledge you might fall off, multiplier of sv_friction (Quake used 2, QuakeWorld used 1 due to a bug in physics code)"};
 cvar_t sv_entpatch = {0, "sv_entpatch", "1", "enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps)"};
 cvar_t sv_fixedframeratesingleplayer = {0, "sv_fixedframeratesingleplayer", "1", "allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate)"};
 cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"};
@@ -212,6 +214,7 @@ prvm_required_field_t reqfields[] =
        {ev_entity, "nodrawtoclient"},
        {ev_entity, "tag_entity"},
        {ev_entity, "viewmodelforclient"},
+       {ev_float, "SendFlags"},
        {ev_float, "Version"},
        {ev_float, "alpha"},
        {ev_float, "ammo_cells1"},
@@ -337,10 +340,12 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_cullentities_stats);
        Cvar_RegisterVariable (&sv_cullentities_trace);
        Cvar_RegisterVariable (&sv_cullentities_trace_delay);
+       Cvar_RegisterVariable (&sv_cullentities_trace_delay_players);
        Cvar_RegisterVariable (&sv_cullentities_trace_enlarge);
        Cvar_RegisterVariable (&sv_cullentities_trace_prediction);
        Cvar_RegisterVariable (&sv_cullentities_trace_samples);
        Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra);
+       Cvar_RegisterVariable (&sv_cullentities_trace_samples_players);
        Cvar_RegisterVariable (&sv_debugmove);
        Cvar_RegisterVariable (&sv_echobprint);
        Cvar_RegisterVariable (&sv_edgefriction);
@@ -728,13 +733,47 @@ void SV_SendServerinfo (client_t *client)
        }
 
        // reset csqc entity versions
-       memset(client->csqcentityversion, 0, sizeof(client->csqcentityversion));
+       for (i = 0;i < prog->max_edicts;i++)
+       {
+               client->csqcentityscope[i] = 0;
+               client->csqcentitysendflags[i] = 0xFFFFFF;
+               client->csqcentityglobalhistory[i] = 0;
+       }
+       for (i = 0;i < NUM_CSQCENTITYDB_FRAMES;i++)
+       {
+               client->csqcentityframehistory[i].num = 0;
+               client->csqcentityframehistory[i].framenum = -1;
+       }
+       client->csqcnumedicts = 0;
+       client->csqcentityframehistory_next = 0;
 
        SZ_Clear (&client->netconnection->message);
        MSG_WriteByte (&client->netconnection->message, svc_print);
        dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)", gamename, buildstring, prog->filecrc);
        MSG_WriteString (&client->netconnection->message,message);
 
+       SV_StopDemoRecording(client); // to split up demos into different files
+       if(sv_autodemo_perclient.integer && client->netconnection)
+       {
+               char demofile[MAX_OSPATH];
+               char levelname[MAX_QPATH];
+               char ipaddress[MAX_QPATH];
+               size_t i;
+
+               // start a new demo file
+               strlcpy(levelname, FS_FileWithoutPath(sv.worldmodel->name), sizeof(levelname));
+               if (strrchr(levelname, '.'))
+                       *(strrchr(levelname, '.')) = 0;
+
+               LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true);
+               for(i = 0; ipaddress[i]; ++i)
+                       if(!isalnum(ipaddress[i]))
+                               ipaddress[i] = '-';
+               dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), levelname, PRVM_NUM_FOR_EDICT(client->edict), ipaddress);
+
+               SV_StartDemoRecording(client, demofile, -1);
+       }
+
        //[515]: init csprogs according to version of svprogs, check the crc, etc.
        if (sv.csqc_progname[0])
        {
@@ -746,6 +785,29 @@ void SV_SendServerinfo (client_t *client)
                MSG_WriteString (&client->netconnection->message, va("csqc_progsize %i\n", sv.csqc_progsize));
                MSG_WriteByte (&client->netconnection->message, svc_stufftext);
                MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", sv.csqc_progcrc));
+
+               if(client->sv_demo_file != NULL)
+               {
+                       void *csqcbuf;
+                       fs_offset_t csqclen;
+                       int csqccrc;
+                       int i;
+                       char buf[NET_MAXMESSAGE];
+                       sizebuf_t sb;
+
+                       csqcbuf = FS_LoadFile(sv.csqc_progname, tempmempool, true, &csqclen);
+                       if(csqcbuf)
+                       {
+                               csqccrc = CRC_Block(csqcbuf, csqclen);
+                               sb.data = (void *) buf;
+                               sb.maxsize = sizeof(buf);
+                               i = 0;
+                               while(MakeDownloadPacket(sv.csqc_progname, csqcbuf, csqclen, csqccrc, i++, &sb, sv.protocol))
+                                       SV_WriteDemoMessage(client, &sb, false);
+                               Mem_Free(csqcbuf);
+                       }
+               }
+
                //[515]: init stufftext string (it is sent before svc_serverinfo)
                val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.SV_InitCmd);
                if (val)
@@ -815,28 +877,6 @@ void SV_SendServerinfo (client_t *client)
        client->num_pings = 0;
 #endif
        client->ping = 0;
-
-       SV_StopDemoRecording(client); // to split up demos into different files
-       if(sv_autodemo_perclient.integer && client->netconnection)
-       {
-               char demofile[MAX_OSPATH];
-               char levelname[MAX_QPATH];
-               char ipaddress[MAX_QPATH];
-               size_t i;
-
-               // start a new demo file
-               strlcpy(levelname, FS_FileWithoutPath(sv.worldmodel->name), sizeof(levelname));
-               if (strrchr(levelname, '.'))
-                       *(strrchr(levelname, '.')) = 0;
-
-               LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true);
-               for(i = 0; ipaddress[i]; ++i)
-                       if(!isalnum(ipaddress[i]))
-                               ipaddress[i] = '-';
-               dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), levelname, PRVM_NUM_FOR_EDICT(client->edict), ipaddress);
-
-               SV_StartDemoRecording(client, demofile, -1);
-       }
 }
 
 /*
@@ -926,12 +966,14 @@ crosses a waterline.
 static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber)
 {
        int i;
+       unsigned int sendflags;
+       unsigned int version;
        unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius;
        unsigned int customizeentityforclient;
        float f;
        vec3_t cullmins, cullmaxs;
        dp_model_t *model;
-       prvm_eval_t *val;
+       prvm_eval_t *val, *val2;
 
        // this 2 billion unit check is actually to detect NAN origins
        // (we really don't want to send those)
@@ -1108,7 +1150,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
        // calculate the visible box of this entity (don't use the physics box
        // as that is often smaller than a model, and would not count
        // specialvisibilityradius)
-       if ((model = sv.models[modelindex]))
+       if ((model = sv.models[modelindex]) && (model->type != mod_null))
        {
                float scale = cs->scale * (1.0f / 16.0f);
                if (cs->angles[0] || cs->angles[2]) // pitch and roll
@@ -1164,6 +1206,30 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
                }
        }
 
+       // we need to do some csqc entity upkeep here
+       // get self.SendFlags and clear them
+       // (to let the QC know that they've been read)
+       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendEntity);
+       if (val->function)
+       {
+               val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendFlags);
+               sendflags = (unsigned int)val->_float;
+               val->_float = 0;
+               // legacy self.Version system
+               val2 = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.Version);
+               if (val2->_float)
+               {
+                       version = (unsigned int)val2->_float;
+                       if (sv.csqcentityversion[enumber] != version)
+                               sendflags = 0xFFFFFF;
+                       sv.csqcentityversion[enumber] = version;
+               }
+               // move sendflags into the per-client sendflags
+               if (sendflags)
+                       for (i = 0;i < svs.maxclients;i++)
+                               svs.clients[i].csqcentitysendflags[enumber] |= sendflags;
+       }
+
        return true;
 }
 
@@ -1272,48 +1338,62 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s)
                        // or not seen by random tracelines
                        if (sv_cullentities_trace.integer && !isbmodel && sv.worldmodel->brush.TraceLineOfSight)
                        {
-                               int samples = s->specialvisibilityradius ? sv_cullentities_trace_samples_extra.integer : sv_cullentities_trace_samples.integer;
+                               int samples =
+                                       s->number <= svs.maxclients
+                                               ? sv_cullentities_trace_samples_players.integer
+                                               :
+                                       s->specialvisibilityradius
+                                               ? sv_cullentities_trace_samples_extra.integer
+                                               : sv_cullentities_trace_samples.integer;
                                float enlarge = sv_cullentities_trace_enlarge.value;
 
                                qboolean visible = TRUE;
 
-                               do
+                               if(samples > 0)
                                {
-                                       if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
-                                               break; // directly visible from the server's view
-
-                                       if(sv_cullentities_trace_prediction.integer)
+                                       do
                                        {
-                                               vec3_t predeye;
-
-                                               // get player velocity
-                                               float predtime = bound(0, host_client->ping, 0.2); // / 2
-                                                       // sorry, no wallhacking by high ping please, and at 200ms
-                                                       // ping a FPS is annoying to play anyway and a player is
-                                                       // likely to have changed his direction
-                                               VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye);
-                                               if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there...
-                                               {
-                                                       if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
-                                                               break; // directly visible from the predicted view
-                                               }
-                                               else
+                                               if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
+                                                       break; // directly visible from the server's view
+
+                                               if(sv_cullentities_trace_prediction.integer)
                                                {
-                                                       //Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n");
+                                                       vec3_t predeye;
+
+                                                       // get player velocity
+                                                       float predtime = bound(0, host_client->ping, 0.2); // / 2
+                                                               // sorry, no wallhacking by high ping please, and at 200ms
+                                                               // ping a FPS is annoying to play anyway and a player is
+                                                               // likely to have changed his direction
+                                                       VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye);
+                                                       if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there...
+                                                       {
+                                                               if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
+                                                                       break; // directly visible from the predicted view
+                                                       }
+                                                       else
+                                                       {
+                                                               //Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n");
+                                                       }
                                                }
-                                       }
 
-                                       // when we get here, we can't see the entity
-                                       visible = false;
-                               }
-                               while(0);
-
-                               if(visible)
-                                       svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = realtime + sv_cullentities_trace_delay.value;
-                               else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number])
-                               {
-                                       sv.writeentitiestoclient_stats_culled_trace++;
-                                       return;
+                                               // when we get here, we can't see the entity
+                                               visible = false;
+                                       }
+                                       while(0);
+
+                                       if(visible)
+                                               svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] =
+                                                       realtime + (
+                                                               s->number <= svs.maxclients
+                                                                       ? sv_cullentities_trace_delay_players.value
+                                                                       : sv_cullentities_trace_delay.value
+                                                       );
+                                       else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number])
+                                       {
+                                               sv.writeentitiestoclient_stats_culled_trace++;
+                                               return;
+                                       }
                                }
                        }
                }
@@ -1374,7 +1454,10 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *
        if (sv_cullentities_stats.integer)
                Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace);
 
-       EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates);
+       if(client->entitydatabase5)
+               EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, client->entitydatabase5->latestframenum + 1);
+       else
+               EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, 0);
 
        if (client->entitydatabase5)
                EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence);
@@ -1915,7 +1998,7 @@ static void SV_UpdateToReliableMessages (void)
                if (strcmp(host_client->old_name, host_client->name))
                {
                        if (host_client->spawned)
-                               SV_BroadcastPrintf("%s changed name to %s\n", host_client->old_name, host_client->name);
+                               SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
                        strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
                        // send notification to all clients
                        MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
@@ -1988,7 +2071,7 @@ static void SV_UpdateToReliableMessages (void)
        }
 
        for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++)
-               if (client->netconnection)
+               if (client->netconnection && (client->spawned || client->clientconnectcalled)) // also send MSG_ALL to people who are past ClientConnect, but not spawned yet
                        SZ_Write (&client->netconnection->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize);
 
        SZ_Clear (&sv.reliable_datagram);
@@ -2148,7 +2231,7 @@ static void SV_Download_f(void)
                }
        }
 
-       host_client->download_file = FS_Open(host_client->download_name, "rb", true, false);
+       host_client->download_file = FS_OpenVirtualFile(host_client->download_name, true);
        if (!host_client->download_file)
        {
                SV_ClientPrintf("Download rejected: server could not open the file \"%s\"\n", host_client->download_name);
@@ -2715,6 +2798,7 @@ void SV_SpawnServer (const char *server)
 // send serverinfo to all connected clients, and set up botclients coming back from a level change
        for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
        {
+               host_client->clientconnectcalled = false; // do NOT call ClientDisconnect if he drops before ClientConnect!
                if (!host_client->active)
                        continue;
                if (host_client->netconnection)
@@ -2807,6 +2891,9 @@ static void SV_VM_CB_InitEdict(prvm_edict_t *e)
 
 static void SV_VM_CB_FreeEdict(prvm_edict_t *ed)
 {
+       int i;
+       int e;
+
        World_UnlinkEdict(ed);          // unlink from world bsp
 
        ed->fields.server->model = 0;
@@ -2819,6 +2906,16 @@ static void SV_VM_CB_FreeEdict(prvm_edict_t *ed)
        VectorClear(ed->fields.server->angles);
        ed->fields.server->nextthink = -1;
        ed->fields.server->solid = 0;
+
+       // make sure csqc networking is aware of the removed entity
+       e = PRVM_NUM_FOR_EDICT(ed);
+       sv.csqcentityversion[e] = 0;
+       for (i = 0;i < svs.maxclients;i++)
+       {
+               if (svs.clients[i].csqcentityscope[e])
+                       svs.clients[i].csqcentityscope[e] = 1; // removed, awaiting send
+               svs.clients[i].csqcentitysendflags[e] = 0xFFFFFF;
+       }
 }
 
 static void SV_VM_CB_CountEdicts(void)
@@ -2874,8 +2971,6 @@ static qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent)
 static void SV_VM_Setup(void)
 {
        extern cvar_t csqc_progname;    //[515]: csqc crc check and right csprogs name according to progs.dat
-       extern cvar_t csqc_progcrc;
-       extern cvar_t csqc_progsize;
        size_t csprogsdatasize;
        PRVM_Begin;
        PRVM_InitProg( PRVM_SERVERPROG );