openxr plugin: tweaked - inputs should be working properly now, and are visible to csqc. subject to further breaking changes, however.

_pext_vrinputs: added cvar to enable vr inputs protocol extension allowing vr inputs to be networked to ssqc too. defaults to 0 for now, will be renamed when deemed final.
updates menu: the prompt to enable sources is now more explicit instead of expecting the user to have a clue.
updates menu: added a v3 sources format, which should be more maintainable. not final.
updates menu: try to give reasons why sources might be failing (to help blame ISPs if they try fucking over TTH dns again).
presets menu: no longer closes the instant a preset is chosen. some presets have a couple of modifiers listed. force the demo loop in the background to serve as a preview.
prompts menus: now does word wrapping.
ftemaster: support importing server lists from other master servers (requested by Eukara).
server: try to detect when non-reply inbound packets are blocked by firewalls/nats/etc (using ftemaster to do so).
qcvm: added pointcontentsmask builtin, allowing it to probe more than just world, with fte's full contentbit range instead of just q1 legacy.
qcvm: memfill8 builtin now works on createbuffer() pointers.
qcvm: add missing unsigned ops. Fixed double comparison ops. fixed bug with op_store_i64. added missing OP_LOADP_I64
qcc: added '#pragma framerate RATE' for overriding implicit nextthink durations.
qcc: fixed '#pragma DONT_COMPILE_THIS_FILE' to not screw up comments.
qcc: added __GITURL__ __GITHASH__ __GITDATE__ __GITDATETIME__ __GITDESC__ for any mods that might want to make use of that.
qcc: fix up -Fhashonly a little
setrenderer: support for vulkan gpu enumeration.
rulesets: reworked to support custom rulesets (using hashes to catch haxxors, though still nothing prevents just changing the client to ignore rulesets)
bspx: use our BIH code for the bspx BRUSHLIST lump instead of the older less efficient code.
(static)iqm+obj: these model formats can now be used for the worldmodel (with a suitable .ent file). Also using BIH for much better collision performance.
pmove: tried to optimise PM_NudgePosition, should boost fps in stress tests.
wayland: fix a crash on startup. mousegrabs now works better.
imagetool: uses sdl for previews.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5813 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2021-04-14 05:21:04 +00:00
parent cd50a54a5a
commit b9cd6ec91b
160 changed files with 13078 additions and 5542 deletions

View file

