update the clustertransfer builtin to work on non-cluster dedicated servers.

implement redirectcmd. because I can.
add a few more qc extensions that define features already implemented.
changing/restarting the map will explicitly flush the worldmodel if the file's modification time is newer. this should make map editing a little nicer.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5106 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2017-05-23 07:03:07 +00:00
parent f64cb13cab
commit 14c665076b
18 changed files with 529 additions and 170 deletions

View file

@ -3194,8 +3194,15 @@ client_connect: //fixme: make function
return; return;
} }
if (net_from.type != NA_LOOPBACK) if (net_from.type != NA_LOOPBACK)
{
Con_TPrintf ("connection\n"); Con_TPrintf ("connection\n");
#ifndef CLIENTONLY
if (sv.state)
SV_UnspawnServer();
#endif
}
if (cls.state >= ca_connected) if (cls.state >= ca_connected)
{ {
if (!NET_CompareAdr(&cls.netchan.remote_address, &net_from)) if (!NET_CompareAdr(&cls.netchan.remote_address, &net_from))
@ -3203,7 +3210,7 @@ client_connect: //fixme: make function
#ifndef CLIENTONLY #ifndef CLIENTONLY
if (sv.state != ss_clustermode) if (sv.state != ss_clustermode)
#endif #endif
CL_Disconnect_f(); CL_Disconnect ();
} }
else else
{ {
@ -3911,10 +3918,100 @@ void CL_CrashMeEndgame_f(void)
void CL_Status_f(void) void CL_Status_f(void)
{ {
char adr[128];
float pi, po, bi, bo; float pi, po, bi, bo;
NET_PrintAddresses(cls.sockets); NET_PrintAddresses(cls.sockets);
if (NET_GetRates(cls.sockets, &pi, &po, &bi, &bo)) 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. 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) void CL_Demo_SetSpeed_f(void)

View file

@ -147,9 +147,9 @@ void R_DrawTextField(int x, int y, int w, int h, const char *text, unsigned int
//mod_purge flags //mod_purge flags
enum mod_purge_e 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_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 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); void Mod_ParseEntities(struct model_s *mod);
extern void Mod_ClearAll (void); extern void Mod_ClearAll (void);
extern void Mod_Purge (enum mod_purge_e type); 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_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_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 extern struct model_s *Mod_LoadModel (struct model_s *mod, enum mlverbosity_e verbose); //makes sure a model is loaded

View file

@ -30,7 +30,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
static void S_Play(void); static void S_Play(void);
static void S_PlayVol(void); static void S_PlayVol(void);
static void S_SoundList_f(void); static void S_SoundList_f(void);
#ifdef HAVE_MIXER
static void S_Update_(soundcardinfo_t *sc); static void S_Update_(soundcardinfo_t *sc);
#endif
void S_StopAllSounds(qboolean clear); void S_StopAllSounds(qboolean clear);
static void S_StopAllSounds_f (void); static void S_StopAllSounds_f (void);
@ -47,6 +49,7 @@ int snd_blocked = 0;
static qboolean snd_ambient = 1; static qboolean snd_ambient = 1;
qboolean snd_initialized = false; qboolean snd_initialized = false;
int snd_speed; int snd_speed;
float voicevolumemod = 1;
static struct static struct
{ {
@ -1542,6 +1545,7 @@ static sounddriver_t *outputdrivers[] =
&OPENAL_Output, //refuses to run as the default device, at least until its perfected. &OPENAL_Output, //refuses to run as the default device, at least until its perfected.
#endif #endif
#ifdef HAVE_MIXER
#ifdef AVAIL_DSOUND #ifdef AVAIL_DSOUND
&DSOUND_Output, &DSOUND_Output,
#endif #endif
@ -1559,6 +1563,7 @@ static sounddriver_t *outputdrivers[] =
&OSS_Output, //good, but not likely to work any more &OSS_Output, //good, but not likely to work any more
#ifdef __DJGPP__ #ifdef __DJGPP__
&SBLASTER_Output, //zomgwtfdos? &SBLASTER_Output, //zomgwtfdos?
#endif
#endif #endif
NULL NULL
}; };
@ -1567,6 +1572,7 @@ typedef struct {
sounddriver *ptr; sounddriver *ptr;
} sdriver_t; } sdriver_t;
static sdriver_t olddrivers[] = { static sdriver_t olddrivers[] = {
#ifdef HAVE_MIXER
//in order of preference //in order of preference
{"MacOS", &pMacOS_InitCard}, //prefered on mac {"MacOS", &pMacOS_InitCard}, //prefered on mac
{"Droid", &pDroid_InitCard}, //prefered on android (java thread) {"Droid", &pDroid_InitCard}, //prefered on android (java thread)
@ -1577,6 +1583,7 @@ static sdriver_t olddrivers[] = {
{"SNDIO", &pSNDIO_InitCard}, //prefered on OpenBSD {"SNDIO", &pSNDIO_InitCard}, //prefered on OpenBSD
{"WaveOut", &pWAV_InitCard}, //doesn't work properly in vista, etc. {"WaveOut", &pWAV_InitCard}, //doesn't work properly in vista, etc.
#endif
{NULL, NULL} {NULL, NULL}
}; };
@ -3450,6 +3457,7 @@ static void S_UpdateCard(soundcardinfo_t *sc)
Con_Printf ("----(%i+%i)----\n", active, mute); Con_Printf ("----(%i+%i)----\n", active, mute);
} }
#ifdef HAVE_MIXER
// mix some sound // mix some sound
if (sc->selfpainting) if (sc->selfpainting)
@ -3462,9 +3470,11 @@ static void S_UpdateCard(soundcardinfo_t *sc)
} }
S_Update_(sc); S_Update_(sc);
#endif
} }
int S_GetMixerTime(soundcardinfo_t *sc) #ifdef HAVE_MIXER
static int S_GetMixerTime(soundcardinfo_t *sc)
{ {
int samplepos; int samplepos;
int fullsamples; int fullsamples;
@ -3500,6 +3510,7 @@ int S_GetMixerTime(soundcardinfo_t *sc)
return sc->buffers*fullsamples + samplepos/sc->sn.numchannels; return sc->buffers*fullsamples + samplepos/sc->sn.numchannels;
} }
#endif
void S_Update (void) void S_Update (void)
{ {
@ -3513,7 +3524,9 @@ void S_Update (void)
void S_ExtraUpdate (void) void S_ExtraUpdate (void)
{ {
#ifdef HAVE_MIXER
soundcardinfo_t *sc; soundcardinfo_t *sc;
#endif
if (!sound_started) if (!sound_started)
return; return;
@ -3521,7 +3534,7 @@ void S_ExtraUpdate (void)
#if defined(_WIN32) && !defined(WINRT) #if defined(_WIN32) && !defined(WINRT)
INS_Accumulate (); INS_Accumulate ();
#endif #endif
#ifdef HAVE_MIXER
if (snd_noextraupdate.ival) if (snd_noextraupdate.ival)
return; // don't pollute timings return; // don't pollute timings
@ -3540,10 +3553,11 @@ void S_ExtraUpdate (void)
S_Update_(sc); S_Update_(sc);
S_UnlockMixer(); S_UnlockMixer();
} }
#endif
} }
#ifdef HAVE_MIXER
static void S_Update_(soundcardinfo_t *sc) static void S_Update_(soundcardinfo_t *sc)
{ {
int soundtime; /*in pairs*/ int soundtime; /*in pairs*/
@ -3604,6 +3618,7 @@ void S_MixerThread(soundcardinfo_t *sc)
S_Update_(sc); S_Update_(sc);
S_UnlockMixer(); S_UnlockMixer();
} }
#endif
/* /*
=============================================================================== ===============================================================================

View file

@ -21,9 +21,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "quakedef.h" #include "quakedef.h"
#ifdef HAVE_MIXER
#define PAINTBUFFER_SIZE 2048 #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. portable_samplegroup_t paintbuffer[PAINTBUFFER_SIZE]; //FIXME: we really ought to be using SSE and floats or something.
int *snd_p, snd_vol; int *snd_p, snd_vol;
@ -682,3 +683,4 @@ static void SND_PaintChannel16_O8I1 (channel_t *ch, sfxcache_t *sc, int count)
} }
} }
} }
#endif

View file

@ -135,11 +135,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//#define SPEEX_STATIC //#define SPEEX_STATIC
#if defined(_WIN32) && defined(GLQUAKE) #if defined(_WIN32) && defined(GLQUAKE)
//#define USE_EGL //#define USE_EGL
#endif #endif
#if defined(_MSC_VER) && !defined(BOTLIB_STATIC) //too lazy to fix up the makefile #if defined(_MSC_VER) && !defined(BOTLIB_STATIC) //too lazy to fix up the makefile
#define BOTLIB_STATIC #define BOTLIB_STATIC
#endif #endif
#ifdef NO_OPENAL #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 #undef AVAIL_WASAPI //wasapi is available in the vista sdk, while that's compatible with earlier versions, its not really expected until 2008
#endif #endif
#define HAVE_TCP //says we can use tcp too (either ipv4 or ipv6) #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_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 //set any additional defines or libs in win32
#define LOADERTHREAD #define LOADERTHREAD
@ -395,6 +396,13 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#undef AVAIL_MP3_ACM #undef AVAIL_MP3_ACM
#endif #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 #ifdef NOMEDIA
#undef HAVE_CDPLAYER //includes cd playback. actual cds. faketracks are supported regardless. #undef HAVE_CDPLAYER //includes cd playback. actual cds. faketracks are supported regardless.
#undef HAVE_JUKEBOX //includes built-in jukebox crap #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 #endif
#ifdef FTE_TARGET_WEB #ifdef FTE_TARGET_WEB
//sandboxing... //sandboxing means some stuff CANNOT work...
#undef HAVE_TCP //websockets are not real tcp. #undef HAVE_TCP //websockets are not real tcp.
#undef HAVE_PACKET //no udp support #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 MAP_PROC //meh
#undef HALFLIFEMODELS //blurgh #undef HALFLIFEMODELS //blurgh
#undef WEBSERVER //hah, yeah, right #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 //extra features stripped to try to reduce memory footprints
#undef RUNTIMELIGHTING //too slow anyway #undef RUNTIMELIGHTING //too slow anyway

View file

@ -6169,9 +6169,11 @@ lh_extension_t QSG_Extensions[] = {
{"DP_QC_GETSURFACEPOINTATTRIBUTE", 1, NULL, {"getsurfacepointattribute"}}, {"DP_QC_GETSURFACEPOINTATTRIBUTE", 1, NULL, {"getsurfacepointattribute"}},
{"DP_QC_MINMAXBOUND", 3, NULL, {"min", "max", "bound"}}, {"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_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_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_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_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_STRFTIME", 1, NULL, {"strftime"}},
{"DP_QC_STRING_CASE_FUNCTIONS", 2, NULL, {"strtolower", "strtoupper"}}, {"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"}}, {"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 #endif
{"FTE_CSQC_SERVERBROWSER", 12, NULL, { "gethostcachevalue", "gethostcachestring", "resethostcachemasks", "sethostcachemaskstring", "sethostcachemasknumber", {"FTE_CSQC_SERVERBROWSER", 12, NULL, { "gethostcachevalue", "gethostcachestring", "resethostcachemasks", "sethostcachemaskstring", "sethostcachemasknumber",
"resorthostcache", "sethostcachesort", "refreshhostcache", "gethostcachenumber", "gethostcacheindexforkey", "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", {"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_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."}, "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."}, {"FTE_MVD_PLAYBACK", NOBI "The server itself is able to play back MVDs."},
#endif #endif
#ifdef SVCHAT #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 #endif
#ifdef PSET_SCRIPT #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."}, {"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_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_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_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_INTCONV", 6, NULL, {"stoi", "itos", "stoh", "htos", "itof", "ftoi"}, "Provides string<>int conversions, including hex representations."},
{"FTE_QC_MATCHCLIENTNAME", 1, NULL, {"matchclientname"}}, {"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_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"}, {"FTE_QC_PAUSED"},
#ifdef QCGC #ifdef QCGC

View file

@ -415,10 +415,8 @@ pubsubserver_t *Sys_ForkServer(void)
return &ctx->pub; 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); write(STDOUT, cmd->data, cmd->cursize);
//FIXME: handle partial writes. //FIXME: handle partial writes.

View file

@ -565,13 +565,11 @@ pubsubserver_t *Sys_ForkServer(void)
return &ctx->pub; 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. //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); HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written = 0; DWORD written = 0;
cmd->data[0] = cmd->cursize & 0xff;
cmd->data[1] = (cmd->cursize>>8) & 0xff;
WriteFile(output, cmd->data, cmd->cursize, &written, NULL); WriteFile(output, cmd->data, cmd->cursize, &written, NULL);
} }

View file

@ -625,35 +625,34 @@ void Mod_ClearAll (void)
mod_datasequence++; mod_datasequence++;
} }
//can be called in one of two ways. qboolean Mod_PurgeModel(model_t *mod, enum mod_purge_e ptype)
//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.
//certain models cannot be safely flushed while still in use. such models will not be flushed even if forced (they may still be partially flushed).
void Mod_Purge(enum mod_purge_e ptype)
{ {
int i;
model_t *mod;
qboolean unused;
for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
{
unused = mod->datasequence != mod_datasequence;
if (mod->loadstate == MLS_NOTLOADED)
continue;
//this model isn't active any more.
if (unused || ptype != MP_MAPCHANGED)
{
if (mod->loadstate == MLS_LOADING) if (mod->loadstate == MLS_LOADING)
{ {
if (ptype == MP_MAPCHANGED && !mod->submodelof) if (ptype == MP_MAPCHANGED && !mod->submodelof)
continue; //don't bother waiting for it on map changes. return false; //don't bother waiting for it on map changes.
COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);
} }
if (unused) #ifdef RUNTIMELIGHTING
Con_DLPrintf(2, "model \"%s\" no longer needed\n", mod->name); 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 #ifdef TERRAIN
//we can safely flush all terrain sections at any time //we can safely flush all terrain sections at any time
@ -665,8 +664,8 @@ void Mod_Purge(enum mod_purge_e ptype)
if (mod->type == mod_brush) if (mod->type == mod_brush)
{ {
//brush models cannot be safely flushed. //brush models cannot be safely flushed.
if (!unused && ptype != MP_RESET) if (ptype != MP_RESET)
continue; return false;
#ifndef SERVERONLY #ifndef SERVERONLY
Surf_Clear(mod); Surf_Clear(mod);
#endif #endif
@ -676,8 +675,8 @@ void Mod_Purge(enum mod_purge_e ptype)
if (mod->type == mod_brush || mod->type == mod_heightmap) if (mod->type == mod_brush || mod->type == mod_heightmap)
{ {
//heightmap/terrain models cannot be safely flushed (brush models might have terrain embedded). //heightmap/terrain models cannot be safely flushed (brush models might have terrain embedded).
if (!unused && ptype != MP_RESET) if (ptype != MP_RESET)
continue; return false;
Terr_FreeModel(mod); Terr_FreeModel(mod);
} }
#endif #endif
@ -701,6 +700,33 @@ void Mod_Purge(enum mod_purge_e ptype)
mod->submodelof = NULL; mod->submodelof = NULL;
mod->pvs = NULL; mod->pvs = NULL;
mod->phs = 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.
//certain models cannot be safely flushed while still in use. such models will not be flushed even if forced (they may still be partially flushed).
void Mod_Purge(enum mod_purge_e ptype)
{
int i;
model_t *mod;
qboolean unused;
for (i=0 , mod=mod_known ; i<mod_numknown ; i++, mod++)
{
unused = mod->datasequence != mod_datasequence;
if (mod->loadstate == MLS_NOTLOADED)
continue;
//this model isn't active any more.
if (unused || ptype != MP_MAPCHANGED)
{
if (unused)
Con_DLPrintf(2, "model \"%s\" no longer needed\n", mod->name);
Mod_PurgeModel(mod, (ptype==MP_FLUSH && unused)?MP_RESET:ptype);
} }
} }
} }

View file

@ -861,8 +861,9 @@ typedef struct model_s
char publicname[MAX_QPATH]; //name that the gamecode etc sees char publicname[MAX_QPATH]; //name that the gamecode etc sees
int datasequence; int datasequence;
int loadstate;//MLS_ 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. 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; struct model_s *submodelof;

View file

@ -4155,8 +4155,9 @@ main
============ ============
*/ */
int qccmline;
char *qccmsrc; char *qccmsrc;
char *qccmsrc2; //char *qccmsrc2;
char qccmfilename[1024]; char qccmfilename[1024];
char qccmprogsdat[1024]; char qccmprogsdat[1024];
@ -4765,10 +4766,12 @@ memset(pr_immediate_string, 0, sizeof(pr_immediate_string));
newstyle: newstyle:
newstylesource = true; newstylesource = true;
originalqccmsrc = qccmsrc; originalqccmsrc = qccmsrc;
pr_source_line = qccmline = 1;
StartNewStyleCompile(); StartNewStyleCompile();
return true; return true;
} }
pr_source_line = qccmline = 1;
pr_file_p = qccmsrc; pr_file_p = qccmsrc;
QCC_PR_LexWhitespace(false); QCC_PR_LexWhitespace(false);
qccmsrc = pr_file_p; qccmsrc = pr_file_p;
@ -4778,6 +4781,7 @@ newstyle:
QCC_PR_SimpleGetToken (); QCC_PR_SimpleGetToken ();
strcpy(qcc_token, pr_token); strcpy(qcc_token, pr_token);
qccmsrc = pr_file_p; qccmsrc = pr_file_p;
qccmline = pr_source_line;
if (!qccmsrc) if (!qccmsrc)
QCC_Error (ERR_NOOUTPUT, "No destination filename. qcc -help for info."); QCC_Error (ERR_NOOUTPUT, "No destination filename. qcc -help for info.");
@ -4850,11 +4854,12 @@ void QCC_ContinueCompile(void)
} }
pr_file_p = qccmsrc; pr_file_p = qccmsrc;
s_filen = ""; s_filen = compilingrootfile;
s_filed = 0; s_filed = 0;
pr_source_line = 0; pr_source_line = qccmline;
QCC_PR_LexWhitespace(false); QCC_PR_LexWhitespace(false);
qccmsrc = pr_file_p; qccmsrc = pr_file_p;
qccmline = pr_source_line;
qccmsrc = QCC_COM_Parse(qccmsrc); qccmsrc = QCC_COM_Parse(qccmsrc);
if (!qccmsrc) if (!qccmsrc)

