1
0
Fork 0
forked from fte/fteqw
fteqw/engine/server/pr_q1qvm.c
Spoike 9000f497ec misc fixes and tweaks:
added //it stuffcmd.
now parsing //wps stuffcmds for ezhud use.
rework worker threads a little so that the main thread is no longer guaranteed to be stalled at least once. This speeds up load times a little.
fix nq server player counts in the server browser.
fix switching from/to spectator mode from destroying frag counts.
tweak custom bf args a little.
fix issue with player names starting with char 0x80.
fix menu.dat not loading.
attempt to fix flush lag on windows.
some fixes for stencil shadows.
try to fix public builds not knowing their svn revision. this should fix autoupdates.
added global_gravitydir vector, for mods that want to be really weird. doesn't clear onground flags however.
reworked the status command a little to attempt to report bursts a little better.
fix multicasts missing in mvds issue.
fix illegible server message with dpp7 downloads.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4992 fc73d0e0-1445-4013-8a0c-d673dee63da5
2015-10-27 15:20:15 +00:00

2128 lines
59 KiB
C
Executable file

/*
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*64bit cpu notes:
string_t is a 32bit quantity.
this datatype needs to have enough bits to express any address that contains a string.
in a 32bit build, this is fine. with a qvm, the offset between the vm base and the string is always less than 32bits so this is fine too.
HOWEVER...
native code uses a base address of 0. this needs a 48bit datatype for any userland address. 32 bits just ain't enough.
even worse: ktx defines string_t as a 'char*'. okay, its 64bit at last... but it means that the entire entity field structure is now the wrong size with the wrong offsets.
this means CRASH!
how to fix? good luck with that. seriously.
the only sane way to fix it is to either define a better base address (say the dll base,
and require that all string_t values are bss or data and not from malloc, which is problematic when loading dynamic stuff from a map)
alternatively, you could create some string_t->pointer lookup. messy.
either way, string_t cannot be a pointer.
probably the best solution is to stop using string_t stuff completely. move all those string values somewhere else.
netnames will still mess things up.
so just use qvms.
oh, wait, ktx no longer supports those properly.
*/
#include "quakedef.h"
#ifdef VM_Q1
#include "pr_common.h"
#define GAME_API_VERSION 13
#define MAX_Q1QVM_EDICTS 768 //according to ktx at api version 12 (fte's protocols go to 2048)
#define MAPNAME_LEN 64
void PR_SV_FillWorldGlobals(world_t *w);
#if GAME_API_VERSION >= 13
#define WASTED_EDICT_T_SIZE (VM_NonNative(q1qvm)?sizeof(int):sizeof(void*))
//in version 13, the actual edict_t struct is gone, and there's a pointer to it in its place (which we don't need, but it changes size based on vm/native).
#else
//this is probably broken on 64bit native code
#define WASTED_EDICT_T_SIZE 114
//qclib has split edict_t and entvars_t.
//mvdsv and the api we're implementing has them in one lump
//so we need to bias our entvars_t and fake the offsets a little.
#endif
//===============================================================
//
// system traps provided by the main engine
//
typedef enum
{
//============== general Quake services ==================
G_GETAPIVERSION, // ( void); //0
G_DPRINT, // ( const char *string ); //1
// print message on the local console
G_ERROR, // ( const char *string ); //2
// abort the game
G_GetEntityToken, //3
G_SPAWN_ENT, //4
G_REMOVE_ENT, //5
G_PRECACHE_SOUND,
G_PRECACHE_MODEL,
G_LIGHTSTYLE,
G_SETORIGIN,
G_SETSIZE, //10
G_SETMODEL,
G_BPRINT,
G_SPRINT,
G_CENTERPRINT,
G_AMBIENTSOUND, //15
G_SOUND,
G_TRACELINE,
G_CHECKCLIENT,
G_STUFFCMD,
G_LOCALCMD, //20
G_CVAR,
G_CVAR_SET,
G_FINDRADIUS,
G_WALKMOVE,
G_DROPTOFLOOR, //25
G_CHECKBOTTOM,
G_POINTCONTENTS,
G_NEXTENT,
G_AIM,
G_MAKESTATIC, //30
G_SETSPAWNPARAMS,
G_CHANGELEVEL,
G_LOGFRAG,
G_GETINFOKEY,
G_MULTICAST, //35
G_DISABLEUPDATES,
G_WRITEBYTE,
G_WRITECHAR,
G_WRITESHORT,
G_WRITELONG, //40
G_WRITEANGLE,
G_WRITECOORD,
G_WRITESTRING,
G_WRITEENTITY,
G_FLUSHSIGNON, //45
g_memset,
g_memcpy,
g_strncpy,
g_sin,
g_cos, //50
g_atan2,
g_sqrt,
g_floor,
g_ceil,
g_acos, //55
G_CMD_ARGC,
G_CMD_ARGV,
G_TraceBox, //was G_TraceCapsule
G_FS_OpenFile,
G_FS_CloseFile, //60
G_FS_ReadFile,
G_FS_WriteFile,
G_FS_SeekFile,
G_FS_TellFile,
G_FS_GetFileList, //65
G_CVAR_SET_FLOAT,
G_CVAR_STRING,
G_Map_Extension,
G_strcmp,
G_strncmp, //70
G_stricmp,
G_strnicmp,
G_Find,
G_executecmd,
G_conprint, //75
G_readcmd,
G_redirectcmd,
G_Add_Bot,
G_Remove_Bot,
G_SetBotUserInfo, //80
G_SetBotCMD,
G_strftime,
G_CMD_ARGS,
G_CMD_TOKENIZE,
G_strlcpy, //85
G_strlcat,
G_MAKEVECTORS,
G_NEXTCLIENT,
G_PRECACHE_VWEP_MODEL,
G_SETPAUSE,
G_SETUSERINFO,
G_MOVETOGOAL,
G_MAX
} gameImport_t;
//
// functions exported by the game subsystem
//
typedef enum
{
GAME_INIT, // ( int levelTime, int randomSeed, int restart );
// init and shutdown will be called every single level
// The game should call G_GET_ENTITY_TOKEN to parse through all the
// entity configuration text and spawn gentities.
GAME_LOADENTS,
GAME_SHUTDOWN, // (void);
GAME_CLIENT_CONNECT, // ( int clientNum ,int isSpectator);
GAME_PUT_CLIENT_IN_SERVER,
GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum,int isSpectator );
GAME_CLIENT_DISCONNECT, // ( int clientNum,int isSpectator );
GAME_CLIENT_COMMAND, // ( int clientNum,int isSpectator );
GAME_CLIENT_PRETHINK,
GAME_CLIENT_THINK, // ( int clientNum,int isSpectator );
GAME_CLIENT_POSTTHINK,
GAME_START_FRAME, // ( int levelTime );
GAME_SETCHANGEPARMS, //self
GAME_SETNEWPARMS,
GAME_CONSOLE_COMMAND, // ( void );
GAME_EDICT_TOUCH, //(self,other)
GAME_EDICT_THINK, //(self,other=world,time)
GAME_EDICT_BLOCKED, //(self,other)
GAME_CLIENT_SAY, //(int isteam)
GAME_PAUSED_TIC, //(int milliseconds)
} q1qvmgameExport_t;
typedef enum
{
F_INT,
F_FLOAT,
F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL
// F_GSTRING, // string on disk, pointer in memory, TAG_GAME
F_VECTOR,
F_ANGLEHACK,
// F_ENTITY, // index on disk, pointer in memory
// F_ITEM, // index on disk, pointer in memory
// F_CLIENT, // index on disk, pointer in memory
F_IGNORE
} fieldtype_t;
typedef struct
{
string_t name;
int ofs;
fieldtype_t type;
// int flags;
} field_t;
typedef struct {
int pad[28];
int self;
int other;
int world;
float time;
float frametime;
int newmis;
float force_retouch;
string_t mapname;
float serverflags;
float total_secrets;
float total_monsters;
float found_secrets;
float killed_monsters;
float parm1;
float parm2;
float parm3;
float parm4;
float parm5;
float parm6;
float parm7;
float parm8;
float parm9;
float parm10;
float parm11;
float parm12;
float parm13;
float parm14;
float parm15;
float parm16;
vec3_t v_forward;
vec3_t v_up;
vec3_t v_right;
float trace_allsolid;
float trace_startsolid;
float trace_fraction;
vec3_t trace_endpos;
vec3_t trace_plane_normal;
float trace_plane_dist;
int trace_ent;
float trace_inopen;
float trace_inwater;
int msg_entity;
func_t main;
func_t StartFrame;
func_t PlayerPreThink;
func_t PlayerPostThink;
func_t ClientKill;
func_t ClientConnect;
func_t PutClientInServer;
func_t ClientDisconnect;
func_t SetNewParms;
func_t SetChangeParms;
} q1qvmglobalvars_t;
//this is not directly usable in 64bit to refer to a 32bit qvm (hence why we have two versions).
typedef struct
{
struct vmedict_s *ents;
int sizeofent;
q1qvmglobalvars_t *global;
field_t *fields;
int APIversion;
} gameDataN_t;
typedef struct
{
unsigned int ents;
int sizeofent;
unsigned int global;
unsigned int fields;
int APIversion;
} gameData32_t;
typedef enum {
FS_READ_BIN,
FS_READ_TXT,
FS_WRITE_BIN,
FS_WRITE_TXT,
FS_APPEND_BIN,
FS_APPEND_TXT
} q1qvmfsMode_t;
typedef enum {
FS_SEEK_CUR,
FS_SEEK_END,
FS_SEEK_SET
} fsOrigin_t;
#define emufields \
emufield(gravity, F_FLOAT) \
emufield(maxspeed, F_FLOAT) \
emufield(movement, F_VECTOR) \
emufield(vw_index, F_FLOAT)
struct
{
#define emufield(n,t) int n;
emufields
#undef emufield
} fofs;
static const char *q1qvmentstring;
static vm_t *q1qvm;
static pubprogfuncs_t q1qvmprogfuncs;
static void *evars; //pointer to the gamecodes idea of an edict_t
static qintptr_t vevars; //offset into the vm base of evars
/*
static char *Q1QVMPF_AddString(pubprogfuncs_t *pf, char *base, int minlength)
{
char *n;
int l = strlen(base);
Con_Printf("warning: string %s will not be readable from the qvm\n", base);
l = l<minlength?minlength:l;
n = Z_TagMalloc(l+1, VMFSID_Q1QVM);
strcpy(n, base);
return n;
}
*/
static edict_t *QDECL Q1QVMPF_EdictNum(pubprogfuncs_t *pf, unsigned int num)
{
edict_t *e;
if (/*num < 0 ||*/ num >= sv.world.max_edicts)
return NULL;
e = q1qvmprogfuncs.edicttable[num];
if (!e)
{
e = q1qvmprogfuncs.edicttable[num] = Z_TagMalloc(sizeof(edict_t)+sizeof(extentvars_t), VMFSID_Q1QVM);
e->v = (stdentvars_t*)((char*)evars + (num * sv.world.edict_size) + WASTED_EDICT_T_SIZE);
e->xv = (extentvars_t*)(e+1);
e->entnum = num;
}
return e;
}
static unsigned int QDECL Q1QVMPF_NumForEdict(pubprogfuncs_t *pf, edict_t *e)
{
return e->entnum;
}
static int QDECL Q1QVMPF_EdictToProgs(pubprogfuncs_t *pf, edict_t *e)
{
return e->entnum*sv.world.edict_size;
}
static edict_t *QDECL Q1QVMPF_ProgsToEdict(pubprogfuncs_t *pf, int num)
{
if (num % sv.world.edict_size)
Con_Printf("Edict To Progs with remainder\n");
num /= sv.world.edict_size;
return Q1QVMPF_EdictNum(pf, num);
}
static void Q1QVMED_ClearEdict (edict_t *e, qboolean wipe)
{
int num = e->entnum;
if (wipe)
memset (e->v, 0, sv.world.edict_size - WASTED_EDICT_T_SIZE);
e->isfree = false;
e->entnum = num;
}
static void QDECL Q1QVMPF_ClearEdict(pubprogfuncs_t *pf, edict_t *e)
{
Q1QVMED_ClearEdict(e, true);
}
static void QDECL Q1QVMPF_EntRemove(pubprogfuncs_t *pf, edict_t *e)
{
if (!ED_CanFree(e))
return;
e->isfree = true;
e->freetime = sv.time;
}
static edict_t *QDECL Q1QVMPF_EntAlloc(pubprogfuncs_t *pf, pbool object, size_t extrasize)
{
int i;
edict_t *e;
for ( i=0 ; i<sv.world.num_edicts ; i++)
{
e = (edict_t*)EDICT_NUM(pf, i);
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!e || (e->isfree && ( e->freetime < 2 || sv.time - e->freetime > 0.5 ) ))
{
Q1QVMED_ClearEdict (e, true);
ED_Spawned((struct edict_s *) e, false);
return (struct edict_s *)e;
}
}
if (i >= sv.world.max_edicts-1) //try again, but use timed out ents.
{
for ( i=0 ; i<sv.world.num_edicts ; i++)
{
e = (edict_t*)EDICT_NUM(pf, i);
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!e || (e->isfree))
{
Q1QVMED_ClearEdict (e, true);
ED_Spawned((struct edict_s *) e, false);
return (struct edict_s *)e;
}
}
if (i >= sv.world.max_edicts-1)
{
Sys_Error ("ED_Alloc: no free edicts");
}
}
sv.world.num_edicts++;
e = (edict_t*)Q1QVMPF_EdictNum(pf, i);
// new ents come ready wiped
// Q1QVMED_ClearEdict (e, false);
ED_Spawned((struct edict_s *) e, false);
return (struct edict_s *)e;
}
static int QDECL Q1QVMPF_LoadEnts(pubprogfuncs_t *pf, const char *mapstring, float spawnflags)
{
q1qvmentstring = mapstring;
VM_Call(q1qvm, GAME_LOADENTS, 0, 0, 0);
q1qvmentstring = NULL;
return sv.world.edict_size;
}
static eval_t *QDECL Q1QVMPF_GetEdictFieldValue(pubprogfuncs_t *pf, edict_t *e, char *fieldname, evalc_t *cache)
{
if (!strcmp(fieldname, "message"))
{
return (eval_t*)&e->v->message;
}
return NULL;
}
static eval_t *QDECL Q1QVMPF_FindGlobal (pubprogfuncs_t *prinst, const char *name, progsnum_t num, etype_t *type)
{
return NULL;
}
static globalvars_t *QDECL Q1QVMPF_Globals(pubprogfuncs_t *prinst, int prnum)
{
return NULL;
}
static string_t QDECL Q1QVMPF_StringToProgs(pubprogfuncs_t *prinst, const char *str)
{
string_t ret = (string_t)(str - (char*)VM_MemoryBase(q1qvm));
if (ret >= VM_MemoryMask(q1qvm))
return 0;
return ret;
}
static const char *ASMCALL QDECL Q1QVMPF_StringToNative(pubprogfuncs_t *prinst, string_t str)
{
char *ret = (char*)VM_MemoryBase(q1qvm) + str;
if (!ret) //qvms can never return a null. make sure native code can't crash things either.
return "";
return ret;
}
static int WrapQCBuiltin(builtin_t func, void *offset, quintptr_t mask, const qintptr_t *arg, char *argtypes)
{
globalvars_t gv;
int argnum=0;
while(*argtypes)
{
switch(*argtypes++)
{
case 'f':
gv.param[argnum++].f = VM_FLOAT(*arg++);
break;
case 'i':
gv.param[argnum++].f = VM_LONG(*arg++); //vanilla qc does not support ints, but qvms do. this means ints need to be converted to floats for the builtin to understand them properly.
break;
case 'n': //ent num
gv.param[argnum++].i = EDICT_TO_PROG(svprogfuncs, Q1QVMPF_EdictNum(svprogfuncs, VM_LONG(*arg++)));
break;
case 'v': //three seperate args -> 1 vector
gv.param[argnum].vec[0] = VM_FLOAT(*arg++);
gv.param[argnum].vec[1] = VM_FLOAT(*arg++);
gv.param[argnum].vec[2] = VM_FLOAT(*arg++);
argnum++;
break;
case 's':
gv.param[argnum].i = VM_LONG(*arg++);
argnum++;
break;
}
}
svprogfuncs->callargc = argnum;
gv.ret.i = 0;
func(svprogfuncs, &gv);
return gv.ret.i;
}
#define VALIDATEPOINTER(o,l) if ((qintptr_t)o + l >= mask || VM_POINTER(o) < offset) SV_Error("Call to game trap passes invalid pointer\n"); //out of bounds.
static qintptr_t QVM_GetAPIVersion (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return GAME_API_VERSION;
}
static qintptr_t QVM_DPrint (void *offset, quintptr_t mask, const qintptr_t *arg)
{
Con_DPrintf("%s", (char*)VM_POINTER(arg[0]));
return 0;
}
static qintptr_t QVM_Error (void *offset, quintptr_t mask, const qintptr_t *arg)
{
SV_Error("Q1QVM: %s", (char*)VM_POINTER(arg[0]));
return 0;
}
static qintptr_t QVM_GetEntityToken (void *offset, quintptr_t mask, const qintptr_t *arg)
{
if (VM_OOB(arg[0], arg[1]) || !arg[1])
return false;
if (q1qvmentstring)
{
char *ret = VM_POINTER(arg[0]);
q1qvmentstring = COM_Parse(q1qvmentstring);
Q_strncpyz(ret, com_token, VM_LONG(arg[1]));
return *com_token != 0;
}
else
{
char *ret = VM_POINTER(arg[0]);
*ret = '\0';
return false;
}
}
static qintptr_t QVM_Spawn_Ent (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return Q1QVMPF_EntAlloc(svprogfuncs, false, 0)->entnum;
}
static qintptr_t QVM_Remove_Ent (void *offset, quintptr_t mask, const qintptr_t *arg)
{
if (arg[0] >= sv.world.max_edicts)
return false;
Q1QVMPF_EntRemove(svprogfuncs, q1qvmprogfuncs.edicttable[arg[0]]);
return true;
}
static qintptr_t QVM_Precache_Sound (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return PF_precache_sound_Internal(svprogfuncs, VM_POINTER(arg[0]));
}
static qintptr_t QVM_Precache_Model (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return PF_precache_model_Internal(svprogfuncs, VM_POINTER(arg[0]), false);
}
static qintptr_t QVM_LightStyle (void *offset, quintptr_t mask, const qintptr_t *arg)
{
vec3_t rgb = {1,1,1};
PF_applylightstyle(VM_LONG(arg[0]), VM_POINTER(arg[1]), rgb);
return 0;
}
static qintptr_t QVM_SetOrigin (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *e = Q1QVMPF_EdictNum(svprogfuncs, VM_LONG(arg[0]));
if (!e || e->isfree)
return false;
e->v->origin[0] = VM_FLOAT(arg[1]);
e->v->origin[1] = VM_FLOAT(arg[2]);
e->v->origin[2] = VM_FLOAT(arg[3]);
World_LinkEdict (&sv.world, (wedict_t*)e, false);
return true;
}
static qintptr_t QVM_SetSize (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *e = Q1QVMPF_EdictNum(svprogfuncs, arg[0]);
if (!e || e->isfree)
return false;
e->v->mins[0] = VM_FLOAT(arg[1]);
e->v->mins[1] = VM_FLOAT(arg[2]);
e->v->mins[2] = VM_FLOAT(arg[3]);
e->v->maxs[0] = VM_FLOAT(arg[4]);
e->v->maxs[1] = VM_FLOAT(arg[5]);
e->v->maxs[2] = VM_FLOAT(arg[6]);
VectorSubtract (e->v->maxs, e->v->mins, e->v->size);
World_LinkEdict (&sv.world, (wedict_t*)e, false);
return true;
}
static qintptr_t QVM_SetModel (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *e = Q1QVMPF_EdictNum(svprogfuncs, arg[0]);
PF_setmodel_Internal(svprogfuncs, e, VM_POINTER(arg[1]));
return 0;
}
static qintptr_t QVM_BPrint (void *offset, quintptr_t mask, const qintptr_t *arg)
{
SV_BroadcastPrintf(arg[0], "%s", (char*)VM_POINTER(arg[1]));
return 0;
}
static qintptr_t QVM_SPrint (void *offset, quintptr_t mask, const qintptr_t *arg)
{
if ((unsigned)VM_LONG(arg[0]) > sv.allocated_client_slots)
return 0;
SV_ClientPrintf(&svs.clients[VM_LONG(arg[0])-1], VM_LONG(arg[1]), "%s", (char*)VM_POINTER(arg[2]));
return 0;
}
static qintptr_t QVM_CenterPrint (void *offset, quintptr_t mask, const qintptr_t *arg)
{
PF_centerprint_Internal(VM_LONG(arg[0]), false, VM_POINTER(arg[1]));
return 0;
}
static qintptr_t QVM_AmbientSound (void *offset, quintptr_t mask, const qintptr_t *arg)
{
vec3_t pos;
pos[0] = VM_FLOAT(arg[0]);
pos[1] = VM_FLOAT(arg[1]);
pos[2] = VM_FLOAT(arg[2]);
PF_ambientsound_Internal(pos, VM_POINTER(arg[3]), VM_FLOAT(arg[4]), VM_FLOAT(arg[5]));
return 0;
}
static qintptr_t QVM_Sound (void *offset, quintptr_t mask, const qintptr_t *arg)
{
// ( int edn, int channel, char *samp, float vol, float att )
int channel = VM_LONG(arg[1]);
int flags = 0;
if (channel & 8)
{ //based on quakeworld, remember
channel = (channel & 7) | ((channel&~15)>>1);
flags |= CF_RELIABLE;
}
SVQ1_StartSound (NULL, (wedict_t*)Q1QVMPF_EdictNum(svprogfuncs, VM_LONG(arg[0])), channel, VM_POINTER(arg[2]), VM_FLOAT(arg[3])*255, VM_FLOAT(arg[4]), 0, 0, flags);
return 0;
}
static qintptr_t QVM_TraceLine (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_svtraceline, offset, mask, arg, "vvin");
return 0;
}
static qintptr_t QVM_CheckClient (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return PF_checkclient_Internal(svprogfuncs);
}
static qintptr_t QVM_StuffCmd (void *offset, quintptr_t mask, const qintptr_t *arg)
{
PF_stuffcmd_Internal(VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]));
return 0;
}
static qintptr_t QVM_LocalCmd (void *offset, quintptr_t mask, const qintptr_t *arg)
{
Cbuf_AddText (VM_POINTER(arg[0]), RESTRICT_INSECURE);
return 0;
}
static qintptr_t QVM_CVar (void *offset, quintptr_t mask, const qintptr_t *arg)
{
cvar_t *c;
char *vname = VM_POINTER(arg[0]);
//paused state is not a cvar in fte.
if (!strcmp(vname, "sv_paused"))
return VM_LONG(sv.paused);
c = Cvar_Get(vname, "", 0, "Gamecode");
return VM_LONG(c->value);
}
static qintptr_t QVM_CVar_Set (void *offset, quintptr_t mask, const qintptr_t *arg)
{
cvar_t *var;
var = Cvar_Get(VM_POINTER(arg[0]), VM_POINTER(arg[1]), 0, "Gamecode variables");
if (!var)
return -1;
Cvar_Set (var, VM_POINTER(arg[1]));
return 0;
}
static qintptr_t QVM_FindRadius (void *offset, quintptr_t mask, const qintptr_t *arg)
{
int start = ((char*)VM_POINTER(arg[0]) - (char*)evars) / sv.world.edict_size;
edict_t *ed;
vec3_t diff;
float *org = VM_POINTER(arg[1]);
float rad = VM_FLOAT(arg[2]);
rad *= rad;
for(start++; start < sv.world.num_edicts; start++)
{
ed = EDICT_NUM(svprogfuncs, start);
if (ed->isfree)
continue;
VectorSubtract(ed->v->origin, org, diff);
if (rad > DotProduct(diff, diff))
return (qintptr_t)(vevars + start*sv.world.edict_size);
}
return 0;
}
static qintptr_t QVM_WalkMove (void *offset, quintptr_t mask, const qintptr_t *arg)
{
wedict_t *ed = WEDICT_NUM(svprogfuncs, arg[0]);
float yaw = VM_FLOAT(arg[1]);
float dist = VM_FLOAT(arg[2]);
vec3_t move;
vec3_t axis[3];
World_GetEntGravityAxis(ed, axis);
yaw = yaw*M_PI*2 / 360;
move[0] = cos(yaw)*dist;
move[1] = sin(yaw)*dist;
move[2] = 0;
return World_movestep(&sv.world, (wedict_t*)ed, move, axis, true, false, NULL, NULL);
}
static qintptr_t QVM_DropToFloor (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *ent;
vec3_t end;
vec3_t start;
trace_t trace;
extern cvar_t pr_droptofloorunits;
ent = EDICT_NUM(svprogfuncs, arg[0]);
VectorCopy (ent->v->origin, end);
if (pr_droptofloorunits.value > 0)
end[2] -= pr_droptofloorunits.value;
else
end[2] -= 256;
VectorCopy (ent->v->origin, start);
trace = World_Move (&sv.world, start, ent->v->mins, ent->v->maxs, end, MOVE_NORMAL, (wedict_t*)ent);
if (trace.fraction == 1 || trace.allsolid)
return false;
else
{
VectorCopy (trace.endpos, ent->v->origin);
World_LinkEdict (&sv.world, (wedict_t*)ent, false);
ent->v->flags = (int)ent->v->flags | FL_ONGROUND;
ent->v->groundentity = EDICT_TO_PROG(svprogfuncs, trace.ent);
return true;
}
}
static qintptr_t QVM_CheckBottom (void *offset, quintptr_t mask, const qintptr_t *arg)
{
vec3_t up = {0,0,1};
return World_CheckBottom(&sv.world, (wedict_t*)EDICT_NUM(svprogfuncs, VM_LONG(arg[0])), up);
}
static qintptr_t QVM_PointContents (void *offset, quintptr_t mask, const qintptr_t *arg)
{
vec3_t v;
v[0] = VM_FLOAT(arg[0]);
v[1] = VM_FLOAT(arg[1]);
v[2] = VM_FLOAT(arg[2]);
return sv.world.worldmodel->funcs.PointContents(sv.world.worldmodel, NULL, v);
}
static qintptr_t QVM_NextEnt (void *offset, quintptr_t mask, const qintptr_t *arg)
{ //input output are entity numbers
unsigned int i;
edict_t *ent;
i = VM_LONG(arg[0]);
while (1)
{
i++;
if (i >= sv.world.num_edicts)
{
return 0;
}
ent = EDICT_NUM(svprogfuncs, i);
if (!ent->isfree)
{
return i;
}
}
}
static qintptr_t QVM_Aim (void *offset, quintptr_t mask, const qintptr_t *arg)
{
Con_DPrintf("QVM_Aim: not implemented\n");
return 0; //not in mvdsv anyway
}
static qintptr_t QVM_MakeStatic (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_makestatic, offset, mask, arg, "n");
return 0;
}
static qintptr_t QVM_SetSpawnParams (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_setspawnparms, offset, mask, arg, "n");
return 0;
}
static qintptr_t QVM_ChangeLevel (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_changelevel, offset, mask, arg, "s");
return 0;
}
static qintptr_t QVM_ChangeLevel2 (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_changelevel, offset, mask, arg, "ss");
return 0;
}
static qintptr_t QVM_LogFrag (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_logfrag, offset, mask, arg, "nn");
return 0;
}
static qintptr_t QVM_Precache_VWep_Model (void *offset, quintptr_t mask, const qintptr_t *arg)
{
int i = WrapQCBuiltin(PF_precache_vwep_model, offset, mask, arg, "s");
float f = *(float*)&i;
return f;
}
static qintptr_t QVM_GetInfoKey (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *v;
if (VM_OOB(arg[2], arg[3]))
return -1;
v = PF_infokey_Internal(VM_LONG(arg[0]), VM_POINTER(arg[1]));
Q_strncpyz(VM_POINTER(arg[2]), v, VM_LONG(arg[3]));
return 0;
}
static qintptr_t QVM_Multicast (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_multicast, offset, mask, arg, "vi");
return 0;
}
static qintptr_t QVM_DisableUpdates (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//FIXME: remember to ask mvdsv people why this is useful
Con_Printf("G_DISABLEUPDATES: not supported\n");
return 0;
}
static qintptr_t QVM_WriteByte (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteByte, offset, mask, arg, "ii");
return 0;
}
static qintptr_t QVM_WriteChar (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteChar, offset, mask, arg, "ii");
return 0;
}
static qintptr_t QVM_WriteShort (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteShort, offset, mask, arg, "ii");
return 0;
}
static qintptr_t QVM_WriteLong (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteLong, offset, mask, arg, "ii");
return 0;
}
static qintptr_t QVM_WriteAngle (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteAngle, offset, mask, arg, "if");
return 0;
}
static qintptr_t QVM_WriteCoord (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteCoord, offset, mask, arg, "if");
return 0;
}
static qintptr_t QVM_WriteString (void *offset, quintptr_t mask, const qintptr_t *arg)
{
PF_WriteString_Internal(VM_LONG(arg[0]), VM_POINTER(arg[1]));
return 0;
}
static qintptr_t QVM_WriteEntity (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_WriteEntity, offset, mask, arg, "in");
return 0;
}
static qintptr_t QVM_FlushSignon (void *offset, quintptr_t mask, const qintptr_t *arg)
{
SV_FlushSignon ();
return 0;
}
static qintptr_t QVM_memset (void *offset, quintptr_t mask, const qintptr_t *arg)
{
void *dst = VM_POINTER(arg[0]);
VALIDATEPOINTER(arg[0], arg[2]);
memset(dst, arg[1], arg[2]);
return arg[0];
}
static qintptr_t QVM_memcpy (void *offset, quintptr_t mask, const qintptr_t *arg)
{
void *dst = VM_POINTER(arg[0]);
void *src = VM_POINTER(arg[1]);
VALIDATEPOINTER(arg[0], arg[2]);
memmove(dst, src, arg[2]);
return arg[0];
}
static qintptr_t QVM_strncpy (void *offset, quintptr_t mask, const qintptr_t *arg)
{
VALIDATEPOINTER(arg[0], arg[2]);
Q_strncpyS(VM_POINTER(arg[0]), VM_POINTER(arg[1]), arg[2]);
return arg[0];
}
static qintptr_t QVM_sin (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = sin(VM_FLOAT(arg[0]));
return u.r;
}
static qintptr_t QVM_cos (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = cos(VM_FLOAT(arg[0]));
return u.r;
}
static qintptr_t QVM_atan2 (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = atan2(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]));
return u.r;
}
static qintptr_t QVM_sqrt (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = sqrt(VM_FLOAT(arg[0]));
return u.r;
}
static qintptr_t QVM_floor (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = floor(VM_FLOAT(arg[0]));
return u.r;
}
static qintptr_t QVM_ceil (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = ceil(VM_FLOAT(arg[0]));
return u.r;
}
static qintptr_t QVM_acos (void *offset, quintptr_t mask, const qintptr_t *arg)
{
union
{
qintptr_t r;
float f;
} u = {0};
u.f = acos(VM_FLOAT(arg[0]));
return u.r;
}
static qintptr_t QVM_Cmd_ArgC (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return Cmd_Argc();
}
static qintptr_t QVM_Cmd_ArgV (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *c;
c = Cmd_Argv(VM_LONG(arg[0]));
if (VM_OOB(arg[1], arg[2]))
return -1;
Q_strncpyz(VM_POINTER(arg[1]), c, VM_LONG(arg[2]));
return 0;
}
static qintptr_t QVM_TraceBox (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_svtraceline, offset, mask, arg, "vvinvv");
return 0;
}
static qintptr_t QVM_FS_OpenFile (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//0 = name
//1 = &handle
//2 = mode
//ret = filesize or -1
// Con_Printf("G_FSOpenFile: %s (mode %i)\n", VM_POINTER(arg[0]), arg[2]);
int mode;
switch((q1qvmfsMode_t)arg[2])
{
default:
return -1;
case FS_READ_BIN:
case FS_READ_TXT:
mode = VM_FS_READ;
break;
case FS_WRITE_BIN:
case FS_WRITE_TXT:
mode = VM_FS_WRITE;
break;
case FS_APPEND_BIN:
case FS_APPEND_TXT:
mode = VM_FS_APPEND;
break;
}
return VM_fopen(VM_POINTER(arg[0]), VM_POINTER(arg[1]), mode, VMFSID_Q1QVM);
}
static qintptr_t QVM_FS_CloseFile (void *offset, quintptr_t mask, const qintptr_t *arg)
{
VM_fclose(arg[0], VMFSID_Q1QVM);
return 0;
}
static qintptr_t QVM_FS_ReadFile (void *offset, quintptr_t mask, const qintptr_t *arg)
{
if (VM_OOB(arg[0], arg[1]))
return 0;
return VM_FRead(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VMFSID_Q1QVM);
}
static qintptr_t QVM_FS_WriteFile (void *offset, quintptr_t mask, const qintptr_t *arg)
{
if (VM_OOB(arg[0], arg[1]))
return 0;
return VM_FWrite(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VMFSID_Q1QVM);
}
static qintptr_t QVM_FS_SeekFile (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//fixme: what should the return value be?
VM_FSeek(VM_LONG(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VMFSID_Q1QVM);
return 0;
}
static qintptr_t QVM_FS_TellFile (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return VM_FTell(VM_LONG(arg[0]), VMFSID_Q1QVM);
}
static qintptr_t QVM_FS_GetFileList (void *offset, quintptr_t mask, const qintptr_t *arg)
{
if (VM_OOB(arg[2], arg[3]))
return 0;
return VM_GetFileList(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3]));
}
static qintptr_t QVM_CVar_Set_Float (void *offset, quintptr_t mask, const qintptr_t *arg)
{
cvar_t *var;
var = Cvar_Get(VM_POINTER(arg[0]), va("%f", VM_FLOAT(arg[1])), 0, "Gamecode variables");
if (!var)
return -1;
Cvar_SetValue (var, VM_FLOAT(arg[1]));
return 0;
}
static qintptr_t QVM_CVar_String (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *n = VM_POINTER(arg[0]);
cvar_t *cv;
if (VM_OOB(arg[1], arg[2]))
return -1;
if (!strcmp(n, "version"))
{
n = version_string();
Q_strncpyz(VM_POINTER(arg[1]), n, VM_LONG(arg[2]));
}
else
{
cv = Cvar_Get(n, "", 0, "QC variables");
if (cv)
Q_strncpyz(VM_POINTER(arg[1]), cv->string, VM_LONG(arg[2]));
else
Q_strncpyz(VM_POINTER(arg[1]), "", VM_LONG(arg[2]));
}
return 0;
}
static qintptr_t QVM_strcmp (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *a = VM_POINTER(arg[0]);
char *b = VM_POINTER(arg[1]);
return strcmp(a, b);
}
static qintptr_t QVM_strncmp (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *a = VM_POINTER(arg[0]);
char *b = VM_POINTER(arg[1]);
return strncmp(a, b, VM_LONG(arg[2]));
}
static qintptr_t QVM_stricmp (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *a = VM_POINTER(arg[0]);
char *b = VM_POINTER(arg[1]);
return stricmp(a, b);
}
static qintptr_t QVM_strnicmp (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *a = VM_POINTER(arg[0]);
char *b = VM_POINTER(arg[1]);
return strnicmp(a, b, VM_LONG(arg[2]));
}
static qintptr_t QVM_Find (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *e = VM_POINTER(arg[0]);
int ofs = VM_LONG(arg[1]) - WASTED_EDICT_T_SIZE;
char *match = VM_POINTER(arg[2]);
char *field;
int first = e?((char*)e - (char*)evars)/sv.world.edict_size:0;
int i;
if (!match)
match = "";
for (i = first+1; i < sv.world.num_edicts; i++)
{
e = q1qvmprogfuncs.edicttable[i];
field = VM_POINTER(*((string_t*)e->v + ofs/4));
if (field == NULL)
{
if (*match == '\0')
return ((char*)e->v - (char*)offset)-WASTED_EDICT_T_SIZE;
}
else
{
if (!strcmp(field, match))
return ((char*)e->v - (char*)offset)-WASTED_EDICT_T_SIZE;
}
}
return 0;
}
static qintptr_t QVM_ExecuteCmd (void *offset, quintptr_t mask, const qintptr_t *arg)
{
WrapQCBuiltin(PF_ExecuteCommand, offset, mask, arg, "");
return 0;
}
static qintptr_t QVM_ConPrint (void *offset, quintptr_t mask, const qintptr_t *arg)
{
Con_Printf("%s", (char*)VM_POINTER(arg[0]));
return 0;
}
static qintptr_t QVM_ReadCmd (void *offset, quintptr_t mask, const qintptr_t *arg)
{
extern char outputbuf[];
extern redirect_t sv_redirected;
extern int sv_redirectedlang;
redirect_t old;
int oldl;
char *s = VM_POINTER(arg[0]);
char *output = VM_POINTER(arg[1]);
int outputlen = VM_LONG(arg[2]);
if (VM_OOB(arg[1], arg[2]))
return -1;
Cbuf_Execute(); //FIXME: this code is flawed
Cbuf_AddText (s, RESTRICT_LOCAL);
old = sv_redirected;
oldl = sv_redirectedlang;
if (old != RD_NONE)
SV_EndRedirect();
SV_BeginRedirect(RD_OBLIVION, TL_FindLanguage(""));
Cbuf_Execute();
Q_strncpyz(output, outputbuf, outputlen);
SV_EndRedirect();
if (old != RD_NONE)
SV_BeginRedirect(old, oldl);
Con_DPrintf("PF_readcmd: %s\n%s", s, output);
return 0;
}
static qintptr_t QVM_RedirectCmd (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//FIXME: KTX uses this, along with a big fat warning.
//it shouldn't be vital to the normal functionality
//just restricts admin a little (did these guys never hear of rcon?)
//I'm too lazy to implement it though.
Con_DPrintf("QVM_RedirectCmd: not implemented\n");
return 0;
}
static qintptr_t QVM_Add_Bot (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//FIXME: not implemented, always returns failure.
//the other bot functions only ever work on bots anyway, so don't need to be implemented until this one is
//return WrapQCBuiltin(PF_spawnclient, offset, mask, arg, "");
Con_DPrintf("QVM_Add_Bot: not implemented\n");
return 0;
}
static qintptr_t QVM_Remove_Bot (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//fixme: should become general kick
Con_DPrintf("QVM_Remove_Bot: not implemented\n");
//WrapQCBuiltin(PF_dropclient, offset, mask, arg, "n");
return 0;
}
static qintptr_t QVM_SetBotCMD (void *offset, quintptr_t mask, const qintptr_t *arg)
{
//in mvdsv, this is run *after* the frame.
Con_DPrintf("QVM_SetBotCMD: not implemented\n");
return 0;
}
static qintptr_t QVM_SetUserInfo (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *key = VM_POINTER(arg[1]);
if (*key == '*' && (VM_LONG(arg[3])&1))
return -1; //denied!
return PF_ForceInfoKey_Internal(VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]));
}
static qintptr_t QVM_SetBotUserInfo (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return PF_ForceInfoKey_Internal(VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]));
}
static qintptr_t QVM_MoveToGoal (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return World_MoveToGoal(&sv.world, (wedict_t*)Q1QVMPF_ProgsToEdict(svprogfuncs, pr_global_struct->self), VM_FLOAT(arg[0]));
}
static qintptr_t QVM_strftime (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *out = VM_POINTER(arg[0]);
char *fmt = VM_POINTER(arg[2]);
time_t curtime;
struct tm *local;
if (VM_OOB(arg[0], arg[1]) || !out)
return -1; //please don't corrupt me
time(&curtime);
curtime += VM_LONG(arg[3]);
local = localtime(&curtime);
strftime(out, VM_LONG(arg[1]), fmt, local);
return 0;
}
static qintptr_t QVM_Cmd_ArgS (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *c;
c = Cmd_Args();
if (VM_OOB(arg[0], arg[1]))
return -1;
Q_strncpyz(VM_POINTER(arg[0]), c, VM_LONG(arg[1]));
return arg[0];
}
static qintptr_t QVM_Cmd_Tokenize (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *str = VM_POINTER(arg[0]);
Cmd_TokenizeString(str, false, false);
return Cmd_Argc();
}
static qintptr_t QVM_strlcpy (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *dst = VM_POINTER(arg[0]);
char *src = VM_POINTER(arg[1]);
if (VM_OOB(arg[0], arg[2]) || VM_LONG(arg[2]) < 1)
return -1;
else if (!src)
{
*dst = 0;
return 0;
}
else
{
Q_strncpyz(dst, src, VM_LONG(arg[2]));
return strlen(src);
}
}
static qintptr_t QVM_strlcat (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *dst = VM_POINTER(arg[0]);
char *src = VM_POINTER(arg[1]);
if (VM_OOB(arg[0], arg[2]))
return -1;
Q_strncatz(dst, src, VM_LONG(arg[2]));
//WARNING: no return value
return 0;
}
static qintptr_t QVM_MakeVectors (void *offset, quintptr_t mask, const qintptr_t *arg)
{
AngleVectors(VM_POINTER(arg[0]), P_VEC(v_forward), P_VEC(v_right), P_VEC(v_up));
return 0;
}
static qintptr_t QVM_NextClient (void *offset, quintptr_t mask, const qintptr_t *arg)
{
unsigned int start = ((char*)VM_POINTER(arg[0]) - (char*)evars) / sv.world.edict_size;
while (start < sv.allocated_client_slots)
{
if (svs.clients[start].state == cs_spawned)
return (qintptr_t)(vevars + (start+1) * sv.world.edict_size);
start++;
}
return 0;
}
static qintptr_t QVM_SetPause (void *offset, quintptr_t mask, const qintptr_t *arg)
{
int pause = VM_LONG(arg[0]);
if ((sv.paused&1) == (pause&1))
return sv.paused&1; //nothing changed, ignore it.
sv.paused = pause;
sv.pausedstart = Sys_DoubleTime();
return sv.paused&1;
}
static qintptr_t QVM_NotYetImplemented (void *offset, quintptr_t mask, const qintptr_t *arg)
{
SV_Error("Q1QVM: Trap not implemented\n");
return 0;
}
static int QVM_FindExtField(char *fname)
{
extentvars_t *xv = NULL;
#define comfieldfloat(name,desc) if (!strcmp(fname, #name)) return ((int*)&xv->name - (int*)xv);
#define comfieldvector(name,desc) if (!strcmp(fname, #name)) return ((int*)&xv->name - (int*)xv);
#define comfieldentity(name,desc) if (!strcmp(fname, #name)) return ((int*)&xv->name - (int*)xv);
#define comfieldstring(name,desc) if (!strcmp(fname, #name)) return ((int*)&xv->name - (int*)xv);
#define comfieldfunction(name, typestr,desc) if (!strcmp(fname, #name)) return ((int*)&xv->name - (int*)xv);
comextqcfields
svextqcfields
#undef comfieldfloat
#undef comfieldvector
#undef comfieldentity
#undef comfieldstring
#undef comfieldfunction
return -1; //unsupported
}
static qintptr_t QVM_SetExtField (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *e = VM_POINTER(arg[0]);
int i = QVM_FindExtField(VM_POINTER(arg[1]));
int value = VM_LONG(arg[2]);
if (i < 0)
return 0;
((int*)e->xv)[i] = value;
return value;
}
static qintptr_t QVM_GetExtField (void *offset, quintptr_t mask, const qintptr_t *arg)
{
edict_t *e = VM_POINTER(arg[0]);
int i = QVM_FindExtField(VM_POINTER(arg[1]));
if (i < 0)
return 0;
return ((int*)e->xv)[i];
}
#ifdef WEBCLIENT
static void QVM_uri_query_callback(struct dl_download *dl)
{
void *cb_context = dl->user_ctx;
int cb_entry = dl->user_float;
int selfnum = dl->user_num;
if (svs.gametype != GT_Q1QVM || svs.spawncount != dl->user_sequence)
return; //the world moved on.
//fixme: pointers might not still be valid if the map changed.
*sv.world.g.self = selfnum;
if (dl->file)
{
size_t len = VFS_GETLEN(dl->file);
char *buffer = malloc(len+1);
buffer[len] = 0;
VFS_READ(dl->file, buffer, len);
Cmd_Args_Set(buffer);
free(buffer);
}
else
Cmd_Args_Set(NULL);
VM_Call(q1qvm, cb_entry, cb_context, dl->replycode, 0, 0, 0);
}
//bool uri_get(char *uri, int cb_entry, void *cb_ctx, char *mime, void *data, unsigned datasize)
static qintptr_t QVM_uri_query (void *offset, quintptr_t mask, const qintptr_t *arg)
{
struct dl_download *dl;
const unsigned char *url = VM_POINTER(arg[0]);
int cb_entry = VM_LONG(arg[1]);
void *cb_context = VM_POINTER(arg[2]);
const char *mimetype = VM_POINTER(arg[3]);
const char *data = VM_POINTER(arg[4]);
size_t datasize = VM_LONG(arg[5]);
extern cvar_t pr_enable_uriget;
if (!pr_enable_uriget.ival)
{
Con_Printf("QVM_uri_query(\"%s\","fPRIp"): %s disabled\n", url, cb_context, pr_enable_uriget.name);
return 0;
}
if (mimetype && *mimetype)
{
VALIDATEPOINTER(arg[4],datasize);
Con_DPrintf("QVM_uri_query(%s,"fPRIp")\n", url, cb_context);
dl = HTTP_CL_Put(url, mimetype, data, datasize, QVM_uri_query_callback);
}
else
{
Con_DPrintf("QVM_uri_query(%s,"fPRIp")\n", url, cb_context);
dl = HTTP_CL_Get(url, NULL, QVM_uri_query_callback);
}
if (dl)
{
dl->user_ctx = cb_context;
dl->user_float = cb_entry;
dl->user_num = *sv.world.g.self;
dl->user_sequence = svs.spawncount;
dl->isquery = true;
return 1;
}
else
return 0;
}
#endif
void QCBUILTIN PF_sv_trailparticles(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_sv_pointparticles(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_sv_particleeffectnum(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
static qintptr_t QVM_particleeffectnum (void *offset, quintptr_t mask, const qintptr_t *arg)
{
int i = WrapQCBuiltin(PF_sv_particleeffectnum, offset, mask, arg, "s");
return VM_FLOAT(i);
}
static qintptr_t QVM_trailparticles (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return WrapQCBuiltin(PF_sv_trailparticles, offset, mask, arg, "invv");
}
static qintptr_t QVM_pointparticles (void *offset, quintptr_t mask, const qintptr_t *arg)
{
return WrapQCBuiltin(PF_sv_pointparticles, offset, mask, arg, "ivvi");
}
static qintptr_t QVM_Map_Extension (void *offset, quintptr_t mask, const qintptr_t *arg);
typedef qintptr_t (*traps_t) (void *offset, quintptr_t mask, const qintptr_t *arg);
traps_t bitraps[G_MAX] =
{
QVM_GetAPIVersion,
QVM_DPrint,
QVM_Error,
QVM_GetEntityToken,
QVM_Spawn_Ent,
QVM_Remove_Ent,
QVM_Precache_Sound,
QVM_Precache_Model,
QVM_LightStyle,
QVM_SetOrigin,
QVM_SetSize, //10
QVM_SetModel,
QVM_BPrint,
QVM_SPrint,
QVM_CenterPrint,
QVM_AmbientSound, //15
QVM_Sound,
QVM_TraceLine,
QVM_CheckClient,
QVM_StuffCmd,
QVM_LocalCmd, //20
QVM_CVar,
QVM_CVar_Set,
QVM_FindRadius,
QVM_WalkMove,
QVM_DropToFloor, //25
QVM_CheckBottom,
QVM_PointContents,
QVM_NextEnt,
QVM_Aim,
QVM_MakeStatic, //30
QVM_SetSpawnParams,
QVM_ChangeLevel,
QVM_LogFrag,
QVM_GetInfoKey,
QVM_Multicast, //35
QVM_DisableUpdates,
QVM_WriteByte,
QVM_WriteChar,
QVM_WriteShort,
QVM_WriteLong, //40
QVM_WriteAngle,
QVM_WriteCoord,
QVM_WriteString,
QVM_WriteEntity,
QVM_FlushSignon, //45
QVM_memset,
QVM_memcpy,
QVM_strncpy,
QVM_sin,
QVM_cos, //50
QVM_atan2,
QVM_sqrt,
QVM_floor,
QVM_ceil,
QVM_acos, //55
QVM_Cmd_ArgC,
QVM_Cmd_ArgV,
QVM_TraceBox, //was G_TraceCapsule
QVM_FS_OpenFile,
QVM_FS_CloseFile, //60
QVM_FS_ReadFile,
QVM_FS_WriteFile,
QVM_FS_SeekFile,
QVM_FS_TellFile,
QVM_FS_GetFileList, //65
QVM_CVar_Set_Float,
QVM_CVar_String,
QVM_Map_Extension,
QVM_strcmp,
QVM_strncmp, //70
QVM_stricmp,
QVM_strnicmp,
QVM_Find,
QVM_ExecuteCmd,
QVM_ConPrint, //75
QVM_ReadCmd,
QVM_RedirectCmd,
QVM_Add_Bot,
QVM_Remove_Bot,
QVM_SetBotUserInfo, //80
QVM_SetBotCMD,
QVM_strftime,
QVM_Cmd_ArgS,
QVM_Cmd_Tokenize,
QVM_strlcpy, //85
QVM_strlcat,
QVM_MakeVectors,
QVM_NextClient,
QVM_Precache_VWep_Model,
QVM_SetPause,
QVM_SetUserInfo,
QVM_MoveToGoal
};
struct
{
char *extname;
traps_t trap;
} qvmextensions[] =
{
{"SetExtField", QVM_SetExtField},
{"GetExtField", QVM_GetExtField},
{"ChangeLevel2", QVM_ChangeLevel2}, //with start spot
{"URI_Query", QVM_uri_query}, //with start spot
{"particleeffectnum", QVM_particleeffectnum},
{"trailparticles", QVM_trailparticles},
{"pointparticles", QVM_pointparticles},
//sql?
//model querying?
//heightmap / brush editing?
//custom stats (mod can always writebyte, I guess, sounds horrible though)
//csqc ents
{NULL, NULL}
};
traps_t traps[512];
static qintptr_t QVM_Map_Extension (void *offset, quintptr_t mask, const qintptr_t *arg)
{
char *extname = VM_POINTER(arg[0]);
unsigned int slot = VM_LONG(arg[1]);
int i;
if (slot >= countof(traps))
return -2; //invalid slot.
if (!extname)
{ //special handling for vauge compat with mvdsv, for testing how many 'known' builtins are implemented.
if (slot < G_MAX)
return -2;
return -1;
}
//find the extension and map it to the slot if found.
for (i = 0; qvmextensions[i].extname; i++)
{
if (!Q_strcasecmp(extname, qvmextensions[i].extname))
{
traps[slot] = qvmextensions[i].trap;
return slot;
}
}
return -1; //extension not known
}
//============== general Quake services ==================
#if FTE_WORDSIZE == 32 && !defined(NACL)
static int syscallqvm (void *offset, quintptr_t mask, int fn, const int *arg)
{
if (fn >= countof(traps))
return QVM_NotYetImplemented(offset, mask, arg);
return traps[fn](offset, mask, arg);
}
#else
static int syscallqvm (void *offset, quintptr_t mask, int fn, const int *arg)
{
qintptr_t args[13];
int i;
for (i = 0; i < 13; i++)
args[i] = arg[i];
if (fn >= countof(traps))
return QVM_NotYetImplemented(offset, mask, args);
return traps[fn](offset, mask, args);
}
#endif
static qintptr_t EXPORT_FN syscallnative (qintptr_t arg, ...)
{
qintptr_t args[13];
va_list argptr;
va_start(argptr, arg);
args[0]=va_arg(argptr, qintptr_t);
args[1]=va_arg(argptr, qintptr_t);
args[2]=va_arg(argptr, qintptr_t);
args[3]=va_arg(argptr, qintptr_t);
args[4]=va_arg(argptr, qintptr_t);
args[5]=va_arg(argptr, qintptr_t);
args[6]=va_arg(argptr, qintptr_t);
args[7]=va_arg(argptr, qintptr_t);
args[8]=va_arg(argptr, qintptr_t);
args[9]=va_arg(argptr, qintptr_t);
args[10]=va_arg(argptr, qintptr_t);
args[11]=va_arg(argptr, qintptr_t);
args[12]=va_arg(argptr, qintptr_t);
va_end(argptr);
if (arg >= countof(traps))
return QVM_NotYetImplemented(NULL, ~(quintptr_t)0, args);
return traps[arg](NULL, ~(quintptr_t)0, args);
}
void Q1QVM_Shutdown(void)
{
int i;
if (q1qvm)
{
for (i = 0; i < sv.allocated_client_slots; i++)
{
if (svs.clients[i].name)
Q_strncpyz(svs.clients[i].namebuf, svs.clients[i].name, sizeof(svs.clients[i].namebuf));
svs.clients[i].name = svs.clients[i].namebuf;
}
VM_Call(q1qvm, GAME_SHUTDOWN, 0, 0, 0);
VM_Destroy(q1qvm);
q1qvm = NULL;
VM_fcloseall(VMFSID_Q1QVM);
if (svprogfuncs == &q1qvmprogfuncs)
sv.world.progs = svprogfuncs = NULL;
Z_FreeTags(VMFSID_Q1QVM);
if (q1qvmprogfuncs.edicttable)
{
Z_Free(q1qvmprogfuncs.edicttable);
q1qvmprogfuncs.edicttable = NULL;
}
}
}
void Q1QVM_Event_Touch(world_t *w, wedict_t *s, wedict_t *o)
{
int oself = pr_global_struct->self;
int oother = pr_global_struct->other;
pr_global_struct->self = EDICT_TO_PROG(w->progs, s);
pr_global_struct->other = EDICT_TO_PROG(w->progs, o);
pr_global_struct->time = w->physicstime;
VM_Call(q1qvm, GAME_EDICT_TOUCH, 0, 0, 0);
pr_global_struct->self = oself;
pr_global_struct->other = oother;
}
void Q1QVM_Event_Think(world_t *w, wedict_t *s)
{
pr_global_struct->self = EDICT_TO_PROG(w->progs, s);
pr_global_struct->other = EDICT_TO_PROG(w->progs, w->edicts);
VM_Call(q1qvm, GAME_EDICT_THINK, 0, 0, 0);
}
qboolean Q1QVM_Event_ContentsTransition(world_t *w, wedict_t *ent, int oldwatertype, int newwatertype)
{
return false; //always do legacy behaviour
}
void QDECL Q1QVMPF_SetStringField(pubprogfuncs_t *progfuncs, struct edict_s *ed, string_t *fld, const char *str, pbool str_is_static)
{
string_t newval = progfuncs->StringToProgs(progfuncs, str);
if (newval || !str)
*fld = newval;
else
Con_DPrintf("Ignoring string set outside of progs VM\n");
}
qboolean PR_LoadQ1QVM(void)
{
static int writable_int;
static float writable;
static float dimensionsend = 255;
static float dimensiondefault = 255;
static float physics_mode = 2;
static vec3_t defaultgravity = {0,0,-1};
int i;
gameDataN_t *gd, gdm;
gameData32_t *gd32;
qintptr_t ret;
qintptr_t limit;
extern cvar_t pr_maxedicts;
Q1QVM_Shutdown();
q1qvm = VM_Create("qwprogs", com_nogamedirnativecode.ival?NULL:syscallnative, syscallqvm);
if (!q1qvm)
q1qvm = VM_Create("qwprogs", syscallnative, NULL);
if (!q1qvm)
{
if (svprogfuncs == &q1qvmprogfuncs)
sv.world.progs = svprogfuncs = NULL;
return false;
}
for(i = 0; i < G_MAX; i++)
traps[i] = bitraps[i];
for(; i < countof(traps); i++)
traps[i] = QVM_NotYetImplemented;
memset(&fofs, 0, sizeof(fofs));
progstype = PROG_QW;
svprogfuncs = &q1qvmprogfuncs;
// q1qvmprogfuncs.AddString = Q1QVMPF_AddString; //using this breaks 64bit support, and is a 'bad plan' elsewhere too,
q1qvmprogfuncs.EDICT_NUM = Q1QVMPF_EdictNum;
q1qvmprogfuncs.NUM_FOR_EDICT = Q1QVMPF_NumForEdict;
q1qvmprogfuncs.EdictToProgs = Q1QVMPF_EdictToProgs;
q1qvmprogfuncs.ProgsToEdict = Q1QVMPF_ProgsToEdict;
q1qvmprogfuncs.EntAlloc = Q1QVMPF_EntAlloc;
q1qvmprogfuncs.EntFree = Q1QVMPF_EntRemove;
q1qvmprogfuncs.FindGlobal = Q1QVMPF_FindGlobal;
q1qvmprogfuncs.load_ents = Q1QVMPF_LoadEnts;
q1qvmprogfuncs.globals = Q1QVMPF_Globals;
q1qvmprogfuncs.GetEdictFieldValue = Q1QVMPF_GetEdictFieldValue;
q1qvmprogfuncs.StringToProgs = Q1QVMPF_StringToProgs;
q1qvmprogfuncs.StringToNative = Q1QVMPF_StringToNative;
q1qvmprogfuncs.SetStringField = Q1QVMPF_SetStringField;
q1qvmprogfuncs.EntClear = Q1QVMPF_ClearEdict;
sv.world.Event_Touch = Q1QVM_Event_Touch;
sv.world.Event_Think = Q1QVM_Event_Think;
sv.world.Event_Sound = SVQ1_StartSound;
sv.world.Event_ContentsTransition = Q1QVM_Event_ContentsTransition;
sv.world.Get_CModel = SVPR_GetCModel;
sv.world.num_edicts = 0; //we're not ready for most of the builtins yet
sv.world.max_edicts = 0; //so clear these out, just in case
sv.world.edict_size = 0; //if we get a division by zero, then at least its a safe crash
q1qvmprogfuncs.edicttable = NULL;
q1qvmprogfuncs.stringtable = VM_MemoryBase(q1qvm);
ret = VM_Call(q1qvm, GAME_INIT, (qintptr_t)(sv.time*1000), rand(), 0, 0, 0);
if (!ret)
{
Q1QVM_Shutdown();
return false;
}
if (VM_NonNative(q1qvm))
{
gd32 = (gameData32_t*)((char*)VM_MemoryBase(q1qvm) + ret); //qvm is 32bit
//when running native64, we need to convert these to real types, so we can use em below
//double casts to silence warnings
gd = &gdm;
gd->ents = (struct vmedict_s *)(qintptr_t)gd32->ents;
gd->sizeofent = gd32->sizeofent;
gd->global = (q1qvmglobalvars_t *)(qintptr_t)gd32->global;
gd->fields = (field_t *)(qintptr_t)gd32->fields;
gd->APIversion = gd32->APIversion;
}
else
{
gd = (gameDataN_t*)((char*)VM_MemoryBase(q1qvm) + ret); //qvm is 32bit
}
sv.world.num_edicts = 1;
sv.world.max_edicts = bound(64, pr_maxedicts.ival, MAX_EDICTS);
q1qvmprogfuncs.edicttable = Z_Malloc(sizeof(*q1qvmprogfuncs.edicttable) * sv.world.max_edicts);
limit = VM_MemoryMask(q1qvm);
if (gd->sizeofent < 0 || gd->sizeofent > (0xffffffff-(qintptr_t)gd->ents) / sv.world.max_edicts)
gd->sizeofent = 0xffffffff / MAX_EDICTS;
if ((quintptr_t)gd->ents+(gd->sizeofent*MAX_Q1QVM_EDICTS) < (quintptr_t)gd->ents || (quintptr_t)gd->ents > (quintptr_t)limit)
gd->ents = NULL;
if ((quintptr_t)(gd->global+1) < (quintptr_t)gd->global || (quintptr_t)gd->global > (quintptr_t)limit)
gd->global = NULL;
if (/*(quintptr_t)gd->fields < (quintptr_t)gd->fields ||*/ (quintptr_t)gd->fields > limit)
gd->fields = NULL;
sv.world.edict_size = gd->sizeofent;
vevars = (qintptr_t)gd->ents;
evars = ((char*)VM_MemoryBase(q1qvm) + vevars);
//WARNING: global is not remapped yet...
//This code is written evilly, but works well enough
#define globalint(required, name) pr_global_ptrs->name = (int*)((char*)VM_MemoryBase(q1qvm)+(qintptr_t)&gd->global->name) //the logic of this is somewhat crazy
#define globalfloat(required, name) pr_global_ptrs->name = (float*)((char*)VM_MemoryBase(q1qvm)+(qintptr_t)&gd->global->name)
#define globalstring(required, name) pr_global_ptrs->name = (string_t*)((char*)VM_MemoryBase(q1qvm)+(qintptr_t)&gd->global->name)
#define globalvec(required, name) pr_global_ptrs->name = (vec3_t*)((char*)VM_MemoryBase(q1qvm)+(qintptr_t)&gd->global->name)
#define globalfunc(required, name) pr_global_ptrs->name = (int*)((char*)VM_MemoryBase(q1qvm)+(qintptr_t)&gd->global->name)
#define globalfloatnull(required, name) pr_global_ptrs->name = NULL
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);
globalfloatnull (false, deathmatch);
globalfloatnull (false, coop);
globalfloatnull (false, teamplay);
globalfloat (true, serverflags);
globalfloat (true, total_secrets);
globalfloat (true, total_monsters);
globalfloat (true, found_secrets);
globalfloat (true, killed_monsters);
globalvec (true, v_forward);
globalvec (true, v_up);
globalvec (true, v_right);
globalfloat (true, trace_allsolid);
globalfloat (true, trace_startsolid);
globalfloat (true, trace_fraction);
globalvec (true, trace_endpos);
globalvec (true, trace_plane_normal);
globalfloat (true, trace_plane_dist);
globalint (true, trace_ent);
globalfloat (true, trace_inopen);
globalfloat (true, trace_inwater);
globalfloatnull (false, trace_endcontents);
globalfloatnull (false, trace_surfaceflags);
globalfloatnull (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);
pr_global_ptrs->trace_surfaceflags = &writable;
pr_global_ptrs->trace_endcontents = &writable;
pr_global_ptrs->dimension_default = &dimensiondefault;
pr_global_ptrs->dimension_send = &dimensionsend;
pr_global_ptrs->physics_mode = &physics_mode;
pr_global_ptrs->trace_brush_id = &writable_int;
pr_global_ptrs->trace_brush_faceid = &writable_int;
pr_global_ptrs->global_gravitydir = &defaultgravity;
// ensureglobal(input_timelength, input_timelength_default);
// ensureglobal(input_impulse, input_impulse_default);
// ensureglobal(input_angles, input_angles_default);
// ensureglobal(input_movevalues, input_movevalues_default);
// ensureglobal(input_buttons, input_buttons_default);
dimensionsend = dimensiondefault = 255;
for (i = 0; i < 16; i++)
pr_global_ptrs->spawnparamglobals[i] = (float*)((char*)VM_MemoryBase(q1qvm)+(qintptr_t)(&gd->global->parm1 + i));
for (; i < NUM_SPAWN_PARMS; i++)
pr_global_ptrs->spawnparamglobals[i] = NULL;
for (i = 0; gd->fields[i].name; i++)
{
const char *fname = Q1QVMPF_StringToNative(&q1qvmprogfuncs, gd->fields[i].name);
#define emufield(n,t) if (gd->fields[i].type == t && !strcmp(#n, fname)) {fofs.n = (gd->fields[i].ofs - WASTED_EDICT_T_SIZE)/sizeof(float); continue;}
emufields
#undef emufield
}
sv.world.progs = &q1qvmprogfuncs;
sv.world.edicts = (wedict_t*)Q1QVMPF_EdictNum(svprogfuncs, 0);
sv.world.usesolidcorpse = true;
if ((unsigned)gd->global->mapname && (unsigned)gd->global->mapname+MAPNAME_LEN < VM_MemoryMask(q1qvm))
Q_strncpyz((char*)VM_MemoryBase(q1qvm) + gd->global->mapname, svs.name, MAPNAME_LEN);
else
gd->global->mapname = Q1QVMPF_StringToProgs(sv.world.progs, svs.name);
PR_SV_FillWorldGlobals(&sv.world);
return true;
}
void Q1QVM_ClientConnect(client_t *cl)
{
if (cl->edict->v->netname)
{
strcpy(cl->namebuf, cl->name);
cl->name = (char*)Q1QVMPF_StringToNative(svprogfuncs, cl->edict->v->netname);
//FIXME: check this pointer
strcpy(cl->name, cl->namebuf);
}
else if (!VM_NonNative(q1qvm))
{
Q_strncpyz(cl->namebuf, cl->name, sizeof(cl->namebuf));
cl->name = cl->namebuf;
cl->edict->v->netname = Q1QVMPF_StringToProgs(svprogfuncs, cl->namebuf);
Con_DPrintf("WARNING: Mod provided no netname buffer and will not function correctly when compiled as a qvm.\n");
}
else
Con_Printf("WARNING: Mod provided no netname buffer. Player names will not be set properly.\n");
if (fofs.gravity)
((float*)sv_player->v)[fofs.gravity] = sv_player->xv->gravity;
if (fofs.maxspeed)
((float*)sv_player->v)[fofs.maxspeed] = sv_player->xv->maxspeed;
// call the spawn function
pr_global_struct->time = sv.world.physicstime;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict);
VM_Call(q1qvm, GAME_CLIENT_CONNECT, cl->spectator, 0, 0, 0);
// actually spawn the player
pr_global_struct->time = sv.world.physicstime;
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict);
VM_Call(q1qvm, GAME_PUT_CLIENT_IN_SERVER, cl->spectator, 0, 0, 0);
}
qboolean Q1QVM_GameConsoleCommand(void)
{
int oldself, oldother;
if (!q1qvm)
return false;
//FIXME: if an rcon command from someone on the server, mvdsv sets self to match the ip of that player
//this is not required (broken by proxies anyway) but is a nice handy feature
pr_global_struct->time = sv.world.physicstime;
oldself = pr_global_struct->self; //these are usually useless
oldother = pr_global_struct->other; //but its possible that someone makes a mod that depends on the 'mod' command working via redirectcmd+co
//this at least matches mvdsv
pr_global_struct->self = 0;
pr_global_struct->other = 0;
VM_Call(q1qvm, GAME_CONSOLE_COMMAND, 0, 0, 0); //mod uses Cmd_Argv+co to get args
pr_global_struct->self = oldself;
pr_global_struct->other = oldother;
return true;
}
qboolean Q1QVM_ClientSay(edict_t *player, qboolean team)
{
qboolean washandled;
if (!q1qvm)
return false;
pr_global_struct->time = sv.world.physicstime;
pr_global_struct->self = Q1QVMPF_EdictToProgs(svprogfuncs, player);
washandled = VM_Call(q1qvm, GAME_CLIENT_SAY, team, 0, 0, 0);
return washandled;
}
qboolean Q1QVM_UserInfoChanged(edict_t *player)
{
if (!q1qvm)
return false;
pr_global_struct->time = sv.world.physicstime;
pr_global_struct->self = Q1QVMPF_EdictToProgs(svprogfuncs, player);
return VM_Call(q1qvm, GAME_CLIENT_USERINFO_CHANGED, 0, 0, 0);
}
void Q1QVM_PlayerPreThink(void)
{
if (fofs.movement)
{
sv_player->xv->movement[0] = ((float*)sv_player->v)[fofs.movement+0];
sv_player->xv->movement[1] = ((float*)sv_player->v)[fofs.movement+1];
sv_player->xv->movement[2] = ((float*)sv_player->v)[fofs.movement+2];
}
if (fofs.gravity)
sv_player->xv->gravity = ((float*)sv_player->v)[fofs.gravity];
if (fofs.maxspeed)
sv_player->xv->maxspeed = ((float*)sv_player->v)[fofs.maxspeed];
VM_Call(q1qvm, GAME_CLIENT_PRETHINK, host_client->spectator, 0, 0, 0);
}
void Q1QVM_RunPlayerThink(void)
{
VM_Call(q1qvm, GAME_EDICT_THINK, 0, 0, 0);
VM_Call(q1qvm, GAME_CLIENT_THINK, host_client->spectator, 0, 0, 0);
}
void Q1QVM_PostThink(void)
{
VM_Call(q1qvm, GAME_CLIENT_POSTTHINK, host_client->spectator, 0, 0, 0);
if (fofs.vw_index)
sv_player->xv->vw_index = ((float*)sv_player->v)[fofs.vw_index];
}
void Q1QVM_StartFrame(void)
{
VM_Call(q1qvm, GAME_START_FRAME, (qintptr_t)(sv.time*1000), 0, 0, 0);
}
void Q1QVM_Blocked(void)
{
VM_Call(q1qvm, GAME_EDICT_BLOCKED, 0, 0, 0);
}
void Q1QVM_SetNewParms(void)
{
VM_Call(q1qvm, GAME_SETNEWPARMS, 0, 0, 0);
}
void Q1QVM_SetChangeParms(void)
{
VM_Call(q1qvm, GAME_SETCHANGEPARMS, 0, 0, 0);
}
qboolean Q1QVM_ClientCommand(void)
{
return VM_Call(q1qvm, GAME_CLIENT_COMMAND, 0, 0, 0);
}
void Q1QVM_GameCodePausedTic(float pausedduration)
{
VM_Call(q1qvm, GAME_PAUSED_TIC, (qintptr_t)(pausedduration*1000), 0, 0, 0);
}
void Q1QVM_DropClient(client_t *cl)
{
if (cl->name)
Q_strncpyz(cl->namebuf, cl->name, sizeof(cl->namebuf));
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict);
VM_Call(q1qvm, GAME_CLIENT_DISCONNECT, 0, 0, 0);
cl->name = cl->namebuf;
}
void Q1QVM_ChainMoved(void)
{
}
void Q1QVM_EndFrame(void)
{
}
#endif