@ -70,15 +70,15 @@ extern cvar_t pr_autocreatecvars;
cvar_t pr_ssqc_memsize = CVARD("pr_ssqc_memsize", "-1", "The ammount of memory available to the QC vm. This has a theoretical maximum of 1gb, but that value can only really be used in 64bit builds. -1 will attempt to use some conservative default, but you may need to increase it. Consider also clearing pr_fixbrokenqccarrays if you need to change this cvar.");
/*cvars purely for compat with others*/
cvar_t pr_imitatemvdsv = CVARFD("pr_imitatemvdsv", "0", CVAR_LATCH, "Enables mvdsv-specific builtins, and fakes identifiers so that mods made for mvdsv can run properly and with the full feature set.");
cvar_t pr_imitatemvdsv = CVARFD("pr_imitatemvdsv", "0", CVAR_MAPLATCH, "Enables mvdsv-specific builtins, and fakes identifiers so that mods made for mvdsv can run properly and with the full feature set.");
/*other stuff*/
cvar_t pr_maxedicts = CVARAFD("pr_maxedicts", "32768", "max_edicts", CVAR_LATCH, "Maximum number of entities spawnable on the map at once. Low values will crash the server on some maps/mods. High values will result in excessive memory useage (see pr_ssqc_memsize). Illegible server messages may occur with old/other clients above 32k. FTE's network protocols have a maximum at a little over 4 million. Please don't ever make a mod that actually uses that many...");
cvar_t pr_maxedicts = CVARAFD("pr_maxedicts", "32768", "max_edicts", CVAR_MAPLATCH, "Maximum number of entities spawnable on the map at once. Low values will crash the server on some maps/mods. High values will result in excessive memory useage (see pr_ssqc_memsize). Illegible server messages may occur with old/other clients above 32k. FTE's network protocols have a maximum at a little over 4 million. Please don't ever make a mod that actually uses that many...");
#ifndef HAVE_LEGACY
cvar_t pr_no_playerphysics = CVARFD("pr_no_playerphysics", "1", CVAR_LATCH, "Prevents support of the 'SV_PlayerPhysics' QC function. This allows servers to prevent needless breakage of player prediction.");
#else
static cvar_t pr_no_playerphysics = CVARFD("pr_no_playerphysics", "0", CVAR_LATCH, "Prevents support of the 'SV_PlayerPhysics' QC function. This allows servers to prevent needless breakage of player prediction.");
static cvar_t pr_no_playerphysics = CVARFD("pr_no_playerphysics", "0", CVAR_MAPLATCH, "Prevents support of the 'SV_PlayerPhysics' QC function. This allows servers to prevent needless breakage of player prediction.");
#endif
static cvar_t pr_no_parsecommand = CVARFD("pr_no_parsecommand", "0", 0, "Provides a way around invalid mod usage of SV_ParseClientCommand, eg xonotic.");
@ -88,7 +88,7 @@ static cvar_t pr_nonetaccess = CVARD("pr_nonetaccess", "0", "Block all direct ac
static cvar_t pr_overridebuiltins = CVAR("pr_overridebuiltins", "1");
static cvar_t pr_compatabilitytest = CVARFD("pr_compatabilitytest", "0", CVAR_LATCH, "Only enables builtins if the extension they are part of was queried.");
static cvar_t pr_compatabilitytest = CVARFD("pr_compatabilitytest", "0", CVAR_MAPLATCH, "Only enables builtins if the extension they are part of was queried.");
cvar_t pr_ssqc_coreonerror = CVAR("pr_coreonerror", "1");
@ -248,15 +248,24 @@ void PR_ResetBuiltins(progstype_t type);
static qcstate_t *PR_CreateThread(pubprogfuncs_t *prinst, float retval, float resumetime, qboolean wait)
{
qcstate_t *state;
edict_t *ed;
state = prinst->parms->memalloc(sizeof(qcstate_t));
state->next = qcthreads;
qcthreads = state;
state->resumetime = resumetime;
state->self = NUM_FOR_EDICT(prinst, PROG_TO_EDICT(prinst, pr_global_struct->self));
state->selfid = PROG_TO_EDICT(prinst, state->self)->xv->uniquespawnid;
state->other = NUM_FOR_EDICT(prinst, PROG_TO_EDICT(prinst, pr_global_struct->other));
state->otherid = PROG_TO_EDICT(prinst, state->other)->xv->uniquespawnid;
if (prinst->edicttable_length)
{
ed = PROG_TO_EDICT(prinst, pr_global_struct->self);
state->self = NUM_FOR_EDICT(prinst, ed);
state->selfid = (ed->ereftype==ER_ENTITY)?ed->xv->uniquespawnid:0;
ed = PROG_TO_EDICT(prinst, pr_global_struct->other);
state->other = NUM_FOR_EDICT(prinst, ed);
state->otherid = (ed->ereftype==ER_ENTITY)?ed->xv->uniquespawnid:0;
}
else //allows us to call this during init().
state->self = state->other = state->selfid = state->otherid = 0;
state->thread = prinst->Fork(prinst);
state->waiting = wait;
state->returnval = retval;
@ -906,89 +915,117 @@ void PR_LoadGlabalStruct(qboolean muted)
globalptrs_t *pr_globals = pr_global_ptrs;
memset(pr_global_ptrs, 0, sizeof(*pr_global_ptrs));
#define globalfloat(need,name) (pr_globals)->name = (pvec_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pvec_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalint(need,name) (pr_globals)->name = (pint_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pint_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalstring(need,name) (pr_globals)->name = (string_t*)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static string_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalvec(need,name) (pr_globals)->name = (pvec3_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pvec3_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalfunc(need,name) (pr_globals)->name = (func_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (!(pr_globals)->name) {static func_t stripped##name; stripped##name = PR_FindFunction(svprogfuncs, #name, 0); if (stripped##name) (pr_globals)->name = &stripped##name; else if (need && !muted) Con_DPrintf("Could not find function \""#name"\" in progs\n"); }
// globalint(pad);
globalint (true, self); //we need the qw ones, but any in standard quake and not quakeworld, we don't really care about.
globalint (true, other);
globalint (true, world);
globalfloat (true, time);
globalfloat (true, frametime);
globalint (false, newmis); //not always in nq.
globalfloat (false, force_retouch);
globalstring (true, mapname);
globalfloat (false, deathmatch);
globalfloat (false, coop);
globalfloat (false, teamplay);
globalfloat (false, serverflags);
globalfloat (false, total_secrets);
globalfloat (false, total_monsters);
globalfloat (false, found_secrets);
globalfloat (false, killed_monsters);
globalvec (true, v_forward);
globalvec (true, v_up);
globalvec (true, v_right);
globalfloat (false, trace_allsolid);
globalfloat (false, trace_startsolid);
globalfloat (false, trace_fraction);
globalvec (false, trace_endpos);
globalvec (false, trace_plane_normal);
globalfloat (false, trace_plane_dist);
globalint (true, trace_ent);
globalfloat (false, trace_inopen);
globalfloat (false, trace_inwater);
//we need the qw ones, but any in standard quake and not quakeworld, we don't really care about.
#ifdef HAVE_LEGACY
globalfloat (false, trace_endcontentsf);
#define ssqcglobals_legacy \
globalfloat (false, trace_endcontentsf) \
globalfloat (false, trace_surfaceflagsf) \
globalstring (false, trace_dphittexturename) \
globalfloat (false, trace_dpstartcontents) \
globalfloat (false, trace_dphitcontents) \
globalfloat (false, trace_dphitq3surfaceflags)
#else
#define ssqcglobals_legacy
#endif
globalint (false, trace_endcontentsi);
#ifdef HAVE_LEGACY
globalfloat (false, trace_surfaceflagsf);
#endif
globalint (false, trace_surfaceflagsi);
globalstring (false, trace_surfacename);
globalint (false, trace_brush_id);
globalint (false, trace_brush_faceid);
globalint (false, trace_surface_id);
globalint (false, trace_bone_id);
globalint (false, trace_triangle_id);
#ifdef HAVE_LEGACY
globalstring (false, trace_dphittexturename);
globalfloat (false, trace_dpstartcontents);
globalfloat (false, trace_dphitcontents);
globalfloat (false, trace_dphitq3surfaceflags);
#endif
globalfloat (false, cycle_wrapped);
globalint (false, msg_entity);
globalfunc (false, main);
globalfunc (true, StartFrame);
globalfunc (true, PlayerPreThink);
globalfunc (true, PlayerPostThink);
globalfunc (true, ClientKill);
globalfunc (true, ClientConnect);
globalfunc (true, PutClientInServer);
globalfunc (true, ClientDisconnect);
globalfunc (false, SetNewParms);
globalfunc (false, SetChangeParms);
globalfloat (false, cycle_wrapped);
globalfloat (false, dimension_send);
globalfloat (false, dimension_default);
globalfloat (false, clientcommandframe);
globalfloat (false, input_timelength);
globalfloat (false, input_impulse);
globalvec (false, input_angles);
globalvec (false, input_movevalues);
globalfloat (false, input_buttons);
globalint (false, serverid);
globalvec (false, global_gravitydir);
globalstring (false, parm_string);
#define ssqcglobals \
globalentity (true, self) \
globalentity (true, other) \
globalentity (true, world) \
globalfloat (true, time) \
globalfloat (true, frametime) \
globalentity (false, newmis) \
globalfloat (false, force_retouch) \
globalstring (true, mapname) \
globalfloat (false, deathmatch) \
globalfloat (false, coop) \
globalfloat (false, teamplay) \
globalfloat (false, serverflags) \
globalfloat (false, total_secrets) \
globalfloat (false, total_monsters) \
globalfloat (false, found_secrets) \
globalfloat (false, killed_monsters) \
globalvec (true, v_forward) \
globalvec (true, v_up) \
globalvec (true, v_right) \
globalfloat (false, trace_allsolid) \
globalfloat (false, trace_startsolid) \
globalfloat (false, trace_fraction) \
globalvec (false, trace_endpos) \
globalvec (false, trace_plane_normal) \
globalfloat (false, trace_plane_dist) \
globalentity (true, trace_ent) \
globalfloat (false, trace_inopen) \
globalfloat (false, trace_inwater) \
globalint (false, trace_endcontentsi) \
globalint (false, trace_surfaceflagsi) \
globalstring (false, trace_surfacename) \
globalint (false, trace_brush_id) \
globalint (false, trace_brush_faceid) \
globalint (false, trace_surface_id) \
globalint (false, trace_bone_id) \
globalint (false, trace_triangle_id) \
\
globalfloat (false, cycle_wrapped) \
globalentity (false, msg_entity) \
globalfunc (false, main, "void()") \
globalfunc (true, StartFrame, "void()") \
globalfunc (true, PlayerPreThink, "void()") \
globalfunc (true, PlayerPostThink, "void()") \
globalfunc (true, ClientKill, "void()") \
globalfunc (true, ClientConnect, "void()") \
globalfunc (true, PutClientInServer, "void()") \
globalfunc (true, ClientDisconnect, "void()") \
globalfunc (false, SetNewParms, "void()") \
globalfunc (false, SetChangeParms, "void()") \
globalfloat (false, cycle_wrapped) \
globalfloat (false, dimension_send) \
globalfloat (false, dimension_default) \
\
globalfloat (false, clientcommandframe) \
globalfloat (false, input_timelength) \
globalfloat (false, input_impulse) \
globalvec (false, input_angles) \
globalvec (false, input_movevalues) \
globalfloat (false, input_buttons) \
globaluint (false, input_weapon) \
globalfloat (false, input_lightlevel) \
globalvec (false, input_cursor_screen) \
globalvec (false, input_cursor_trace_start) \
globalvec (false, input_cursor_trace_endpos) \
globalfloat (false, input_cursor_entitynumber) \
globaluint (false, input_head_status) \
globalvec (false, input_head_origin) \
globalvec (false, input_head_angles) \
globalvec (false, input_head_velocity) \
globalvec (false, input_head_avelocity) \
globaluint (false, input_left_status) \
globalvec (false, input_left_origin) \
globalvec (false, input_left_angles) \
globalvec (false, input_left_velocity) \
globalvec (false, input_left_avelocity) \
globaluint (false, input_right_status) \
globalvec (false, input_right_origin) \
globalvec (false, input_right_angles) \
globalvec (false, input_right_velocity) \
globalvec (false, input_right_avelocity) \
\
globalint (false, serverid) \
globalvec (false, global_gravitydir) \
globalstring (false, parm_string) \
ssqcglobals_legacy
#define globalfloat(need,name) (pr_globals)->name = (pvec_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pvec_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalentity(need,name) (pr_globals)->name = (pint_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pint_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalint(need,name) (pr_globals)->name = (pint_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pint_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globaluint(need,name) (pr_globals)->name = (puint_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static puint_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalstring(need,name) (pr_globals)->name = (string_t*)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static string_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalvec(need,name) (pr_globals)->name = (pvec3_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (need && !(pr_globals)->name) {static pvec3_t fallback##name; (pr_globals)->name = &fallback##name; if (!muted) Con_DPrintf("Could not find \""#name"\" export in progs\n");}
#define globalfunc(need,name,typestr) (pr_globals)->name = (func_t *)PR_FindGlobal(svprogfuncs, #name, 0, NULL); if (!(pr_globals)->name) {static func_t stripped##name; stripped##name = PR_FindFunction(svprogfuncs, #name, 0); if (stripped##name) (pr_globals)->name = &stripped##name; else if (need && !muted) Con_DPrintf("Could not find function \""#name"\" in progs\n"); }
ssqcglobals
#undef globalfloat
#undef globalentity
#undef globalint
#undef globaluint
#undef globalstring
#undef globalvec
#undef globalfunc
@ -1617,6 +1654,11 @@ void PR_Init(void)
#ifdef SQL
SQL_Init();
#endif
{ //hide this extension, because AD maps often fail to signal that its not needed, resulting in horrendous stalls when people fire shotguns.
static cvar_t var = CVARFD("pr_ext_dp_qc_getsurface", "", CVAR_ARCHIVE, "Set to 0 to block detection of the extension");
Cvar_Register (&var, "QC Extensions");
}
}
void SVQ1_CvarChanged(cvar_t *var)
@ -4891,6 +4933,15 @@ static void QCBUILTIN PF_pointcontents (pubprogfuncs_t *prinst, struct globalvar
else
G_FLOAT(OFS_RETURN) = Q1CONTENTS_EMPTY;
}
static void QCBUILTIN PF_pointcontentsmask (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *w = prinst->parms->user;
VM_VECTORARG(v, OFS_PARM0);
if (prinst->callargc < 1 || G_FLOAT(OFS_PARM1))
G_UINT(OFS_RETURN) = World_PointContentsWorldOnly(w, v);
else
G_UINT(OFS_RETURN) = World_PointContentsAllBSPs(w, v);
}
/*
=============
@ -7092,8 +7143,8 @@ void PR_SQLCycle(void)
lh_extension_t *checkfteextensioncl(int mask, const char *name) //true if the cient extension mask matches an extension name
/*
static qc_extension_t *checkfteextensioncl(int mask, const char *name) //true if the cient extension mask matches an extension name
{
int i;
for (i = 0; i < 32; i++)
@ -7108,15 +7159,15 @@ lh_extension_t *checkfteextensioncl(int mask, const char *name) //true if the ci
return NULL;
}
lh_extension_t *checkfteextensionsv(const char *name) //true if the server supports an protocol extension.
static qc_extension_t *checkfteextensionsv(const char *name) //true if the server supports an protocol extension.
{
return checkfteextensioncl(Net_PextMask(PROTOCOL_VERSION_FTE1, false), name);
}
lh_extension_t *checkextension(const char *name)
static qc_extension_t *checkextension(const char *name)
{
int i;
for (i = 32; i < QSG_Extensions_count; i++)
for (i = 0; i < QSG_Extensions_count; i++)
{
if (!QSG_Extensions[i].name)
continue;
@ -7124,7 +7175,7 @@ lh_extension_t *checkextension(const char *name)
return &QSG_Extensions[i];
}
return NULL;
}
}*/
/*
=================
@ -7137,58 +7188,68 @@ checkextension(string extensionname, [entity client])
*/
static void QCBUILTIN PF_checkextension (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
lh_extension_t *ext = NULL;
qc_extension_t *ext;
const char *s = PR_GetStringOfs(prinst, OFS_PARM0);
int clnum = (svprogfuncs->callargc == 2)?NUM_FOR_EDICT(prinst, G_EDICT(prinst, OFS_PARM1)):0;
int i;
cvar_t *v;
char *cvn;
ext = checkextension(s);
if (!ext)
G_FLOAT(OFS_RETURN) = false;
for (i = 0; i < QSG_Extensions_count; i++)
{
if (svprogfuncs->callargc == 2)
if (!stricmp(s, QSG_Extensions[i].name))
{
int clnum = NUM_FOR_EDICT(prinst, G_EDICT(prinst, OFS_PARM1));
if (clnum >= 1 && clnum <= sv.allocated_client_slots) //valid client as second parameter
ext = &QSG_Extensions[i];
if (ext->extensioncheck && clnum >= 1 && clnum <= sv.allocated_client_slots)
{
ext = checkfteextensioncl(svs.clients[clnum-1].fteprotocolextensions, s);
client_t *cl = &svs.clients[clnum-1];
extcheck_t check = {cl->fteprotocolextensions, cl->fteprotocolextensions2};
if (!ext->extensioncheck(&check))
return; //blocked by some setting somewhere, somehow.
}
else if (clnum == 0)
ext = checkfteextensionsv(s);
else
else if (ext->extensioncheck)
{
//ent wasn't valid
Con_Printf("PF_CheckExtension with invalid client number");
extcheck_t check = {Net_PextMask(PROTOCOL_VERSION_FTE1, false), Net_PextMask(PROTOCOL_VERSION_FTE2, false)};
if (!ext->extensioncheck(&check))
return; //blocked by some setting somewhere, somehow.
}
}
else
{
ext = checkfteextensionsv(s);
//user cvar blocks. note that the qc isn't meant to be aware of them, we use CVAR_NOUNSAFEEXPAND so mods don't can't bypass checkextension in a non-portable way.
cvn = va("pr_ext_%s", ext->name);
for (i = 0; cvn[i]; i++)
if (cvn[i] >= 'A' && cvn[i] <= 'Z')
cvn[i] = 'a' + (cvn[i]-'A');
v = Cvar_Get2(cvn, "1", CVAR_ARCHIVE|CVAR_NOUNSAFEEXPAND, "Set to 0 to block detection of the extension, or 1 to enable it.", "QC Extensions");
if (v && !v->ival)
{
if (!*v->string && (v->flags&CVAR_POINTER))
; //user-defined cvars arn't explicitly defined elsewhere. interpret them as 1 when empty, because they're probably old ones that we're no longer trying to block...
else
break;
}
for (i = 0; i < ext->numbuiltins; i++)
{
if (*ext->builtinnames[i] == '.' || *ext->builtinnames[i] == '#')
{
/*field or global*/
}
else if (!PR_EnableEBFSBuiltin(ext->builtinnames[i], 0))
{
Con_Printf("Failed to initialise builtin \"%s\" for extension \"%s\"\n", ext->builtinnames[i], s);
return; //whoops, we failed.
}
}
G_FLOAT(OFS_RETURN) = true;
Con_DPrintf("Extension %s is supported\n", s);
break;
}
}
if (ext)
{
int i;
G_FLOAT(OFS_RETURN) = false;
for (i = 0; i < ext->numbuiltins; i++)
{
if (*ext->builtinnames[i] == '.' || *ext->builtinnames[i] == '#')
{
/*field or global*/
}
else if (!PR_EnableEBFSBuiltin(ext->builtinnames[i], 0))
{
Con_Printf("Failed to initialise builtin \"%s\" for extension \"%s\"\n", ext->builtinnames[i], s);
return; //whoops, we failed.
}
}
if (ext->queried)
*ext->queried = true;
G_FLOAT(OFS_RETURN) = true;
Con_DPrintf("Extension %s is supported\n", s);
}
else
G_FLOAT(OFS_RETURN) = false;
}
static void QCBUILTIN PF_checkbuiltin (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
@ -10291,11 +10352,6 @@ qboolean SV_RunFullQCMovement(client_t *client, usercmd_t *ucmd)
V_CalcRoll (sv_player->v->angles, sv_player->v->velocity)*4;
}
//prethink should be consistant with what the engine normally does
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, client->edict);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPreThink);
WPhys_RunThink (&sv.world, (wedict_t*)client->edict);
@ -10326,6 +10382,81 @@ qboolean SV_RunFullQCMovement(client_t *client, usercmd_t *ucmd)
(pr_global_struct->input_movevalues)[1] = ucmd->sidemove;
(pr_global_struct->input_movevalues)[2] = ucmd->upmove;
pr_global_struct->input_buttons = ucmd->buttons;
if (pr_global_ptrs->input_weapon)
pr_global_struct->input_weapon = ucmd->weapon;
if (pr_global_ptrs->input_lightlevel)
pr_global_struct->input_lightlevel = ucmd->lightlevel;
if (pr_global_ptrs->input_cursor_screen)
VectorSet(pr_global_struct->input_cursor_screen, ucmd->cursor_screen[0], ucmd->cursor_screen[1], 0);
if (pr_global_ptrs->input_cursor_trace_start)
VectorCopy(ucmd->cursor_start, pr_global_struct->input_cursor_trace_start);
if (pr_global_ptrs->input_cursor_trace_endpos)
VectorCopy(ucmd->cursor_impact, pr_global_struct->input_cursor_trace_endpos);
if (pr_global_ptrs->input_cursor_entitynumber)
pr_global_struct->input_cursor_entitynumber = ucmd->cursor_entitynumber;
if (pr_global_ptrs->input_head_status)
pr_global_struct->input_head_status = ucmd->vr[VRDEV_HEAD].status;
if (pr_global_ptrs->input_head_origin)
VectorCopy(ucmd->vr[VRDEV_HEAD].origin, pr_global_struct->input_head_origin);
if (pr_global_ptrs->input_head_velocity)
VectorCopy(ucmd->vr[VRDEV_HEAD].velocity, pr_global_struct->input_head_velocity);
if (pr_global_ptrs->input_head_angles)
{
(pr_global_struct->input_head_angles)[0] = SHORT2ANGLE(ucmd->vr[VRDEV_HEAD].angles[0]);
(pr_global_struct->input_head_angles)[1] = SHORT2ANGLE(ucmd->vr[VRDEV_HEAD].angles[1]);
(pr_global_struct->input_head_angles)[2] = SHORT2ANGLE(ucmd->vr[VRDEV_HEAD].angles[2]);
}
if (pr_global_ptrs->input_head_avelocity)
{
(pr_global_struct->input_head_avelocity)[0] = SHORT2ANGLE(ucmd->vr[VRDEV_HEAD].avelocity[0]);
(pr_global_struct->input_head_avelocity)[1] = SHORT2ANGLE(ucmd->vr[VRDEV_HEAD].avelocity[1]);
(pr_global_struct->input_head_avelocity)[2] = SHORT2ANGLE(ucmd->vr[VRDEV_HEAD].avelocity[2]);
}
if (pr_global_ptrs->input_left_status)
pr_global_struct->input_left_status = ucmd->vr[VRDEV_LEFT].status;
if (pr_global_ptrs->input_left_origin)
VectorCopy(ucmd->vr[VRDEV_LEFT].origin, pr_global_struct->input_left_origin);
if (pr_global_ptrs->input_left_velocity)
VectorCopy(ucmd->vr[VRDEV_LEFT].velocity, pr_global_struct->input_left_velocity);
if (pr_global_ptrs->input_left_angles)
{
(pr_global_struct->input_left_angles)[0] = SHORT2ANGLE(ucmd->vr[VRDEV_LEFT].angles[0]);
(pr_global_struct->input_left_angles)[1] = SHORT2ANGLE(ucmd->vr[VRDEV_LEFT].angles[1]);
(pr_global_struct->input_left_angles)[2] = SHORT2ANGLE(ucmd->vr[VRDEV_LEFT].angles[2]);
}
if (pr_global_ptrs->input_left_avelocity)
{
(pr_global_struct->input_left_avelocity)[0] = SHORT2ANGLE(ucmd->vr[VRDEV_LEFT].avelocity[0]);
(pr_global_struct->input_left_avelocity)[1] = SHORT2ANGLE(ucmd->vr[VRDEV_LEFT].avelocity[1]);
(pr_global_struct->input_left_avelocity)[2] = SHORT2ANGLE(ucmd->vr[VRDEV_LEFT].avelocity[2]);
}
if (pr_global_ptrs->input_right_status)
pr_global_struct->input_right_status = ucmd->vr[VRDEV_RIGHT].status;
if (pr_global_ptrs->input_right_origin)
VectorCopy(ucmd->vr[VRDEV_RIGHT].origin, pr_global_struct->input_right_origin);
if (pr_global_ptrs->input_right_velocity)
VectorCopy(ucmd->vr[VRDEV_RIGHT].velocity, pr_global_struct->input_right_velocity);
if (pr_global_ptrs->input_right_angles)
{
(pr_global_struct->input_right_angles)[0] = SHORT2ANGLE(ucmd->vr[VRDEV_RIGHT].angles[0]);
(pr_global_struct->input_right_angles)[1] = SHORT2ANGLE(ucmd->vr[VRDEV_RIGHT].angles[1]);
(pr_global_struct->input_right_angles)[2] = SHORT2ANGLE(ucmd->vr[VRDEV_RIGHT].angles[2]);
}
if (pr_global_ptrs->input_right_avelocity)
{
(pr_global_struct->input_right_avelocity)[0] = SHORT2ANGLE(ucmd->vr[VRDEV_RIGHT].avelocity[0]);
(pr_global_struct->input_right_avelocity)[1] = SHORT2ANGLE(ucmd->vr[VRDEV_RIGHT].avelocity[1]);
(pr_global_struct->input_right_avelocity)[2] = SHORT2ANGLE(ucmd->vr[VRDEV_RIGHT].avelocity[2]);
}
//prethink should be consistant with what the engine normally does
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, client->edict);
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPreThink);
WPhys_RunThink (&sv.world, (wedict_t*)client->edict);
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, client->edict);
PR_ExecuteProgram(svprogfuncs, gfuncs.RunClientCommand);
@ -10719,7 +10850,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
{"ceil", PF_ceil, 38, 38, 38, 0, D("float(float)", "Rounds the given float upwards, even when negative.")},
{"qtest_canreach", PF_Ignore, 39, 0, 0, 0, "DEP float(vector v)"}, // QTest builtin called in effectless statement
{"checkbottom", PF_checkbottom, 40, 40, 40, 0, D("float(entity ent)", "Expensive checks to ensure that the entity is actually sitting on something solid, returns true if it is.")},
{"pointcontents", PF_pointcontents, 41, 41, 41, 0, D("float(vector pos)", "Checks the given point to see what is there. Returns one of the SOLID_* constants. Just because a spot is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests.")},
{"pointcontents", PF_pointcontents, 41, 41, 41, 0, D("float(vector pos)", "Checks the given point to see what is there. Returns one of the CONTENTS_* constants. Just because a point is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests.")},
{"pointcontentsmask",PF_pointcontentsmask,0, 0, 0, 0, D("__uint(vector pos, optional float worldonly=1)", "Checks the given point to see what is there. Returns a mask of the CONTENTBIT_* constants. Just because a point is empty does not mean that the player can stand there due to the size of the player - use tracebox for such tests.")},
// {"qtest_stopsound", NULL, 42}, // defined QTest builtin that is never called
{"fabs", PF_fabs, 43, 43, 43, 0, D("float(float)", "Removes the sign of the float, making it positive if it is negative.")},
{"aim", PF_aim, 44, 44, 44, 0, D("vector(entity player, float missilespeed)", "Returns a tweaked copy of the v_forward vector (must be set! ie: makevectors(player.v_angle) ). This is important for keyboard users (that don't want to have to look up/down the whole time), as well as joystick users (who's aim is otherwise annoyingly imprecise). Only the upwards component of the result will differ from the value of v_forward. The builtin will select the enemy closest to the crosshair within the angle of acos(sv_aim).")}, //44
@ -11859,13 +11991,6 @@ void PR_ResetBuiltins(progstype_t type) //fix all nulls to PF_FIXME and add any
}
}
for (i = 0; i < QSG_Extensions_count; i++)
{
if (QSG_Extensions[i].queried)
*QSG_Extensions[i].queried = false;
}
if (type == PROG_QW)
{
//this conflicts with dp's logarithm builtin.
@ -11907,7 +12032,7 @@ void PR_SVExtensionList_f(void)
int i, j;
int ebi;
int bi;
lh_extension_t *extlist;
qc_extension_t *extlist;
#define SHOW_ACTIVEEXT 1
#define SHOW_ACTIVEBI 2
@ -12573,11 +12698,6 @@ void PR_DumpPlatform_f(void)
{"input_buttons", "float", QW|NQ},
{"input_impulse", "float", QW|NQ},
{"input_cursor_screen", "vector", CS/*|QW|NQ*/},
{"input_cursor_trace_start", "vector", CS/*|QW|NQ*/},
{"input_cursor_trace_endpos", "vector", CS/*|QW|NQ*/},
{"input_cursor_trace_entnum", "float", CS/*|QW|NQ*/},
{"trace_endcontents", "int", QW|NQ|CS},
{"trace_surfaceflags", "int", QW|NQ|CS},
// {"trace_surfacename", "string", QW|NQ|CS},
@ -12742,6 +12862,21 @@ void PR_DumpPlatform_f(void)
{"drawfont", "float", CS|MENU, "Allows you to choose exactly which font is to be used to draw text. Fonts can be registered/allocated with the loadfont builtin."},
{"FONT_DEFAULT", "const float", CS|MENU, NULL, 0},
#define globalfloat(need,name) {#name, "float", QW|NQ, "ssqc global"},
#define globalentity(need,name) {#name, "entity", QW|NQ, "ssqc global"},
#define globalint(need,name) {#name, "int", QW|NQ, "ssqc global"},
#define globaluint(need,name) {#name, "unsigned int", QW|NQ, "ssqc global"},
#define globalstring(need,name) {#name, "string", QW|NQ, "ssqc global"},
#define globalvec(need,name) {#name, "vector", QW|NQ, "ssqc global"},
#define globalfunc(need,name,typestr) {#name, typestr, QW|NQ, "ssqc global"},
ssqcglobals
#undef globalfloat
#undef globalentity
#undef globalint
#undef globaluint
#undef globalstring
#undef globalvec
#undef globalfunc
#define globalfloatdep(name,depreason) {#name, "DEP(\""depreason"\") float", CS},
#define globalfunction(name,typestr) {#name, typestr, CS},
@ -12750,6 +12885,7 @@ void PR_DumpPlatform_f(void)
#define globalvector(name) {#name, "vector", CS},
#define globalstring(name) {#name, "string", CS},
#define globalint(name) {#name, "int", CS},
#define globaluint(name) {#name, "unsigned int", CS},
csqcglobals
#undef globalfunction
#undef globalfloat
@ -12757,6 +12893,7 @@ void PR_DumpPlatform_f(void)
#undef globalvector
#undef globalstring
#undef globalint
#undef globaluint
#undef globalfloatdep
{"TRUE", "const float", ALL, NULL, 1},
@ -13296,6 +13433,7 @@ void PR_DumpPlatform_f(void)
{"LFIELD_RADIUSDECAY", "const float", CS, NULL, lfield_radiusdecay},
{"LFIELD_STYLESTRING", "const float", CS, NULL, lfield_stylestring},
{"LFIELD_NEARCLIP", "const float", CS, NULL, lfield_nearclip},
{"LFIELD_OWNER", "const float", CS, NULL, lfield_owner},
{"LFLAG_NORMALMODE", "const float", CS, NULL, LFLAG_NORMALMODE},
{"LFLAG_REALTIMEMODE", "const float", CS, NULL, LFLAG_REALTIMEMODE},
@ -13607,12 +13745,27 @@ void PR_DumpPlatform_f(void)
VFS_PRINTF(f, "\n");
for (idx = 0; knowndefs[idx].name; idx++)
{ //system fields
if (!strcmp(knowndefs[idx].name, "end_sys_fields"))
break;
}
for (i = 0; knowndefs[i].name; i++)
{
for (j = 0; j < i; j++)
{
if (!strcmp(knowndefs[i].name, knowndefs[j].name))
{ //promote the first one to the relevant module...
if (j >= idx)
if (!strcmp(knowndefs[i].type, knowndefs[j].type))
if (knowndefs[i].desc==knowndefs[j].desc||!knowndefs[i].desc||!knowndefs[j].desc||!strcmp(knowndefs[i].desc, knowndefs[j].desc))
if (knowndefs[i].valuestr==knowndefs[j].valuestr||(knowndefs[i].valuestr&&knowndefs[j].valuestr&&!strcmp(knowndefs[i].valuestr, knowndefs[j].valuestr)))
if (knowndefs[i].value==knowndefs[j].value)
{
knowndefs[j].module |= knowndefs[i].module; /*give the first def the later one's module flag*/
}
knowndefs[i].module &= ~knowndefs[j].module; /*clear the flag on the later dupe def*/
}
}
}

View file

@ -102,12 +102,35 @@ typedef struct nqglobalvars_s
pvec_t *dimension_default;
pvec_t *physics_mode;
pvec_t *clientcommandframe;
pvec_t *input_timelength;
pvec_t *input_impulse;
pvec3_t *input_angles;
pvec3_t *input_movevalues;
pvec_t *input_buttons;
puint_t *input_weapon;
pvec_t *input_lightlevel;
pvec3_t *input_cursor_screen;
pvec3_t *input_cursor_trace_start;
pvec3_t *input_cursor_trace_endpos;
pvec_t *input_cursor_entitynumber;
puint_t *input_head_status;
pvec3_t *input_head_origin;
pvec3_t *input_head_angles;
pvec3_t *input_head_velocity;
pvec3_t *input_head_avelocity;
puint_t *input_left_status;
pvec3_t *input_left_origin;
pvec3_t *input_left_angles;
pvec3_t *input_left_velocity;
pvec3_t *input_left_avelocity;
puint_t *input_right_status;
pvec3_t *input_right_origin;
pvec3_t *input_right_angles;
pvec3_t *input_right_velocity;
pvec3_t *input_right_avelocity;
pvec3_t *global_gravitydir;
pvec_t *spawnparamglobals[NUM_SPAWN_PARMS];
string_t *parm_string;

View file

@ -1089,6 +1089,9 @@ extern cvar_t sv_antilag_frac;
void SV_Master_ReResolve(void);
void SV_Master_Shutdown(void);
void SV_Master_Heartbeat (void);
qboolean SV_Master_AddressIsMaster(netadr_t *adr);
void SV_Master_HeartbeatResponse(netadr_t *adr, const char *challenge);
extern cvar_t sv_antilag_frac;
extern cvar_t pr_ssqc_progs;
extern cvar_t sv_csqcdebug;
@ -1381,8 +1384,8 @@ void SV_SendClientPrespawnInfo(client_t *client);
void SV_ClientProtocolExtensionsChanged(client_t *client);
//sv_master.c
void SVM_Think(int port);
vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname);
float SVM_Think(int port);
vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname, const char **mimetype, const char *query);
void SVM_AddBrokerGame(const char *brokerid, const char *info);
void SVM_RemoveBrokerGame(const char *brokerid);

