mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-29 23:22:01 +00:00
1487 lines
39 KiB
C
1487 lines
39 KiB
C
#include "q3common.h"
|
|
#if defined(VM_CG) && defined(HAVE_CLIENT)
|
|
#include "clq3defs.h"
|
|
#include "com_mesh.h"
|
|
|
|
//cl_ui.c
|
|
void CG_Command_f(void);
|
|
|
|
#define CGAME_IMPORT_API_VERSION 4
|
|
|
|
static model_t *box_model;
|
|
cvar_t *cl_shownet_ptr, *cl_c2sdupe_ptr, *cl_nodelta_ptr;
|
|
|
|
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;
|
|
}
|
|
|
|
typedef struct {
|
|
int stringOffsets[MAX_Q3_CONFIGSTRINGS];
|
|
char stringData[MAX_Q3_GAMESTATE_CHARS];
|
|
int dataCount;
|
|
} gameState_t;
|
|
static gameState_t cggamestate;
|
|
|
|
void CG_ClearGameState(void)
|
|
{
|
|
memset(&cggamestate, 0, sizeof(cggamestate));
|
|
}
|
|
|
|
void CG_InsertIntoGameState(int num, const char *str)
|
|
{
|
|
if (num < 5)
|
|
{
|
|
Con_DPrintf("%i: %s", num, str);
|
|
}
|
|
|
|
if (num == CFGSTR_SYSINFO)
|
|
{
|
|
//check some things.
|
|
ccs.servercount = atoi(worldfuncs->GetInfoKey(str, "sv_serverid"));
|
|
}
|
|
|
|
if (cggamestate.dataCount + strlen(str)+1 > countof(cggamestate.stringData))
|
|
{
|
|
char oldstringData[countof(cggamestate.stringData)];
|
|
int i;
|
|
char *oldstr;
|
|
//copy the old strings to a temporary buffer
|
|
memcpy(oldstringData, cggamestate.stringData, countof(cggamestate.stringData));
|
|
cggamestate.dataCount = 0;
|
|
for (i = 0; i < countof(cggamestate.stringOffsets); i++)
|
|
{
|
|
oldstr = oldstringData+cggamestate.stringOffsets[i];
|
|
if (*oldstr)
|
|
{
|
|
if (cggamestate.dataCount + strlen(oldstr)+1 > countof(cggamestate.stringData))
|
|
plugfuncs->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);
|
|
}
|
|
|
|
const char *CG_GetConfigString(int num)
|
|
{
|
|
if ((unsigned)num >= countof(cggamestate.stringOffsets))
|
|
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<latestsequence are valid
|
|
return inputfuncs->GetMoveCount()-1;
|
|
}
|
|
static qboolean CGQ3_GetUserCmd(int cmdNumber, q3usercmd_t *ucmd)
|
|
{
|
|
//q3 prediction uses for(int frame = CGQ3_GetCurrentCmdNumber() - CMD_BACKUP{64} + 1; frame <= CGQ3_GetCurrentCmdNumber(); frame++)
|
|
//skipping any that are older than the snapshot's time.
|
|
|
|
usercmd_t *cmd;
|
|
|
|
//q3 does not do partials.
|
|
if (cmdNumber < 0)
|
|
return false; //grr, stoopid q3.
|
|
if (cmdNumber >= inputfuncs->GetMoveCount())
|
|
plugfuncs->EndGame("CLQ3_GetUserCmd: %i >= %i", cmdNumber, inputfuncs->GetMoveCount());
|
|
|
|
cmd = inputfuncs->GetMoveEntry(cmdNumber);
|
|
if (!cmd)
|
|
return false;
|
|
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;
|
|
}
|
|
|
|
vm_t *cgvm;
|
|
static const char *mapentspointer;
|
|
|
|
extern int keycatcher;
|
|
|
|
qboolean CG_GetServerCommand(int cmdnum)
|
|
{
|
|
static char bigconfigstring[65536];
|
|
char *arg0;
|
|
|
|
//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);
|
|
cmdfuncs->TokenizeString(str);
|
|
arg0 = cmdfuncs->Argv(0, NULL, 0);
|
|
|
|
if (!strcmp(arg0, "bcs0"))
|
|
{ //start
|
|
Q_snprintfz(bigconfigstring, sizeof(bigconfigstring), "cs %s \"%s", cmdfuncs->Argv(1, NULL, 0), cmdfuncs->Argv(2, NULL, 0));
|
|
return false;
|
|
}
|
|
if (!strcmp(arg0, "bcs1"))
|
|
{ //continuation
|
|
Q_strncatz(bigconfigstring, cmdfuncs->Argv(2, NULL, 0), sizeof(bigconfigstring));
|
|
return false;
|
|
}
|
|
if (!strcmp(arg0, "bcs2"))
|
|
{ //end
|
|
Q_strncatz(bigconfigstring, cmdfuncs->Argv(2, NULL, 0), sizeof(bigconfigstring));
|
|
Q_strncatz(bigconfigstring, "\"", sizeof(bigconfigstring));
|
|
cmdfuncs->TokenizeString(bigconfigstring);
|
|
}
|
|
|
|
if (!strcmp(arg0, "cs"))
|
|
CG_InsertIntoGameState(atoi(cmdfuncs->Argv(1, NULL, 0)), cmdfuncs->Argv(2, NULL, 0));
|
|
else if (!strcmp(arg0, "map_restart"))
|
|
clientfuncs->ClearNotify();
|
|
else if (!strcmp(arg0, "disconnect"))
|
|
plugfuncs->EndGame("Server disconnected - %s", (cmdfuncs->Argc()>1)?cmdfuncs->Argv(1, NULL, 0):"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;
|
|
scenefuncs->ClipDecal(ccs.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, int range, const char *soundname, float volume, 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 = audiofuncs->PrecacheSound(soundname);
|
|
loopers[i].ispersistent = persistent;
|
|
// loopers[i].range = range;
|
|
// loopers[i].volume = volume;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* same principle as the looper code above */
|
|
static struct
|
|
{
|
|
unsigned int entnum;
|
|
vec3_t origin;
|
|
} *entsounds;
|
|
static size_t numentsounds;
|
|
static size_t maxentsounds;
|
|
|
|
/* called by CG_S_UPDATEENTITYPOSITION to inform us of an ent its actual position */
|
|
static void CG_UpdateEntityPosition(unsigned int entnum, float *origin)
|
|
{
|
|
size_t i;
|
|
|
|
if (entnum == ENTITYNUM_NONE)
|
|
return;
|
|
|
|
for (i = 0; i < numentsounds; i++)
|
|
{
|
|
if (entsounds[i].entnum == entnum)
|
|
break;
|
|
}
|
|
|
|
/* not present in list. */
|
|
if (i == numentsounds)
|
|
{
|
|
if (numentsounds == maxentsounds)
|
|
Z_ReallocElements((void**)&entsounds, & maxentsounds, maxentsounds+1, sizeof(*entsounds));
|
|
numentsounds++;
|
|
|
|
entsounds[i].entnum = entnum;
|
|
}
|
|
|
|
VectorCopy(origin, entsounds[i].origin);
|
|
}
|
|
|
|
/* returns the last updated position of an entity. */
|
|
static float* CG_GetEntityPosition(unsigned int entnum)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < numentsounds; i++)
|
|
{
|
|
if (entsounds[i].entnum == entnum) {
|
|
return entsounds[i].origin;
|
|
}
|
|
}
|
|
|
|
Con_Printf("CG_GetEntityPosition: entity num %i not known.\n", entnum);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int VM_LerpTag(float *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) plugfuncs->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((cgameImport_t)fn)
|
|
{
|
|
case CG_PRINT:
|
|
{
|
|
const char *text = VM_POINTER(arg[0]);
|
|
Con_Printf("%s", text);
|
|
}
|
|
break;
|
|
case CG_ERROR:
|
|
plugfuncs->EndGame("cgame: %s", (char*)VM_POINTER(arg[0]));
|
|
break;
|
|
|
|
case CG_ARGC:
|
|
VM_LONG(ret) = cmdfuncs->Argc();
|
|
break;
|
|
case CG_ARGV:
|
|
VALIDATEPOINTER(arg[1], arg[2]);
|
|
cmdfuncs->Argv(VM_LONG(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]));
|
|
break;
|
|
case CG_ARGS:
|
|
VALIDATEPOINTER(arg[0], arg[1]);
|
|
cmdfuncs->Args(VM_POINTER(arg[0]), 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:
|
|
cvarfuncs->SetString(VM_POINTER(arg[0]), VM_POINTER(arg[1])?VM_POINTER(arg[1]):"");
|
|
break;
|
|
case CG_CVAR_VARIABLESTRINGBUFFER:
|
|
{
|
|
cvar_t *var = cvarfuncs->GetNVFDG(VM_POINTER(arg[0]), NULL, 0, NULL, "Q3CG created");
|
|
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]));
|
|
cmdfuncs->AddText(VM_POINTER(arg[0]), false);
|
|
break;
|
|
case CG_ADDCOMMAND:
|
|
cmdfuncs->AddCommand(VM_POINTER(arg[0]), NULL, NULL);
|
|
break;
|
|
case CG_SENDCLIENTCOMMAND:
|
|
Con_DPrintf("CG_SENDCLIENTCOMMAND: %s", (char*)VM_POINTER(arg[0]));
|
|
CLQ3_SendClientCommand("%s", (char*)VM_POINTER(arg[0]));
|
|
break;
|
|
|
|
case CG_UPDATESCREEN: //force a buffer swap cos loading won't refresh it soon.
|
|
drawfuncs->RedrawScreen();
|
|
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_SEEK:
|
|
return VM_FSeek(arg[0], arg[1], arg[2], 1);
|
|
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 >= countof(ccs.model_precache))
|
|
{
|
|
// if (modhandle == countof(ccs.model_precache)+1)
|
|
// mod = &capsule_model;
|
|
// else
|
|
mod = box_model;
|
|
}
|
|
else
|
|
mod = ccs.model_precache[modhandle];
|
|
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 >= countof(ccs.model_precache))
|
|
{
|
|
// if (modhandle == countof(ccs.model_precache)+1)
|
|
// mod = &capsule_model;
|
|
// else
|
|
mod = box_model;
|
|
}
|
|
else
|
|
mod = ccs.model_precache[modhandle];
|
|
|
|
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 >= countof(ccs.model_precache))
|
|
{
|
|
// if (modhandle == countof(ccs.model_precache)+1)
|
|
// mod = &capsule_model;
|
|
// else
|
|
mod = box_model;
|
|
}
|
|
else
|
|
mod = ccs.model_precache[modhandle];
|
|
|
|
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)
|
|
worldfuncs->TransformedTrace(mod, 0, 0, start, end, mins, maxs, fn==CG_CM_TRANSFORMEDCAPSULETRACE, &tr, origin, angles, brushmask);
|
|
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]);
|
|
unsigned int brushmask = VM_LONG(arg[6]);
|
|
model_t *mod;
|
|
if (modhandle >= countof(ccs.model_precache))
|
|
{
|
|
// if (modhandle == countof(ccs.model_precache)+1)
|
|
// mod = &capsule_model;
|
|
// else
|
|
mod = box_model;
|
|
}
|
|
else
|
|
mod = ccs.model_precache[modhandle];
|
|
|
|
if (mod->loadstate != MLS_LOADED)
|
|
{
|
|
if (mod->loadstate == MLS_NOTLOADED)
|
|
scenefuncs->LoadModel(mod->publicname, MLV_SILENTSYNC);
|
|
if (mod->loadstate == MLS_LOADING && threadfuncs)
|
|
threadfuncs->WaitForCompletion(mod, &mod->loadstate, MLS_LOADING);
|
|
if (mod->loadstate != MLS_LOADED)
|
|
{
|
|
memset(results, 0, sizeof(*results));
|
|
results->fraction = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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:
|
|
{ //rendering
|
|
scenefuncs->NewMap(worldfuncs->LoadModel(VM_POINTER(arg[0]), MLV_SILENTSYNC));
|
|
}
|
|
break;
|
|
|
|
case CG_CM_LOADMAP:
|
|
{ //collisions
|
|
int i;
|
|
char *mapname = VM_POINTER(arg[0]);
|
|
ccs.worldmodel = ccs.model_precache[0] = worldfuncs->LoadModel(mapname, MLV_SILENTSYNC);
|
|
if (ccs.worldmodel->loadstate != MLS_LOADED)
|
|
plugfuncs->EndGame("Couldn't load map %s", mapname);
|
|
|
|
|
|
for (i=1 ; i<=ccs.worldmodel->numsubmodels && i < countof(ccs.model_precache); i++)
|
|
ccs.model_precache[i] = worldfuncs->LoadModel(worldfuncs->FixName(va("*%i", i), mapname), MLV_SILENTSYNC);
|
|
}
|
|
|
|
break;
|
|
|
|
case CG_CM_INLINEMODEL:
|
|
if ((unsigned int)VM_LONG(arg[0]) > (ccs.worldmodel?ccs.worldmodel->numsubmodels:0))
|
|
plugfuncs->EndGame("cgame asked for invalid model number\n");
|
|
VM_LONG(ret) = VM_LONG(arg[0]);
|
|
break;
|
|
case CG_CM_NUMINLINEMODELS:
|
|
VM_LONG(ret) = ccs.worldmodel?ccs.worldmodel->numsubmodels:0;
|
|
break;
|
|
|
|
case CG_CM_TEMPBOXMODEL:
|
|
box_model = worldfuncs->TempBoxModel(VM_POINTER(arg[0]), VM_POINTER(arg[1]));
|
|
VM_LONG(ret) = countof(ccs.model_precache);
|
|
break;
|
|
case CG_CM_TEMPCAPSULEMODEL:
|
|
box_model = worldfuncs->TempBoxModel(VM_POINTER(arg[0]), VM_POINTER(arg[1]));
|
|
VM_LONG(ret) = countof(ccs.model_precache)+1;
|
|
break;
|
|
|
|
case CG_R_MODELBOUNDS:
|
|
VALIDATEPOINTER(arg[1], sizeof(vec3_t));
|
|
VALIDATEPOINTER(arg[2], sizeof(vec3_t));
|
|
{
|
|
model_t *mod = scenefuncs->ModelFromId(arg[0]);
|
|
if (mod)
|
|
{
|
|
if (mod->loadstate == MLS_LOADING && threadfuncs)
|
|
threadfuncs->WaitForCompletion(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;
|
|
if (!name)
|
|
return 0;
|
|
mod = scenefuncs->LoadModel(worldfuncs->FixName(name, ccs.worldmodel->name), MLV_SILENTSYNC);
|
|
if (mod->loadstate == MLS_FAILED || mod->type == mod_dummy)
|
|
VM_LONG(ret) = 0;
|
|
else
|
|
VM_LONG(ret) = scenefuncs->ModelToId(mod);
|
|
}
|
|
break;
|
|
|
|
case CG_R_REGISTERSKIN:
|
|
VM_LONG(ret) = scenefuncs->RegisterSkinFile(VM_POINTER(arg[0]));
|
|
break;
|
|
|
|
case CG_R_REGISTERSHADER:
|
|
if (!*(char*)VM_POINTER(arg[0]))
|
|
VM_LONG(ret) = 0;
|
|
else
|
|
VM_LONG(ret) = drawfuncs->LoadImage(VM_POINTER(arg[0]));
|
|
break;
|
|
case CG_R_REGISTERSHADERNOMIP:
|
|
if (!*(char*)VM_POINTER(arg[0]))
|
|
VM_LONG(ret) = 0;
|
|
else
|
|
VM_LONG(ret) = drawfuncs->LoadImage(VM_POINTER(arg[0]));
|
|
break;
|
|
|
|
case CG_R_CLEARSCENE: //clear scene (not rtlights, only dynamic ones)
|
|
scenefuncs->ClearScene();
|
|
break;
|
|
case CG_R_ADDPOLYTOSCENE:
|
|
VQ3_AddPolys(drawfuncs->ShaderFromId(arg[0]), VM_LONG(arg[1]), VM_POINTER(arg[2]), 1);
|
|
break;
|
|
case CG_R_ADDPOLYSTOSCENE:
|
|
VQ3_AddPolys(drawfuncs->ShaderFromId(arg[0]), VM_LONG(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3]));
|
|
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.
|
|
{
|
|
dlight_t *dl = scenefuncs->AllocDlightOrg(-1, VM_POINTER(arg[0]));
|
|
dl->flags = LFLAG_NORMALMODE|LFLAG_REALTIMEMODE;
|
|
dl->radius = VM_FLOAT(arg[1]);
|
|
dl->die = ccs.time+0.1;
|
|
VectorSet(dl->color, VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4]));
|
|
}
|
|
break;
|
|
case CG_R_RENDERSCENE: //render scene
|
|
VQ3_RenderView(VM_POINTER(arg[0]));
|
|
break;
|
|
|
|
case CG_R_SETCOLOR: //setcolour float*
|
|
{
|
|
float *f = VM_POINTER(arg[0]);
|
|
if (f)
|
|
drawfuncs->Colour4f(f[0], f[1], f[2], f[3]);
|
|
else
|
|
drawfuncs->Colour4f(1, 1, 1, 1);
|
|
}
|
|
break;
|
|
|
|
case CG_R_DRAWSTRETCHPIC:
|
|
drawfuncs->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_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]), scenefuncs->ModelFromId(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 = audiofuncs->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])
|
|
audiofuncs->LocalSound(VM_FROMSTRCACHE(arg[0]), CHAN_AUTO, 1.0);
|
|
break;
|
|
|
|
case CG_S_STARTSOUND:// ( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx )
|
|
/* if NULL gets passed for an origin, it wants us to look at those
|
|
provided by CG_S_UPDATEENTITYPOSITION updates instead. */
|
|
if (VM_POINTER(arg[0]) == NULL)
|
|
audiofuncs->StartSound(VM_LONG(arg[1])+1, VM_LONG(arg[2]), audiofuncs->PrecacheSound(VM_FROMSTRCACHE(arg[3])), CG_GetEntityPosition(VM_LONG(arg[1])+1), NULL, 1, 1, 0, 0, CF_CLI_NODUPES | CF_FOLLOW);
|
|
else
|
|
audiofuncs->StartSound(VM_LONG(arg[1])+1, VM_LONG(arg[2]), audiofuncs->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]), -1, VM_FROMSTRCACHE(arg[3]), 1, 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]), -1, VM_FROMSTRCACHE(arg[3]), 1, 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_UpdateEntityPosition(VM_LONG(arg[0])+1, VM_POINTER(arg[1]));
|
|
break;
|
|
|
|
case CG_S_STARTBACKGROUNDTRACK:
|
|
audiofuncs->ChangeMusicTrack(VM_POINTER(arg[0]), VM_POINTER(arg[1]));
|
|
return 0;
|
|
case CG_S_STOPBACKGROUNDTRACK:
|
|
audiofuncs->ChangeMusicTrack(NULL, NULL);
|
|
return 0;
|
|
|
|
case CG_S_RESPATIALIZE://void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
|
|
audiofuncs->Spacialize(0, VM_LONG(arg[0])+1, VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3])?1:0, vec3_origin);
|
|
break;
|
|
|
|
case CG_KEY_ISDOWN:
|
|
{
|
|
if (inputfuncs->IsKeyDown(VM_LONG(arg[0])))
|
|
VM_LONG(ret) = 1;
|
|
else
|
|
VM_LONG(ret) = 0;
|
|
}
|
|
break;
|
|
|
|
case CG_KEY_GETKEY:
|
|
{
|
|
int ret[1];
|
|
inputfuncs->FindKeysForCommand(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:
|
|
{
|
|
float vsize[2];
|
|
q3glconfig_t *cfg;
|
|
VALIDATEPOINTER(arg[0], sizeof(q3glconfig_t));
|
|
drawfuncs->GetVideoSize(vsize, NULL);
|
|
cfg = VM_POINTER(arg[0]);
|
|
|
|
//do any needed work
|
|
memset(cfg, 0, sizeof(*cfg));
|
|
|
|
Q_strncpyz(cfg->renderer_string, "", sizeof(cfg->renderer_string));
|
|
Q_strncpyz(cfg->vendor_string, "", sizeof(cfg->vendor_string));
|
|
Q_strncpyz(cfg->version_string, "", sizeof(cfg->version_string));
|
|
Q_strncpyz(cfg->extensions_string, "", sizeof(cfg->extensions_string));
|
|
|
|
cfg->colorBits = 32;
|
|
cfg->depthBits = 24;
|
|
cfg->stencilBits = 8;//sh_config.stencilbits;
|
|
cfg->textureCompression = true;//!!sh_config.hw_bc;
|
|
cfg->textureEnvAddAvailable = true;//sh_config.env_add;
|
|
|
|
//these are the only three that really matter.
|
|
cfg->vidWidth = vsize[0];
|
|
cfg->vidHeight = vsize[1];
|
|
cfg->windowAspect = (float)vsize[0]/vsize[1];
|
|
}
|
|
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]);
|
|
inputfuncs->SetSensitivityScale(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) = plugfuncs->GetMilliseconds();
|
|
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:
|
|
scenefuncs->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_GET_ENTITY_TOKEN:
|
|
mapentspointer = cmdfuncs->ParseToken(mapentspointer, VM_POINTER(arg[0]), arg[1], NULL);
|
|
return !!mapentspointer;
|
|
|
|
|
|
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;
|
|
*/
|
|
case CG_CM_LOADMODEL:
|
|
case CG_REMOVECOMMAND:
|
|
case CG_TESTPRINTINT:
|
|
case CG_TESTPRINTFLOAT:
|
|
case CG_R_INPVS:
|
|
case CG_R_LIGHTFORPOINT:
|
|
// 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(double time)
|
|
{
|
|
if (!cgvm)
|
|
return false;
|
|
|
|
ccs.time = time;
|
|
vmfuncs->Call(cgvm, CG_DRAW_ACTIVE_FRAME, (int)(ccs.time*1000), 0, false);
|
|
drawfuncs->Colour4f(1, 1, 1, 1);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void CG_Stop (void)
|
|
{
|
|
Q3_SetKeyCatcher(Q3_GetKeyCatcher()&~2);
|
|
if (cgvm)
|
|
{
|
|
vmfuncs->Call(cgvm, CG_SHUTDOWN);
|
|
vmfuncs->Destroy(cgvm);
|
|
VM_fcloseall(1);
|
|
cgvm = NULL;
|
|
|
|
}
|
|
}
|
|
|
|
qboolean CG_VideoRestarted(void)
|
|
{
|
|
if (cgvm)
|
|
{
|
|
vmfuncs->Call(cgvm, CG_INIT, ccs.serverMessageNum, ccs.lastServerCommandNum, ccs.playernum);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CG_Start (void)
|
|
{
|
|
CG_Stop();
|
|
|
|
box_model = worldfuncs->TempBoxModel(vec3_origin, vec3_origin); //just in case.
|
|
|
|
clientfuncs->SetLoadingState(true);
|
|
cgvm = vmfuncs->Create("cgame", cvarfuncs->GetFloat("com_gamedirnativecode")?CG_SystemCallsNative:NULL, "vm/cgame", CG_SystemCallsVM);
|
|
clientfuncs->SetLoadingState(false);
|
|
if (cgvm)
|
|
{ //hu... cgame doesn't appear to have a query version call!
|
|
vmfuncs->Call(cgvm, CG_INIT, ccs.serverMessageNum, ccs.lastServerCommandNum, ccs.playernum);
|
|
}
|
|
else
|
|
plugfuncs->EndGame("Failed to initialise cgame module\n");
|
|
}
|
|
|
|
qboolean CG_ConsoleCommand(void)
|
|
{
|
|
if (!cgvm)
|
|
return false;
|
|
// Con_DPrintf("CG_Command: %s %s\n", cmdfuncs->Argv(0), cmdfuncs->Args());
|
|
return vmfuncs->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_KeyPressed(int key, int unicode, int down)
|
|
{
|
|
|
|
if (!cgvm || !(Q3_GetKeyCatcher()&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 vmfuncs->Call(cgvm, CG_KEY_EVENT, key, down);
|
|
}
|
|
|
|
void CG_Restart(void)
|
|
{
|
|
CG_Stop();
|
|
CG_Start();
|
|
}
|
|
|
|
#endif
|