#include "quakedef.h" //#include "cg_public.h" #ifdef VM_CG #include "shader.h" #if 1 #include "glquake.h"//hack #else typedef float m3by3_t[3][3]; #endif #include "clq3defs.h" //cl_ui.c void CG_Command_f(void); #define CGAME_IMPORT_API_VERSION 4 #define CGTAGNUM 5423 extern model_t *mod_known; extern int mod_numknown; #define VM_FROMMHANDLE(a) ((a&&((unsigned int)a)<=mod_numknown)?mod_known+a-1:NULL) #define VM_TOMHANDLE(a) (a?a-mod_known+1:0) #define VM_FROMSHANDLE(a) ((a&&(unsigned int)a<=r_numshaders)?r_shaders[a-1]:NULL) #define VM_TOSHANDLE(a) (a?a->id+1:0) static model_t *box_model; typedef enum { CG_PRINT, CG_ERROR, CG_MILLISECONDS, CG_CVAR_REGISTER, CG_CVAR_UPDATE, CG_CVAR_SET, CG_CVAR_VARIABLESTRINGBUFFER, CG_ARGC, CG_ARGV, CG_ARGS, CG_FS_FOPENFILE, //10 CG_FS_READ, CG_FS_WRITE, CG_FS_FCLOSEFILE, CG_SENDCONSOLECOMMAND, CG_ADDCOMMAND, CG_SENDCLIENTCOMMAND, CG_UPDATESCREEN, CG_CM_LOADMAP, CG_CM_NUMINLINEMODELS, CG_CM_INLINEMODEL, //20 CG_CM_LOADMODEL, CG_CM_TEMPBOXMODEL, CG_CM_POINTCONTENTS, CG_CM_TRANSFORMEDPOINTCONTENTS, CG_CM_BOXTRACE, CG_CM_TRANSFORMEDBOXTRACE, CG_CM_MARKFRAGMENTS, CG_S_STARTSOUND, CG_S_STARTLOCALSOUND, CG_S_CLEARLOOPINGSOUNDS, //30 CG_S_ADDLOOPINGSOUND, CG_S_UPDATEENTITYPOSITION, CG_S_RESPATIALIZE, CG_S_REGISTERSOUND, CG_S_STARTBACKGROUNDTRACK, CG_R_LOADWORLDMAP, CG_R_REGISTERMODEL, CG_R_REGISTERSKIN, CG_R_REGISTERSHADER, CG_R_CLEARSCENE, //40 CG_R_ADDREFENTITYTOSCENE, CG_R_ADDPOLYTOSCENE, CG_R_ADDLIGHTTOSCENE, CG_R_RENDERSCENE, CG_R_SETCOLOR, CG_R_DRAWSTRETCHPIC, CG_R_MODELBOUNDS, CG_R_LERPTAG, CG_GETGLCONFIG, CG_GETGAMESTATE, //50 CG_GETCURRENTSNAPSHOTNUMBER, CG_GETSNAPSHOT, CG_GETSERVERCOMMAND, CG_GETCURRENTCMDNUMBER, CG_GETUSERCMD, CG_SETUSERCMDVALUE, CG_R_REGISTERSHADERNOMIP, CG_MEMORY_REMAINING, CG_R_REGISTERFONT, CG_KEY_ISDOWN, //60 CG_KEY_GETCATCHER, CG_KEY_SETCATCHER, CG_KEY_GETKEY, CG_PC_ADD_GLOBAL_DEFINE, CG_PC_LOAD_SOURCE, CG_PC_FREE_SOURCE, CG_PC_READ_TOKEN, CG_PC_SOURCE_FILE_AND_LINE, CG_S_STOPBACKGROUNDTRACK, CG_REAL_TIME, //70 CG_SNAPVECTOR, CG_REMOVECOMMAND, CG_R_LIGHTFORPOINT, //73 CG_CIN_PLAYCINEMATIC, //74 CG_CIN_STOPCINEMATIC, //75 CG_CIN_RUNCINEMATIC, //76 CG_CIN_DRAWCINEMATIC, //77 CG_CIN_SETEXTENTS, //78 CG_R_REMAP_SHADER, //79 CG_S_ADDREALLOOPINGSOUND, //80 CG_S_STOPLOOPINGSOUND, //81 CG_CM_TEMPCAPSULEMODEL, //82 CG_CM_CAPSULETRACE, //83 CG_CM_TRANSFORMEDCAPSULETRACE, //84 CG_R_ADDADDITIVELIGHTTOSCENE, //85 CG_GET_ENTITY_TOKEN, //86 CG_R_ADDPOLYSTOSCENE, //87 CG_R_INPVS, //88 // 1.32 CG_FS_SEEK, //89 CG_MEMSET = 100, CG_MEMCPY, CG_STRNCPY, CG_SIN, CG_COS, CG_ATAN2, CG_SQRT, CG_FLOOR, CG_CEIL, CG_TESTPRINTINT, CG_TESTPRINTFLOAT, CG_ACOS, CG_FTE_FINDPARTICLEEFFECT = 200, CG_FTE_SPAWNPARTICLEEFFECT, CG_FTE_SPAWNPARTICLETRAIL, CG_FTE_FREEPARTICLESTATE } cgameImport_t; /* int CG_FTE_FINDPARTICLEEFFECT(char *particleeffectname) = #200; int CG_FTE_SPAWNPARTICLEEFFECT(vec3_t pos, vec3_t dir, float count, int effectnum, void *pstate) = #201; int CG_FTE_SPAWNPARTICLETRAIL(vec3_t start, vec3_t end, int effectnum, void *pstate) = #202; void CG_FTE_FREEPARTICLESTATE(void *pstate) = #203; */ /* ================================================================== functions exported to the main executable ================================================================== */ typedef enum { CG_INIT, // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) // called when the level loads or when the renderer is restarted // all media should be registered at this time // cgame will display loading status by calling SCR_Update, which // will call CG_DrawInformation during the loading process // reliableCommandSequence will be 0 on fresh loads, but higher for // demos, tourney restarts, or vid_restarts CG_SHUTDOWN, // void (*CG_Shutdown)( void ); // oportunity to flush and close any open files CG_CONSOLE_COMMAND, // qboolean (*CG_ConsoleCommand)( void ); // a console command has been issued locally that is not recognized by the // main game system. // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the // command is not known to the game CG_DRAW_ACTIVE_FRAME, // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); // Generates and draws a game scene and status information at the given time. // If demoPlayback is set, local movement prediction will not be enabled CG_CROSSHAIR_PLAYER, // int (*CG_CrosshairPlayer)( void ); CG_LAST_ATTACKER, // int (*CG_LastAttacker)( void ); CG_KEY_EVENT, // void (*CG_KeyEvent)( int key, qboolean down ); CG_MOUSE_EVENT, // void (*CG_MouseEvent)( int dx, int dy ); CG_EVENT_HANDLING // void (*CG_EventHandling)(int type); } cgameExport_t; unsigned int Contents_To_Q3(unsigned int fte) { int ret = 0; if (fte & FTECONTENTS_SOLID) //should use q3 constants. ret |= 1; if (fte & FTECONTENTS_WATER) //should use q3 constants. ret |= 32; if (fte & FTECONTENTS_SLIME) //should use q3 constants. ret |= 16; if (fte & FTECONTENTS_LAVA) //should use q3 constants. ret |= 8; if (fte & FTECONTENTS_SKY) //should use q3 constants. ret |= 0x80000000; return ret; } unsigned int Contents_From_Q3(unsigned int Q3) { int ret = 0; if (Q3 & 1) //should use q3 constants. ret |= FTECONTENTS_SOLID; if (Q3 & 32) //should use q3 constants. ret |= FTECONTENTS_WATER; if (Q3 & 16) //should use q3 constants. ret |= FTECONTENTS_SLIME; if (Q3 & 8) //should use q3 constants. ret |= FTECONTENTS_LAVA; if (Q3 & 0x80000000) //should use q3 constants. ret |= FTECONTENTS_SKY; return ret; } #define MAX_GAMESTATE_CHARS 16000 #define MAX_CONFIGSTRINGS 1024 typedef struct { int stringOffsets[MAX_CONFIGSTRINGS]; char stringData[MAX_GAMESTATE_CHARS]; int dataCount; } gameState_t; gameState_t cggamestate; void CG_InsertIntoGameState(int num, char *str) { if (num < 5) { Con_DPrintf("%i: %s", num, str); } if (num == CFGSTR_SYSINFO) { //check some things. cl.servercount = atoi(Info_ValueForKey(str, "sv_serverid")); } if (cggamestate.dataCount + strlen(str)+1 > MAX_GAMESTATE_CHARS) { char oldstringData[MAX_GAMESTATE_CHARS]; int i; char *oldstr; //copy the old strings to a temporary buffer memcpy(oldstringData, cggamestate.stringData, MAX_GAMESTATE_CHARS); cggamestate.dataCount = 0; for (i = 0; i < MAX_CONFIGSTRINGS; i++) { oldstr = oldstringData+cggamestate.stringOffsets[i]; if (*oldstr) { if (cggamestate.dataCount + strlen(oldstr)+1 > MAX_GAMESTATE_CHARS) Host_EndGame("Too much configstring text\n"); cggamestate.dataCount+=1; strcpy(cggamestate.stringData+cggamestate.dataCount, oldstr); cggamestate.stringOffsets[i] = cggamestate.dataCount; cggamestate.dataCount += strlen(oldstr); } else cggamestate.stringOffsets[i] = 0; } } if (!*str) { cggamestate.stringOffsets[num] = 0; return; } cggamestate.dataCount+=1; strcpy(cggamestate.stringData+cggamestate.dataCount, str); cggamestate.stringOffsets[num] = cggamestate.dataCount; cggamestate.dataCount += strlen(str); } char *CG_GetConfigString(int num) { if ((unsigned)num >= MAX_CONFIGSTRINGS) return ""; return cggamestate.stringData + cggamestate.stringOffsets[num]; } int CG_GetGameState(gameState_t *gs) { memcpy(gs, &cggamestate, sizeof(gameState_t)); return sizeof(gameState_t); } static int CGQ3_GetCurrentCmdNumber(void) { //Q3 sequences are 1-based, so 1<=idx<=latestsequence are valid //FTE's sequences are 0-based, so 0<=idx= cl.movesequence) Host_EndGame("CLQ3_GetUserCmd: %i >= %i", cmdNumber, cl.movesequence); if (cl.movesequence - (cmdNumber+1) > Q3CMD_BACKUP) return false; //note: frames and commands are desynced in q3. cmd = &cl.outframes[(cmdNumber) & Q3CMD_MASK].cmd[0]; ucmd->angles[0] = cmd->angles[0]; ucmd->angles[1] = cmd->angles[1]; ucmd->angles[2] = cmd->angles[2]; ucmd->serverTime = cmd->servertime; ucmd->forwardmove = cmd->forwardmove; ucmd->rightmove = cmd->sidemove; ucmd->upmove = cmd->upmove; ucmd->buttons = cmd->buttons; ucmd->weapon = cmd->weapon; return true; } static vm_t *cgvm; extern int keycatcher; qboolean CG_GetServerCommand(int cmdnum) { static char bigconfigstring[65536]; //quote from cgame code: // get the gamestate from the client system, which will have the // new configstring already integrated char *str = ccs.serverCommands[cmdnum & Q3TEXTCMD_MASK]; Con_DPrintf("Dispaching %s\n", str); Cmd_TokenizeString(str, false, false); if (!strcmp(Cmd_Argv(0), "bcs0")) { //start Q_snprintfz(bigconfigstring, sizeof(bigconfigstring), "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2)); return false; } if (!strcmp(Cmd_Argv(0), "bcs1")) { //continuation Q_strncatz(bigconfigstring, Cmd_Argv(2), sizeof(bigconfigstring)); return false; } if (!strcmp(Cmd_Argv(0), "bcs2")) { //end Q_strncatz(bigconfigstring, Cmd_Argv(2), sizeof(bigconfigstring)); Q_strncatz(bigconfigstring, "\"", sizeof(bigconfigstring)); Cmd_TokenizeString(bigconfigstring, false, false); } if (!strcmp(Cmd_Argv(0), "cs")) CG_InsertIntoGameState(atoi(Cmd_Argv(1)), Cmd_Argv(2)); else if (!strcmp(Cmd_Argv(0), "map_restart")) Con_ClearNotify(); else if (!strcmp(Cmd_Argv(0), "disconnect")) Host_EndGame("Server disconnected - %s", (Cmd_Argc()>1)?Cmd_Argv(1):"No reason given"); return true; } typedef struct { int firstPoint; int numPoints; } q3markFragment_t; typedef struct { float *points; size_t maxpoints; size_t numpoints; q3markFragment_t *frags; size_t maxfrags; size_t numfrags; } q3markFragment_ctx_t; static void CG_MarkFragments_Callback(void *vctx, vec3_t *fte_restrict points, size_t numtris, shader_t *shader) { q3markFragment_ctx_t *ctx = vctx; size_t i; if (numtris > ctx->maxfrags-ctx->numfrags) numtris = ctx->maxfrags-ctx->numfrags; if (numtris > (ctx->maxpoints-ctx->numpoints)/3) numtris = (ctx->maxpoints-ctx->numpoints)/3; for (i = 0; i < numtris; i++) { ctx->frags[ctx->numfrags].numPoints = 3; ctx->frags[ctx->numfrags].firstPoint = ctx->numpoints; VectorCopy(points[0], ctx->points+3*(ctx->numpoints+0)); VectorCopy(points[1], ctx->points+3*(ctx->numpoints+1)); VectorCopy(points[2], ctx->points+3*(ctx->numpoints+2)); points += 3; ctx->numfrags += 1; ctx->numpoints += 3; } } int CG_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, float *pointBuffer, int maxFragments, q3markFragment_t *fragmentBuffer ) { vec3_t center; vec3_t axis[3]; vec3_t p[4]; int i; float radius; q3markFragment_ctx_t ctx; if (numPoints != 4) return 0; /* q3 gamecode includes something like this originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; We want that origional axis and the origin axis[0] is given in the 'projection' parameter. Yes, reversing this stuff means that we'll have no support for triangles. */ VectorClear(center); VectorMA(center, 0.25, points[0], center); VectorMA(center, 0.25, points[1], center); VectorMA(center, 0.25, points[2], center); VectorMA(center, 0.25, points[3], center); VectorSubtract(points[0], center, p[0]); VectorSubtract(points[1], center, p[1]); VectorSubtract(points[2], center, p[2]); VectorSubtract(points[3], center, p[3]); for (i = 0; i < 3; i++) { axis[1][i] = (p[2][i]-p[1][i]); axis[2][i] = (p[3][i]-p[2][i]); } radius = VectorNormalize(axis[1]); VectorNormalize(axis[2]); VectorNormalize2(projection, axis[0]); ctx.points = pointBuffer; ctx.maxpoints = maxPoints; ctx.numpoints = 0; ctx.frags = fragmentBuffer; ctx.numfrags = 0; ctx.maxfrags = maxFragments; Mod_ClipDecal(cl.worldmodel, center, axis[0], axis[1], axis[2], radius, 0,0, CG_MarkFragments_Callback, &ctx); return ctx.numfrags; } //called by the sound code. static struct { unsigned int entnum; vec3_t origin; // vec3_t velocity; sfx_t *sfx; qboolean ispersistent; } *loopers; static size_t numloopers; static size_t maxloopers; unsigned int CG_GatherLoopingSounds(vec3_t *positions, unsigned int *entnums, sfx_t **sounds, unsigned int max) { size_t i; if (max > numloopers) max = numloopers; for (i = 0; i < max; i++) { entnums[i] = loopers[i].entnum; VectorCopy(loopers[i].origin, positions[i]); sounds[i] = loopers[i].sfx; } return i; } static void CG_StopLoopingSounds(unsigned int entnum) { size_t i; for (i = 0; i < numloopers; i++) { if (loopers[i].entnum == entnum) break; } if (i == numloopers) return; loopers[i] = loopers[numloopers-1]; numloopers--; } static void CG_StartLoopingSounds(unsigned int entnum, float *origin, float *velocity, const char *soundname, qboolean persistent) { size_t i; for (i = 0; i < numloopers; i++) { if (loopers[i].entnum == entnum) break; } if (i == numloopers) { if (numloopers == maxloopers) Z_ReallocElements((void**)&loopers, &maxloopers, maxloopers+1, sizeof(*loopers)); numloopers++; } loopers[i].entnum = entnum; VectorCopy(origin, loopers[i].origin); //VectorCopy(velocity, loopers[i].velocity); loopers[i].sfx = S_PrecacheSound(soundname); loopers[i].ispersistent = persistent; } static void CG_MoveLoopingSound(unsigned int entnum, float *origin) { size_t i; for (i = 0; i < numloopers; i++) { if (loopers[i].entnum == entnum) break; } if (i == numloopers) return; VectorCopy(origin, loopers[i].origin); } static void CG_ClearLoopingSounds(qboolean clearall) { if (clearall) numloopers = 0; else { size_t i; for (i = 0; i < numloopers; ) { if (!loopers[i].ispersistent) { loopers[i] = loopers[numloopers-1]; numloopers--; } else i++; } } } int VM_LerpTag(void *out, model_t *model, int f1, int f2, float l2, char *tagname); #define VALIDATEPOINTER(o,l) if ((int)o + l >= mask || VM_POINTER(o) < offset) Host_EndGame("Call to cgame trap %u passes invalid pointer\n", (unsigned int)fn); //out of bounds. static qintptr_t CG_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, const qintptr_t *arg) { int ret=0; //Remember to range check pointers. //The QVM must not be allowed to write to anything outside it's memory. //This includes getting the exe to copy it for it. //don't bother with reading, as this isn't a virus risk. //could be a cheat risk, but hey. //make sure that any called functions are also range checked. //like reading from files copies names into alternate buffers, allowing stack screwups. //OutputDebugString(va("cl_cg: %i\n", fn)); switch(fn) { case CG_PRINT: Con_Printf("%s", (char*)VM_POINTER(arg[0])); break; case CG_ERROR: Host_EndGame("cgame: %s", (char*)VM_POINTER(arg[0])); break; case CG_ARGC: VM_LONG(ret) = Cmd_Argc(); break; case CG_ARGV: VALIDATEPOINTER(arg[1], arg[2]); Q_strncpyz(VM_POINTER(arg[1]), Cmd_Argv(VM_LONG(arg[0])), VM_LONG(arg[2])); break; case CG_ARGS: VALIDATEPOINTER(arg[0], arg[1]); Q_strncpyz(VM_POINTER(arg[0]), Cmd_Args(), VM_LONG(arg[1])); break; case CG_CVAR_REGISTER: if (arg[0]) VALIDATEPOINTER(arg[0], sizeof(q3vmcvar_t)); return VMQ3_Cvar_Register(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3])); case CG_CVAR_UPDATE: VALIDATEPOINTER(arg[0], sizeof(q3vmcvar_t)); return VMQ3_Cvar_Update(VM_POINTER(arg[0])); case CG_CVAR_SET: { cvar_t *var; var = Cvar_FindVar(VM_POINTER(arg[0])); if (var) Cvar_Set(var, VM_POINTER(arg[1])?VM_POINTER(arg[1]):""); //set it else Cvar_Get(VM_POINTER(arg[0]), VM_POINTER(arg[1]), 0, "Q3CG created"); //create one } break; case CG_CVAR_VARIABLESTRINGBUFFER: { cvar_t *var; var = Cvar_FindVar(VM_POINTER(arg[0])); if (!VM_LONG(arg[2])) VM_LONG(ret) = 0; else if (!var) { VALIDATEPOINTER(arg[1], 1); *(char *)VM_POINTER(arg[1]) = '\0'; VM_LONG(ret) = -1; } else { VALIDATEPOINTER(arg[1], arg[2]); Q_strncpyz(VM_POINTER(arg[1]), var->string, VM_LONG(arg[2])); } } break; case CG_SENDCONSOLECOMMAND: Con_DPrintf("CG_SENDCONSOLECOMMAND: %s", (char*)VM_POINTER(arg[0])); Cbuf_AddText(VM_POINTER(arg[0]), RESTRICT_SERVER); break; case CG_ADDCOMMAND: Cmd_AddCommand(VM_POINTER(arg[0]), NULL); break; case CG_SENDCLIENTCOMMAND: Con_DPrintf("CG_SENDCLIENTCOMMAND: %s", (char*)VM_POINTER(arg[0])); CL_SendClientCommand(true, "%s", (char*)VM_POINTER(arg[0])); break; case CG_UPDATESCREEN: //force a buffer swap cos loading won't refresh it soon. if (!Key_Dest_Has(kdm_console)) scr_con_current = 0; SCR_UpdateScreen(); break; case CG_FS_FOPENFILE: //fopen if (arg[1]) VALIDATEPOINTER(arg[1], 4); VM_LONG(ret) = VM_fopen(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]), 1); break; case CG_FS_READ: //fread VALIDATEPOINTER(arg[1], 4); VM_LONG(ret) = VM_FRead(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), 1); break; case CG_FS_WRITE: //fwrite Con_DPrintf("CG_FS_WRITE: not implemented\n"); break; case CG_FS_FCLOSEFILE: //fclose VM_fclose(VM_LONG(arg[0]), 1); break; case CG_CM_POINTCONTENTS: //int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); { unsigned int pc; unsigned int modhandle = VM_LONG(arg[1]); model_t *mod; if (modhandle >= MAX_PRECACHE_MODELS) { // if (modhandle == MAX_PRECACHE_MODELS+1) // mod = &capsule_model; // else mod = box_model; } else mod = cl.model_precache[modhandle+1]; if (mod && mod->loadstate == MLS_LOADED) pc = mod->funcs.NativeContents(mod, 0, 0, NULL, VM_POINTER(arg[0]), vec3_origin, vec3_origin); else pc = 1;//FTECONTENTS_SOLID; VM_LONG(ret) = pc;//Contents_To_Q3(pc); } break; case CG_CM_TRANSFORMEDPOINTCONTENTS: //int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { { unsigned int pc; float *p = VM_POINTER(arg[0]); unsigned int modhandle = VM_LONG(arg[1]); float *origin = VM_POINTER(arg[2]); float *angles = VM_POINTER(arg[3]); model_t *mod; if (modhandle >= MAX_PRECACHE_MODELS) { // if (modhandle == MAX_PRECACHE_MODELS+1) // mod = &capsule_model; // else mod = box_model; } else mod = cl.model_precache[modhandle+1]; if (mod && mod->loadstate == MLS_LOADED) { vec3_t p_l; vec3_t axis[3]; // subtract origin offset VectorSubtract (p, origin, p_l); // rotate start and end into the models frame of reference if (angles[0] || angles[1] || angles[2]) { AngleVectors (angles, axis[0], axis[1], axis[2]); VectorNegate(axis[1], axis[1]); pc = mod->funcs.NativeContents(mod, 0, 0, axis, p_l, vec3_origin, vec3_origin); } else pc = mod->funcs.NativeContents(mod, 0, 0, NULL, p_l, vec3_origin, vec3_origin); } else pc = Q3CONTENTS_SOLID; VM_LONG(ret) = pc; } break; case CG_CM_TRANSFORMEDCAPSULETRACE: case CG_CM_TRANSFORMEDBOXTRACE: // void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, // const vec3_t mins, const vec3_t maxs, // clipHandle_t model, int brushmask ); { //FIXME: no protection of result trace. trace_t tr; q3trace_t *results = VM_POINTER(arg[0]); float *start = VM_POINTER(arg[1]); float *end = VM_POINTER(arg[2]); float *mins = VM_POINTER(arg[3]); float *maxs = VM_POINTER(arg[4]); unsigned int modhandle = VM_LONG(arg[5]); int brushmask = VM_LONG(arg[6]); float *origin = VM_POINTER(arg[7]); float *angles = VM_POINTER(arg[8]); model_t *mod; if (modhandle >= MAX_PRECACHE_MODELS) { // if (modhandle == MAX_PRECACHE_MODELS+1) // mod = &capsule_model; // else mod = box_model; } else mod = cl.model_precache[modhandle+1]; if (!mins) mins = vec3_origin; if (!maxs) maxs = vec3_origin; if (!origin) origin = vec3_origin; if (!angles) angles = vec3_origin; if (mod && mod->loadstate == MLS_LOADED) #if !defined(CLIENTONLY) || defined(CSQC_DAT) World_TransformedTrace(mod, 0, 0, start, end, mins, maxs, fn==CG_CM_TRANSFORMEDCAPSULETRACE, &tr, origin, angles, brushmask); #else { #ifdef warningmsg #pragma warningmsg("FIXME: G3 CGame requires World_TransformedTrace!") #endif memset(&tr, 0, sizeof(tr)); tr.allsolid = tr.startsolid = true; tr.contents = 1; } #endif else { memset(&tr, 0, sizeof(tr)); tr.allsolid = tr.startsolid = true; tr.contents = 1; } results->allsolid = tr.allsolid; results->contents = tr.contents; results->fraction = tr.fraction; results->entityNum = 0; results->startsolid = tr.startsolid; results->surfaceFlags = tr.surface?tr.surface->flags:0; memcpy(results->endpos, tr.endpos, sizeof(vec3_t)); memcpy(&results->plane, &tr.plane, sizeof(cplane_t)); } break; case CG_CM_CAPSULETRACE: case CG_CM_BOXTRACE: // void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, // const vec3_t mins, const vec3_t maxs, // clipHandle_t model, int brushmask ); { //FIXME: no protection of result trace. trace_t tr; q3trace_t *results = VM_POINTER(arg[0]); float *start = VM_POINTER(arg[1]); float *end = VM_POINTER(arg[2]); float *mins = VM_POINTER(arg[3]); float *maxs = VM_POINTER(arg[4]); unsigned int modhandle = VM_LONG(arg[5]); int brushmask = VM_LONG(arg[6]); model_t *mod; if (modhandle >= MAX_PRECACHE_MODELS) { // if (modhandle == MAX_PRECACHE_MODELS+1) // mod = &capsule_model; // else mod = box_model; } else mod = cl.model_precache[modhandle+1]; if (mod->loadstate != MLS_LOADED) { if (mod->loadstate == MLS_NOTLOADED) Mod_LoadModel(mod, MLV_SILENT); if (mod->loadstate == MLS_LOADING) COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); if (mod->loadstate != MLS_LOADED) mod = box_model; //stop crashes, even if this is wrong. } if (!mins) mins = vec3_origin; if (!maxs) maxs = vec3_origin; mod->funcs.NativeTrace(mod, 0, NULLFRAMESTATE, NULL, start, end, mins, maxs, fn==CG_CM_CAPSULETRACE, brushmask, &tr); results->allsolid = tr.allsolid; results->contents = tr.contents; results->fraction = tr.fraction; results->entityNum = 0; results->startsolid = tr.startsolid; results->surfaceFlags = tr.surface?tr.surface->flags:0; memcpy(results->endpos, tr.endpos, sizeof(vec3_t)); memcpy(&results->plane, &tr.plane, sizeof(cplane_t)); } break; case CG_R_LOADWORLDMAP: //FTE can't distinguish. :/ Con_DPrintf("CG_R_LOADWORLDMAP: not implemented\n"); break; //So long as noone has one collision model with a different rendering one, we'll be fine case CG_CM_LOADMAP: { int i; char *mapname = VM_POINTER(arg[0]); strcpy(cl.model_name[1], mapname); cl.worldmodel = cl.model_precache[1] = Mod_ForName(Mod_FixName(mapname, mapname), MLV_SILENT); if (cl.worldmodel->loadstate == MLS_LOADING) COM_WorkerPartialSync(cl.worldmodel, &cl.worldmodel->loadstate, MLS_LOADING); if (cl.worldmodel->loadstate != MLS_LOADED) Host_EndGame("Couldn't load map %s", mapname); for (i=1 ; inumsubmodels ; i++) { strcpy(cl.model_name[1+i], va("*%i", i)); cl.model_precache[i+1] = Mod_ForName (Mod_FixName(cl.model_name[i+1], mapname), MLV_SILENT); } } break; case CG_CM_INLINEMODEL: if ((unsigned int)VM_LONG(arg[0]) > (cl.worldmodel?cl.worldmodel->numsubmodels:0)) Host_EndGame("cgame asked for invalid model number\n"); VM_LONG(ret) = VM_LONG(arg[0]); break; case CG_CM_NUMINLINEMODELS: VM_LONG(ret) = cl.worldmodel?cl.worldmodel->numsubmodels:0; break; case CG_CM_TEMPBOXMODEL: box_model = CM_TempBoxModel(VM_POINTER(arg[0]), VM_POINTER(arg[1])); VM_LONG(ret) = MAX_PRECACHE_MODELS; break; case CG_CM_TEMPCAPSULEMODEL: box_model = CM_TempBoxModel(VM_POINTER(arg[0]), VM_POINTER(arg[1])); VM_LONG(ret) = MAX_PRECACHE_MODELS+1; break; case CG_R_MODELBOUNDS: VALIDATEPOINTER(arg[1], sizeof(vec3_t)); VALIDATEPOINTER(arg[2], sizeof(vec3_t)); { model_t *mod = VM_FROMMHANDLE(arg[0]); if (mod) { if (mod->loadstate == MLS_LOADING) COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); VectorCopy(mod->mins, ((float*)VM_POINTER(arg[1]))); VectorCopy(mod->maxs, ((float*)VM_POINTER(arg[2]))); } } break; case CG_R_REGISTERMODEL: //precache model { char *name = VM_POINTER(arg[0]); model_t *mod; mod = Mod_ForName(Mod_FixName(name, cl.model_name[1]), MLV_SILENT); if (mod->loadstate == MLS_LOADING) { //needed to ensure it really is missing if (!COM_FCheckExists(mod->name)) COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING); } if (mod->loadstate == MLS_FAILED || mod->type == mod_dummy) VM_LONG(ret) = 0; else VM_LONG(ret) = VM_TOMHANDLE(mod); } break; case CG_R_REGISTERSKIN: VM_LONG(ret) = Mod_RegisterSkinFile(VM_POINTER(arg[0])); break; case CG_R_REGISTERSHADER: if (!*(char*)VM_POINTER(arg[0])) VM_LONG(ret) = 0; else VM_LONG(ret) = VM_TOSHANDLE(R_RegisterPic(VM_POINTER(arg[0]), NULL)); break; case CG_R_REGISTERSHADERNOMIP: if (!*(char*)VM_POINTER(arg[0])) VM_LONG(ret) = 0; else VM_LONG(ret) = VM_TOSHANDLE(R_RegisterPic(VM_POINTER(arg[0]), NULL)); break; case CG_R_CLEARSCENE: //clear scene (not rtlights, only dynamic ones) CL_ClearEntityLists(); rtlights_first = RTL_FIRST; break; case CG_R_ADDPOLYTOSCENE: VQ3_AddPoly(VM_FROMSHANDLE(arg[0]), VM_LONG(arg[1]), VM_POINTER(arg[2])); break; case CG_R_ADDREFENTITYTOSCENE: //add ent to scene VQ3_AddEntity(VM_POINTER(arg[0])); break; case CG_R_ADDADDITIVELIGHTTOSCENE: case CG_R_ADDLIGHTTOSCENE: //add light to scene. { float *org = VM_POINTER(arg[0]); CL_NewDlight(-1, org, VM_FLOAT(arg[1]), 0, VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4])); } break; case CG_R_RENDERSCENE: //render scene R_PushDlights(); VQ3_RenderView(VM_POINTER(arg[0])); break; case CG_R_SETCOLOR: //setcolour float* { float *f = VM_POINTER(arg[0]); if (f) R2D_ImageColours(f[0], f[1], f[2], f[3]); else R2D_ImageColours(1, 1, 1, 1); } break; case CG_R_DRAWSTRETCHPIC: R2D_Image(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4]), VM_FLOAT(arg[5]), VM_FLOAT(arg[6]), VM_FLOAT(arg[7]), VM_FROMSHANDLE(VM_LONG(arg[8]))); break; case CG_R_LERPTAG: //Lerp tag... VALIDATEPOINTER(arg[0], sizeof(float)*12); VM_LONG(ret) = VM_LerpTag(VM_POINTER(arg[0]), VM_FROMMHANDLE(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_FLOAT(arg[4]), VM_POINTER(arg[5])); break; case CG_S_REGISTERSOUND: { sfx_t *sfx; sfx = S_PrecacheSound(VM_POINTER(arg[0])); if (sfx) VM_LONG(ret) = VM_TOSTRCACHE(arg[0]); else VM_LONG(ret) = -1; } break; case CG_S_STARTLOCALSOUND: if (VM_LONG(arg[0]) != -1 && arg[0]) S_LocalSound(VM_FROMSTRCACHE(arg[0])); break; case CG_S_STARTSOUND:// ( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) S_StartSound(VM_LONG(arg[1])+1, VM_LONG(arg[2]), S_PrecacheSound(VM_FROMSTRCACHE(arg[3])), VM_POINTER(arg[0]), NULL, 1, 1, 0, 0, CF_CLI_NODUPES); break; case CG_S_ADDLOOPINGSOUND: //entnum, origin, velocity, sfx CG_StartLoopingSounds(VM_LONG(arg[0])+1, VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_FROMSTRCACHE(arg[3]), false); break; case CG_S_ADDREALLOOPINGSOUND: //entnum, origin, velocity, sfx CG_StartLoopingSounds(VM_LONG(arg[0])+1, VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_FROMSTRCACHE(arg[3]), true); break; case CG_S_STOPLOOPINGSOUND: //entnum CG_StopLoopingSounds(VM_LONG(arg[0])+1); break; case CG_S_CLEARLOOPINGSOUNDS: //clearall CG_ClearLoopingSounds(VM_LONG(arg[0])); break; case CG_S_UPDATEENTITYPOSITION://void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); //entnum, org CG_MoveLoopingSound(VM_LONG(arg[0])+1, VM_POINTER(arg[1])); break; case CG_S_STARTBACKGROUNDTRACK: Media_NamedTrack(VM_POINTER(arg[0]), VM_POINTER(arg[1])); return 0; case CG_S_STOPBACKGROUNDTRACK: Media_NamedTrack(NULL, NULL); return 0; case CG_S_RESPATIALIZE://void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); { int entnum = VM_LONG(arg[0])+1; float *org = VM_POINTER(arg[1]); vec3_t *axis = VM_POINTER(arg[2]); int inwater = VM_LONG(arg[3]); cl.playerview[0].audio.defaulted = false; cl.playerview[0].audio.entnum = entnum; VectorCopy(org, cl.playerview[0].audio.origin); VectorCopy(axis[0], cl.playerview[0].audio.forward); VectorCopy(axis[1], cl.playerview[0].audio.right); VectorCopy(axis[2], cl.playerview[0].audio.up); cl.playerview[0].audio.reverbtype = inwater?1:0; VectorClear(cl.playerview[0].audio.velocity); } break; case CG_KEY_ISDOWN: { if (keydown[VM_LONG(arg[0])]) VM_LONG(ret) = 1; else VM_LONG(ret) = 0; } break; case CG_KEY_GETKEY: { int ret[1]; M_FindKeysForCommand (0, 0, VM_POINTER(arg[0]), ret, NULL, countof(ret)); return ret[0]; } break; case CG_KEY_GETCATCHER: VM_LONG(ret) = Q3_GetKeyCatcher(); break; case CG_KEY_SETCATCHER: Q3_SetKeyCatcher(VM_LONG(arg[0])); break; case CG_GETGLCONFIG: VALIDATEPOINTER(arg[0], 11332); { //FIXME: Clean this shit up //do any needed work unsigned char *glconfig = VM_POINTER(arg[0]); memset(glconfig, 0, 11304); *(int *)(glconfig+11304) = vid.width; *(int *)(glconfig+11308) = vid.height; *(float *)(glconfig+11312) = (float)vid.width/vid.height; memset((glconfig+11316), 0, 11332-11316); } break; case CG_GETGAMESTATE: VALIDATEPOINTER(arg[0], sizeof(gameState_t)); VM_LONG(ret) = CG_GetGameState(VM_POINTER(arg[0])); break; case CG_CM_MARKFRAGMENTS: VM_LONG(ret) = CG_MarkFragments( VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3]), VM_POINTER(arg[4]), VM_LONG(arg[5]), VM_POINTER(arg[6]) ); break; case CG_GETCURRENTSNAPSHOTNUMBER: VALIDATEPOINTER(arg[0], sizeof(int)); VALIDATEPOINTER(arg[1], sizeof(int)); *(int *)VM_POINTER(arg[0]) = ccs.snap.serverMessageNum; *(int *)VM_POINTER(arg[1]) = ccs.snap.serverTime; break; case CG_GETSNAPSHOT: VALIDATEPOINTER(arg[1], sizeof(snapshot_t)); VM_LONG(ret) = CG_FillQ3Snapshot(VM_LONG(arg[0]), VM_POINTER(arg[1])); break; case CG_GETCURRENTCMDNUMBER: VM_LONG(ret) = CGQ3_GetCurrentCmdNumber(); break; case CG_GETUSERCMD: VALIDATEPOINTER(arg[1], sizeof(q3usercmd_t)); VM_LONG(ret) = CGQ3_GetUserCmd(VM_LONG(arg[0]), VM_POINTER(arg[1])); break; case CG_SETUSERCMDVALUE: //weaponselect, zoomsensitivity. ccs.selected_weapon = VM_LONG(arg[0]); in_sensitivityscale = VM_FLOAT(arg[1]); break; case CG_GETSERVERCOMMAND: VM_LONG(ret) = CG_GetServerCommand(VM_LONG(arg[0])); break; case CG_MEMORY_REMAINING: VM_LONG(ret) = 1024*1024*8;//Hunk_LowMemAvailable(); break; case CG_MILLISECONDS: VM_LONG(ret) = Sys_Milliseconds(); break; case CG_REAL_TIME: VALIDATEPOINTER(arg[0], sizeof(q3time_t)); return Q3VM_GetRealtime(VM_POINTER(arg[0])); case CG_SNAPVECTOR: // ( float *v ) VALIDATEPOINTER(arg[0], sizeof(vec3_t)); { float *fp = (float *)VM_POINTER(arg[0]); #define rint(x) (int)((x > 0)?(x + 0.5f):(x-0.5f)) fp[0] = rint(fp[0]); fp[1] = rint(fp[1]); fp[2] = rint(fp[2]); } break; case CG_PC_ADD_GLOBAL_DEFINE: Con_Printf("CG_PC_ADD_GLOBAL_DEFINE not supported\n"); break; case CG_PC_SOURCE_FILE_AND_LINE: Script_Get_File_And_Line(arg[0], VM_POINTER(arg[1]), VM_POINTER(arg[2])); break; case CG_PC_LOAD_SOURCE: return Script_LoadFile(VM_POINTER(arg[0])); case CG_PC_FREE_SOURCE: Script_Free(arg[0]); break; case CG_PC_READ_TOKEN: //fixme: memory protect. VALIDATEPOINTER(arg[1], sizeof(struct pc_token_s)); return Script_Read(arg[0], VM_POINTER(arg[1])); // standard Q3 case CG_MEMSET: VALIDATEPOINTER(arg[0], arg[2]); { void *dst = VM_POINTER(arg[0]); memset(dst, arg[1], arg[2]); } break; case CG_MEMCPY: VALIDATEPOINTER(arg[0], arg[2]); { void *dst = VM_POINTER(arg[0]); void *src = VM_POINTER(arg[1]); memcpy(dst, src, arg[2]); } break; case CG_STRNCPY: VALIDATEPOINTER(arg[0], arg[2]); { void *dst = VM_POINTER(arg[0]); void *src = VM_POINTER(arg[1]); strncpy(dst, src, arg[2]); } break; case CG_SIN: VM_FLOAT(ret)=(float)sin(VM_FLOAT(arg[0])); break; case CG_COS: VM_FLOAT(ret)=(float)cos(VM_FLOAT(arg[0])); break; case CG_ACOS: VM_FLOAT(ret)=(float)acos(VM_FLOAT(arg[0])); break; case CG_ATAN2: VM_FLOAT(ret)=(float)atan2(VM_FLOAT(arg[0]), VM_FLOAT(arg[1])); break; case CG_SQRT: VM_FLOAT(ret)=(float)sqrt(VM_FLOAT(arg[0])); break; case CG_FLOOR: VM_FLOAT(ret)=(float)floor(VM_FLOAT(arg[0])); break; case CG_CEIL: VM_FLOAT(ret)=(float)ceil(VM_FLOAT(arg[0])); break; case CG_R_REMAP_SHADER: R_RemapShader(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_FLOAT(arg[2])); break; case CG_R_REGISTERFONT: VALIDATEPOINTER(arg[2], sizeof(fontInfo_t)); UI_RegisterFont(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_POINTER(arg[2])); break; case CG_CIN_PLAYCINEMATIC: return UI_Cin_Play(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_LONG(arg[4]), VM_LONG(arg[5])); case CG_CIN_STOPCINEMATIC: return UI_Cin_Stop(VM_LONG(arg[0])); case CG_CIN_RUNCINEMATIC: return UI_Cin_Run(VM_LONG(arg[0])); case CG_CIN_DRAWCINEMATIC: return UI_Cin_Draw(VM_LONG(arg[0])); case CG_CIN_SETEXTENTS: return UI_Cin_SetExtents(VM_LONG(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_LONG(arg[4])); case CG_FTE_FINDPARTICLEEFFECT: return pe->FindParticleType(VM_POINTER(arg[0])); case CG_FTE_SPAWNPARTICLEEFFECT: return pe->RunParticleEffectState(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_FLOAT(arg[2]), VM_LONG(arg[3]), VM_POINTER(arg[4])); case CG_FTE_SPAWNPARTICLETRAIL: return pe->ParticleTrail(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]), 0, 1, NULL, VM_POINTER(arg[3])); case CG_FTE_FREEPARTICLESTATE: pe->DelinkTrailstate(VM_POINTER(arg[0])); break; default: Con_Printf("Q3CG: Bad system trap: %i\n", (int)fn); } return ret; } static int CG_SystemCallsVM(void *offset, quintptr_t mask, int fn, const int *arg) { if (sizeof(qintptr_t) == sizeof(int)) return CG_SystemCalls(offset, mask, fn, (qintptr_t*)arg); else { qintptr_t args[10]; args[0]=arg[0]; args[1]=arg[1]; args[2]=arg[2]; args[3]=arg[3]; args[4]=arg[4]; args[5]=arg[5]; args[6]=arg[6]; args[7]=arg[7]; args[8]=arg[8]; args[9]=arg[9]; return CG_SystemCalls(offset, mask, fn, args); } } //I'm not keen on this. //but dlls call it without saying what sort of vm it comes from, so I've got to have them as specifics static qintptr_t EXPORT_FN CG_SystemCallsNative(qintptr_t arg, ...) { qintptr_t args[10]; 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); va_end(argptr); return CG_SystemCalls(NULL, ~(quintptr_t)0, arg, args); } int CG_Refresh(void) { int time; if (!cgvm) return false; r_refdef.skyroom_enabled = false; time = cl.time*1000; VM_Call(cgvm, CG_DRAW_ACTIVE_FRAME, time, 0, false); R2D_ImageColours(1, 1, 1, 1); return true; } void CG_Stop (void) { Q3_SetKeyCatcher(Q3_GetKeyCatcher()&~2); if (cgvm) { VM_Call(cgvm, CG_SHUTDOWN); VM_Destroy(cgvm); VM_fcloseall(1); cgvm = NULL; } } qboolean CG_VideoRestarted(void) { if (cgvm) { VM_Call(cgvm, CG_INIT, ccs.serverMessageNum, ccs.lastServerCommandNum, cl.playerview[0].playernum); return true; } return false; } void CG_Start (void) { SCR_SetLoadingStage(0); if (cls.protocol != CP_QUAKE3) { //q3 clients only. CG_Stop(); return; } Z_FreeTags(CGTAGNUM); SCR_BeginLoadingPlaque(); box_model = CM_TempBoxModel(vec3_origin, vec3_origin); cgvm = VM_Create("cgame", com_nogamedirnativecode.ival?NULL:CG_SystemCallsNative, "vm/cgame", CG_SystemCallsVM); if (cgvm) { //hu... cgame doesn't appear to have a query version call! SCR_EndLoadingPlaque(); VM_Call(cgvm, CG_INIT, ccs.serverMessageNum, ccs.lastServerCommandNum, cl.playerview[0].playernum); } else { SCR_EndLoadingPlaque(); Host_EndGame("Failed to initialise cgame module\n"); } } qboolean CG_Command(void) { if (!cgvm) return false; Con_DPrintf("CG_Command: %s %s\n", Cmd_Argv(0), Cmd_Args()); return VM_Call(cgvm, CG_CONSOLE_COMMAND); } void CG_Command_f(void) { if (cgvm) { Con_DPrintf("CG_Command_f: %s %s\n", Cmd_Argv(0), Cmd_Args()); if (!VM_Call(cgvm, CG_CONSOLE_COMMAND)) { Cmd_ForwardToServer(); } } } qboolean CG_KeyPress(int key, int unicode, int down) { int catcher = Q3_GetKeyCatcher(); if (!cgvm || !(catcher&8)) return false; /* if you change this here, it'll have to be changed in cl_cg.c too */ switch (key) { /* all these get interpreted as enter in Q3's UI... */ case K_JOY1: case K_JOY2: case K_JOY3: case K_JOY4: case K_AUX1: case K_AUX2: case K_AUX3: case K_AUX4: case K_AUX5: case K_AUX6: case K_AUX7: case K_AUX8: case K_AUX9: case K_AUX10: case K_AUX11: case K_AUX14: case K_AUX15: case K_AUX16: return true; break; /* Q3 doesn't know about these keys, remap them */ case K_GP_START: key = K_ESCAPE; break; case K_GP_DPAD_UP: key = K_UPARROW; break; case K_GP_DPAD_DOWN: key = K_DOWNARROW; break; case K_GP_DPAD_LEFT: key = K_LEFTARROW; break; case K_GP_DPAD_RIGHT: key = K_RIGHTARROW; break; case K_GP_A: key = K_ENTER; break; case K_GP_B: key = K_ESCAPE; break; case K_GP_X: key = K_BACKSPACE; break; } return VM_Call(cgvm, CG_KEY_EVENT, key, down); } void CG_Restart_f(void) { CG_Stop(); CG_Start(); } void CG_Init(void) { Cmd_AddCommand("cg_restart", CG_Restart_f); } #endif