View file

@ -40,7 +40,7 @@ qboolean SV_MayCheat(void)
cvar_t sv_autooffload = CVARD("sv_autooffload", "0", "Automatically start the server in a separate process, so that sporadic or persistent gamecode slowdowns do not affect visual framerates (equivelent to the mapcluster command). Note: Offloaded servers have separate cvar+command states which may complicate usage.");
#endif
extern cvar_t cl_warncmd;
cvar_t sv_cheats = CVARF("sv_cheats", "0", CVAR_LATCH);
cvar_t sv_cheats = CVARF("sv_cheats", "0", CVAR_MAPLATCH);
extern redirect_t sv_redirected;
extern cvar_t sv_public;
@ -833,11 +833,11 @@ void SV_Map_f (void)
{
cvar_t *gametype;
Cvar_ApplyLatches(CVAR_LATCH);
Cvar_ApplyLatches(CVAR_MAPLATCH, false);
host_mapname.flags |= CVAR_SERVERINFO;
gametype = Cvar_Get("g_gametype", "", CVAR_LATCH|CVAR_SERVERINFO, "Q3 compatability");
gametype = Cvar_Get("g_gametype", "", CVAR_MAPLATCH|CVAR_SERVERINFO, "Q3 compatability");
// gametype->callback = gtcallback;
/* map_restart doesn't need to handle gametype changes - eukara */
@ -1462,7 +1462,7 @@ static void SV_FilterIP_f (void)
//if no flags were specified,
if (!proto.banflags)
{
if (!strcmp(Cmd_Argv(0), "ban"))
if (!strcmp(Cmd_Argv(0), "ban"))
proto.banflags = BAN_BAN;
else
proto.banflags = filterban.ival?BAN_BAN:BAN_PERMIT;

View file

@ -2325,7 +2325,7 @@ void SV_WritePlayerToClient(sizebuf_t *msg, clstate_t *ent)
else
cmd.impulse = 0;
MSG_WriteDeltaUsercmd (msg, &nullcmd, &cmd);
MSGQW_WriteDeltaUsercmd (msg, &nullcmd, &cmd);
}
if (ent->velocity)

View file

@ -590,17 +590,12 @@ unsigned SV_CheckModel(char *mdl)
size_t fsize;
qbyte *buf;
unsigned short crc;
// int len;
buf = (qbyte *)FS_LoadMallocFile (mdl, &fsize);
if (!buf)
return 0;
crc = QCRC_Block(buf, fsize);
// for (len = com_filesize; len; len--, buf++)
// CRC_ProcessByte(&crc, *buf);
crc = CalcHashInt(&hash_crc16, buf, fsize);
BZ_Free(buf);
return crc;
}
@ -955,7 +950,7 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents,
#define SCR_SetLoadingFile(s)
#endif
Cvar_ApplyLatches(CVAR_LATCH);
Cvar_ApplyLatches(CVAR_MAPLATCH, false);
//work out the gamespeed
//reset the server time.
@ -1033,11 +1028,16 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents,
}
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);
// if (sv.world.worldmodel->type != mod_brush && sv.world.worldmodel->type != mod_heightmap)
if (!sv.world.worldmodel->funcs.NativeTrace && !sv.world.worldmodel->funcs.PointContents)
Sys_Error("\"%s\" is not a bsp model\n", sv.modelname);
else if (!Mod_GetEntitiesString(sv.world.worldmodel))
Sys_Error("\"%s\" has no entity data\n", sv.modelname);
}
if (!sv.world.worldmodel || sv.world.worldmodel->loadstate != MLS_LOADED)
Sys_Error("\"%s\" is missing or corrupt\n", sv.modelname);
if (sv.world.worldmodel->type != mod_brush && sv.world.worldmodel->type != mod_heightmap)
Sys_Error("\"%s\" is not a bsp model\n", sv.modelname);
sv.state = ss_dead;
#ifndef SERVERONLY
@ -1054,7 +1054,9 @@ void SV_SpawnServer (const char *server, const char *startspot, qboolean noents,
SCR_SetLoadingFile("gamecode");
#endif
if (sv.world.worldmodel->fromgame == fg_doom)
if (sv.world.worldmodel->type != mod_brush)
InfoBuf_SetStarKey(&svs.info, "*bspversion", "");
else if (sv.world.worldmodel->fromgame == fg_doom)
InfoBuf_SetStarKey(&svs.info, "*bspversion", "1");
else if (sv.world.worldmodel->fromgame == fg_halflife)
InfoBuf_SetStarKey(&svs.info, "*bspversion", "30");
@ -1469,7 +1471,7 @@ MSV_OpenUserDatabase();
eval = PR_FindGlobal(svprogfuncs, "deathmatch", 0, NULL);
if (eval) eval->_float = deathmatch.value;
}
cv = Cvar_Get("randomclass", "0", CVAR_LATCH, "Hexen2");
cv = Cvar_Get("randomclass", "0", CVAR_MAPLATCH, "Hexen2");
eval = PR_FindGlobal(svprogfuncs, "randomclass", 0, NULL);
if (eval && cv) eval->_float = cv->value;

View file