View file

@ -5807,7 +5807,16 @@ char *PF_infokey_Internal (int entnum, const char *key)
return value; 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; edict_t *e;
int e1; int e1;
@ -5820,7 +5829,7 @@ static void QCBUILTIN PF_infokey (pubprogfuncs_t *prinst, struct globalvars_s *p
value = PF_infokey_Internal (e1, key); 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) 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 PF_redirectcmd
void redirectcmd (entity to, string str) 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) static void QCBUILTIN PF_redirectcmd (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{ {
char *s; struct frameendtasks_s *task, **link;
int entnum; int entnum = G_EDICTNUM(prinst, OFS_PARM0);
extern redirect_t sv_redirected; const char *s = PR_GetStringOfs(prinst, OFS_PARM1);
if (sv_redirected)
return;
entnum = G_EDICTNUM(OFS_PARM0);
if (entnum < 1 || entnum > sv.allocated_client_slots) 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); task = Z_Malloc(sizeof(*task)+strlen(s));
task->callback = PF_Redirectcmdcallback;
Cbuf_AddText (s); strcpy(task->data, s);
task->ctxint = entnum-1;
SV_BeginRedirect(RD_MOD + entnum); for(link = &svs.frameendtasks; *link; link = &(*link)->next)
Cbuf_Execute(); ; //add them on the end, so they're execed in order
SV_EndRedirect(); *link = task;
}*/ }
/* /*
================= =================
@ -9728,7 +9743,8 @@ static void QCBUILTIN PF_clustertransfer(pubprogfuncs_t *prinst, struct globalva
if (dest) 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); Con_DPrintf("PF_clustertransfer: not running in mapcluster mode, ignoring transfer to %s\n", dest);
return; 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); Con_DPrintf("PF_clustertransfer: Already transferring to %s, ignoring transfer to %s\n", svs.clients[p].transfer, dest);
return; 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); 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) {"tq_fputs", PF_fputs, 0, 0, 0, 89, D("void(filestream fhandle, string s)",NULL), true},// (QSG_FILE)
// Tomaz - QuakeC File System End // 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 {"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" {"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 "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 #ifndef QUAKETC
//mvdsv (don't require ebfs usage in qw) //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}, {"mvdtokenize", PF_Tokenize, 0, 0, 0, 84, D("void(string str)",NULL), true},
{"mvdargc", PF_ArgC, 0, 0, 0, 85, D("float()",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}, {"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}, {"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}, {"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}, {"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}, {"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}, {"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}, {"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}, {"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}, {"mvdcalltimeofday",PF_calltimeofday, 0, 0, 0, 102, D("void()",NULL), true},
{"forcedemoframe", PF_forcedemoframe, 0, 0, 0, 103, D("void(float now)",NULL), true}, {"forcedemoframe", PF_forcedemoframe, 0, 0, 0, 103, D("void(float now)",NULL), true},
//end of mvdsv //end of mvdsv
#endif #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.")}, {"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.")}, {"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 Q105 /*too few parms*/\n");
VFS_PRINTF(f, "#pragma warning error Q106 /*assignment to constant/lvalue*/\n"); VFS_PRINTF(f, "#pragma warning error Q106 /*assignment to constant/lvalue*/\n");
VFS_PRINTF(f, "#pragma warning error Q208 /*system crc unknown*/\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 F301 /*non-utf-8 strings*/\n");
VFS_PRINTF(f, "#pragma warning enable F302 /*uninitialised locals*/\n"); VFS_PRINTF(f, "#pragma warning enable F302 /*uninitialised locals*/\n");

