diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index e968e5a0..baf42e7d 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -3194,8 +3194,15 @@ client_connect: //fixme: make function return; } if (net_from.type != NA_LOOPBACK) + { Con_TPrintf ("connection\n"); +#ifndef CLIENTONLY + if (sv.state) + SV_UnspawnServer(); +#endif + } + if (cls.state >= ca_connected) { if (!NET_CompareAdr(&cls.netchan.remote_address, &net_from)) @@ -3203,7 +3210,7 @@ client_connect: //fixme: make function #ifndef CLIENTONLY if (sv.state != ss_clustermode) #endif - CL_Disconnect_f(); + CL_Disconnect (); } else { @@ -3911,10 +3918,100 @@ void CL_CrashMeEndgame_f(void) void CL_Status_f(void) { + char adr[128]; float pi, po, bi, bo; NET_PrintAddresses(cls.sockets); if (NET_GetRates(cls.sockets, &pi, &po, &bi, &bo)) Con_Printf("packets,bytes/sec: in: %g %g out: %g %g\n", pi, bi, po, bo); //not relevent as a limit. + + if (cls.state) + { + Con_Printf("Server address: %s\n", NET_AdrToString(adr, sizeof(adr), &cls.netchan.remote_address)); //not relevent as a limit. + + switch(cls.protocol) + { + case CP_UNKNOWN: + Con_Printf("Unknown protocol\n"); + break; + case CP_QUAKEWORLD: + Con_Printf("QuakeWorld-based protocol\n"); + break; + #ifdef NQPROT + case CP_NETQUAKE: + switch(cls.protocol_nq) + { + case CPNQ_ID: + Con_Printf("NetQuake-based protocol\n"); + if (cls.proquake_angles_hack) + Con_Printf("With ProQuake's extended angles\n"); + break; + case CPNQ_BJP1: + Con_Printf("BJP1 protocol\n"); + break; + case CPNQ_BJP2: + Con_Printf("BJP2 protocol\n"); + break; + case CPNQ_BJP3: + Con_Printf("BJP3 protocol\n"); + break; + case CPNQ_FITZ666: + Con_Printf("FitzQuake-based protocol\n"); + break; + case CPNQ_DP5: + Con_Printf("DPP5 protocol\n"); + break; + case CPNQ_DP6: + Con_Printf("DPP6 protocol\n"); + break; + case CPNQ_DP7: + Con_Printf("DPP7 protocol\n"); + break; + } + break; + #endif + #ifdef Q2CLIENT + case CP_QUAKE2: + Con_Printf("Quake2-based protocol\n"); + if (cls.protocol_q2 && cls.protocol_q2 < PROTOCOL_VERSION_Q2) + Con_Printf("\toutdated protocol version\n"); + else switch (cls.protocol_q2) + { + case PROTOCOL_VERSION_Q2: + Con_Printf("\tStandard Quake2\n"); + break; + case PROTOCOL_VERSION_R1Q2: + Con_Printf("\tR1Q2\n"); + break; + case PROTOCOL_VERSION_Q2PRO: + Con_Printf("\tQ2Pro\n"); + break; + } + break; + #endif + #ifdef Q3CLIENT + case CP_QUAKE3: + Con_Printf("Quake3-based protocol\n"); + break; + #endif + #ifdef PLUGINS + case CP_PLUGIN: + Con_Printf("external protocol\n"); + break; + #endif + } + + //just show the more interesting extensions. + if (cls.fteprotocolextensions & PEXT_FLOATCOORDS) + Con_Printf("\textended coords\n"); + if (cls.fteprotocolextensions & PEXT_SPLITSCREEN) + Con_Printf("\tsplit screen\n"); + if (cls.fteprotocolextensions & PEXT_CSQC) + Con_Printf("\tcsqc info\n"); + if (cls.fteprotocolextensions2 & PEXT2_VOICECHAT) + Con_Printf("\tvoice chat\n"); + if (cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + Con_Printf("\treplacement deltas\n"); + } } void CL_Demo_SetSpeed_f(void) diff --git a/engine/client/merged.h b/engine/client/merged.h index 5d0fa6a6..99386a47 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -147,9 +147,9 @@ void R_DrawTextField(int x, int y, int w, int h, const char *text, unsigned int //mod_purge flags enum mod_purge_e { - MP_MAPCHANGED, //new map. old stuff no longer needed + MP_MAPCHANGED, //new map. old stuff no longer needed, can skip stuff if it'll be expensive. MP_FLUSH, //user flush command. anything flushable goes. - MP_RESET //*everything* is destroyed. renderer is going down. + MP_RESET //*everything* is destroyed. renderer is going down, or at least nothing depends upon it. }; enum mlverbosity_e { @@ -165,6 +165,7 @@ void Mod_SetEntitiesString(struct model_s *mod, const char *str, qboolean docopy void Mod_ParseEntities(struct model_s *mod); extern void Mod_ClearAll (void); extern void Mod_Purge (enum mod_purge_e type); +extern qboolean Mod_PurgeModel (struct model_s *mod, enum mod_purge_e ptype); extern struct model_s *Mod_FindName (const char *name); //find without loading. needload should be set. extern struct model_s *Mod_ForName (const char *name, enum mlverbosity_e verbosity); //finds+loads extern struct model_s *Mod_LoadModel (struct model_s *mod, enum mlverbosity_e verbose); //makes sure a model is loaded diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index 4bed5d99..ede2d6af 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -30,7 +30,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. static void S_Play(void); static void S_PlayVol(void); static void S_SoundList_f(void); +#ifdef HAVE_MIXER static void S_Update_(soundcardinfo_t *sc); +#endif void S_StopAllSounds(qboolean clear); static void S_StopAllSounds_f (void); @@ -47,6 +49,7 @@ int snd_blocked = 0; static qboolean snd_ambient = 1; qboolean snd_initialized = false; int snd_speed; +float voicevolumemod = 1; static struct { @@ -1542,6 +1545,7 @@ static sounddriver_t *outputdrivers[] = &OPENAL_Output, //refuses to run as the default device, at least until its perfected. #endif +#ifdef HAVE_MIXER #ifdef AVAIL_DSOUND &DSOUND_Output, #endif @@ -1559,6 +1563,7 @@ static sounddriver_t *outputdrivers[] = &OSS_Output, //good, but not likely to work any more #ifdef __DJGPP__ &SBLASTER_Output, //zomgwtfdos? +#endif #endif NULL }; @@ -1567,6 +1572,7 @@ typedef struct { sounddriver *ptr; } sdriver_t; static sdriver_t olddrivers[] = { +#ifdef HAVE_MIXER //in order of preference {"MacOS", &pMacOS_InitCard}, //prefered on mac {"Droid", &pDroid_InitCard}, //prefered on android (java thread) @@ -1577,6 +1583,7 @@ static sdriver_t olddrivers[] = { {"SNDIO", &pSNDIO_InitCard}, //prefered on OpenBSD {"WaveOut", &pWAV_InitCard}, //doesn't work properly in vista, etc. +#endif {NULL, NULL} }; @@ -3450,6 +3457,7 @@ static void S_UpdateCard(soundcardinfo_t *sc) Con_Printf ("----(%i+%i)----\n", active, mute); } +#ifdef HAVE_MIXER // mix some sound if (sc->selfpainting) @@ -3462,9 +3470,11 @@ static void S_UpdateCard(soundcardinfo_t *sc) } S_Update_(sc); +#endif } -int S_GetMixerTime(soundcardinfo_t *sc) +#ifdef HAVE_MIXER +static int S_GetMixerTime(soundcardinfo_t *sc) { int samplepos; int fullsamples; @@ -3500,6 +3510,7 @@ int S_GetMixerTime(soundcardinfo_t *sc) return sc->buffers*fullsamples + samplepos/sc->sn.numchannels; } +#endif void S_Update (void) { @@ -3513,7 +3524,9 @@ void S_Update (void) void S_ExtraUpdate (void) { +#ifdef HAVE_MIXER soundcardinfo_t *sc; +#endif if (!sound_started) return; @@ -3521,7 +3534,7 @@ void S_ExtraUpdate (void) #if defined(_WIN32) && !defined(WINRT) INS_Accumulate (); #endif - +#ifdef HAVE_MIXER if (snd_noextraupdate.ival) return; // don't pollute timings @@ -3540,10 +3553,11 @@ void S_ExtraUpdate (void) S_Update_(sc); S_UnlockMixer(); } +#endif } - +#ifdef HAVE_MIXER static void S_Update_(soundcardinfo_t *sc) { int soundtime; /*in pairs*/ @@ -3604,6 +3618,7 @@ void S_MixerThread(soundcardinfo_t *sc) S_Update_(sc); S_UnlockMixer(); } +#endif /* =============================================================================== diff --git a/engine/client/snd_mix.c b/engine/client/snd_mix.c index bedcfbc4..0b8d903f 100644 --- a/engine/client/snd_mix.c +++ b/engine/client/snd_mix.c @@ -21,9 +21,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" +#ifdef HAVE_MIXER + #define PAINTBUFFER_SIZE 2048 -float voicevolumemod = 1; portable_samplegroup_t paintbuffer[PAINTBUFFER_SIZE]; //FIXME: we really ought to be using SSE and floats or something. int *snd_p, snd_vol; @@ -682,3 +683,4 @@ static void SND_PaintChannel16_O8I1 (channel_t *ch, sfxcache_t *sc, int count) } } } +#endif diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index f5c0afd6..43e2820d 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -135,11 +135,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //#define SPEEX_STATIC #if defined(_WIN32) && defined(GLQUAKE) -//#define USE_EGL + //#define USE_EGL #endif #if defined(_MSC_VER) && !defined(BOTLIB_STATIC) //too lazy to fix up the makefile -#define BOTLIB_STATIC + #define BOTLIB_STATIC #endif #ifdef NO_OPENAL @@ -163,8 +163,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef AVAIL_WASAPI //wasapi is available in the vista sdk, while that's compatible with earlier versions, its not really expected until 2008 #endif -#define HAVE_TCP //says we can use tcp too (either ipv4 or ipv6) -#define HAVE_PACKET //if we have the socket api at all... + #define HAVE_TCP //says we can use tcp too (either ipv4 or ipv6) + #define HAVE_PACKET //if we have the socket api at all... + #define HAVE_MIXER //can be disabled if you have eg openal instead. //set any additional defines or libs in win32 #define LOADERTHREAD @@ -175,7 +176,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define PACKAGE_TEXWAD //quake's image wad support #ifdef GLQUAKE - #define HEADLESSQUAKE + #define HEADLESSQUAKE #endif #define AVAIL_MP3_ACM //microsoft's Audio Compression Manager api @@ -395,6 +396,13 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef AVAIL_MP3_ACM #endif +#ifndef HAVE_MIXER + //disable various sound drivers if we can't use them anyway. + #undef AVAIL_DSOUND + #undef AVAIL_XAUDIO2 + #undef AVAIL_WASAPI +#endif + #ifdef NOMEDIA #undef HAVE_CDPLAYER //includes cd playback. actual cds. faketracks are supported regardless. #undef HAVE_JUKEBOX //includes built-in jukebox crap @@ -465,7 +473,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #ifdef FTE_TARGET_WEB - //sandboxing... + //sandboxing means some stuff CANNOT work... #undef HAVE_TCP //websockets are not real tcp. #undef HAVE_PACKET //no udp support @@ -483,7 +491,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef MAP_PROC //meh #undef HALFLIFEMODELS //blurgh #undef WEBSERVER //hah, yeah, right - #undef SUPPORT_ICE //kinda requires udp, so not usable + #undef SUPPORT_ICE //requires udp, so not usable. webrtc could be used instead, but that logic is out of our hands. + #undef HAVE_MIXER //depend upon openal instead. //extra features stripped to try to reduce memory footprints #undef RUNTIMELIGHTING //too slow anyway diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index df4c35ad..74891542 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -6169,9 +6169,11 @@ lh_extension_t QSG_Extensions[] = { {"DP_QC_GETSURFACEPOINTATTRIBUTE", 1, NULL, {"getsurfacepointattribute"}}, {"DP_QC_MINMAXBOUND", 3, NULL, {"min", "max", "bound"}}, {"DP_QC_MULTIPLETEMPSTRINGS", 0, NULL, {NULL}, "Superseded by DP_QC_UNLIMITEDTEMPSTRINGS. Functions that return a temporary string will not overwrite/destroy previous temporary strings until at least 16 strings are returned (or control returns to the engine)."}, + {"DP_SV_PRINT", 1, NULL, {"print"}, "Says that the print builtin can be used from nqssqc (as well as just csqc), bypassing the developer cvar issues."}, {"DP_QC_RANDOMVEC", 1, NULL, {"randomvec"}}, {"DP_QC_RENDER_SCENE", 0, NULL, {NULL}, "clearscene+addentity+setviewprop+renderscene+setmodel are available to menuqc. WARNING: DP advertises this extension without actually supporting it, FTE does actually support it."}, {"DP_QC_SINCOSSQRTPOW", 4, NULL, {"sin", "cos", "sqrt", "pow"}}, + {"DP_QC_SPRINTF", 1, NULL, {"sprintf"}, "Provides the sprintf builtin, which allows for rich formatting along the lines of C's function with the same name. Not to be confused with QC's sprint builtin."}, {"DP_QC_STRFTIME", 1, NULL, {"strftime"}}, {"DP_QC_STRING_CASE_FUNCTIONS", 2, NULL, {"strtolower", "strtoupper"}}, {"DP_QC_STRINGBUFFERS", 10, NULL, {"buf_create", "buf_del", "buf_getsize", "buf_copy", "buf_sort", "buf_implode", "bufstr_get", "bufstr_set", "bufstr_add", "bufstr_free"}}, @@ -6244,7 +6246,7 @@ lh_extension_t QSG_Extensions[] = { #endif {"FTE_CSQC_SERVERBROWSER", 12, NULL, { "gethostcachevalue", "gethostcachestring", "resethostcachemasks", "sethostcachemaskstring", "sethostcachemasknumber", "resorthostcache", "sethostcachesort", "refreshhostcache", "gethostcachenumber", "gethostcacheindexforkey", - "addwantedhostcachekey", "getextresponse"}}, //normally only available to the menu. this also adds them to csqc. + "addwantedhostcachekey", "getextresponse"}, "Provides builtins to query the engine's serverbrowser servers list from ssqc. Note that these builtins are always available in menuqc."}, {"FTE_CSQC_SKELETONOBJECTS", 15, NULL, { "skel_create", "skel_build", "skel_get_numbones", "skel_get_bonename", "skel_get_boneparent", "skel_find_bone", "skel_get_bonerel", "skel_get_boneabs", "skel_set_bone", "skel_mul_bone", "skel_mul_bones", "skel_copybones", "skel_delete", "frameforname", "frameduration"}, "Provides container objects for skeletal bone data, which can be modified on a per bone basis if needed. This allows you to dynamically generate animations (or just blend them with greater customisation) instead of being limited to a single animation or two."}, @@ -6281,7 +6283,7 @@ lh_extension_t QSG_Extensions[] = { {"FTE_MVD_PLAYBACK", NOBI "The server itself is able to play back MVDs."}, #endif #ifdef SVCHAT - {"FTE_NPCCHAT", 1, NULL, {"chat"}}, //server looks at chat files. It automagically branches through calling qc functions as requested. + {"FTE_QC_NPCCHAT", 1, NULL, {"chat"}}, //server looks at chat files. It automagically branches through calling qc functions as requested. #endif #ifdef PSET_SCRIPT {"FTE_PART_SCRIPT", 0, NULL, {NULL}, "Specifies that the r_particledesc cvar can be used to select a list of particle effects to load from particles/*.cfg, the format of which is documented elsewhere."}, @@ -6299,8 +6301,10 @@ lh_extension_t QSG_Extensions[] = { {"FTE_QC_FS_SEARCH_SIZEMTIME", 2, NULL, {"search_getfilesize", "search_getfilemtime"}}, {"FTE_QC_HARDWARECURSORS", 0, NULL, {NULL}, "setcursormode exists in both csqc+menuqc, and accepts additional arguments to specify a cursor image to use when this module has focus. If the image exceeds hardware limits (or hardware cursors are unsupported), it will be emulated using regular draws - this at least still avoids conflicting cursors as only one will ever be used, even if console+menu+csqc are all overlayed."}, {"FTE_QC_HASHTABLES", 6, NULL, {"hash_createtab", "hash_destroytab", "hash_add", "hash_get", "hash_delete", "hash_getkey"}, "Provides efficient string-based lookups."}, + {"FTE_QC_INFOKEY", 2, NULL, {"infokey", "stof"}, "QuakeWorld's infokey builtin works, and reports at least name+topcolor+bottomcolor+ping(in ms)+ip(unmasked, but not always ipv4)+team(aka bottomcolor in nq). Does not require actual localinfo/serverinfo/userinfo, but they're _highly_ recommended to any engines with csqc"}, {"FTE_QC_INTCONV", 6, NULL, {"stoi", "itos", "stoh", "htos", "itof", "ftoi"}, "Provides string<>int conversions, including hex representations."}, {"FTE_QC_MATCHCLIENTNAME", 1, NULL, {"matchclientname"}}, + {"FTE_QC_MULTICAST", 1, NULL, {"multicast"}, "QuakeWorld's multicast builtin works along with MSG_MULTICAST, but also with unicast support."}, // {"FTE_QC_MESHOBJECTS", 0, NULL, {"mesh_create", "mesh_build", "mesh_getvertex", "mesh_getindex", "mesh_setvertex", "mesh_setindex", "mesh_destroy"}, "Provides qc with the ability to create its own meshes."}, {"FTE_QC_PAUSED"}, #ifdef QCGC diff --git a/engine/common/sys_linux_threads.c b/engine/common/sys_linux_threads.c index 0bfa03bf..d6cc59b4 100644 --- a/engine/common/sys_linux_threads.c +++ b/engine/common/sys_linux_threads.c @@ -415,10 +415,8 @@ pubsubserver_t *Sys_ForkServer(void) return &ctx->pub; } -void SSV_InstructMaster(sizebuf_t *cmd) +void Sys_InstructMaster(sizebuf_t *cmd) { - cmd->data[0] = cmd->cursize & 0xff; - cmd->data[1] = (cmd->cursize>>8) & 0xff; write(STDOUT, cmd->data, cmd->cursize); //FIXME: handle partial writes. diff --git a/engine/common/sys_win_threads.c b/engine/common/sys_win_threads.c index e2f0c40a..4d196444 100644 --- a/engine/common/sys_win_threads.c +++ b/engine/common/sys_win_threads.c @@ -565,13 +565,11 @@ pubsubserver_t *Sys_ForkServer(void) return &ctx->pub; } -void SSV_InstructMaster(sizebuf_t *cmd) +void Sys_InstructMaster(sizebuf_t *cmd) { //FIXME: this is blocking. this is bad if the target is also blocking while trying to write to us. HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE); DWORD written = 0; - cmd->data[0] = cmd->cursize & 0xff; - cmd->data[1] = (cmd->cursize>>8) & 0xff; WriteFile(output, cmd->data, cmd->cursize, &written, NULL); } diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index 46b9719b..9d767702 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -625,6 +625,85 @@ void Mod_ClearAll (void) mod_datasequence++; } +qboolean Mod_PurgeModel(model_t *mod, enum mod_purge_e ptype) +{ + if (mod->loadstate == MLS_LOADING) + { + if (ptype == MP_MAPCHANGED && !mod->submodelof) + return false; //don't bother waiting for it on map changes. + COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); + } + +#ifdef RUNTIMELIGHTING + if (lightmodel == mod) + { +#ifdef MULTITHREAD + int i; + wantrelight = false; + for (i = 0; i < relightthreads; i++) + { + Sys_WaitOnThread(relightthread[i]); + relightthread[i] = NULL; + } + relightthreads = 0; +#else + free(lightmainthreadctx); + lightmainthreadctx = NULL; +#endif + lightmodel = NULL; + } +#endif + +#ifdef TERRAIN + //we can safely flush all terrain sections at any time + if (mod->terrain && ptype != MP_MAPCHANGED) + Terr_PurgeTerrainModel(mod, false, true); +#endif + + //purge any vbos + if (mod->type == mod_brush) + { + //brush models cannot be safely flushed. + if (ptype != MP_RESET) + return false; +#ifndef SERVERONLY + Surf_Clear(mod); +#endif + } + +#ifdef TERRAIN + if (mod->type == mod_brush || mod->type == mod_heightmap) + { + //heightmap/terrain models cannot be safely flushed (brush models might have terrain embedded). + if (ptype != MP_RESET) + return false; + Terr_FreeModel(mod); + } +#endif + if (mod->type == mod_alias) + { + Mod_DestroyMesh(mod->meshinfo); + mod->meshinfo = NULL; + } + + Mod_SetEntitiesString(mod, NULL, false); + +#ifdef PSET_SCRIPT + PScript_ClearSurfaceParticles(mod); +#endif + + //and obliterate anything else remaining in memory. + ZG_FreeGroup(&mod->memgroup); + mod->meshinfo = NULL; + mod->loadstate = MLS_NOTLOADED; + + mod->submodelof = NULL; + mod->pvs = NULL; + mod->phs = NULL; + + return true; +} + //can be called in one of two ways. //force=true: explicit flush. everything goes, even if its still in use. //force=false: map change. lots of stuff is no longer in use and can be freely flushed. @@ -645,62 +724,9 @@ void Mod_Purge(enum mod_purge_e ptype) //this model isn't active any more. if (unused || ptype != MP_MAPCHANGED) { - if (mod->loadstate == MLS_LOADING) - { - if (ptype == MP_MAPCHANGED && !mod->submodelof) - continue; //don't bother waiting for it on map changes. - COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); - } - if (unused) Con_DLPrintf(2, "model \"%s\" no longer needed\n", mod->name); - -#ifdef TERRAIN - //we can safely flush all terrain sections at any time - if (mod->terrain && ptype != MP_MAPCHANGED) - Terr_PurgeTerrainModel(mod, false, true); -#endif - - //purge any vbos - if (mod->type == mod_brush) - { - //brush models cannot be safely flushed. - if (!unused && ptype != MP_RESET) - continue; -#ifndef SERVERONLY - Surf_Clear(mod); -#endif - } - -#ifdef TERRAIN - if (mod->type == mod_brush || mod->type == mod_heightmap) - { - //heightmap/terrain models cannot be safely flushed (brush models might have terrain embedded). - if (!unused && ptype != MP_RESET) - continue; - Terr_FreeModel(mod); - } -#endif - if (mod->type == mod_alias) - { - Mod_DestroyMesh(mod->meshinfo); - mod->meshinfo = NULL; - } - - Mod_SetEntitiesString(mod, NULL, false); - -#ifdef PSET_SCRIPT - PScript_ClearSurfaceParticles(mod); -#endif - - //and obliterate anything else remaining in memory. - ZG_FreeGroup(&mod->memgroup); - mod->meshinfo = NULL; - mod->loadstate = MLS_NOTLOADED; - - mod->submodelof = NULL; - mod->pvs = NULL; - mod->phs = NULL; + Mod_PurgeModel(mod, (ptype==MP_FLUSH && unused)?MP_RESET:ptype); } } } diff --git a/engine/gl/gl_model.h b/engine/gl/gl_model.h index b9637d3e..fc24e724 100644 --- a/engine/gl/gl_model.h +++ b/engine/gl/gl_model.h @@ -861,8 +861,9 @@ typedef struct model_s char publicname[MAX_QPATH]; //name that the gamecode etc sees int datasequence; int loadstate;//MLS_ - qboolean tainted; + qboolean tainted; // differs from the server's version. this model will be invisible as a result, to avoid spiked models. qboolean pushdepth; // bsp submodels have this flag set so you don't get z fighting on co-planar surfaces. + time_t mtime; // modification time. so we can flush models if they're changed on disk. or at least worldmodels. struct model_s *submodelof; diff --git a/engine/qclib/qccmain.c b/engine/qclib/qccmain.c index 1dab18d6..dbce6611 100644 --- a/engine/qclib/qccmain.c +++ b/engine/qclib/qccmain.c @@ -4155,8 +4155,9 @@ main ============ */ +int qccmline; char *qccmsrc; -char *qccmsrc2; +//char *qccmsrc2; char qccmfilename[1024]; char qccmprogsdat[1024]; @@ -4765,10 +4766,12 @@ memset(pr_immediate_string, 0, sizeof(pr_immediate_string)); newstyle: newstylesource = true; originalqccmsrc = qccmsrc; + pr_source_line = qccmline = 1; StartNewStyleCompile(); return true; } + pr_source_line = qccmline = 1; pr_file_p = qccmsrc; QCC_PR_LexWhitespace(false); qccmsrc = pr_file_p; @@ -4778,6 +4781,7 @@ newstyle: QCC_PR_SimpleGetToken (); strcpy(qcc_token, pr_token); qccmsrc = pr_file_p; + qccmline = pr_source_line; if (!qccmsrc) QCC_Error (ERR_NOOUTPUT, "No destination filename. qcc -help for info."); @@ -4850,11 +4854,12 @@ void QCC_ContinueCompile(void) } pr_file_p = qccmsrc; - s_filen = ""; + s_filen = compilingrootfile; s_filed = 0; - pr_source_line = 0; + pr_source_line = qccmline; QCC_PR_LexWhitespace(false); qccmsrc = pr_file_p; + qccmline = pr_source_line; qccmsrc = QCC_COM_Parse(qccmsrc); if (!qccmsrc) diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 954f8021..26eb685f 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -5807,7 +5807,16 @@ char *PF_infokey_Internal (int entnum, const char *key) return value; } -static void QCBUILTIN PF_infokey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +static void QCBUILTIN PF_infokey_s (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + edict_t *e = G_EDICT(prinst, OFS_PARM0); + int e1 = NUM_FOR_EDICT(prinst, e); + const char *key = PR_GetStringOfs(prinst, OFS_PARM1); + const char *value = PF_infokey_Internal (e1, key); + + G_INT(OFS_RETURN) = PR_TempString(prinst, value); +} +static void QCBUILTIN PF_infokey_f (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { edict_t *e; int e1; @@ -5820,7 +5829,7 @@ static void QCBUILTIN PF_infokey (pubprogfuncs_t *prinst, struct globalvars_s *p value = PF_infokey_Internal (e1, key); - G_INT(OFS_RETURN) = PR_TempString(prinst, value); + G_FLOAT(OFS_RETURN) = atof(value); } static void QCBUILTIN PF_sv_serverkey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) @@ -6633,30 +6642,36 @@ static void QCBUILTIN PF_readcmd (pubprogfuncs_t *prinst, struct globalvars_s *p PF_redirectcmd void redirectcmd (entity to, string str) + +the mvdsv implementation executes it now. we delay till the end of the frame, to avoid issues with map changes etc. ================= */ -/* +static void PF_Redirectcmdcallback(struct frameendtasks_s *task) +{ //called at the end of the frame when there's no qc running + host_client = svs.clients + task->ctxint; + if (host_client->state >= cs_connected) + { + SV_BeginRedirect(RD_CLIENT, host_client->language); + Cmd_ExecuteString(task->data, RESTRICT_INSECURE); + SV_EndRedirect(); + } +} static void QCBUILTIN PF_redirectcmd (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { - char *s; - int entnum; - extern redirect_t sv_redirected; - - if (sv_redirected) - return; - - entnum = G_EDICTNUM(OFS_PARM0); + struct frameendtasks_s *task, **link; + int entnum = G_EDICTNUM(prinst, OFS_PARM0); + const char *s = PR_GetStringOfs(prinst, OFS_PARM1); if (entnum < 1 || entnum > sv.allocated_client_slots) - PR_RunError ("Parm 0 not a client"); + PR_RunError (prinst, "Parm 0 not a client"); - s = G_STRING(OFS_PARM1); - - Cbuf_AddText (s); - - SV_BeginRedirect(RD_MOD + entnum); - Cbuf_Execute(); - SV_EndRedirect(); -}*/ + task = Z_Malloc(sizeof(*task)+strlen(s)); + task->callback = PF_Redirectcmdcallback; + strcpy(task->data, s); + task->ctxint = entnum-1; + for(link = &svs.frameendtasks; *link; link = &(*link)->next) + ; //add them on the end, so they're execed in order + *link = task; +} /* ================= @@ -9728,7 +9743,8 @@ static void QCBUILTIN PF_clustertransfer(pubprogfuncs_t *prinst, struct globalva if (dest) { - if (!SSV_IsSubServer()) + //FIXME: allow cluster transfers even when not a cluster node ourselves. + if (!SSV_IsSubServer() && !isDedicated) { Con_DPrintf("PF_clustertransfer: not running in mapcluster mode, ignoring transfer to %s\n", dest); return; @@ -9738,7 +9754,7 @@ static void QCBUILTIN PF_clustertransfer(pubprogfuncs_t *prinst, struct globalva Con_DPrintf("PF_clustertransfer: Already transferring to %s, ignoring transfer to %s\n", svs.clients[p].transfer, dest); return; } - svs.clients[p].transfer = Z_StrDup(svs.clients[p].transfer); + svs.clients[p].transfer = Z_StrDup(dest); SSV_InitiatePlayerTransfer(&svs.clients[p], svs.clients[p].transfer); } @@ -10005,7 +10021,8 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"tq_fputs", PF_fputs, 0, 0, 0, 89, D("void(filestream fhandle, string s)",NULL), true},// (QSG_FILE) // Tomaz - QuakeC File System End - {"infokey", PF_infokey, 0, 80, 0, 80, D("string(entity e, string key)", "If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo.")}, //80 + {"infokey", PF_infokey_s, 0, 80, 0, 80, D("string(entity e, string key)", "If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo.")}, //80 + {"infokeyf", PF_infokey_f, 0, 0, 0, 0, D("float(entity e, string key)", "Identical to regular infokey, except returns a float.")}, //80 {"stof", PF_stof, 0, 81, 0, 81, "float(string)"}, //81 {"multicast", PF_multicast, 0, 82, 0, 82, D("#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0)\n" "void(vector where, float set)", "Once the MSG_MULTICAST network message buffer has been filled with data, this builtin is used to dispatch it to the given target, filtering by pvs for reduced network bandwidth.")}, //82 @@ -10013,7 +10030,7 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs #ifndef QUAKETC //mvdsv (don't require ebfs usage in qw) - {"executecommand", PF_ExecuteCommand, 0, 0, 0, 83, D("void()",NULL), true}, + {"executecommand", PF_ExecuteCommand, 0, 0, 0, 83, D("void()","Attempt to flush the localcmd buffer NOW. This is unsafe, as many events might cause the map to be purged while still executing qc code."), true}, {"mvdtokenize", PF_Tokenize, 0, 0, 0, 84, D("void(string str)",NULL), true}, {"mvdargc", PF_ArgC, 0, 0, 0, 85, D("float()",NULL), true}, {"mvdargv", PF_ArgV, 0, 0, 0, 86, D("string(float num)",NULL), true}, @@ -10031,16 +10048,16 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"mvdnewstr", PF_mvdsv_newstring, 0, 0, 0, 93, D("string(string s, optional float bufsize)","Allocs a copy of the string. If bufsize is longer than the string then there will be extra space available on the end. The resulting string can then be modified freely."), true}, {"mvdfreestr", PF_mvdsv_freestring,0, 0, 0, 94, D("void(string s)","Frees memory allocated by mvdnewstr."), true}, {"conprint", PF_conprint, 0, 0, 0, 95, D("void(string s, ...)","Prints the string(s) onto the local console, bypassing redirects."), true}, - {"readcmd", PF_readcmd, 0, 0, 0, 0/*96*/, D("string(string str)",NULL), true}, + {"readcmd", PF_readcmd, 0, 0, 0, 0/*96*/, D("string(string str)","Executes the given command NOW. This is unsafe, as many events might cause the map to be purged while still executing qc code, so be careful about the commands you try reading, and avoid aliases."), true}, {"mvdstrcpy", PF_MVDSV_strcpy, 0, 0, 0, 97, D("void(string dst, string src)",NULL), true}, {"strstr", PF_strstr, 0, 0, 0, 98, D("string(string str, string sub)",NULL), true}, {"mvdstrncpy", PF_MVDSV_strncpy, 0, 0, 0, 99, D("void(string dst, string src, float count)",NULL), true}, {"logtext", PF_logtext, 0, 0, 0, 100, D("void(string name, float console, string text)",NULL), true}, -// {"redirectcmd", PF_redirectcmd, 0, 0, 0, 101, D("void(entity to, string str)",NULL), true}, {"mvdcalltimeofday",PF_calltimeofday, 0, 0, 0, 102, D("void()",NULL), true}, {"forcedemoframe", PF_forcedemoframe, 0, 0, 0, 103, D("void(float now)",NULL), true}, //end of mvdsv #endif + {"redirectcmd", PF_redirectcmd, 0, 0, 0, 101, D("void(entity to, string str)","Executes a single console command, and sends the text generated by it to the specified player. The command will be executed at the end of the frame once QC is no longer running - you may wish to pre/postfix it with 'echo'.")}, {"getlightstyle", PF_getlightstyle, 0, 0, 0, 0, D("string(float style, optional __out vector rgb)", "Obtains the light style string for the given style.")}, {"getlightstylergb",PF_getlightstylergb,0, 0, 0, 0, D("vector(float style)", "Obtains the current rgb value of the specified light style. In csqc, this is correct with regard to the current frame, while ssqc gives no guarentees about time and ignores client cvars. Note: use getlight if you want the actual light value at a point.")}, @@ -12110,6 +12127,11 @@ void PR_DumpPlatform_f(void) VFS_PRINTF(f, "#pragma warning error Q105 /*too few parms*/\n"); VFS_PRINTF(f, "#pragma warning error Q106 /*assignment to constant/lvalue*/\n"); VFS_PRINTF(f, "#pragma warning error Q208 /*system crc unknown*/\n"); +#ifdef NOLEGACY + VFS_PRINTF(f, "#pragma warning error F211 /*system crc outdated (eg: dp's csqc)*/\n"); +#else + VFS_PRINTF(f, "#pragma warning disable F211 /*system crc outdated (eg: dp's csqc)*/\n"); +#endif VFS_PRINTF(f, "#pragma warning enable F301 /*non-utf-8 strings*/\n"); VFS_PRINTF(f, "#pragma warning enable F302 /*uninitialised locals*/\n"); diff --git a/engine/server/pr_q1qvm.c b/engine/server/pr_q1qvm.c index 8ae35594..b70e335d 100755 --- a/engine/server/pr_q1qvm.c +++ b/engine/server/pr_q1qvm.c @@ -1314,14 +1314,34 @@ Con_DPrintf("PF_readcmd: %s\n%s", s, output); return 0; } + + + +static void QVM_RedirectCmdCallback(struct frameendtasks_s *task) +{ //called at the end of the frame when there's no qc running + host_client = svs.clients + task->ctxint; + if (host_client->state >= cs_connected) + { + SV_BeginRedirect(RD_CLIENT, host_client->language); + Cmd_ExecuteString(task->data, RESTRICT_INSECURE); + SV_EndRedirect(); + } +} static qintptr_t QVM_RedirectCmd (void *offset, quintptr_t mask, const qintptr_t *arg) { - //FIXME: KTX uses this, along with a big fat warning. - //it shouldn't be vital to the normal functionality - //just restricts admin a little (did these guys never hear of rcon?) - //I'm too lazy to implement it though. + struct frameendtasks_s *task, **link; + unsigned int entnum = ((char*)VM_POINTER(arg[0]) - (char*)evars) / sv.world.edict_size; + const char *s = VM_POINTER(arg[1]); + if (entnum < 1 || entnum > sv.allocated_client_slots) + SV_Error ("QVM_RedirectCmd: Parm 0 not a client"); - Con_DPrintf("QVM_RedirectCmd: not implemented\n"); + task = Z_Malloc(sizeof(*task)+strlen(s)); + task->callback = QVM_RedirectCmdCallback; + strcpy(task->data, s); + task->ctxint = entnum-1; + for(link = &svs.frameendtasks; *link; link = &(*link)->next) + ; //add them on the end, so they're execed in order + *link = task; return 0; } static qintptr_t QVM_Add_Bot (void *offset, quintptr_t mask, const qintptr_t *arg) diff --git a/engine/server/server.h b/engine/server/server.h index e2171047..d4e56ef6 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -440,7 +440,7 @@ typedef struct client_s qboolean prespawn_allow_soundlist; int spectator; // non-interactive - int redirect; + int redirect; //1=redirected because full, 2=cluster transfer qboolean sendinfo; // at end of frame, send info to all // this prevents malicious multiple broadcasts @@ -921,6 +921,15 @@ typedef struct char name[64]; // map name (base filename). static because of restart command after disconnecting. levelcache_t *levcache; + + struct frameendtasks_s + { + struct frameendtasks_s *next; + void(*callback)(struct frameendtasks_s *task); + void *ctxptr; + intptr_t ctxint; + char data[1]; + } *frameendtasks; } server_static_t; //============================================================================= @@ -1135,13 +1144,15 @@ typedef struct pubsubserver_s { struct { - void (*InstructSlave)(struct pubsubserver_s *ps, sizebuf_t *cmd); //send to + void (*InstructSlave)(struct pubsubserver_s *ps, sizebuf_t *cmd); //send to. first two bytes of the message should be ignored (overwrite them to carry size) int (*SubServerRead)(struct pubsubserver_s *ps); //read from. fills up net_message } funcs; struct pubsubserver_s *next; unsigned int id; char name[64]; + int activeplayers; + int transferingplayers; netadr_t addrv4; netadr_t addrv6; } pubsubserver_t; @@ -1156,6 +1167,7 @@ void SSV_SavePlayerStats(client_t *cl, int reason); //initial, periodic (in case void SSV_RequestShutdown(void); //asks the cluster to not send us new players pubsubserver_t *Sys_ForkServer(void); +void Sys_InstructMaster(sizebuf_t *cmd); //first two bytes will always be the length of the data #define SSV_IsSubServer() isClusterSlave diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index c1bbce82..620f9e8e 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -2964,7 +2964,6 @@ void SV_InitOperatorCommands (void) Cmd_AddCommand ("banlist", SV_BanList_f); //shows only bans, not other penalties Cmd_AddCommand ("unban", SV_Unfilter_f); //merely renamed. - Cmd_AddCommand ("banlist", SV_BanList_f); //shows only bans, not other penalties Cmd_AddCommand ("addip", SV_FilterIP_f); Cmd_AddCommand ("removeip", SV_Unfilter_f); diff --git a/engine/server/sv_cluster.c b/engine/server/sv_cluster.c index 9cae2152..99af1ebe 100644 --- a/engine/server/sv_cluster.c +++ b/engine/server/sv_cluster.c @@ -1,6 +1,15 @@ #include "quakedef.h" #include "netinc.h" +//these are the node names as parsed by MSV_FindSubServerName +//"5" finds server 5 only +//"5:" is equivelent to the above +//"5:dm4" finds server 5 +// if not running, it will start up using dm4, even if dm4 is already running on node 4, say. +//"0:dm4" starts a new server running dm4, even if its already running +//":dm4" finds any server running dm4. starts a new one if none are running dm4. +//"dm4" is ambiguous, in the case of a map beginning with a number (bah). don't use. + #ifdef SUBSERVERS #ifdef SQL @@ -79,33 +88,6 @@ static void MSV_ServerCrashed(pubsubserver_t *server) Z_Free(server); } -pubsubserver_t *MSV_StartSubServer(unsigned int id, const char *mapname) -{ - sizebuf_t send; - char send_buf[64]; - pubsubserver_t *s = Sys_ForkServer(); - if (s) - { - if (!id) - id = ++nextserverid; - s->id = id; - s->next = subservers; - subservers = s; - - Q_strncpyz(s->name, mapname, sizeof(s->name)); - - memset(&send, 0, sizeof(send)); - send.data = send_buf; - send.maxsize = sizeof(send_buf); - send.cursize = 2; - MSG_WriteByte(&send, ccmd_acceptserver); - MSG_WriteLong(&send, s->id); - MSG_WriteString(&send, s->name); - s->funcs.InstructSlave(s, &send); - } - return s; -} - pubsubserver_t *MSV_FindSubServer(unsigned int id) { pubsubserver_t *s; @@ -118,12 +100,87 @@ pubsubserver_t *MSV_FindSubServer(unsigned int id) return NULL; } -//"5" finds server 5 only -//"5:" is equivelent to the above -//"5:dm4" finds server 5, and will start it if not known (even if a different node is running the same map) -//"0:dm4" starts a new server running dm4, even if its already running -//":dm4" finds any server running dm4. starts a new one if needed. -//"dm4" is ambiguous, in the case of a map beginning with a number (bah). don't use. +vfsfile_t *msv_loop_to_ss; +vfsfile_t *msv_loop_from_ss; +static void MSV_Loop_Instruct(pubsubserver_t *ps, sizebuf_t *cmd) +{ + unsigned short size = cmd->cursize; + cmd->data[0] = cmd->cursize & 0xff; + cmd->data[1] = (cmd->cursize>>8) & 0xff; + VFS_WRITE(msv_loop_to_ss, cmd->data, size); +} +static int MSV_Loop_Read(pubsubserver_t *ps) +{ + unsigned short size; + if (sv.state < ss_loading) + return -1; //failure + if (!VFS_READ(msv_loop_from_ss, &size, sizeof(size))) + return 0; + net_message.cursize = size-2; + VFS_READ(msv_loop_from_ss, net_message.data, net_message.cursize); + + MSG_BeginReading (msg_nullnetprim); + return 1; +} + +static void MSV_Link_Server(pubsubserver_t *s, int id, const char *mapname) +{ + sizebuf_t send; + char send_buf[1024]; + if (!id) + { + do id = ++nextserverid; while(MSV_FindSubServer(id)); + } + s->id = id; + s->next = subservers; + subservers = s; + + if (mapname) + { + Q_strncpyz(s->name, mapname, sizeof(s->name)); + + memset(&send, 0, sizeof(send)); + send.data = send_buf; + send.maxsize = sizeof(send_buf); + send.cursize = 2; + MSG_WriteByte(&send, ccmd_acceptserver); + MSG_WriteLong(&send, s->id); + MSG_WriteString(&send, s->name); + s->funcs.InstructSlave(s, &send); + } +} + +pubsubserver_t *MSV_Loop_GetLocalServer(void) +{ + pubsubserver_t *s = MSV_FindSubServer(svs.clusterserverid); + if (s) + return s; + + if (!clusterplayers.next) //make sure we're initialised properly + ClearLink(&clusterplayers); + + msv_loop_to_ss = VFSPIPE_Open(1, false); + msv_loop_from_ss = VFSPIPE_Open(1, false); + s = Z_Malloc(sizeof(*s)); + s->funcs.InstructSlave = MSV_Loop_Instruct; + s->funcs.SubServerRead = MSV_Loop_Read; + + MSV_Link_Server(s, 0, ""); + Q_strncpyz(s->name, sv.mapname, sizeof(s->name)); + svs.clusterserverid = s->id; + return s; +} + +pubsubserver_t *MSV_StartSubServer(unsigned int id, const char *mapname) +{ + pubsubserver_t *s = Sys_ForkServer(); + + if (s) + MSV_Link_Server(s, id, mapname); + return s; +} + +//server names documented at the start of this file pubsubserver_t *MSV_FindSubServerName(const char *servername) { pubsubserver_t *s; @@ -298,7 +355,7 @@ void MSV_SubServerCommand_f(void) Con_Printf("Active servers on this cluster:\n"); for (s = subservers; s; s = s->next) { - Con_Printf("%i: %s", s->id, s->name); + Con_Printf("%i: %s %i+%i", s->id, s->name, s->activeplayers, s->transferingplayers); if (s->addrv4.type != NA_INVALID) Con_Printf(" %s", NET_AdrToString(bufmem, sizeof(bufmem), &s->addrv4)); if (s->addrv6.type != NA_INVALID) @@ -334,6 +391,8 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) netadr_t adr; char *str; int c; + pubsubserver_t *toptr; + clusterplayer_t *pl; c = MSG_ReadByte(); switch(c) @@ -347,7 +406,6 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) break; case ccmd_saveplayer: { - clusterplayer_t *pl; float stats[NUM_SPAWN_PARMS]; int i; unsigned char reason = MSG_ReadByte(); @@ -372,25 +430,39 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) switch (reason) { case 0: //server reports that it accepted the player - if (pl->server && pl->server != s) - { //let the previous server know - sizebuf_t send; - qbyte send_buf[64]; - memset(&send, 0, sizeof(send)); - send.data = send_buf; - send.maxsize = sizeof(send_buf); - send.cursize = 2; - MSG_WriteByte(&send, ccmd_transferedplayer); - MSG_WriteLong(&send, s->id); - MSG_WriteLong(&send, plid); - pl->server->funcs.InstructSlave(pl->server, &send); + if (pl->server != s) + { + if (pl->server) + { //let the previous server know + sizebuf_t send; + qbyte send_buf[64]; + memset(&send, 0, sizeof(send)); + send.data = send_buf; + send.maxsize = sizeof(send_buf); + send.cursize = 2; + MSG_WriteByte(&send, ccmd_transferedplayer); + MSG_WriteLong(&send, s->id); + MSG_WriteLong(&send, plid); + pl->server->funcs.InstructSlave(pl->server, &send); + pl->server->activeplayers--; + } + pl->server = s; + pl->server->activeplayers++; + } + break; + case 1: + //belongs to another node now, (but we might not have had the other node's response yet) + if (pl->server == s) + { + s->activeplayers--; + pl->server = NULL; } - pl->server = s; break; case 2: //drop case 3: //transfer abort if (pl->server == s) { + pl->server->activeplayers--; Con_Printf("%s(%s) dropped\n", pl->name, s->name); RemoveLink(&pl->allplayers); Z_Free(pl); @@ -409,7 +481,6 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) char *newmap = MSG_ReadStringBuffer(mapname, sizeof(mapname)); char *claddr = MSG_ReadString(); char *clguid = MSG_ReadStringBuffer(guid, sizeof(guid)); - pubsubserver_t *toptr; memset(&send, 0, sizeof(send)); send.data = send_buf; @@ -434,6 +505,9 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) MSG_WriteFloat(&send, MSG_ReadFloat()); toptr->funcs.InstructSlave(toptr, &send); + + s->transferingplayers--; + toptr->transferingplayers++; } else { @@ -475,7 +549,7 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) if (!to) { if (svadr.type != NA_INVALID) - { + { //the client was trying to connect to the cluster master. rmsg = va("fredir\n%s", NET_AdrToString(adrbuf, sizeof(adrbuf), &svadr)); Netchan_OutOfBand (NS_SERVER, &cladr, strlen(rmsg), (qbyte *)rmsg); } @@ -487,8 +561,14 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) MSG_WriteLong(&send, plid); MSG_WriteString(&send, NET_AdrToString(adrbuf, sizeof(adrbuf), &svadr)); - MSV_InstructSlave(to, &send); + toptr = MSV_FindSubServer(to); + if (toptr) + { + toptr->funcs.InstructSlave(toptr, &send); + toptr->transferingplayers++; + } } + s->transferingplayers--; } break; case ccmd_serveraddress: @@ -586,6 +666,19 @@ void MSV_ReadFromSubServer(pubsubserver_t *s) void MSV_PollSlaves(void) { pubsubserver_t **link, *s; + + if (msv_loop_to_ss) + { + unsigned short size; + while (VFS_READ(msv_loop_to_ss, &size, sizeof(size))>0) + { + VFS_READ(msv_loop_to_ss, net_message.data, size); + net_message.cursize = size-2; + MSG_BeginReading (msg_nullnetprim); + SSV_ReadFromControlServer(); + } + } + for (link = &subservers; (s=*link); ) { switch(s->funcs.SubServerRead(s)) @@ -607,6 +700,16 @@ void MSV_PollSlaves(void) } } +void SSV_InstructMaster(sizebuf_t *cmd) +{ + cmd->data[0] = cmd->cursize & 0xff; + cmd->data[1] = (cmd->cursize>>8) & 0xff; + if (msv_loop_from_ss) + VFS_WRITE(msv_loop_from_ss, cmd->data, cmd->cursize); + else + Sys_InstructMaster(cmd); +} + void SSV_ReadFromControlServer(void) { int c; @@ -842,7 +945,7 @@ void SSV_UpdateAddresses(void) qbyte send_buf[MAX_QWMSGLEN]; int i; - if (!SSV_IsSubServer()) + if (!SSV_IsSubServer() && !msv_loop_from_ss) return; count = NET_EnumerateAddresses(svs.sockets, con, flags, addr, sizeof(addr)/sizeof(addr[0])); @@ -934,6 +1037,27 @@ void SSV_InitiatePlayerTransfer(client_t *cl, const char *newserver) send.data = send_buf; send.maxsize = sizeof(send_buf); send.cursize = 2; + + if (!SSV_IsSubServer()) + { + //main->sub. + //make sure the main server exists, and the player does too. + pubsubserver_t *s = MSV_Loop_GetLocalServer(); + + //make sure there's a player entry for this player, as they probably bypassed the initial connection thing + if (!MSV_FindPlayerId(cl->userid)) + { + clusterplayer_t *pl = Z_Malloc(sizeof(*pl)); + Q_strncpyz(pl->name, cl->name, sizeof(pl->name)); + Q_strncpyz(pl->guid, cl->guid, sizeof(pl->guid)); + NET_AdrToString(pl->address, sizeof(pl->address), &cl->netchan.remote_address); + pl->playerid = cl->userid; + InsertLinkBefore(&pl->allplayers, &clusterplayers); + pl->server = s; + s->activeplayers++; + } + } + MSG_WriteByte(&send, ccmd_transferplayer); MSG_WriteLong(&send, cl->userid); MSG_WriteString(&send, cl->name); @@ -1019,6 +1143,7 @@ qboolean MSV_ClusterLoginReply(netadr_t *legacyclientredirect, unsigned int serv pl->playerid = playerid; InsertLinkBefore(&pl->allplayers, &clusterplayers); pl->server = s; + s->activeplayers++; MSG_WriteByte(&send, ccmd_takeplayer); MSG_WriteLong(&send, playerid); diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index e25e016e..ebb21082 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -961,6 +961,8 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, //if you want to load a .map, just use 'map foo.map' instead. char *exts[] = {"maps/%s", "maps/%s.bsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ NULL}; int depth, bestdepth; + flocation_t loc; + time_t filetime; Q_strncpyz (svs.name, server, sizeof(svs.name)); Q_snprintfz (sv.modelname, sizeof(sv.modelname), exts[0], server); bestdepth = COM_FDepthFile(sv.modelname, false); @@ -973,8 +975,18 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents, Q_snprintfz (sv.modelname, sizeof(sv.modelname), exts[i], server); } } - COM_FCheckExists(sv.modelname); sv.world.worldmodel = Mod_ForName (sv.modelname, MLV_ERROR); + + if (FS_FLocateFile(sv.modelname,FSLF_IFFOUND, &loc) && FS_GetLocMTime(&loc, &filetime)) + { + if (filetime > sv.world.worldmodel->mtime) + { + COM_WorkerFullSync(); //sync all the workers, just in case. + Mod_PurgeModel(sv.world.worldmodel, MP_RESET); //nuke it now + sv.world.worldmodel = Mod_ForName (sv.modelname, MLV_ERROR); //and we can reload it now + } + sv.world.worldmodel->mtime = filetime; + } } if (!sv.world.worldmodel || sv.world.worldmodel->loadstate != MLS_LOADED) Sys_Error("\"%s\" is missing or corrupt\n", sv.modelname); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 2fa159d1..70007782 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -507,7 +507,7 @@ void SV_DropClient (client_t *drop) #endif } #ifdef SUBSERVERS - SSV_SavePlayerStats(drop, 2); + SSV_SavePlayerStats(drop, (drop->redirect==2)?1:2); #endif #ifdef SVCHAT SV_WipeChat(drop); @@ -4401,8 +4401,9 @@ void SV_CheckTimeouts (void) #ifdef SUBSERVERS SSV_SavePlayerStats(cl, 3); #endif - cl->state = cs_free; SV_BroadcastTPrintf (PRINT_HIGH, "TransferZombie %s timed out\n", cl->name); + cl->state = cs_free; + *cl->name = 0; } cl->netchan.remote_address.type = NA_INVALID; //don't mess up from not knowing their address. } @@ -4733,12 +4734,15 @@ float SV_Frame (void) Plug_Tick(); #endif +#ifdef SUBSERVERS + MSV_PollSlaves(); +#endif + if (sv.state < ss_active || !sv.world.worldmodel) { #ifdef SUBSERVERS if (sv.state == ss_clustermode) { - MSV_PollSlaves(); isidle = !SV_ReadPackets (&delay); #ifdef SQL PR_SQLCycle(); @@ -4812,6 +4816,15 @@ float SV_Frame (void) } } + //run any end-of-frame tasks that were set up + while (svs.frameendtasks) + { + struct frameendtasks_s *t = svs.frameendtasks; + svs.frameendtasks = t->next; + t->callback(t); + Z_Free(t); + } + if (!isidle || idletime > 0.15) { //this is the q2 frame number found in the q2 protocol. each packet should contain a new frame or interpolation gets confused