@ -135,6 +135,7 @@ cvar_t sv_use_dns = CVARD("sv_use_dns", "", "Performs a reverse-dns lookup in
extern cvar_t net_enable_dtls;
cvar_t sv_reportheartbeats = CVARD("sv_reportheartbeats", "2", "Print a notice each time a heartbeat is sent to a master server. When set to 2, the message will be displayed once.");
cvar_t sv_heartbeat_interval = CVARD("sv_heartbeat_interval", "110", "Interval between heartbeats. Low values are abusive, high values may cause NAT/ghost issues.");
cvar_t sv_heartbeat_checks = CVARD("sv_heartbeat_checks", "1", "Report when sv_public 1 fails due to PROBABLE router/NAT issues.");
cvar_t sv_highchars = CVAR("sv_highchars", "1");
cvar_t sv_maxrate = CVARCD("sv_maxrate", "50000", CvarPostfixKMG, "This controls the maximum number of bytes any indivual player may receive (when not downloading). The individual user's rate will also be controlled by the user's rate cvar.");
cvar_t sv_maxdrate = CVARAFCD("sv_maxdrate", "500000",
@ -142,7 +143,7 @@ cvar_t sv_maxdrate = CVARAFCD("sv_maxdrate", "500000",
cvar_t sv_minping = CVARFD("sv_minping", "", CVAR_SERVERINFO, "Simulate fake lag for any players with a ping under the value specified here. Value is in milliseconds.");
cvar_t sv_bigcoords = CVARFD("sv_bigcoords", "1", 0, "Uses floats for coordinates instead of 16bit values.\nAlso boosts angle precision, so can be useful even on small maps.\nAffects clients thusly:\nQW: enforces a mandatory protocol extension\nDP: enables DPP7 protocol support\nNQ: uses RMQ protocol (protocol 999).");
cvar_t sv_calcphs = CVARFD("sv_calcphs", "2", CVAR_LATCH, "Enables culling of sound effects. 0=always skip phs. Sounds are globally broadcast. 1=always generate phs. Sounds are always culled. On large maps the phs will be dumped to disk. 2=On large single-player maps, generation of phs is skipped. Otherwise like option 1.");
cvar_t sv_calcphs = CVARFD("sv_calcphs", "2", CVAR_MAPLATCH, "Enables culling of sound effects. 0=always skip phs. Sounds are globally broadcast. 1=always generate phs. Sounds are always culled. On large maps the phs will be dumped to disk. 2=On large single-player maps, generation of phs is skipped. Otherwise like option 1.");
cvar_t sv_showconnectionlessmessages = CVARD("sv_showconnectionlessmessages", "0", "Display a line describing each connectionless message that arrives on the server. Primarily a debugging feature, but also potentially useful to admins.");
cvar_t sv_cullplayers_trace = CVARFD("sv_cullplayers_trace", "", CVAR_SERVERINFO, "Attempt to cull player entities using tracelines as an anti-wallhack.");
@ -1198,7 +1199,7 @@ static void SVC_Status (void)
}
#if 1//def NQPROT
static void SVC_GetInfo (char *challenge, int fullstatus)
static void SVC_GetInfo (const char *challenge, int fullstatus)
{
//dpmaster support
char response[MAX_UDP_PACKET];
@ -3847,7 +3848,7 @@ qboolean SVC_ThrottleInfo (void)
static unsigned int blockuntil;
unsigned int curtime, inc = 1000/THROTTLE_PPS;
if (Net_AddressIsMaster(&net_from))
if (SV_Master_AddressIsMaster(&net_from))
return true; //allow it without contributing to any throttling.
curtime = Sys_Milliseconds();
@ -4018,8 +4019,13 @@ qboolean SV_ConnectionlessPacket (void)
else if (!strcmp(c, "getinfo"))
{ //q3/dpmaster support
if (sv_public.ival >= 0)
{
const char *chal = Cmd_Args();
SV_Master_HeartbeatResponse(&net_from, chal);
if (SVC_ThrottleInfo())
SVC_GetInfo(Cmd_Args(), false);
SVC_GetInfo(chal, false);
}
}
else if (!strcmp(c, "rcon"))
{
@ -4525,11 +4531,22 @@ void SV_ReadPacket(void)
if (cl->delay > 0)
goto dominping;
if (NQNetChan_Process(&cl->netchan))
switch(NQNetChan_Process(&cl->netchan))
{
case NQNC_IGNORED:
break;
case NQNC_ACK:
if (cl->netchan.message.cursize)
inboundsequence++; //we need to wake up...
if (cl->netchan.reliable_length > cl->netchan.reliable_start)
inboundsequence++; //we need to wake up...
break;
case NQNC_RELIABLE:
case NQNC_UNRELIABLE:
inboundsequence++;
svs.stats.packets++;
SVNQ_ExecuteClientMessage(cl);
break;
}
}
break;
@ -5063,7 +5080,7 @@ float SV_Frame (void)
svs.framenum = 0;
delay = sv_maxtic.value;
if (isDedicated && sv.spawned_client_slots == 0 && sv.spawned_observer_slots == 0)
if (isDedicated && sv.allocated_client_slots == 0)
delay = max(delay, 1); //when idle, don't keep waking up for no reason
// keep the random time dependent
@ -5477,6 +5494,8 @@ void SV_InitLocal (void)
SVNET_RegisterCvars();
Cvar_Register (&sv_reportheartbeats, cvargroup_servercontrol);
Cvar_Register (&sv_heartbeat_interval, cvargroup_servercontrol);
Cvar_Register (&sv_heartbeat_checks, cvargroup_servercontrol);
Cvar_Register (&sv_showconnectionlessmessages, cvargroup_servercontrol);
Cvar_Register (&sv_banproxies, cvargroup_serverpermissions);

View file

@ -41,14 +41,18 @@ typedef struct svm_server_s {
netadr_t adr;
const char *brokerid; //from rtc broker, for ICE connections (persistent until killed).
int protover;
unsigned int clients;
unsigned int maxclients;
int needpass;
char hostname[48]; //just for our own listings.
unsigned int spectators; //
unsigned int bots; //non-human players
unsigned int clients; //human players
unsigned int maxclients; //limit of bots+clients, but not necessarily spectators.
int needpass:1;
int coop:1;
char hostname[64]; //just for our own listings.
char mapname[16]; //just for our own listings.
char gamedir[16]; //again...
char version[48];
unsigned short gametype;
float expiretime;
double expiretime;
bucket_t bucket; //for faster address lookups.
struct svm_game_s *game;
@ -66,7 +70,7 @@ typedef struct svm_game_s {
} svm_game_t;
typedef struct {
float time;
double time;
svm_game_t *firstgame;
size_t numgames;
@ -77,9 +81,10 @@ typedef struct {
struct rates_s
{
double timestamp;
size_t heartbeats;
size_t queries;
size_t junk;
size_t heartbeats; //heartbeats/serverinfos (general maintainence things due to server counts)
size_t queries; //players querying for info
size_t junk; //unknown packets
size_t stun; //special packets handled by the network layer... though I suppose these should count as queries.
size_t drops;
size_t adds;
@ -107,6 +112,35 @@ static cvar_t sv_maxservers = CVARD("sv_maxservers", "10000", "Limits the number
static cvar_t sv_hideinactivegames = CVARD("sv_hideinactivegames", "1", "Don't show known games that currently have no servers in html listings.");
static cvar_t sv_sortlist = CVARD("sv_sortlist", "3", "Controls sorting of the http output:\n0: don't bother\n1: clients then address\n2: hostname then address\n3: clients then hostname then address\n4: just address");
static cvar_t sv_hostname = CVARD("hostname", "Unnamed FTE-Master", "Controls sorting of the http output:\n0: don't bother\n1: clients then address\n2: hostname then address\n3: clients then hostname then address\n4: just address");
static cvar_t sv_slaverequery = CVARD("sv_slaverequery", "120", "Requery slave masters at this frequency.");
static struct sv_masterslave_s
{
int type;
cvar_t var;
size_t numaddr;
netadr_t addr[8];
} sv_masterslave[] = {
{0, CVARD("sv_qwmasterslave1", "", "Specifies a different (quakeworld-protocol) master from which to steal server listings.")},
{0, CVARD("sv_qwmasterslave2", "", "Specifies a different (quakeworld-protocol) master from which to steal server listings.")},
{0, CVARD("sv_qwmasterslave3", "", "Specifies a different (quakeworld-protocol) master from which to steal server listings.")},
// {1, CVARD("sv_q2masterslave1", "", "Specifies a different (quake2-protocol) master from which to steal server listings.")},
// {1, CVARD("sv_q2masterslave2", "", "Specifies a different (quake2-protocol) master from which to steal server listings.")},
// {1, CVARD("sv_q2masterslave3", "", "Specifies a different (quake2-protocol) master from which to steal server listings.")},
// {2, CVARD("sv_q3masterslave1", "", "Specifies a different (quake3-protocol) master from which to steal server listings.")},
// {2, CVARD("sv_q3masterslave2", "", "Specifies a different (quake3-protocol) master from which to steal server listings.")},
// {2, CVARD("sv_q3masterslave3", "", "Specifies a different (quake3-protocol) master from which to steal server listings.")},
{3, CVARD("sv_dpmasterslave1", "", "Specifies a different (dpmaster-protocol) master from which to steal server listings.")},
{3, CVARD("sv_dpmasterslave2", "", "Specifies a different (dpmaster-protocol) master from which to steal server listings.")},
{3, CVARD("sv_dpmasterslave3", "", "Specifies a different (dpmaster-protocol) master from which to steal server listings.")},
};
static struct
{
netadr_t a;
char *query;
} *pingring;
static size_t pingring_first;
static size_t pingring_count;
static size_t pingring_max;
static char *master_css;
static unsigned int SVM_GenerateBrokerKey(const char *brokerid)
@ -229,8 +263,20 @@ static int QDECL SVM_SortOrder(const void *v1, const void *v2)
return s1->expiretime > s2->expiretime;
if (sv_sortlist.ival&1)
{
if ((t=(s2->clients-s1->clients)))
return (t>0)?1:-1;
if ((t=(s2->spectators-s1->spectators)))
return (t>0)?1:-1;
if ((t=(s2->bots-s1->bots)))
return (t>0)?1:-1;
}
if (sv_sortlist.ival&16)
if ((t=strcmp(s1->version, s2->version)))
return (t>0)?1:-1;
if (sv_sortlist.ival&32)
if ((t=strcmp(s1->gamedir, s2->gamedir)))
return (t>0)?1:-1;
if (sv_sortlist.ival&2)
if ((t=strcmp(s1->hostname, s2->hostname)))
return (t>0)?1:-1;
@ -246,8 +292,8 @@ static int QDECL SVM_SortOrder(const void *v1, const void *v2)
i = sizeof(s1->adr.address.ip6);
else i = 0;
for(t = 0; t < i; t++)
if (s1->adr.address.ip6[i] != s2->adr.address.ip6[i])
return (s2->adr.address.ip6[i]>s1->adr.address.ip6[i])?1:-1;
if (s1->adr.address.ip6[t] != s2->adr.address.ip6[t])
return (s1->adr.address.ip6[t]>s2->adr.address.ip6[t])?1:-1;
//and now do port numbers too.
t = BigShort(s1->adr.port) - BigShort(s2->adr.port);
@ -340,7 +386,7 @@ int SVM_AddIPAddresses(sizebuf_t *sb, int first, int ver, const char *gamename,
continue;
if (server->clients == 0 && !empty)
continue;
if (server->clients >= server->maxclients && !full)
if (server->clients+server->bots >= server->maxclients && !full)
continue;
if (gametype != -1 && server->gametype != gametype)
continue;
@ -456,6 +502,12 @@ static char *QuakeCharsToHTML(char *outhtml, size_t outsize, const char *quake,
Q_strncpyz(outhtml, "&apos;", outsize);
b=strlen(outhtml);
}
else if (codepoint >= 0xe086 && codepoint <= 0xe089)
{
const char *lednames[] = {"green", "red", "yellow", "blue"};
Q_snprintfz(outhtml, outsize, "<span style=\"color:%s\">&#x25A0;</span>", lednames[codepoint-0xe086]);
b=strlen(outhtml);
}
else if (codepoint == '_' && deunderscore)
*outhtml = ' ', b =1;
else
@ -475,6 +527,7 @@ static void SVM_Init(void)
master_css = FS_MallocFile("master.css", FS_ROOT, NULL);
if (!master_css)
master_css = Z_StrDup(
"<meta charset=\"UTF-8\">"
"<style type=\"text/css\">"
"body {"
"background-color: #303030;"
@ -500,7 +553,7 @@ static void SVM_Init(void)
);
}
vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname, const char **mimetype, const char *query)
{
char tmpbuf[256];
char hostname[1024];
@ -508,7 +561,7 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
svm_game_t *game;
svm_server_t *server;
vfsfile_t *f = NULL;
unsigned clients = 0, maxclients=0, totalclients=0;
unsigned clients=0,bots=0,specs=0, totalclients=0, totalbots=0, totalspecs=0;
if (!master_css)
SVM_Init();
if (!strcmp(fname, "index.html"))
@ -520,17 +573,40 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
VFS_PRINTF(f, "<tr><th>Active Games</th><th>Players</th><th>Server Count</th></tr>\n");
for (game = svm.firstgame; game; game = game->next)
{
for (clients=0, server = game->firstserver; server; server = server->next)
for (clients=0,bots=0,specs=0, server = game->firstserver; server; server = server->next)
{
clients += server->clients;
bots += server->bots;
specs += server->spectators;
}
if (game->numservers || !sv_hideinactivegames.ival) //only show active servers
{
QuakeCharsToHTML(tmpbuf, sizeof(tmpbuf), game->name, true);
VFS_PRINTF(f, "<tr><td><a href=\"game/%s\">%s</a></td><td>%u player(s)</td><td>%u server(s)</td></tr>\n", game->name, tmpbuf, clients, (unsigned)game->numservers);
VFS_PRINTF(f, "<tr><td><a href=\"game/%s%s%s\">%s</a></td><td>%u player%s", game->name, query?"?":"", query?query:"", tmpbuf, clients, clients==1?"":"s");
if (bots)
VFS_PRINTF(f, ", %u bot%s", bots, bots==1?"":"s");
if (specs)
VFS_PRINTF(f, ", %u spectator%s", specs, specs==1?"":"s");
VFS_PRINTF(f, "</td><td>%u server%s</td></tr>\n", (unsigned)game->numservers, game->numservers==1?"":"s");
}
totalclients += clients;
totalbots += bots;
totalspecs += specs;
}
VFS_PRINTF(f, "</table>\n");
VFS_PRINTF(f, "%u game(s), %u player(s), %u server(s)\n", (unsigned)svm.numgames, totalclients, (unsigned)svm.numservers);
VFS_PRINTF(f, "%u game%s", (unsigned)svm.numgames, svm.numgames==1?"":"s");
if (totalclients)
VFS_PRINTF(f, ", %u player%s", totalclients, totalclients==1?"":"s");
if (totalbots)
VFS_PRINTF(f, ", %u bot%s", totalbots, totalbots==1?"":"s");
if (totalspecs)
VFS_PRINTF(f, ", %u spectator%s", totalspecs, totalspecs==1?"":"s");
VFS_PRINTF(f, ", %u server%s<br/>\n", (unsigned)svm.numservers, svm.numservers==1?"":"s");
net_from.prot = NP_DGRAM;
VFS_PRINTF(f, "Your IP is %s<br/>\n", NET_BaseAdrToString(hostname, sizeof(hostname), &net_from));
*mimetype = "text/html";
}
else if (!strncmp(fname, "server/", 7))
{
@ -557,9 +633,12 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
VFS_PRINTF(f, "<tr><td>?</td><td>%s</td><td>?</td><td>?</td><td>?</td><td>?/?</td></tr>\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &adr[count]));
}
VFS_PRINTF(f, "</table>\n");
*mimetype = "text/html";
}
else if (!strncmp(fname, "game/", 5))
{
qboolean showver = query && !!strstr(query, "ver=1");
const char *gamename = fname+5;
game = SVM_FindGame(gamename, false);
@ -588,7 +667,10 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
SVM_SortServers(game);
VFS_PRINTF(f, "<table border=1>\n");
VFS_PRINTF(f, "<tr><th>Address</th><th>Hostname</th><th>Gamedir</th><th>Mapname</th><th>Players</th></tr>\n");
VFS_PRINTF(f, "<tr><th>Address</th><th>Hostname</th><th>Gamedir</th><th>Mapname</th><th>Players</th>");
if (showver)
VFS_PRINTF(f, "<th>Version</th>");
VFS_PRINTF(f, "</tr>\n");
for (server = game->firstserver; server; server = server->next)
{
if (server->brokerid)
@ -599,15 +681,34 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
else
url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr);
QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false);
VFS_PRINTF(f, "<tr><td>%s</td><td>%s%s</td><td>%s</td><td>%s</td><td>%u/%u</td></tr>\n", url, (server->needpass&1)?"&#x1F512;":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients);
VFS_PRINTF(f, "<tr><td>%s</td><td>%s%s%s</td><td>%s</td><td>%s</td><td>%u", url, (server->needpass&1)?"&#x1F512;":"", (server->coop&1)?"&#x1F6B8;":"", hostname, server->gamedir, server->mapname, server->clients);
if (server->bots)
VFS_PRINTF(f, "+%ub", server->bots);
VFS_PRINTF(f, "/%u", server->maxclients);
if (server->spectators)
VFS_PRINTF(f, ", %us", server->spectators);
VFS_PRINTF(f, "</td>");
if (showver)
VFS_PRINTF(f, "<td>%s</td>", server->version);
VFS_PRINTF(f, "</tr>\n");
clients += server->clients;
maxclients += server->maxclients;
bots += server->bots;
specs += server->spectators;
}
VFS_PRINTF(f, "</table>\n");
VFS_PRINTF(f, "%u server(s), %u/%u client(s)\n", (unsigned)game->numservers, clients, maxclients);
VFS_PRINTF(f, "%u server%s", (unsigned)game->numservers, game->numservers==1?"":"s");
if (clients)
VFS_PRINTF(f, ", %u client%s", (unsigned)clients, clients==1?"":"s");
if (bots)
VFS_PRINTF(f, ", %u bot%s", (unsigned)bots, bots==1?"":"s");
if (specs)
VFS_PRINTF(f, ", %u spectator%s", (unsigned)specs, specs==1?"":"s");
VFS_PRINTF(f, "\n");
}
else
VFS_PRINTF(f, "Protocol '%s' is not known\n", gamename);
*mimetype = "text/html";
}
else if (!strncmp(fname, "raw/", 4))
{ //just spews all
@ -619,10 +720,12 @@ vfsfile_t *SVM_GenerateIndex(const char *requesthost, const char *fname)
for (server = (game?game->firstserver:NULL); server; server = server->next)
{
if (server->brokerid)
VFS_PRINTF(f, "rtc:///%s \\maxclients\\%u\\clients\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\n", server->brokerid, server->maxclients, server->clients, server->hostname, server->gamedir, server->mapname, server->needpass);
VFS_PRINTF(f, "rtc:///%s \\maxclients\\%u\\clients\\%u\\bots\\%u\\hostname\\%s\\modname\\%s\\mapname\\%s\\needpass\\%i\n", server->brokerid, server->maxclients, server->clients, server->bots, server->hostname, server->gamedir, server->mapname, server->needpass);
else
VFS_PRINTF(f, "%s\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr));
}
*mimetype = "text/plain";
}
return f;
}
@ -724,9 +827,14 @@ void SVM_AddBrokerGame(const char *brokerid, const char *info)
Q_strncpyz(server->hostname, Info_ValueForKey(info, "hostname"), sizeof(server->hostname));
Q_strncpyz(server->gamedir, Info_ValueForKey(info, "modname"), sizeof(server->gamedir));
Q_strncpyz(server->mapname, Info_ValueForKey(info, "mapname"), sizeof(server->mapname));
Q_strncpyz(server->version, Info_ValueForKey(info, "version"), sizeof(server->version));
if (!*server->version)
Q_strncpyz(server->version, Info_ValueForKey(info, "*version"), sizeof(server->version));
if (!*server->version)
Q_strncpyz(server->version, Info_ValueForKey(info, "ver"), sizeof(server->version));
}
static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numclients, float validuntil)
static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numclients, int numbots, int numspecs, double validuntil)
{
svm_server_t *server = SVM_GetServer(adr);
svm_game_t *game;
@ -735,7 +843,7 @@ static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numc
{ //no gamename is a placeholder server, to say that there's a server there but it isn't responding to our getinfos... (ie: to list misconfigured servers too)
if (server)
{ //it still exists, renew it, but don't otherwise care too much.
server->expiretime = validuntil;
server->expiretime = max(validuntil, server->expiretime);
return server;
}
game = SVM_FindGame("UNKNOWN", true);
@ -772,6 +880,7 @@ static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numc
game->firstserver = server;
game->numservers++;
svm.numservers++;
server->expiretime = validuntil;
server->adr = *adr;
@ -786,10 +895,12 @@ static svm_server_t *SVM_Heartbeat(const char *gamename, netadr_t *adr, int numc
char buf[256];
Con_Printf("heartbeat(refresh): %s\n", NET_AdrToString(buf, sizeof(buf), &server->adr));
}
server->expiretime = max(server->expiretime, validuntil);
}
server->clients = numclients;
server->expiretime = validuntil;
server->bots = numbots;
server->spectators = numspecs;
return server;
}
@ -841,6 +952,45 @@ static qboolean SVM_SwitchQuerySocket(void)
return false;
}
static void SVM_DiscoveredServer(netadr_t *a, const char *query)
{
size_t idx, j;
//add it, despite having no actual info. don't give it a valid protocol, so dead ones don't get reported with infinite feedback.
svm_server_t *srv = SVM_Heartbeat(NULL, a, 0,0,0, svm.time + sv_slaverequery.value);
if (strcmp(srv->game->name, "UNKNOWN") && srv->expiretime > svm.time + sv_slaverequery.value)
return; //we already know something about it, don't spam pings...
if (!*srv->hostname && !strcmp(srv->game->name, "UNKNOWN"))
{
for (idx = 0; idx < countof(sv_masterslave); idx++)
{
for (j = 0; j < sv_masterslave[idx].numaddr; j++)
{
if (NET_CompareAdr(&net_from, &sv_masterslave[idx].addr[j]))
{
Q_snprintfz(srv->hostname, sizeof(srv->hostname), "[via %s]", sv_masterslave[idx].var.string);
idx = countof(sv_masterslave);
break;
}
}
}
}
if (pingring_count == pingring_max)
{ //just too many
Z_ReallocElements((void**)&pingring, &pingring_max, pingring_max*2+1, sizeof(*pingring));
}
else if (pingring_first + pingring_count == pingring_max)
{ //we're at the end.
memmove(pingring, pingring+pingring_first, sizeof(*pingring)*pingring_count);
pingring_first=0;
}
idx = pingring_first+pingring_count++;
pingring[idx].a = *a;
pingring[idx].query = strdup(query);
}
static void SVM_ProcessUDPPacket(void)
{
char *s, *line;
@ -870,7 +1020,8 @@ static void SVM_ProcessUDPPacket(void)
if (NET_WasSpecialPacket(svm_sockets))
{
Con_DPrintf("master: ignoring special packet\n");
svm.total.stun++;
// Con_DPrintf("master: ignoring special packet\n");
return;
}
@ -957,7 +1108,7 @@ static void SVM_ProcessUDPPacket(void)
if (*s == '\n' && s[1] == '\\')
{ //there's some serverinfo there, must be q2...
svm.total.heartbeats++;
SVM_Heartbeat(QUAKE2PROTOCOLNAME, &net_from, 0, svm.time + sv_heartbeattimeout.ival);
SVM_Heartbeat(QUAKE2PROTOCOLNAME, &net_from, 0,0,0, svm.time + sv_heartbeattimeout.ival);
}
else
{ //dp/q3/etc are annoying, but we can query from an emphemerial socket to check NAT rules.
@ -969,13 +1120,14 @@ static void SVM_ProcessUDPPacket(void)
svm.total.queries++;
//placeholder listing...
if (SVM_Heartbeat(NULL, &net_from, 0, svm.time + sv_heartbeattimeout.ival))
if (SVM_Heartbeat(NULL, &net_from, 0,0,0, svm.time + sv_heartbeattimeout.ival))
a = net_from;
else
a.type = NA_INVALID;
if (!SVM_SwitchQuerySocket())
if (!SVM_SwitchQuerySocket()) //changes net_from to use a different master-side port so their firewall sees us as someone else
a.type = NA_INVALID;
//send a packet from our alternative port
memset(&sb, 0, sizeof(sb));
sb.maxsize = sizeof(net_message_buffer);
sb.data = net_message_buffer;
@ -985,7 +1137,7 @@ static void SVM_ProcessUDPPacket(void)
NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from);
if (a.type != NA_INVALID)
{ //they were unknown... send a special getinfo, so we can get their hostname while leaving them as 'unknown'
{ //they were unknown... send a formal response so we can get their hostname while leaving them as 'unknown'
memset(&sb, 0, sizeof(sb));
sb.maxsize = sizeof(net_message_buffer);
sb.data = net_message_buffer;
@ -999,7 +1151,7 @@ static void SVM_ProcessUDPPacket(void)
else if (!strcmp(com_token, "infoResponse"))
{
char ourchallenge[256];
int clients;
int clients, bots, specs;
const char *game, *chal;
svm_server_t *srv;
qboolean unknownresp = false;
@ -1011,25 +1163,38 @@ static void SVM_ProcessUDPPacket(void)
SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from);
if (!strcmp(chal, ourchallenge))
{
bots = atoi(Info_ValueForKey(s, "bots"));
clients = atoi(Info_ValueForKey(s, "clients"));
clients = max(0, clients-bots);
specs = atoi(Info_ValueForKey(s, "specs"));
game = Info_ValueForKey(s, "gamename");
if (!*game)
game = QUAKE3PROTOCOLNAME;
if (unknownresp)
game = NULL; //ignore the gamename and classify it as unknown. this won't break anything if we've already has a proper heartbeat from them.
srv = SVM_Heartbeat(game, &net_from, clients, svm.time + sv_heartbeattimeout.ival);
srv = SVM_Heartbeat(game, &net_from, clients,bots,specs, svm.time + sv_heartbeattimeout.ival);
if (srv)
{
if (developer.ival)
Info_Print(s, "\t");
srv->clients = clients;
if (game)
srv->protover = atoi(Info_ValueForKey(s, "protocol"));
srv->maxclients = atoi(Info_ValueForKey(s, "sv_maxclients"));
srv->needpass = atoi(Info_ValueForKey(s, "needpass"));
srv->coop = atoi(Info_ValueForKey(s, "coop"));
if (!srv->coop)
{ //deathmatch 0 also means coop 1... servers that report neither are probably annoying DP servers that report nothing useful and should default to DM.
const char *v = Info_ValueForKey(s, "deathmatch");
srv->coop = *v && !atoi(v);
}
Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname));
Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "modname"), sizeof(srv->gamedir));
Q_strncpyz(srv->mapname, Info_ValueForKey(s, "mapname"), sizeof(srv->mapname));
Q_strncpyz(srv->version, Info_ValueForKey(s, "version"), sizeof(srv->version));
if (!*srv->version)
Q_strncpyz(srv->version, Info_ValueForKey(s, "*version"), sizeof(srv->version));
if (!*srv->version)
Q_strncpyz(srv->version, Info_ValueForKey(s, "ver"), sizeof(srv->version));
}
}
}
@ -1058,7 +1223,7 @@ static void SVM_ProcessUDPPacket(void)
//placeholder listing...
SVM_Heartbeat(NULL, &net_from, players, svm.time + sv_heartbeattimeout.ival);
SVM_Heartbeat(NULL, &net_from, players,0,0, svm.time + sv_heartbeattimeout.ival);
SVM_SwitchQuerySocket();
//send it a proper query. We'll fill in the other details on response.
@ -1066,23 +1231,123 @@ static void SVM_ProcessUDPPacket(void)
sb.maxsize = sizeof(net_message_buffer);
sb.data = net_message_buffer;
MSG_WriteLong(&sb, -1);
MSG_WriteString(&sb, va("status %i\n", 1));
MSG_WriteString(&sb, va("status %i\n", 15));
sb.cursize--;
NET_SendPacket(svm_sockets, sb.cursize, sb.data, &net_from);
}
else if (*com_token == M2C_MASTER_REPLY && !com_token[1])
{ //response from a QW master request (lots of IPs from a 'slave' master that we're stealing)
netadr_t a = {NA_IP};
svm.total.heartbeats++;
for (;;)
{
a.address.ip[0] = MSG_ReadByte();
a.address.ip[1] = MSG_ReadByte();
a.address.ip[2] = MSG_ReadByte();
a.address.ip[3] = MSG_ReadByte();
a.port = MSG_ReadShort();
if (msg_badread)
break;
SVM_DiscoveredServer(&a, "\xff\xff\xff\xff""status 15\n");
}
}
else if (!strncmp(com_token, "getserversExtResponse", 21) && com_token[21] == '\\')
{ //response from a FTE-master request (lots of IPs from a 'slave' master that we're stealing)
netadr_t a = {NA_INVALID};
msg_readcount = 4+21; //grr
svm.total.heartbeats++;
for (;;)
{
qbyte lead = MSG_ReadByte();
if (lead == '\\')
{
a.type = NA_IP;
MSG_ReadData(a.address.ip, sizeof(a.address.ip));
}
else if (lead == '/')
{
a.type = NA_IPV6;
MSG_ReadData(a.address.ip6, sizeof(a.address.ip6));
}
else
break; //no idea
a.port = MSG_ReadShort();
if (msg_badread)
break; //read too much junk
{
char ourchallenge[256];
SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &a);
SVM_DiscoveredServer(&a, va("\xff\xff\xff\xffgetinfo %s\n", ourchallenge));
}
}
}
else if (*com_token == A2C_PRINT)
{ //quakeworld response from 'status' requests, providing for actual info (and so that we know its reachable from other addresses)
//there's no challenge, these could easily be spoofed. :(
int clients;
int clients = 0, bots = 0, specs = 0;
const char *game;
svm_server_t *srv;
const char *t, *playerinfo = MSG_ReadString();
s = ++line;
clients = atoi(Info_ValueForKey(s, "clients"));
t = Info_ValueForKey(s, "clients");
if (*t)
{
bots = atoi(Info_ValueForKey(s, "bots"))-bots;
clients = atoi(Info_ValueForKey(s, "clients"));
specs = atoi(Info_ValueForKey(s, "specs"));
}
else
{
while (*playerinfo)
{
//USERID FRAGS TIME PING NAME SKIN TOP BOTTOM [TEAM]
const char *s = playerinfo;
qboolean isspec;
qboolean isbot;
int ping;
s = COM_Parse(s);//userid
s = COM_Parse(s);//frags
isspec = !strcmp(com_token, "S");
s = COM_Parse(s);//time
s = COM_Parse(s);//ping
ping = atoi(com_token);
s = COM_Parse(s);//name
isbot = (ping == 807 /*random hack*/) || !strncmp(com_token, "BOT:", 4);
//s = COM_Parse(s);//skin
//s = COM_Parse(s);//top
//s = COM_Parse(s);//bottom
//s = COM_Parse(s);//team
if (isbot)
bots++;
else if (isspec)
specs++;
else
clients++;
while(*playerinfo)
{
if (*playerinfo++ == '\n')
break;
}
}
}
game = Info_ValueForKey(s, "gamename");
if (!*game)
game = QUAKEWORLDPROTOCOLNAME;
srv = SVM_Heartbeat(game, &net_from, clients, svm.time + sv_heartbeattimeout.ival);
{
game = Info_ValueForKey(s, "*version");
if (!strncmp(game, "QTV", 3))
game = "QTV";
else if (!strncmp(game, "qwfwd", 5))
game = "qwfwd";
else
game = QUAKEWORLDPROTOCOLNAME;
}
srv = SVM_Heartbeat(game, &net_from, clients,bots,specs, svm.time + sv_heartbeattimeout.ival);
if (srv)
{
if (developer.ival)
@ -1090,9 +1355,20 @@ static void SVM_ProcessUDPPacket(void)
srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol"));
srv->maxclients = atoi(Info_ValueForKey(s, "maxclients"));
srv->needpass = atoi(Info_ValueForKey(s, "needpass"));
srv->coop = atoi(Info_ValueForKey(s, "coop"));
if (!srv->coop)
{ //deathmatch 0 also means coop 1... servers that report neither are probably annoying proxies servers that report nothing useful and should default to DM.
const char *v = Info_ValueForKey(s, "deathmatch");
srv->coop = *v && !atoi(v);
}
Q_strncpyz(srv->hostname, Info_ValueForKey(s, "hostname"), sizeof(srv->hostname));
Q_strncpyz(srv->gamedir, Info_ValueForKey(s, "*gamedir"), sizeof(srv->gamedir));
Q_strncpyz(srv->mapname, Info_ValueForKey(s, "map"), sizeof(srv->mapname));
Q_strncpyz(srv->version, Info_ValueForKey(s, "version"), sizeof(srv->version));
if (!*srv->version)
Q_strncpyz(srv->version, Info_ValueForKey(s, "*version"), sizeof(srv->version));
if (!*srv->version)
Q_strncpyz(srv->version, Info_ValueForKey(s, "ver"), sizeof(srv->version));
}
}
else if (*com_token == C2M_MASTER_REQUEST)
@ -1129,13 +1405,87 @@ static void SVM_ProcessUDPPacket(void)
svm.total.junk++;
}
void SVM_Think(int port)
float SVM_RequerySlaves(void)
{
static int slaveseq = 0;
static double nextslavetime;
if (slaveseq == countof(sv_masterslave) || !nextslavetime)
{
if (nextslavetime < realtime && !pingring_count)
{
nextslavetime = realtime + sv_slaverequery.value;
slaveseq = 0;
pingring_first = 0; //no active entries.
}
}
while (slaveseq < countof(sv_masterslave))
{
struct sv_masterslave_s *s = &sv_masterslave[slaveseq];
slaveseq++;
if (*s->var.string)
{
size_t h;
int defaultport[] = {PORT_QWMASTER, PORT_Q2MASTER, PORT_Q3MASTER, PORT_DPMASTER};
const char *querystring[] = {
/*C2M_MASTER_REQUEST*/"c\n", //quakeworld
NULL, //quake2
"\xff\xff\xff\xffgetservers 68 empty full\n", //quake3
"\xff\xff\xff\xffgetserversExt %s %g empty full ipv4 ipv6\n" //fte/dp master
};
const char *q;
s->numaddr = NET_StringToAdr2(s->var.string, defaultport[s->type], s->addr, countof(s->addr), NULL);
if (s->numaddr)
{ //send it to each...
if (strstr(querystring[s->type], "%s"))
{
const char *prots = com_protocolname.string;
while ((prots=COM_Parse(prots)))
{
q = va(querystring[s->type], com_token, com_protocolversion.value);
for (h = 0; h < s->numaddr; h++)
NET_SendPacket(svm_sockets, strlen(q), q, &s->addr[h]);
}
}
else
{
q = querystring[s->type];
for (h = 0; h < s->numaddr; h++)
NET_SendPacket(svm_sockets, strlen(q), q, &s->addr[h]);
}
}
else
Con_Printf("%s: unable to resolve %s\n", s->var.name, s->var.string);
return 1; //something happened. might just be a name lookup lockup. :(
}
}
if (pingring_count)
{
netadr_t *a = &pingring[pingring_first].a;
char *q = pingring[pingring_first].query;
pingring[pingring_first].query = NULL;
pingring_first++;
pingring_count--;
NET_SendPacket(svm_sockets, strlen(q), q, a);
free(q);
return sv_slaverequery.value / pingring_max;
}
return 4; //nothing happening.
}
float SVM_Think(int port)
{
NET_ReadPackets (svm_sockets);
SVM_RemoveOldServers();
return SVM_RequerySlaves();
}
#else
void SVM_Think(int port){}
float SVM_Think(int port){return 4;}
#endif
@ -1175,6 +1525,11 @@ static void SVM_Status_f(void)
period=1;
Con_Printf("Heartbeats/min: %f\n", (s1->heartbeats-s2->heartbeats)/period);
Con_Printf("Queries/min: %f\n", (s1->queries-s2->queries)/period);
if (s1->stun!=s2->stun)
Con_Printf("Stun/min: %f\n", (s1->stun-s2->stun)/period);
if (s1->junk!=s2->junk)
Con_Printf("Junk/min: %f\n", (s1->junk-s2->junk)/period);
}
static void SVM_RegisterAlias(svm_game_t *game, char *aliasname)
@ -1244,6 +1599,7 @@ static void SVM_GameAlias_f(void)
void SV_Init (struct quakeparms_s *parms)
{
int manarg;
size_t u;
COM_InitArgv (parms->argc, parms->argv);
@ -1282,6 +1638,9 @@ void SV_Init (struct quakeparms_s *parms)
Cvar_Register(&sv_hideinactivegames, "server control variables");
Cvar_Register(&sv_sortlist, "server control variables");
Cvar_Register(&sv_hostname, "server control variables");
Cvar_Register(&sv_slaverequery, "server control variables");
for (u = 0; u < countof(sv_masterslave); u++)
Cvar_Register(&sv_masterslave[u].var, "server control variables");
Cvar_ParseWatches();
host_initialized = true;
@ -1307,6 +1666,7 @@ void SV_Init (struct quakeparms_s *parms)
}
float SV_Frame (void)
{
float sleeptime;
realtime = Sys_DoubleTime();
while (1)
{
@ -1319,7 +1679,7 @@ float SV_Frame (void)
}
Cbuf_Execute ();
SVM_Think(sv_masterport.ival);
sleeptime = SVM_Think(sv_masterport.ival);
//record lots of info over multiple frames, for smoother stats info.
svm.total.timestamp = realtime;
@ -1330,6 +1690,6 @@ float SV_Frame (void)
svm.nextstamp = realtime+60;
}
return 4;
return sleeptime;
}
#endif