View file

@ -1314,14 +1314,34 @@ Con_DPrintf("PF_readcmd: %s\n%s", s, output);
return 0; 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) static qintptr_t QVM_RedirectCmd (void *offset, quintptr_t mask, const qintptr_t *arg)
{ {
//FIXME: KTX uses this, along with a big fat warning. struct frameendtasks_s *task, **link;
//it shouldn't be vital to the normal functionality unsigned int entnum = ((char*)VM_POINTER(arg[0]) - (char*)evars) / sv.world.edict_size;
//just restricts admin a little (did these guys never hear of rcon?) const char *s = VM_POINTER(arg[1]);
//I'm too lazy to implement it though. 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; return 0;
} }
static qintptr_t QVM_Add_Bot (void *offset, quintptr_t mask, const qintptr_t *arg) static qintptr_t QVM_Add_Bot (void *offset, quintptr_t mask, const qintptr_t *arg)

View file

@ -440,7 +440,7 @@ typedef struct client_s
qboolean prespawn_allow_soundlist; qboolean prespawn_allow_soundlist;
int spectator; // non-interactive 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 qboolean sendinfo; // at end of frame, send info to all
// this prevents malicious multiple broadcasts // 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. char name[64]; // map name (base filename). static because of restart command after disconnecting.
levelcache_t *levcache; 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; } server_static_t;
//============================================================================= //=============================================================================
@ -1135,13 +1144,15 @@ typedef struct pubsubserver_s
{ {
struct 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 int (*SubServerRead)(struct pubsubserver_s *ps); //read from. fills up net_message
} funcs; } funcs;
struct pubsubserver_s *next; struct pubsubserver_s *next;
unsigned int id; unsigned int id;
char name[64]; char name[64];
int activeplayers;
int transferingplayers;
netadr_t addrv4; netadr_t addrv4;
netadr_t addrv6; netadr_t addrv6;
} pubsubserver_t; } 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 void SSV_RequestShutdown(void); //asks the cluster to not send us new players
pubsubserver_t *Sys_ForkServer(void); 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 #define SSV_IsSubServer() isClusterSlave

