X-Git-Url: http://git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=sv_main.c;h=ba0c5d525e3416d05719684870a7120802f7e0a7;hp=3bedf60926ffa592dd6edcdc50775724d634a16c;hb=727c2cbec73fbd199cef8e78c5403ae607f56347;hpb=962759ddd16628af50c4f13fb1a463c545f9b473 diff --git a/sv_main.c b/sv_main.c index 3bedf609..ba0c5d52 100644 --- a/sv_main.c +++ b/sv_main.c @@ -50,6 +50,7 @@ cvar_t pausable = {CF_SERVER, "pausable","1", "allow players to pause or not (ot cvar_t pr_checkextension = {CF_SERVER | CF_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 samelevel = {CF_SERVER | CF_NOTIFY, "samelevel","0", "repeats same level if level ends (due to timelimit or someone hitting an exit)"}; cvar_t skill = {CF_SERVER, "skill","1", "difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice)"}; +cvar_t campaign = {CF_SERVER, "campaign", "0", "singleplayer mode"}; cvar_t host_timescale = {CF_CLIENT | CF_SERVER, "host_timescale", "1.0", "controls game speed, 0.5 is half speed, 2 is double speed"}; cvar_t sv_accelerate = {CF_SERVER, "sv_accelerate", "10", "rate at which a player accelerates to sv_maxspeed"}; @@ -76,7 +77,7 @@ cvar_t sv_checkforpacketsduringsleep = {CF_SERVER, "sv_checkforpacketsduringslee cvar_t sv_clmovement_enable = {CF_SERVER, "sv_clmovement_enable", "1", "whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players"}; cvar_t sv_clmovement_minping = {CF_SERVER, "sv_clmovement_minping", "0", "if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it)"}; cvar_t sv_clmovement_minping_disabletime = {CF_SERVER, "sv_clmovement_minping_disabletime", "1000", "when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently)"}; -cvar_t sv_clmovement_inputtimeout = {CF_SERVER, "sv_clmovement_inputtimeout", "0.2", "when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld)"}; +cvar_t sv_clmovement_inputtimeout = {CF_SERVER, "sv_clmovement_inputtimeout", "0.1", "when a client does not send input for this many seconds (max 0.1), force them to move anyway (unlike QuakeWorld)"}; cvar_t sv_cullentities_nevercullbmodels = {CF_SERVER, "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!)"}; cvar_t sv_cullentities_pvs = {CF_SERVER, "sv_cullentities_pvs", "1", "fast but loose culling of hidden entities"}; cvar_t sv_cullentities_stats = {CF_SERVER, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; @@ -147,8 +148,8 @@ cvar_t sv_sound_watersplash = {CF_SERVER, "sv_sound_watersplash", "misc/h2ohit1. cvar_t sv_stepheight = {CF_SERVER | CF_NOTIFY, "sv_stepheight", "18", "how high you can step up (TW_SV_STEPCONTROL extension)"}; cvar_t sv_stopspeed = {CF_SERVER | CF_NOTIFY, "sv_stopspeed","100", "how fast you come to a complete stop"}; cvar_t sv_wallfriction = {CF_SERVER | CF_NOTIFY, "sv_wallfriction", "1", "how much you slow down when sliding along a wall"}; -cvar_t sv_wateraccelerate = {CF_SERVER, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; -cvar_t sv_waterfriction = {CF_SERVER | CF_NOTIFY, "sv_waterfriction","-1", "how fast you slow down, if less than 0 the sv_friction variable is used instead"}; +cvar_t sv_wateraccelerate = {CF_SERVER, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in water, if less than 0 the sv_accelerate variable is used instead"}; +cvar_t sv_waterfriction = {CF_SERVER | CF_NOTIFY, "sv_waterfriction","-1", "how fast you slow down in water, if less than 0 the sv_friction variable is used instead"}; cvar_t sv_warsowbunny_airforwardaccel = {CF_SERVER, "sv_warsowbunny_airforwardaccel", "1.00001", "how fast you accelerate until you reach sv_maxspeed"}; cvar_t sv_warsowbunny_accel = {CF_SERVER, "sv_warsowbunny_accel", "0.1585", "how fast you accelerate until after reaching sv_maxspeed (it gets harder as you near sv_warsowbunny_topspeed)"}; cvar_t sv_warsowbunny_topspeed = {CF_SERVER, "sv_warsowbunny_topspeed", "925", "soft speed limit (can get faster with rjs and on ramps)"}; @@ -205,6 +206,8 @@ cvar_t halflifebsp = {CF_SERVER, "halflifebsp", "0", "indicates the current map cvar_t sv_mapformat_is_quake2 = {CF_SERVER, "sv_mapformat_is_quake2", "0", "indicates the current map is q2bsp format (useful to know because of different entity behaviors, .frame on submodels and other things)"}; cvar_t sv_mapformat_is_quake3 = {CF_SERVER, "sv_mapformat_is_quake3", "0", "indicates the current map is q2bsp format (useful to know because of different entity behaviors)"}; +cvar_t sv_writepicture_quality = {CF_SERVER | CF_ARCHIVE, "sv_writepicture_quality", "10", "WritePicture quality offset (higher means better quality, but slower)"}; + server_t sv; server_static_t svs; @@ -432,6 +435,56 @@ static void SV_AreaStats_f(cmd_state_t *cmd) World_PrintAreaStats(&sv.world, "server"); } +static void SV_ServerOptions (void) +{ + int i; + + // general default + svs.maxclients = 8; + +// COMMANDLINEOPTION: Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8 +// COMMANDLINEOPTION: Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8 + // if no client is in the executable or -dedicated is specified on + // commandline, start a dedicated server + i = Sys_CheckParm ("-dedicated"); + if (i || !cl_available) + { + cls.state = ca_dedicated; + // check for -dedicated specifying how many players + if (i && i + 1 < sys.argc && atoi (sys.argv[i+1]) >= 1) + svs.maxclients = atoi (sys.argv[i+1]); + if (Sys_CheckParm ("-listen")) + Con_Printf ("Only one of -dedicated or -listen can be specified\n"); + // default sv_public on for dedicated servers (often hosted by serious administrators), off for listen servers (often hosted by clueless users) + Cvar_SetValue(&cvars_all, "sv_public", 1); + } + else if (cl_available) + { + // client exists and not dedicated, check if -listen is specified + cls.state = ca_disconnected; + i = Sys_CheckParm ("-listen"); + if (i) + { + // default players unless specified + if (i + 1 < sys.argc && atoi (sys.argv[i+1]) >= 1) + svs.maxclients = atoi (sys.argv[i+1]); + } + else + { + // default players in some games, singleplayer in most + if (gamemode != GAME_GOODVSBAD2 && !IS_NEXUIZ_DERIVED(gamemode) && gamemode != GAME_BATTLEMECH) + svs.maxclients = 1; + } + } + + svs.maxclients = svs.maxclients_next = bound(1, svs.maxclients, MAX_SCOREBOARD); + + svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); + + if (svs.maxclients > 1 && !deathmatch.integer && !coop.integer) + Cvar_SetValueQuick(&deathmatch, 1); +} + /* =============== SV_Init @@ -472,10 +525,11 @@ void SV_Init (void) Cvar_RegisterVariable (&pr_checkextension); Cvar_RegisterVariable (&samelevel); Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&campaign); Cvar_RegisterVariable (&host_timescale); Cvar_RegisterCallback (&host_timescale, Host_Timescale_c); - Cvar_RegisterAlias (&host_timescale, "slowmo"); - Cvar_RegisterAlias (&host_timescale, "timescale"); + Cvar_RegisterVirtual (&host_timescale, "slowmo"); + Cvar_RegisterVirtual (&host_timescale, "timescale"); Cvar_RegisterVariable (&sv_accelerate); Cvar_RegisterVariable (&sv_aim); Cvar_RegisterVariable (&sv_airaccel_qw); @@ -566,7 +620,7 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_protocolname); Cvar_RegisterVariable (&sv_random_seed); Cvar_RegisterVariable (&host_limitlocal); - Cvar_RegisterAlias(&host_limitlocal, "sv_ratelimitlocalplayer"); + Cvar_RegisterVirtual(&host_limitlocal, "sv_ratelimitlocalplayer"); Cvar_RegisterVariable (&sv_sound_land); Cvar_RegisterVariable (&sv_sound_watersplash); Cvar_RegisterVariable (&sv_stepheight); @@ -631,9 +685,15 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_mapformat_is_quake2); Cvar_RegisterVariable (&sv_mapformat_is_quake3); + Cvar_RegisterVariable (&sv_writepicture_quality); + SV_InitOperatorCommands(); + host.hook.SV_Shutdown = SV_Shutdown; sv_mempool = Mem_AllocPool("server", 0, NULL); + + SV_ServerOptions(); + Cvar_Callback(&sv_netport); } static void SV_SaveEntFile_f(cmd_state_t *cmd) @@ -833,11 +893,6 @@ void SV_SendServerinfo (client_t *client) client->movesequence = 0; client->movement_highestsequence_seen = 0; memset(&client->movement_count, 0, sizeof(client->movement_count)); -#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; // allow the client some time to send his keepalives, even if map loading took ages @@ -931,14 +986,31 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection) SV_DropClient Called when the player is getting totally kicked off the host -if (crash = true), don't bother sending signofs +if (leaving = true), don't bother sending signofs ===================== */ -void SV_DropClient(qbool crash) +void SV_DropClient(qbool leaving, const char *fmt, ... ) { prvm_prog_t *prog = SVVM_prog; int i; - Con_Printf("Client \"%s\" dropped\n", host_client->name); + + va_list argptr; + char reason[512] = ""; + + Con_Printf("Client \"%s\" dropped", host_client->name); + + if(fmt) + { + va_start(argptr, fmt); + dpvsnprintf(reason, sizeof(reason), fmt, argptr); + va_end(argptr); + + Con_Printf(" (%s)\n", reason); + } + else + { + Con_Printf(" \n"); + } SV_StopDemoRecording(host_client); @@ -948,15 +1020,22 @@ void SV_DropClient(qbool crash) if (host_client->netconnection) { // tell the client to be gone - if (!crash) + if (!leaving) { // LadyHavoc: no opportunity for resending, so use unreliable 3 times - unsigned char bufdata[8]; + unsigned char bufdata[520]; // Disconnect reason string can be 512 characters sizebuf_t buf; memset(&buf, 0, sizeof(buf)); buf.data = bufdata; buf.maxsize = sizeof(bufdata); MSG_WriteByte(&buf, svc_disconnect); + if(fmt) + { + if(sv.protocol == PROTOCOL_DARKPLACES8) + MSG_WriteString(&buf, reason); + else + SV_ClientPrintf("%s\n", reason); + } NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); @@ -984,6 +1063,10 @@ void SV_DropClient(qbool crash) NetConn_Close(host_client->netconnection); host_client->netconnection = NULL; } + if(fmt) + SV_BroadcastPrintf("\003^3%s left the game (%s)\n", host_client->name, reason); + else + SV_BroadcastPrintf("\003^3%s left the game\n", host_client->name); // if a download is active, close it if (host_client->download_file) @@ -1221,7 +1304,7 @@ static void SV_Download_f(cmd_state_t *cmd) if (!sv_allowdownloads_archive.integer) { - if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3") || !strcasecmp(extension, "dpk")) { 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); SV_ClientCommands("\nstopdownload\n"); @@ -1300,7 +1383,7 @@ SV_ModelIndex */ int SV_ModelIndex(const char *s, int precachemode) { - int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_MODELS); + int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE) ? 256 : MAX_MODELS); char filename[MAX_QPATH]; if (!s || !*s) return 0; @@ -1363,7 +1446,7 @@ SV_SoundIndex */ int SV_SoundIndex(const char *s, int precachemode) { - int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_SOUNDS); + int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP) ? 256 : MAX_SOUNDS); char filename[MAX_QPATH]; if (!s || !*s) return 0; @@ -1494,16 +1577,16 @@ int SV_ParticleEffectIndex(const char *name) return 0; } -dp_model_t *SV_GetModelByIndex(int modelindex) +model_t *SV_GetModelByIndex(int modelindex) { return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; } -dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed) +model_t *SV_GetModelFromEdict(prvm_edict_t *ed) { prvm_prog_t *prog = SVVM_prog; int modelindex; - if (!ed || ed->priv.server->free) + if (!ed || ed->free) return NULL; modelindex = (int)PRVM_serveredictfloat(ed, modelindex); return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; @@ -1530,7 +1613,7 @@ static void SV_CreateBaseline (void) // LadyHavoc: always clear state values, whether the entity is in use or not svent->priv.server->baseline = defaultstate; - if (svent->priv.server->free) + if (svent->free) continue; if (entnum > svs.maxclients && !PRVM_serveredictfloat(svent, modelindex)) continue; @@ -1669,11 +1752,10 @@ void SV_SaveSpawnparms (void) } } -qbool SV_IsLocalServer(void) +// Returns 1 if we're singleplayer, > 1 if we're a listen server +int SV_IsLocalServer(void) { - if(host_isclient.integer && host_client && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP) - return true; - return false; + return (host_isclient.integer && sv.active ? svs.maxclients : 0); } /* @@ -1690,7 +1772,7 @@ void SV_SpawnServer (const char *map) prvm_edict_t *ent; int i; char *entities; - dp_model_t *worldmodel; + model_t *worldmodel; char modelname[sizeof(sv.worldname)]; char vabuf[1024]; @@ -1720,15 +1802,6 @@ void SV_SpawnServer (const char *map) if(sv.active) { - client_t *client; - for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) - { - if (client->netconnection) - { - MSG_WriteByte(&client->netconnection->message, svc_stufftext); - MSG_WriteString(&client->netconnection->message, "reconnect\n"); - } - } World_End(&sv.world); if(PRVM_serverfunction(SV_Shutdown)) { @@ -1771,14 +1844,37 @@ void SV_SpawnServer (const char *map) // // tell all connected clients that we are going to a new level // - if (!sv.active) + if (sv.active) + { + client_t *client; + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (client->netconnection) + { + MSG_WriteByte(&client->netconnection->message, svc_stufftext); + MSG_WriteString(&client->netconnection->message, "reconnect\n"); + } + } + } + else + { + // open server port NetConn_OpenServerPorts(true); + } // // make cvars consistant // + if (coop.integer) + { Cvar_SetValueQuick(&deathmatch, 0); + Cvar_SetValueQuick(&campaign, 0); + } + else if(!deathmatch.integer) + Cvar_SetValueQuick(&campaign, 1); + else + Cvar_SetValueQuick(&campaign, 0); // LadyHavoc: it can be useful to have skills outside the range 0-3... //current_skill = bound(0, (int)(skill.value + 0.5), 3); //Cvar_SetValue ("skill", (float)current_skill); @@ -1879,7 +1975,7 @@ void SV_SpawnServer (const char *map) // AK possible hack since num_edicts is still 0 ent = PRVM_EDICT_NUM(0); memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); - ent->priv.server->free = false; + ent->free = false; PRVM_serveredictstring(ent, model) = PRVM_SetEngineString(prog, sv.worldname); PRVM_serveredictfloat(ent, modelindex) = 1; // world model PRVM_serveredictfloat(ent, solid) = SOLID_BSP; @@ -2001,10 +2097,12 @@ void SV_Shutdown(void) prvm_prog_t *prog = SVVM_prog; int i; - Con_DPrintf("SV_Shutdown\n"); + SV_LockThreadMutex(); if (!sv.active) - return; + goto end; + + Con_DPrintf("SV_Shutdown\n"); NetConn_Heartbeat(2); NetConn_Heartbeat(2); @@ -2023,7 +2121,7 @@ void SV_Shutdown(void) } for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) if (host_client->active) - SV_DropClient(false); // server shutdown + SV_DropClient(false, "Server shutting down"); // server shutdown NetConn_CloseServerPorts(); @@ -2033,8 +2131,8 @@ void SV_Shutdown(void) // memset(&sv, 0, sizeof(sv)); memset(svs.clients, 0, svs.maxclients*sizeof(client_t)); - - cl.islocalgame = false; +end: + SV_UnlockThreadMutex(); } ///////////////////////////////////////////////////// @@ -2053,7 +2151,7 @@ static void SVVM_end_increase_edicts(prvm_prog_t *prog) // link every entity except world for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) - if (!ent->priv.server->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax))) + if (!ent->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax))) SV_LinkEdict(ent); } @@ -2129,9 +2227,10 @@ static void SVVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) PRVM_serveredictfloat(ed, solid) = 0; VM_RemoveEdictSkeleton(prog, ed); +#ifdef USEODE World_Physics_RemoveFromEntity(&sv.world, ed); World_Physics_RemoveJointFromEntity(&sv.world, ed); - +#endif // make sure csqc networking is aware of the removed entity e = PRVM_NUM_FOR_EDICT(ed); sv.csqcentityversion[e] = 0; @@ -2149,7 +2248,7 @@ static void SVVM_count_edicts(prvm_prog_t *prog) for (i=0 ; inum_edicts ; i++) { ent = PRVM_EDICT_NUM(i); - if (ent->priv.server->free) + if (ent->free) continue; active++; if (PRVM_serveredictfloat(ent, solid)) @@ -2192,7 +2291,7 @@ static qbool SVVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) static void SV_VM_Setup(void) { prvm_prog_t *prog = SVVM_prog; - PRVM_Prog_Init(prog, &cmd_server); + PRVM_Prog_Init(prog, cmd_local); // allocate the mempools // TODO: move the magic numbers/constants into #defines [9/13/2006 Black] @@ -2379,6 +2478,28 @@ static void SV_VM_Setup(void) SV_Prepare_CSQC(); } +static void SV_CheckTimeouts(void) +{ + int i; + + // never timeout loopback connections + for (i = (host_isclient.integer ? 1 : 0), host_client = &svs.clients[i]; i < svs.maxclients; i++, host_client++) + if (host_client->netconnection && host.realtime > host_client->netconnection->timeout) + SV_DropClient(false, "Timed out"); +} + +/* +================== +SV_TimeReport + +Returns a time report string, for example for +================== +*/ +const char *SV_TimingReport(char *buf, size_t buflen) +{ + return va(buf, buflen, "%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", svs.perf_cpuload * 100, svs.perf_lost * 100, svs.perf_offset_avg * 1000, svs.perf_offset_max * 1000, svs.perf_offset_sdev * 1000); +} + extern cvar_t host_maxwait; extern cvar_t host_framerate; extern cvar_t cl_maxphysicsframesperserverframe; @@ -2412,7 +2533,7 @@ double SV_Frame(double time) } if(svs.perf_lost > 0 && developer_extra.integer && playing) // only complain if anyone is looking - Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); + Con_DPrintf("Server can't keep up: %s\n", SV_TimingReport(vabuf, sizeof(vabuf))); } if(svs.perf_acc_realtime > 5 || sv.time < 10) @@ -2432,7 +2553,10 @@ double SV_Frame(double time) * be undersleeping due to select() detecting a new packet */ if (sv.active) + { NetConn_ServerFrame(); + SV_CheckTimeouts(); + } } /* @@ -2605,13 +2729,16 @@ static int SV_ThreadFunc(void *voiddata) } if(svs.perf_lost > 0 && developer_extra.integer) if(playing) - Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); + Con_DPrintf("Server can't keep up: %s\n", SV_TimingReport(vabuf, sizeof(vabuf))); svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; } // get new packets if (sv.active) + { NetConn_ServerFrame(); + SV_CheckTimeouts(); + } // if the accumulators haven't become positive yet, wait a while wait = sv_timer * -1000000.0;