View file

@ -54,8 +54,8 @@ cvar_t sv_wateraccelerate = CVAR( "sv_wateraccelerate", "10");
cvar_t sv_friction = CVAR( "sv_friction", "4");
cvar_t sv_waterfriction = CVAR( "sv_waterfriction", "4");
cvar_t sv_wallfriction = CVARD( "sv_wallfriction", "1", "Additional friction when running into walls");
cvar_t sv_gameplayfix_noairborncorpse = CVAR( "sv_gameplayfix_noairborncorpse", "0");
cvar_t sv_gameplayfix_multiplethinks = CVARD( "sv_gameplayfix_multiplethinks", "1", "Enables multiple thinks per entity per frame so small nextthink times are accurate. QuakeWorld mods expect a value of 1, while NQ expects 0.");
cvar_t sv_gameplayfix_noairborncorpse = CVAR( "sv_gameplayfix_noairborncorpse", "0");
cvar_t sv_gameplayfix_multiplethinks = CVARAD("sv_gameplayfix_multiplethinks", "1", /*dp*/"sv_gameplayfix_multiplethinksperframe", "Enables multiple thinks per entity per frame so small nextthink times are accurate. QuakeWorld mods expect a value of 1, while NQ expects 0.");
cvar_t sv_gameplayfix_stepdown = CVARD( "sv_gameplayfix_stepdown", "0", "Attempt to step down steps, instead of only up them. Affects non-predicted movetype_walk.");
cvar_t sv_gameplayfix_bouncedownslopes = CVARD( "sv_gameplayfix_grenadebouncedownslopes", "0", "MOVETYPE_BOUNCE speeds are calculated relative to the impacted surface, instead of the vertical, reducing the chance of grenades just sitting there on slopes.");
cvar_t sv_gameplayfix_trappedwithin = CVARD( "sv_gameplayfix_trappedwithin", "0", "Blocks further entity movement when an entity is already inside another entity. This ensures that bsp precision issues cannot allow the entity to completely pass through eg the world.");