View file

@ -2964,7 +2964,6 @@ void SV_InitOperatorCommands (void)
Cmd_AddCommand ("banlist", SV_BanList_f); //shows only bans, not other penalties Cmd_AddCommand ("banlist", SV_BanList_f); //shows only bans, not other penalties
Cmd_AddCommand ("unban", SV_Unfilter_f); //merely renamed. 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 ("addip", SV_FilterIP_f);
Cmd_AddCommand ("removeip", SV_Unfilter_f); Cmd_AddCommand ("removeip", SV_Unfilter_f);

View file

@ -1,6 +1,15 @@
#include "quakedef.h" #include "quakedef.h"
#include "netinc.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 SUBSERVERS
#ifdef SQL #ifdef SQL
@ -79,33 +88,6 @@ static void MSV_ServerCrashed(pubsubserver_t *server)
Z_Free(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 *MSV_FindSubServer(unsigned int id)
{ {
pubsubserver_t *s; pubsubserver_t *s;
@ -118,12 +100,87 @@ pubsubserver_t *MSV_FindSubServer(unsigned int id)
return NULL; return NULL;
} }
//"5" finds server 5 only vfsfile_t *msv_loop_to_ss;
//"5:" is equivelent to the above vfsfile_t *msv_loop_from_ss;
//"5:dm4" finds server 5, and will start it if not known (even if a different node is running the same map) static void MSV_Loop_Instruct(pubsubserver_t *ps, sizebuf_t *cmd)
//"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. unsigned short size = cmd->cursize;
//"dm4" is ambiguous, in the case of a map beginning with a number (bah). don't use. 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 *MSV_FindSubServerName(const char *servername)
{ {
pubsubserver_t *s; pubsubserver_t *s;
@ -298,7 +355,7 @@ void MSV_SubServerCommand_f(void)
Con_Printf("Active servers on this cluster:\n"); Con_Printf("Active servers on this cluster:\n");
for (s = subservers; s; s = s->next) 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) if (s->addrv4.type != NA_INVALID)
Con_Printf(" %s", NET_AdrToString(bufmem, sizeof(bufmem), &s->addrv4)); Con_Printf(" %s", NET_AdrToString(bufmem, sizeof(bufmem), &s->addrv4));
if (s->addrv6.type != NA_INVALID) if (s->addrv6.type != NA_INVALID)
@ -334,6 +391,8 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
netadr_t adr; netadr_t adr;
char *str; char *str;
int c; int c;
pubsubserver_t *toptr;
clusterplayer_t *pl;
c = MSG_ReadByte(); c = MSG_ReadByte();
switch(c) switch(c)
@ -347,7 +406,6 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
break; break;
case ccmd_saveplayer: case ccmd_saveplayer:
{ {
clusterplayer_t *pl;
float stats[NUM_SPAWN_PARMS]; float stats[NUM_SPAWN_PARMS];
int i; int i;
unsigned char reason = MSG_ReadByte(); unsigned char reason = MSG_ReadByte();
@ -372,7 +430,9 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
switch (reason) switch (reason)
{ {
case 0: //server reports that it accepted the player case 0: //server reports that it accepted the player
if (pl->server && pl->server != s) if (pl->server != s)
{
if (pl->server)
{ //let the previous server know { //let the previous server know
sizebuf_t send; sizebuf_t send;
qbyte send_buf[64]; qbyte send_buf[64];
@ -384,13 +444,25 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
MSG_WriteLong(&send, s->id); MSG_WriteLong(&send, s->id);
MSG_WriteLong(&send, plid); MSG_WriteLong(&send, plid);
pl->server->funcs.InstructSlave(pl->server, &send); pl->server->funcs.InstructSlave(pl->server, &send);
pl->server->activeplayers--;
} }
pl->server = s; 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;
}
break; break;
case 2: //drop case 2: //drop
case 3: //transfer abort case 3: //transfer abort
if (pl->server == s) if (pl->server == s)
{ {
pl->server->activeplayers--;
Con_Printf("%s(%s) dropped\n", pl->name, s->name); Con_Printf("%s(%s) dropped\n", pl->name, s->name);
RemoveLink(&pl->allplayers); RemoveLink(&pl->allplayers);
Z_Free(pl); Z_Free(pl);
@ -409,7 +481,6 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
char *newmap = MSG_ReadStringBuffer(mapname, sizeof(mapname)); char *newmap = MSG_ReadStringBuffer(mapname, sizeof(mapname));
char *claddr = MSG_ReadString(); char *claddr = MSG_ReadString();
char *clguid = MSG_ReadStringBuffer(guid, sizeof(guid)); char *clguid = MSG_ReadStringBuffer(guid, sizeof(guid));
pubsubserver_t *toptr;
memset(&send, 0, sizeof(send)); memset(&send, 0, sizeof(send));
send.data = send_buf; send.data = send_buf;
@ -434,6 +505,9 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
MSG_WriteFloat(&send, MSG_ReadFloat()); MSG_WriteFloat(&send, MSG_ReadFloat());
toptr->funcs.InstructSlave(toptr, &send); toptr->funcs.InstructSlave(toptr, &send);
s->transferingplayers--;
toptr->transferingplayers++;
} }
else else
{ {
@ -475,7 +549,7 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
if (!to) if (!to)
{ {
if (svadr.type != NA_INVALID) 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)); rmsg = va("fredir\n%s", NET_AdrToString(adrbuf, sizeof(adrbuf), &svadr));
Netchan_OutOfBand (NS_SERVER, &cladr, strlen(rmsg), (qbyte *)rmsg); Netchan_OutOfBand (NS_SERVER, &cladr, strlen(rmsg), (qbyte *)rmsg);
} }
@ -487,9 +561,15 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
MSG_WriteLong(&send, plid); MSG_WriteLong(&send, plid);
MSG_WriteString(&send, NET_AdrToString(adrbuf, sizeof(adrbuf), &svadr)); 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; break;
case ccmd_serveraddress: case ccmd_serveraddress:
{ {
@ -586,6 +666,19 @@ void MSV_ReadFromSubServer(pubsubserver_t *s)
void MSV_PollSlaves(void) void MSV_PollSlaves(void)
{ {
pubsubserver_t **link, *s; 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); ) for (link = &subservers; (s=*link); )
{ {
switch(s->funcs.SubServerRead(s)) 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) void SSV_ReadFromControlServer(void)
{ {
int c; int c;
@ -842,7 +945,7 @@ void SSV_UpdateAddresses(void)
qbyte send_buf[MAX_QWMSGLEN]; qbyte send_buf[MAX_QWMSGLEN];
int i; int i;
if (!SSV_IsSubServer()) if (!SSV_IsSubServer() && !msv_loop_from_ss)
return; return;
count = NET_EnumerateAddresses(svs.sockets, con, flags, addr, sizeof(addr)/sizeof(addr[0])); 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.data = send_buf;
send.maxsize = sizeof(send_buf); send.maxsize = sizeof(send_buf);
send.cursize = 2; 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_WriteByte(&send, ccmd_transferplayer);
MSG_WriteLong(&send, cl->userid); MSG_WriteLong(&send, cl->userid);
MSG_WriteString(&send, cl->name); MSG_WriteString(&send, cl->name);
@ -1019,6 +1143,7 @@ qboolean MSV_ClusterLoginReply(netadr_t *legacyclientredirect, unsigned int serv
pl->playerid = playerid; pl->playerid = playerid;
InsertLinkBefore(&pl->allplayers, &clusterplayers); InsertLinkBefore(&pl->allplayers, &clusterplayers);
pl->server = s; pl->server = s;
s->activeplayers++;
MSG_WriteByte(&send, ccmd_takeplayer); MSG_WriteByte(&send, ccmd_takeplayer);
MSG_WriteLong(&send, playerid); MSG_WriteLong(&send, playerid);

View file

@ -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. //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}; char *exts[] = {"maps/%s", "maps/%s.bsp", "maps/%s.cm", "maps/%s.hmp", /*"maps/%s.map",*/ NULL};
int depth, bestdepth; int depth, bestdepth;
flocation_t loc;
time_t filetime;
Q_strncpyz (svs.name, server, sizeof(svs.name)); Q_strncpyz (svs.name, server, sizeof(svs.name));
Q_snprintfz (sv.modelname, sizeof(sv.modelname), exts[0], server); Q_snprintfz (sv.modelname, sizeof(sv.modelname), exts[0], server);
bestdepth = COM_FDepthFile(sv.modelname, false); 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); Q_snprintfz (sv.modelname, sizeof(sv.modelname), exts[i], server);
} }
} }
COM_FCheckExists(sv.modelname);
sv.world.worldmodel = Mod_ForName (sv.modelname, MLV_ERROR); 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) if (!sv.world.worldmodel || sv.world.worldmodel->loadstate != MLS_LOADED)
Sys_Error("\"%s\" is missing or corrupt\n", sv.modelname); Sys_Error("\"%s\" is missing or corrupt\n", sv.modelname);

View file

@ -507,7 +507,7 @@ void SV_DropClient (client_t *drop)
#endif #endif
} }
#ifdef SUBSERVERS #ifdef SUBSERVERS
SSV_SavePlayerStats(drop, 2); SSV_SavePlayerStats(drop, (drop->redirect==2)?1:2);
#endif #endif
#ifdef SVCHAT #ifdef SVCHAT
SV_WipeChat(drop); SV_WipeChat(drop);
@ -4401,8 +4401,9 @@ void SV_CheckTimeouts (void)
#ifdef SUBSERVERS #ifdef SUBSERVERS
SSV_SavePlayerStats(cl, 3); SSV_SavePlayerStats(cl, 3);
#endif #endif
cl->state = cs_free;
SV_BroadcastTPrintf (PRINT_HIGH, "TransferZombie %s timed out\n", cl->name); 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. 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(); Plug_Tick();
#endif #endif
#ifdef SUBSERVERS
MSV_PollSlaves();
#endif
if (sv.state < ss_active || !sv.world.worldmodel) if (sv.state < ss_active || !sv.world.worldmodel)
{ {
#ifdef SUBSERVERS #ifdef SUBSERVERS
if (sv.state == ss_clustermode) if (sv.state == ss_clustermode)
{ {
MSV_PollSlaves();
isidle = !SV_ReadPackets (&delay); isidle = !SV_ReadPackets (&delay);
#ifdef SQL #ifdef SQL
PR_SQLCycle(); 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) 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 //this is the q2 frame number found in the q2 protocol. each packet should contain a new frame or interpolation gets confused