View file

@ -30,6 +30,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define CHAN_ITEM 3
#define CHAN_BODY 4
extern cvar_t sv_showpredloss;
extern cvar_t sv_gravity, sv_friction, sv_waterfriction, sv_gamespeed, sv_stopspeed, sv_spectatormaxspeed, sv_accelerate, sv_airaccelerate, sv_wateraccelerate, pm_edgefriction, sv_reliable_sound;
extern cvar_t dpcompat_stats;
@ -2184,14 +2185,23 @@ void SV_UpdateQCStats(edict_t *ent, int *statsi, char const** statss, float *sta
case ev_float:
statsf[qcstats[i].statnum] = eval->_float;
break;
case ev_vector:
case ev_double:
statsf[qcstats[i].statnum] = eval->_double; //FIXME: precision loss.
break;
case ev_vector: //split over 3 stats.
statsf[qcstats[i].statnum+0] = eval->_vector[0];
statsf[qcstats[i].statnum+1] = eval->_vector[1];
statsf[qcstats[i].statnum+2] = eval->_vector[2];
break;
case ev_integer:
case ev_uint:
statsi[qcstats[i].statnum] = eval->_int;
break;
case ev_int64:
case ev_uint64: //split over 2 stats.
statsi[qcstats[i].statnum] = eval->_uint64&0xffffffff;
statsi[qcstats[i].statnum+1] = eval->_uint64>>32;
break;
case ev_entity:
statsi[qcstats[i].statnum] = NUM_FOR_EDICT(svprogfuncs, PROG_TO_EDICT(svprogfuncs, eval->edict));
break;
@ -3511,11 +3521,14 @@ void SV_SendClientMessages (void)
SV_PreRunCmd();
stepmsec = 12;
cmd.msec = stepmsec;
if (sv_showpredloss.ival)
Con_Printf("%s: forcing %g msecs (anti-hover)\n", c->name, cmd.msec);
VectorCopy(c->lastcmd.angles, cmd.angles);
cmd.buttons = c->lastcmd.buttons;
SV_RunCmd (&cmd, true);
SV_PostRunCmd();
c->lastruncmd = sv.time*1000;
c->lastruncmd = sv.time*1000-c->msecs;
if (stepmsec > c->msecs)
c->msecs = 0;
else

View file

@ -64,6 +64,7 @@ cvar_t sv_mapcheck = CVAR("sv_mapcheck", "1");
cvar_t sv_fullredirect = CVARD("sv_fullredirect", "", "This is the ip:port to redirect players to when the server is full");
cvar_t sv_antilag = CVARFD("sv_antilag", "", CVAR_SERVERINFO, "Attempt to backdate impacts to compensate for lag via the MOVE_ANTILAG feature.\n0=completely off.\n1=mod-controlled (default).\n2=forced, which might break certain uses of traceline.\n3=Also attempt to recalculate trace start positions to avoid lagged knockbacks.");
cvar_t sv_antilag_frac = CVARF("sv_antilag_frac", "", CVAR_SERVERINFO);
cvar_t sv_showpredloss = CVARD("sv_showpredloss", "0", "Print messages whenever input frames are ignored or forced serverside, to prevent speedcheats or hover cheats. Any such prints will be accompanied by prediction misses in the named client.");
#ifndef NEWSPEEDCHEATPROT
cvar_t sv_cheatpc = CVARD("sv_cheatpc", "125", "If the client tried to claim more than this percentage of time within any speed-cheat period, the client will be deemed to have cheated.");
cvar_t sv_cheatspeedchecktime = CVARD("sv_cheatspeedchecktime", "30", "The interval between each speed-cheat check.");
@ -103,7 +104,7 @@ cvar_t voteminimum = CVARD("voteminimum", "4", "At least this many players must
cvar_t votepercent = CVARD("votepercent", "-1", "At least this percentage of players must vote the same way for the vote to pass.");
cvar_t votetime = CVARD("votetime", "10", "Votes will be discarded after this many minutes");
cvar_t pr_allowbutton1 = CVARFD("pr_allowbutton1", "1", CVAR_LATCH, "The button1 field is believed to have been intended to work with the +use command, but it was never hooked up. In NetQuake, this field was often repurposed for other things as it was not otherwise used (and cannot be removed without breaking the crc), while third-party QuakeWorld engines did decide to implement it as believed was intended. As a result, this cvar only applies to QuakeWorld mods and a value of 1 is only likely to cause issues with NQ mods that were ported to QW.");
cvar_t pr_allowbutton1 = CVARFD("pr_allowbutton1", "1", CVAR_MAPLATCH, "The button1 field is believed to have been intended to work with the +use command, but it was never hooked up. In NetQuake, this field was often repurposed for other things as it was not otherwise used (and cannot be removed without breaking the crc), while third-party QuakeWorld engines did decide to implement it as believed was intended. As a result, this cvar only applies to QuakeWorld mods and a value of 1 is only likely to cause issues with NQ mods that were ported to QW.");
extern cvar_t sv_minping;
@ -739,7 +740,7 @@ void SVNQ_New_f (void)
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
MSG_WriteString (&host_client->netchan.message, va("csqc_progsize %u\n", (unsigned int)sz));
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
MSG_WriteString (&host_client->netchan.message, va("csqc_progcrc %i\n", QCRC_Block(f, sz)));
MSG_WriteString (&host_client->netchan.message, va("csqc_progcrc %i\n", CalcHashInt(&hash_crc16, f, sz)));
MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
MSG_WriteString (&host_client->netchan.message, "cmd enablecsqc\n");
@ -2417,10 +2418,11 @@ void SV_DarkPlacesDownloadAck(client_t *cl)
else
{
char *s;
unsigned short crc;
const hashfunc_t *hfunc = &hash_crc16;
void *hctx = alloca(hfunc->contextsize);
int pos=0, csize;
qbyte chunk[1024];
QCRC_Init(&crc);
qbyte chunk[65536];
hfunc->init(hctx);
VFS_SEEK(host_client->download, 0);
while (pos < host_client->downloadsize)
{
@ -2428,11 +2430,11 @@ void SV_DarkPlacesDownloadAck(client_t *cl)
if (pos + csize > host_client->downloadsize)
csize = host_client->downloadsize - pos;
VFS_READ(host_client->download, chunk, csize);
QCRC_AddBlock(&crc, chunk, csize);
hfunc->process(hctx, chunk, csize);
pos += csize;
}
s = va("\ncl_downloadfinished %u %i \"\"\n", (unsigned int)host_client->downloadsize, crc);
s = va("\ncl_downloadfinished %u %i \"%s\"\n", (unsigned int)host_client->downloadsize, hashfunc_terminate_uint(hfunc, hctx), "");
ClientReliableWrite_Begin (cl, svc_stufftext, 2+strlen(s));
ClientReliableWrite_String(cl, s);
@ -6622,7 +6624,7 @@ static qboolean AddEntityToPmove(world_t *w, wedict_t *player, wedict_t *check)
{
physent_t *pe;
int solid = check->v->solid;
int q1contents;
enum q1contents_e q1contents;
if (pmove.numphysent == MAX_PHYSENTS)
return false;
@ -6641,33 +6643,26 @@ static qboolean AddEntityToPmove(world_t *w, wedict_t *player, wedict_t *check)
q1contents = (int)check->v->skin;
if (solid == SOLID_LADDER)
q1contents = Q1CONTENTS_LADDER; //legacy crap
switch(q1contents)
safeswitch(q1contents)
{
case Q1CONTENTS_SOLID:
pe->nonsolid = false;
pe->forcecontentsmask = FTECONTENTS_SOLID;
break;
case Q1CONTENTS_WATER:
pe->nonsolid = true;
pe->forcecontentsmask = FTECONTENTS_WATER;
break;
case Q1CONTENTS_LAVA:
pe->nonsolid = true;
pe->forcecontentsmask = FTECONTENTS_LAVA;
break;
case Q1CONTENTS_SLIME:
pe->nonsolid = true;
pe->forcecontentsmask = FTECONTENTS_SLIME;
break;
case Q1CONTENTS_SKY:
pe->nonsolid = true;
pe->forcecontentsmask = FTECONTENTS_SKY;
break;
case Q1CONTENTS_LADDER:
pe->nonsolid = true;
pe->forcecontentsmask = FTECONTENTS_LADDER;
break;
default:
case Q1CONTENTS_EMPTY: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_EMPTY; break;
case Q1CONTENTS_TRANS:
case Q1CONTENTS_SOLID: pe->nonsolid = false; pe->forcecontentsmask = FTECONTENTS_SOLID; break;
case Q1CONTENTS_WATER: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER; break;
case Q1CONTENTS_LAVA: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_LAVA; break;
case Q1CONTENTS_SLIME: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_SLIME; break;
case Q1CONTENTS_SKY: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_SKY; break;
case Q1CONTENTS_CLIP: pe->nonsolid = false; pe->forcecontentsmask = FTECONTENTS_PLAYERCLIP|FTECONTENTS_MONSTERCLIP; break;
case Q1CONTENTS_CURRENT_0: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_0; break;
case Q1CONTENTS_CURRENT_90: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_90; break;
case Q1CONTENTS_CURRENT_180: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_180; break;
case Q1CONTENTS_CURRENT_270: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_270; break;
case Q1CONTENTS_CURRENT_UP: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_UP; break;
case Q1CONTENTS_CURRENT_DOWN: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_DOWN; break;
case Q1CONTENTS_LADDER: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_LADDER; break;
case Q1CONTENTS_MONSTERCLIP: pe->nonsolid = true; pe->forcecontentsmask = FTECONTENTS_MONSTERCLIP; break;
case Q1CONTENTS_PLAYERCLIP: pe->nonsolid = false; pe->forcecontentsmask = FTECONTENTS_PLAYERCLIP; break;
safedefault:
pe->forcecontentsmask = 0;
break;
}
@ -7073,7 +7068,11 @@ void SV_RunCmd (usercmd_t *ucmd, qboolean recurse)
if (ucmd->msec > 10)
ucmd->msec -= 1;
if (ucmd->msec > host_client->msecs)
{
if (sv_showpredloss.ival)
Con_Printf("%s: ignoring %g msecs (anti speed cheat)\n", host_client->name, ucmd->msec);
return;
}
ucmd->msec = host_client->msecs;
}
host_client->msecs -= ucmd->msec;
@ -7682,12 +7681,21 @@ void SV_PostRunCmd(void)
}
}
void SV_ReadPrydonCursor(void)
static void SV_ReadPrydonCursor(usercmd_t *cmd)
{
float f;
int entnum;
eval_t *cursor_screen, *cursor_start, *cursor_impact, *cursor_entitynumber;
cmd->cursor_screen[0] = MSG_ReadShort() * (1.0f / 32767.0f);
cmd->cursor_screen[1] = MSG_ReadShort() * (1.0f / 32767.0f);
cmd->cursor_start[0] = MSG_ReadFloat();
cmd->cursor_start[1] = MSG_ReadFloat();
cmd->cursor_start[2] = MSG_ReadFloat();
cmd->cursor_impact[0] = MSG_ReadFloat();
cmd->cursor_impact[1] = MSG_ReadFloat();
cmd->cursor_impact[2] = MSG_ReadFloat();
cmd->cursor_entitynumber = MSGSV_ReadEntity(host_client);
if (svprogfuncs)
{
cursor_screen = svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_screen", ev_vector, NULL);
@ -7702,39 +7710,20 @@ void SV_ReadPrydonCursor(void)
cursor_impact = NULL;
cursor_entitynumber = NULL;
}
if (cursor_screen)
Vector2Copy(cmd->cursor_screen, cursor_screen->_vector);
if (cursor_start)
VectorCopy(cmd->cursor_start, cursor_start->_vector);
if (cursor_impact)
VectorCopy(cmd->cursor_impact, cursor_impact->_vector);
f = MSG_ReadShort() * (1.0f / 32767.0f);
if (cursor_screen) cursor_screen->_vector[0] = f;
f = MSG_ReadShort() * (1.0f / 32767.0f);
if (cursor_screen) cursor_screen->_vector[1] = f;
f = MSG_ReadFloat();
if (cursor_start) cursor_start->_vector[0] = f;
f = MSG_ReadFloat();
if (cursor_start) cursor_start->_vector[1] = f;
f = MSG_ReadFloat();
if (cursor_start) cursor_start->_vector[2] = f;
f = MSG_ReadFloat();
if (cursor_impact) cursor_impact->_vector[0] = f;
f = MSG_ReadFloat();
if (cursor_impact) cursor_impact->_vector[1] = f;
f = MSG_ReadFloat();
if (cursor_impact) cursor_impact->_vector[2] = f;
entnum = MSGSV_ReadEntity(host_client);
if (entnum >= sv.world.max_edicts)
{
Con_DPrintf("SV_ReadPrydonCursor: client sent bad cursor_entitynumber\n");
entnum = 0;
}
// as requested by FrikaC, cursor_trace_ent is reset to world if the
// entity is free at time of receipt
if (!svprogfuncs || ED_ISFREE(EDICT_NUM_UB(svprogfuncs, entnum)))
entnum = 0;
if (!svprogfuncs || ED_ISFREE(EDICT_NUM_UB(svprogfuncs, cmd->cursor_entitynumber)))
cmd->cursor_entitynumber = 0;
if (msg_badread) Con_Printf("SV_ReadPrydonCursor: badread at %s:%i\n", __FILE__, __LINE__);
if (cursor_entitynumber) cursor_entitynumber->edict = entnum;
if (cursor_entitynumber) cursor_entitynumber->edict = cmd->cursor_entitynumber;
}
void SV_ReadQCRequest(void)
@ -7789,7 +7778,7 @@ void SV_ReadQCRequest(void)
break;
case ev_double:
args[i] = 'F';
G_FLOAT(OFS_PARM0+i*3) = MSG_ReadDouble();
G_DOUBLE(OFS_PARM0+i*3) = MSG_ReadDouble();
break;
case ev_vector:
args[i] = 'v';
@ -8015,9 +8004,22 @@ void SV_ExecuteClientMessage (client_t *cl)
if (split)
split->lossage = cl->lossage;
}
MSG_ReadDeltaUsercmd (&nullcmd, &oldest, PROTOCOL_VERSION_QW);
MSG_ReadDeltaUsercmd (&oldest, &oldcmd, PROTOCOL_VERSION_QW);
MSG_ReadDeltaUsercmd (&oldcmd, &newcmd, PROTOCOL_VERSION_QW);
if (cl->fteprotocolextensions2 & PEXT2_VRINPUTS)
{
MSGFTE_ReadDeltaUsercmd (&nullcmd, &oldest);
MSGFTE_ReadDeltaUsercmd (&oldest, &oldcmd);
MSGFTE_ReadDeltaUsercmd (&oldcmd, &newcmd);
}
else
{
MSGQW_ReadDeltaUsercmd (&nullcmd, &oldest, PROTOCOL_VERSION_QW);
Vector2Copy(split->lastcmd.cursor_screen, oldest.cursor_screen);
VectorCopy(split->lastcmd.cursor_start, oldest.cursor_start);
VectorCopy(split->lastcmd.cursor_impact, oldest.cursor_impact);
oldest.cursor_entitynumber = split->lastcmd.cursor_entitynumber;
MSGQW_ReadDeltaUsercmd (&oldest, &oldcmd, PROTOCOL_VERSION_QW);
MSGQW_ReadDeltaUsercmd (&oldcmd, &newcmd, PROTOCOL_VERSION_QW);
}
if (!split)
break; // either someone is trying to cheat, or they sent input commands for splitscreen clients they no longer own.
@ -8096,23 +8098,57 @@ void SV_ExecuteClientMessage (client_t *cl)
split->isindependant = true;
SV_PreRunCmd();
if (net_drop < 20)
{
while (net_drop > 2)
if (cl->fteprotocolextensions2 & PEXT2_VRINPUTS)
{ //this protocol uses bigger timestamps instead of msecs
usercmd_t *c;
unsigned int curtime = sv.time*1000;
if (newcmd.servertime < split->lastruncmd)
{
SV_RunCmd (&split->lastcmd, false);
net_drop--;
if (sv_showpredloss.ival)
Con_Printf("%s: client jumped %u msecs backwards (anti speed cheat)\n", split->name, split->lastruncmd - newcmd.servertime);
}
else while (split->lastruncmd < newcmd.servertime)
{
//try to find the oldest (valid) command.
if (split->lastcmd.servertime < oldest.servertime)
c = &oldest;
else if (split->lastcmd.servertime < oldcmd.servertime)
c = &oldcmd;
else
c = &newcmd;
if (c->servertime > curtime)
{
if (sv_showpredloss.ival)
Con_Printf("%s: client is %u msecs in the future (anti speed cheat)\n", split->name, c->servertime - curtime);
break; //from last map?... attempted speedcheat?
}
c->msec = c->servertime - split->lastruncmd;
SV_RunCmd (c, false);
split->lastruncmd = c->servertime;
}
if (net_drop > 1)
SV_RunCmd (&oldest, false);
if (net_drop > 0)
SV_RunCmd (&oldcmd, false);
}
SV_RunCmd (&newcmd, false);
else
{
if (net_drop < 20)
{
while (net_drop > 2)
{
SV_RunCmd (&split->lastcmd, false);
net_drop--;
}
if (net_drop > 1)
SV_RunCmd (&oldest, false);
if (net_drop > 0)
SV_RunCmd (&oldcmd, false);
}
SV_RunCmd (&newcmd, false);
host_client->lastruncmd = sv.time*1000;
}
if (!SV_PlayerPhysicsQC || host_client->spectator)
SV_PostRunCmd();
host_client->lastruncmd = sv.time*1000;
}
}
@ -8131,7 +8167,7 @@ void SV_ExecuteClientMessage (client_t *cl)
break;
case clcfte_prydoncursor:
SV_ReadPrydonCursor();
SV_ReadPrydonCursor(&host_client->lastcmd); //lame...
break;
case clcfte_qcrequest:
SV_ReadQCRequest();
@ -8410,13 +8446,14 @@ void SVQ2_ExecuteClientMessage (client_t *cl)
}
#endif
#ifdef NQPROT
void SVNQ_ReadClientMove (usercmd_t *move, qboolean forceangle16)
void SVNQ_ReadClientMove (qboolean forceangle16)
{
int i;
int bits;
client_frame_t *frame;
float timesincelast;
float cltime;
usercmd_t *from = &host_client->lastcmd;
usercmd_t cmd;
frame = &host_client->frameunion.frames[host_client->netchan.incoming_acknowledged & UPDATE_MASK];
@ -8431,20 +8468,75 @@ void SVNQ_ReadClientMove (usercmd_t *move, qboolean forceangle16)
}
else
host_client->last_sequence = 0;
cltime = MSG_ReadFloat ();
if (cltime < move->fservertime)
cltime = move->fservertime;
if (cltime > sv.time)
cltime = sv.time;
if (cltime < sv.time - 2) //if you do lag more than this, you won't get your free time.
cltime = sv.time - 2;
timesincelast = cltime - move->fservertime;
move->fservertime = cltime;
move->servertime = move->fservertime*1000;
if (host_client->fteprotocolextensions2 & PEXT2_VRINPUTS)
{ //this actually drops from 37 to 23 bytes (according to showpackets), so that's cool. obviously it goes up when vr inputs are actually networked...
MSGFTE_ReadDeltaUsercmd(&nullcmd, &cmd);
cmd.fservertime = cmd.servertime/1000.0;
}
else
{
cmd = nullcmd;
frame->ping_time = sv.time - cltime;
//read the time, woo... should be an ack of our serverside time.
cmd.fservertime = MSG_ReadFloat ();
if (cmd.fservertime < from->fservertime)
cmd.fservertime = from->fservertime;
if (cmd.fservertime > sv.time)
cmd.fservertime = sv.time;
if (cmd.fservertime < sv.time - 2) //if you do lag more than this, you won't get your free time.
cmd.fservertime = sv.time - 2;
cmd.servertime = cmd.fservertime*1000;
//read angles
for (i=0 ; i<3 ; i++)
{
float a;
if (forceangle16)
a = MSG_ReadAngle16 ();
else
a = MSG_ReadAngle ();
cmd.angles[i] = ANGLE2SHORT(a);
}
// read movement
cmd.forwardmove = MSG_ReadShort ();
cmd.sidemove = MSG_ReadShort ();
cmd.upmove = MSG_ReadShort ();
// read buttons
if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7)
cmd.buttons = MSG_ReadLong() | (1u<<31);
else if (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR)
cmd.buttons = MSG_ReadLong();
else
cmd.buttons = MSG_ReadByte ();
//impulse...
cmd.impulse = MSG_ReadByte ();
//weapon extension
if (cmd.buttons & (1u<<30))
cmd.weapon = MSG_ReadLong();
else
cmd.weapon = 0;
//cursor extension
if (cmd.buttons & (1u<<31))
SV_ReadPrydonCursor(&cmd);
//clear out extension buttons that are part of the protocol rather than actual buttons..
cmd.buttons &= ~((1u<<30)|(1u<<31));
}
//figure out ping
frame->ping_time = sv.time - cmd.fservertime;
//figure out how far we moved.
timesincelast = cmd.fservertime - from->fservertime;
cmd.msec=bound(0, timesincelast*1000, 250);
frame->move_msecs = timesincelast*1000;
if (frame->ping_time*1000 > sv_minping.value+1)
{
@ -8459,35 +8551,11 @@ void SVNQ_ReadClientMove (usercmd_t *move, qboolean forceangle16)
host_client->delay = 1;
}
// read current angles
for (i=0 ; i<3 ; i++)
{
if (forceangle16)
host_client->edict->v->v_angle[i] = MSG_ReadAngle16 ();
else
host_client->edict->v->v_angle[i] = MSG_ReadAngle ();
move->angles[i] = ANGLE2SHORT(host_client->edict->v->v_angle[i]);
}
// read movement
move->forwardmove = MSG_ReadShort ();
move->sidemove = MSG_ReadShort ();
move->upmove = MSG_ReadShort ();
move->msec=bound(0, timesincelast*1000, 250);
frame->move_msecs = timesincelast*1000;
// read buttons
if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR))
bits = MSG_ReadLong();
else
bits = MSG_ReadByte ();
if (host_client->spectator)
{
qboolean tracknext = false;
if (bits & (move->buttons ^ bits) & BUTTON_ATTACK)
unsigned int pressed = cmd.buttons & ~from->buttons;
if (pressed & BUTTON_ATTACK)
{ //enable/disable tracking
if (host_client->spec_track)
{ //disable tracking
@ -8499,7 +8567,7 @@ void SVNQ_ReadClientMove (usercmd_t *move, qboolean forceangle16)
else //otherwise track the next person, if we can
tracknext = true;
}
if ((bits & (move->buttons ^ bits) & BUTTON_JUMP) && host_client->spec_track)
if ((pressed & BUTTON_JUMP) && host_client->spec_track)
tracknext = true;
if (tracknext)
@ -8530,19 +8598,14 @@ void SVNQ_ReadClientMove (usercmd_t *move, qboolean forceangle16)
SV_ClientTPrintf (host_client, PRINT_HIGH, "tracking %s\n", svs.clients[i-1].name);
}
}
move->buttons = bits;
i = MSG_ReadByte ();
move->impulse = i;
host_client->edict->v->v_angle[0] = SHORT2ANGLE(cmd.angles[0]);
host_client->edict->v->v_angle[1] = SHORT2ANGLE(cmd.angles[1]);
host_client->edict->v->v_angle[2] = SHORT2ANGLE(cmd.angles[2]);
if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR))
{
SV_ReadPrydonCursor();
}
if (SV_RunFullQCMovement(host_client, move))
{
host_client->msecs -= move->msec;
if (SV_RunFullQCMovement(host_client, &cmd))
{ //mod provides its own movement logic. this forces independance.
host_client->msecs -= cmd.msec;
pr_global_struct->time = sv.world.physicstime;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
#ifdef VM_Q1
@ -8555,33 +8618,31 @@ void SVNQ_ReadClientMove (usercmd_t *move, qboolean forceangle16)
PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPostThink);
}
host_client->isindependant = true;
return;
}
// if (i && SV_FilterImpulse(i, host_client->trustlevel))
// host_client->edict->v->impulse = i;
SV_SetEntityButtons(host_client->edict, bits);
if (host_client->last_sequence && !sv_nqplayerphysics.ival && host_client->state == cs_spawned)
{
host_frametime = timesincelast;
host_client->isindependant = true;
SV_PreRunCmd();
SV_RunCmd (move, false);
SV_PostRunCmd();
move->impulse = 0;
host_client->lastruncmd = sv.time*1000;
}
else
{
host_client->last_sequence = 0; //let the client know that prediction is fucked, by not acking any input frames.
if (i)
host_client->edict->v->impulse = i;
host_client->isindependant = false;
SV_SetEntityButtons(host_client->edict, cmd.buttons);
if (host_client->last_sequence && !sv_nqplayerphysics.ival && host_client->state == cs_spawned)
{
host_frametime = timesincelast;
host_client->isindependant = true;
SV_PreRunCmd();
SV_RunCmd (&cmd, false);
SV_PostRunCmd();
cmd.impulse = 0;
host_client->lastruncmd = sv.time*1000;
}
else
{
host_client->last_sequence = 0; //let the client know that prediction is fucked, by not acking any input frames.
if (cmd.impulse)
host_client->edict->v->impulse = cmd.impulse;
host_client->isindependant = false;
}
}
*from = cmd;
}
void SVNQ_ExecuteClientMessage (client_t *cl)
@ -8665,7 +8726,8 @@ void SVNQ_ExecuteClientMessage (client_t *cl)
//Hack to work around buggy DP clients that don't reset the proquake hack for the next server
//this ONLY works because the other clc commands are very unlikely to both be 3 bytes big and sent unreliably
//aka: DP ProQuake angles hack hack
//note that if a client then decides to use 16bit coords because of this hack then it would be the 'fte dp proquake angles hack hack hack'....
//note that if a client then decides to use 16bit angles via this hack then it would be the 'fte dp proquake angles hack hack hack'....
if (!cl->fteprotocolextensions && !cl->fteprotocolextensions2)
if ((net_message.cursize-(msg_readcount-1) == 16 && cl->proquake_angles_hack) ||
(net_message.cursize-(msg_readcount-1) == 19 && !cl->proquake_angles_hack))
{
@ -8683,7 +8745,7 @@ void SVNQ_ExecuteClientMessage (client_t *cl)
break;
}
SVNQ_ReadClientMove (&cl->lastcmd, forceangle16);
SVNQ_ReadClientMove (forceangle16);
// cmd = host_client->lastcmd;
// SV_ClientThink();
break;
@ -8767,6 +8829,7 @@ void SV_UserInit (void)
Cvar_Register (&sv_cheatpc, cvargroup_servercontrol);
Cvar_Register (&sv_cheatspeedchecktime, cvargroup_servercontrol);
#endif
Cvar_Register (&sv_showpredloss, cvargroup_servercontrol);
Cvar_Register (&sv_playermodelchecks, cvargroup_servercontrol);
Cvar_Register (&sv_getrealip, cvargroup_servercontrol);

View file

@ -715,7 +715,7 @@ static cvar_t *VARGS Q2Cvar_Get (const char *var_name, const char *value, int fl
{
cvar_t *var;
//q2 gamecode knows about these flags. anything else is probably a bug, or 3rd-party extension.
flags &= (CVAR_NOSET|CVAR_SERVERINFO|CVAR_USERINFO|CVAR_ARCHIVE|CVAR_LATCH);
flags &= (CVAR_NOSET|CVAR_SERVERINFO|CVAR_USERINFO|CVAR_ARCHIVE|CVAR_MAPLATCH);
if (!strcmp(var_name, "gamedir"))
var_name = "fs_gamedir";
@ -923,9 +923,9 @@ qboolean SVQ2_InitGameProgs(void)
if (svq2_maxclients != maxclients.value)
Cvar_SetValue(&maxclients, svq2_maxclients);
maxclients.flags |= CVAR_LATCH;
deathmatch.flags |= CVAR_LATCH;
coop.flags |= CVAR_LATCH;
maxclients.flags |= CVAR_MAPLATCH;
deathmatch.flags |= CVAR_MAPLATCH;
coop.flags |= CVAR_MAPLATCH;
SVQ2_InitWorld();
ge->Init ();

View file

@ -1927,7 +1927,7 @@ static void SV_InitBotLib(void)
botlib = NULL;
if (!botlib)
{
bot_enable->flags |= CVAR_LATCH;
bot_enable->flags |= CVAR_MAPLATCH;
Cvar_ForceSet(bot_enable, "0");
}
#else
@ -3428,7 +3428,7 @@ qboolean SVQ3_RestartGamecode(void)
if (sv.allocated_client_slots != newmaxclients)
return false; //can't do it if maxclients needs to change.
Cvar_ApplyLatches(CVAR_LATCH);
Cvar_ApplyLatches(CVAR_MAPLATCH, false);
//reload the gamecode
sv.state = ss_loading;
@ -3485,7 +3485,7 @@ void SVQ3_NewMapConnects(void)
/* Kick old bots in SP - eukara */
cvar_t *gametype;
gametype = Cvar_Get("g_gametype", "", CVAR_LATCH|CVAR_SERVERINFO, "Q3 compatability");
gametype = Cvar_Get("g_gametype", "", CVAR_MAPLATCH|CVAR_SERVERINFO, "Q3 compatability");
for (i = 0; i < sv.allocated_client_slots; i++)
{

View file

@ -1275,7 +1275,7 @@ static trace_t World_ClipMoveToEntity (world_t *w, wedict_t *ent, vec3_t eorg, v
if ((ent->v->solid == SOLID_BSP || ent->v->solid == SOLID_BSPTRIGGER || ent->v->solid == SOLID_PORTAL) && mdlidx)
{
model = w->Get_CModel(w, mdlidx);
if (!model || (model->type != mod_brush && model->type != mod_heightmap))
if (!model || !model->funcs.PointContents)
{
// Host_Error("SOLID_BSP with non bsp model (classname: %s)", PR_GetString(w->progs, ent->v->classname));
model = NULL;
@ -1325,16 +1325,26 @@ static trace_t World_ClipMoveToEntity (world_t *w, wedict_t *ent, vec3_t eorg, v
{ //if forcedcontents is set, then ALL brushes in this model are forced to the specified contents value.
//we achive this by tracing against ALL then forcing it after.
int forcedcontents;
switch((int)ent->v->skin)
safeswitch((enum q1contents_e)(int)ent->v->skin)
{
case Q1CONTENTS_EMPTY: forcedcontents = FTECONTENTS_EMPTY; break;
case Q1CONTENTS_SOLID: forcedcontents = FTECONTENTS_SOLID; break;
case Q1CONTENTS_WATER: forcedcontents = FTECONTENTS_WATER; break;
case Q1CONTENTS_SLIME: forcedcontents = FTECONTENTS_SLIME; break;
case Q1CONTENTS_LAVA: forcedcontents = FTECONTENTS_LAVA; break;
case Q1CONTENTS_SKY: forcedcontents = FTECONTENTS_SKY; break;
case Q1CONTENTS_LADDER: forcedcontents = FTECONTENTS_LADDER;break;
default: forcedcontents = 0; break;
case Q1CONTENTS_EMPTY: forcedcontents = FTECONTENTS_EMPTY; break;
case Q1CONTENTS_SOLID: forcedcontents = FTECONTENTS_SOLID; break;
case Q1CONTENTS_WATER: forcedcontents = FTECONTENTS_WATER; break;
case Q1CONTENTS_SLIME: forcedcontents = FTECONTENTS_SLIME; break;
case Q1CONTENTS_LAVA: forcedcontents = FTECONTENTS_LAVA; break;
case Q1CONTENTS_SKY: forcedcontents = FTECONTENTS_SKY; break;
case Q1CONTENTS_LADDER: forcedcontents = FTECONTENTS_LADDER; break;
case Q1CONTENTS_CLIP: forcedcontents = FTECONTENTS_PLAYERCLIP|FTECONTENTS_MONSTERCLIP;break;
case Q1CONTENTS_CURRENT_0: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_0; break;
case Q1CONTENTS_CURRENT_90: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_90; break;
case Q1CONTENTS_CURRENT_180: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_180; break;
case Q1CONTENTS_CURRENT_270: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_270; break;
case Q1CONTENTS_CURRENT_UP: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_UP; break;
case Q1CONTENTS_CURRENT_DOWN: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_DOWN; break;
case Q1CONTENTS_TRANS: forcedcontents = FTECONTENTS_SOLID; break;
case Q1CONTENTS_MONSTERCLIP: forcedcontents = FTECONTENTS_MONSTERCLIP; break;
case Q1CONTENTS_PLAYERCLIP: forcedcontents = FTECONTENTS_PLAYERCLIP; break;
safedefault: forcedcontents = 0; break;
}
if (hitcontentsmask & forcedcontents)
{
@ -1345,7 +1355,7 @@ static trace_t World_ClipMoveToEntity (world_t *w, wedict_t *ent, vec3_t eorg, v
else
{
memset (&trace, 0, sizeof(trace_t));
trace.fraction = 1;
trace.fraction = trace.truefraction = 1;
trace.allsolid = false;
trace.startsolid = false;
trace.inopen = true; //probably wrong...
@ -2167,16 +2177,26 @@ static unsigned int World_ContentsOfLinks (world_t *w, areagridlink_t *node, vec
{ //if forcedcontents is set, then ALL brushes in this model are forced to the specified contents value.
//we achive this by tracing against ALL then forcing it after.
unsigned int forcedcontents;
switch((int)touch->v->skin)
safeswitch((enum q1contents_e)(int)touch->v->skin)
{
case Q1CONTENTS_EMPTY: forcedcontents = FTECONTENTS_EMPTY; break;
case Q1CONTENTS_SOLID: forcedcontents = FTECONTENTS_SOLID; break;
case Q1CONTENTS_WATER: forcedcontents = FTECONTENTS_WATER; break;
case Q1CONTENTS_SLIME: forcedcontents = FTECONTENTS_SLIME; break;
case Q1CONTENTS_LAVA: forcedcontents = FTECONTENTS_LAVA; break;
case Q1CONTENTS_SKY: forcedcontents = FTECONTENTS_SKY; break;
case Q1CONTENTS_LADDER: forcedcontents = FTECONTENTS_LADDER;break;
default: forcedcontents = 0; break;
case Q1CONTENTS_EMPTY: forcedcontents = FTECONTENTS_EMPTY; break;
case Q1CONTENTS_SOLID: forcedcontents = FTECONTENTS_SOLID; break;
case Q1CONTENTS_WATER: forcedcontents = FTECONTENTS_WATER; break;
case Q1CONTENTS_SLIME: forcedcontents = FTECONTENTS_SLIME; break;
case Q1CONTENTS_LAVA: forcedcontents = FTECONTENTS_LAVA; break;
case Q1CONTENTS_SKY: forcedcontents = FTECONTENTS_SKY; break;
case Q1CONTENTS_CLIP: forcedcontents = FTECONTENTS_PLAYERCLIP|FTECONTENTS_MONSTERCLIP; break;
case Q1CONTENTS_CURRENT_0: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_0; break;
case Q1CONTENTS_CURRENT_90: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_90; break;
case Q1CONTENTS_CURRENT_180: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_180; break;
case Q1CONTENTS_CURRENT_270: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_270; break;
case Q1CONTENTS_CURRENT_UP: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_UP; break;
case Q1CONTENTS_CURRENT_DOWN: forcedcontents = FTECONTENTS_WATER|Q2CONTENTS_CURRENT_DOWN; break;
case Q1CONTENTS_TRANS: forcedcontents = FTECONTENTS_SOLID; break;
case Q1CONTENTS_LADDER: forcedcontents = FTECONTENTS_LADDER; break;
case Q1CONTENTS_MONSTERCLIP: forcedcontents = FTECONTENTS_MONSTERCLIP; break;
case Q1CONTENTS_PLAYERCLIP: forcedcontents = FTECONTENTS_PLAYERCLIP; break;
safedefault: forcedcontents = 0; break;
}
c = forcedcontents;
}