mirror of
https://github.com/DrBeef/JKXR.git
synced 2025-02-09 17:01:09 +00:00
464 lines
11 KiB
C
464 lines
11 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999 - 2005, Id Software, Inc.
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
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, see <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
/*
|
|
** cg_spawn.c
|
|
**
|
|
** Client-side functions for parsing entity data.
|
|
*/
|
|
|
|
#include "cg_local.h"
|
|
|
|
/*
|
|
=============
|
|
CG_NewString
|
|
|
|
Builds a copy of the string, translating \n to real linefeeds
|
|
so message texts can be multi-line
|
|
=============
|
|
*/
|
|
#if 0
|
|
// Enable if you put in cgame alloc and need it for some reason...
|
|
// (ie: if you need to re-implement cross game field_t)
|
|
char *CG_NewString( const char *string ) {
|
|
char *newb, *new_p;
|
|
int i, l;
|
|
|
|
l = strlen( string ) + 1;
|
|
|
|
newb = CG_Alloc( l );
|
|
|
|
new_p = newb;
|
|
|
|
// turn \n into a real linefeed
|
|
for( i = 0; i < l; i++ ) {
|
|
if( string[i] == '\\' && i < l - 1 ) {
|
|
if( string[i + 1] == 'n' ) {
|
|
*new_p++ = '\n';
|
|
i++;
|
|
}
|
|
else {
|
|
*new_p++ = '\\';
|
|
}
|
|
}
|
|
else {
|
|
*new_p++ = string[i];
|
|
}
|
|
|
|
/*old code
|
|
if (string[i] == '\\' && i < l-1) {
|
|
i++;
|
|
if (string[i] == 'n') {
|
|
*new_p++ = '\n';
|
|
} else {
|
|
*new_p++ = '\\';
|
|
}
|
|
} else {
|
|
*new_p++ = string[i];
|
|
}
|
|
*/
|
|
}
|
|
|
|
return newb;
|
|
}
|
|
#endif
|
|
|
|
qboolean CG_SpawnString( const char *key, const char *defaultString, char **out ) {
|
|
int i;
|
|
|
|
if( !cg.spawning ) {
|
|
*out = (char*)defaultString;
|
|
// trap->Error( ERR_DROP, "CG_SpawnString() called while not spawning" );
|
|
}
|
|
|
|
for( i = 0; i < cg.numSpawnVars; i++ ) {
|
|
if( !Q_stricmp( key, cg.spawnVars[i][0] ) ) {
|
|
*out = cg.spawnVars[i][1];
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
*out = (char*)defaultString;
|
|
return qfalse;
|
|
}
|
|
qboolean CG_SpawnFloat( const char *key, const char *defaultString, float *out ) {
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = CG_SpawnString( key, defaultString, &s );
|
|
*out = atof( s );
|
|
return present;
|
|
}
|
|
qboolean CG_SpawnInt( const char *key, const char *defaultString, int *out ) {
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = CG_SpawnString( key, defaultString, &s );
|
|
*out = atoi( s );
|
|
return present;
|
|
}
|
|
qboolean CG_SpawnBoolean( const char *key, const char *defaultString, qboolean *out ) {
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = CG_SpawnString( key, defaultString, &s );
|
|
if( !Q_stricmp( s, "qfalse" ) || !Q_stricmp( s, "false" ) || !Q_stricmp( s, "no" ) || !Q_stricmp( s, "0" ) ) {
|
|
*out = qfalse;
|
|
}
|
|
else if( !Q_stricmp( s, "qtrue" ) || !Q_stricmp( s, "true" ) || !Q_stricmp( s, "yes" ) || !Q_stricmp( s, "1" ) ) {
|
|
*out = qtrue;
|
|
}
|
|
else {
|
|
*out = qfalse;
|
|
}
|
|
|
|
return present;
|
|
}
|
|
qboolean CG_SpawnVector( const char *key, const char *defaultString, float *out ) {
|
|
char *s;
|
|
qboolean present;
|
|
|
|
present = CG_SpawnString( key, defaultString, &s );
|
|
if ( sscanf( s, "%f %f %f", &out[0], &out[1], &out[2] ) != 3 ) {
|
|
trap->Print( "CG_SpawnVector: Failed sscanf on %s (default: %s)\n", key, defaultString );
|
|
VectorClear( out );
|
|
return qfalse;
|
|
}
|
|
return present;
|
|
}
|
|
/*
|
|
=============
|
|
VectorToString
|
|
|
|
This is just a convenience function
|
|
for printing vectors
|
|
=============
|
|
*/
|
|
char *vtos( const vec3_t v ) {
|
|
static int index;
|
|
static char str[8][32];
|
|
char *s;
|
|
|
|
// use an array so that multiple vtos won't collide
|
|
s = str[index];
|
|
index = ( index + 1 ) & 7;
|
|
|
|
Com_sprintf( s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2] );
|
|
|
|
return s;
|
|
}
|
|
void SP_misc_model_static( void ) {
|
|
char* model;
|
|
float angle;
|
|
vec3_t angles;
|
|
float scale;
|
|
vec3_t vScale;
|
|
vec3_t org;
|
|
float zoffset;
|
|
int i;
|
|
int modelIndex;
|
|
cg_staticmodel_t *staticmodel;
|
|
|
|
if( cgs.numMiscStaticModels >= MAX_STATIC_MODELS ) {
|
|
trap->Error( ERR_DROP, "MAX_STATIC_MODELS(%i) hit", MAX_STATIC_MODELS );
|
|
}
|
|
|
|
CG_SpawnString( "model", "", &model );
|
|
|
|
if( !model || !model[0] ) {
|
|
trap->Error( ERR_DROP, "misc_model_static with no model." );
|
|
}
|
|
|
|
CG_SpawnVector( "origin", "0 0 0", org );
|
|
CG_SpawnFloat( "zoffset", "0", &zoffset );
|
|
|
|
if( !CG_SpawnVector( "angles", "0 0 0", angles ) ) {
|
|
if( CG_SpawnFloat( "angle", "0", &angle ) ) {
|
|
angles[YAW] = angle;
|
|
}
|
|
}
|
|
|
|
if( !CG_SpawnVector( "modelscale_vec", "1 1 1", vScale ) ) {
|
|
if( CG_SpawnFloat( "modelscale", "1", &scale ) ) {
|
|
VectorSet( vScale, scale, scale, scale );
|
|
}
|
|
}
|
|
|
|
modelIndex = trap->R_RegisterModel( model );
|
|
if( modelIndex == 0 ) {
|
|
trap->Error( ERR_DROP, "misc_model_static failed to load model '%s'", model );
|
|
return;
|
|
}
|
|
|
|
staticmodel = &cgs.miscStaticModels[cgs.numMiscStaticModels++];
|
|
staticmodel->model = modelIndex;
|
|
AnglesToAxis( angles, staticmodel->axes );
|
|
for( i = 0; i < 3; i++ ) {
|
|
VectorScale( staticmodel->axes[i], vScale[i], staticmodel->axes[i] );
|
|
}
|
|
|
|
VectorCopy( org, staticmodel->org );
|
|
staticmodel->zoffset = zoffset;
|
|
|
|
if( staticmodel->model ) {
|
|
vec3_t mins, maxs;
|
|
|
|
trap->R_ModelBounds( staticmodel->model, mins, maxs );
|
|
|
|
VectorScaleVector( mins, vScale, mins );
|
|
VectorScaleVector( maxs, vScale, maxs );
|
|
|
|
staticmodel->radius = RadiusFromBounds( mins, maxs );
|
|
}
|
|
else {
|
|
staticmodel->radius = 0;
|
|
}
|
|
}
|
|
qboolean cg_noFogOutsidePortal = qfalse;
|
|
void SP_misc_skyportal( void ) {
|
|
qboolean onlyfoghere;
|
|
|
|
CG_SpawnBoolean( "onlyfoghere", "0", &onlyfoghere );
|
|
|
|
if( onlyfoghere )
|
|
cg_noFogOutsidePortal = qtrue;
|
|
}
|
|
qboolean cg_skyOri = qfalse;
|
|
vec3_t cg_skyOriPos;
|
|
float cg_skyOriScale = 0.0f;
|
|
void SP_misc_skyportal_orient( void ) {
|
|
if( cg_skyOri )
|
|
trap->Print( S_COLOR_YELLOW "WARNING: multiple misc_skyportal_orients found.\n" );
|
|
|
|
cg_skyOri = qtrue;
|
|
CG_SpawnVector( "origin", "0 0 0", cg_skyOriPos );
|
|
CG_SpawnFloat( "modelscale", "0", &cg_skyOriScale );
|
|
}
|
|
void SP_misc_weather_zone( void ) {
|
|
char *model;
|
|
vec3_t mins, maxs;
|
|
|
|
CG_SpawnString( "model", "", &model );
|
|
|
|
if( !model || !model[0] ) {
|
|
trap->Error( ERR_DROP, "misc_weather_zone with invalid brush model data." );
|
|
return;
|
|
}
|
|
|
|
trap->R_ModelBounds( trap->R_RegisterModel( model ), mins, maxs );
|
|
|
|
trap->WE_AddWeatherZone( mins, maxs );
|
|
}
|
|
typedef struct spawn_s {
|
|
const char *name;
|
|
void (*spawn)( void );
|
|
} spawn_t;
|
|
|
|
spawn_t spawns [] = {
|
|
{ "misc_model_static", SP_misc_model_static },
|
|
{ "misc_skyportal", SP_misc_skyportal },
|
|
{ "misc_skyportal_orient", SP_misc_skyportal_orient },
|
|
{ "misc_weather_zone", SP_misc_weather_zone },
|
|
};
|
|
|
|
/*
|
|
===================
|
|
CG_ParseEntityFromSpawnVars
|
|
|
|
Spawn an entity and fill in all of the level fields from
|
|
cg.spawnVars[], then call the class specfic spawn function
|
|
===================
|
|
*/
|
|
static int spawncmp( const void *a, const void *b ) {
|
|
return Q_stricmp( (const char *)a, ((spawn_t*)b)->name );
|
|
}
|
|
|
|
void CG_ParseEntityFromSpawnVars( void ) {
|
|
spawn_t *s;
|
|
int i;
|
|
char *classname;
|
|
char *p, *value, *gametypeName;
|
|
static char *gametypeNames[GT_MAX_GAME_TYPE] = { "ffa", "holocron", "jedimaster", "duel", "powerduel", "single", "team", "siege", "ctf", "cty" };
|
|
|
|
// check for "notsingle" flag
|
|
if( cgs.gametype == GT_SINGLE_PLAYER ) {
|
|
CG_SpawnInt( "notsingle", "0", &i );
|
|
if( i ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check for "notteam" flag (GT_FFA, GT_DUEL, GT_SINGLE_PLAYER)
|
|
if( cgs.gametype >= GT_TEAM ) {
|
|
CG_SpawnInt( "notteam", "0", &i );
|
|
if( i ) {
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
CG_SpawnInt( "notfree", "0", &i );
|
|
if( i ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if( CG_SpawnString( "gametype", NULL, &value ) ) {
|
|
if( cgs.gametype >= GT_FFA && cgs.gametype < GT_MAX_GAME_TYPE ) {
|
|
gametypeName = gametypeNames[cgs.gametype];
|
|
|
|
p = strstr( value, gametypeName );
|
|
if( !p ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( CG_SpawnString( "classname", "", &classname ) ) {
|
|
s = (spawn_t *)Q_LinearSearch( classname, spawns, ARRAY_LEN( spawns ), sizeof( spawn_t ), spawncmp );
|
|
if ( s )
|
|
s->spawn();
|
|
}
|
|
}
|
|
/*
|
|
====================
|
|
CG_AddSpawnVarToken
|
|
====================
|
|
*/
|
|
char *CG_AddSpawnVarToken( const char *string ) {
|
|
int l;
|
|
char *dest;
|
|
|
|
l = strlen( string );
|
|
if( cg.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) {
|
|
trap->Error( ERR_DROP, "CG_AddSpawnVarToken: MAX_SPAWN_VARS_CHARS" );
|
|
}
|
|
|
|
dest = cg.spawnVarChars + cg.numSpawnVarChars;
|
|
memcpy( dest, string, l + 1 );
|
|
|
|
cg.numSpawnVarChars += l + 1;
|
|
|
|
return dest;
|
|
}
|
|
/*
|
|
====================
|
|
CG_ParseSpawnVars
|
|
|
|
Parses a brace bounded set of key / value pairs out of the
|
|
level's entity strings into cg.spawnVars[]
|
|
|
|
This does not actually spawn an entity.
|
|
====================
|
|
*/
|
|
qboolean CG_ParseSpawnVars( void ) {
|
|
char keyname[MAX_TOKEN_CHARS];
|
|
char com_token[MAX_TOKEN_CHARS];
|
|
|
|
cg.numSpawnVars = 0;
|
|
cg.numSpawnVarChars = 0;
|
|
|
|
// parse the opening brace
|
|
if( !trap->R_GetEntityToken( com_token, sizeof( com_token ) ) ) {
|
|
// end of spawn string
|
|
return qfalse;
|
|
}
|
|
|
|
if( com_token[0] != '{' ) {
|
|
trap->Error( ERR_DROP, "CG_ParseSpawnVars: found %s when expecting {", com_token );
|
|
}
|
|
|
|
// go through all the key / value pairs
|
|
while( 1 ) {
|
|
// parse key
|
|
if( !trap->R_GetEntityToken( keyname, sizeof( keyname ) ) ) {
|
|
trap->Error( ERR_DROP, "CG_ParseSpawnVars: EOF without closing brace" );
|
|
}
|
|
|
|
if( keyname[0] == '}' ) {
|
|
break;
|
|
}
|
|
|
|
// parse value
|
|
if( !trap->R_GetEntityToken( com_token, sizeof( com_token ) ) ) {
|
|
trap->Error( ERR_DROP, "CG_ParseSpawnVars: EOF without closing brace" );
|
|
}
|
|
|
|
if( com_token[0] == '}' ) {
|
|
trap->Error( ERR_DROP, "CG_ParseSpawnVars: closing brace without data" );
|
|
}
|
|
|
|
if( cg.numSpawnVars == MAX_SPAWN_VARS ) {
|
|
trap->Error( ERR_DROP, "CG_ParseSpawnVars: MAX_SPAWN_VARS" );
|
|
}
|
|
|
|
cg.spawnVars[cg.numSpawnVars][0] = CG_AddSpawnVarToken( keyname );
|
|
cg.spawnVars[cg.numSpawnVars][1] = CG_AddSpawnVarToken( com_token );
|
|
cg.numSpawnVars++;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
extern float cg_linearFogOverride; // cg_view.c
|
|
extern float cg_radarRange; // cg_draw.c
|
|
void SP_worldspawn( void ) {
|
|
char *s;
|
|
|
|
CG_SpawnString( "classname", "", &s );
|
|
if( Q_stricmp( s, "worldspawn" ) ) {
|
|
trap->Error( ERR_DROP, "SP_worldspawn: The first entity isn't 'worldspawn'" );
|
|
}
|
|
|
|
CG_SpawnFloat( "fogstart", "0", &cg_linearFogOverride );
|
|
CG_SpawnFloat( "radarrange", "2500", &cg_radarRange );
|
|
}
|
|
/*
|
|
==============
|
|
CG_ParseEntitiesFromString
|
|
|
|
Parses textual entity definitions out of an entstring
|
|
==============
|
|
*/
|
|
void CG_ParseEntitiesFromString( void ) {
|
|
// make sure it is reset
|
|
trap->R_GetEntityToken( NULL, -1 );
|
|
|
|
// allow calls to CG_Spawn*()
|
|
cg.spawning = qtrue;
|
|
cg.numSpawnVars = 0;
|
|
|
|
// the worldspawn is not an actual entity, but it still
|
|
// has a "spawn" function to perform any global setup
|
|
// needed by a level (setting configstrings or cvars, etc)
|
|
if( !CG_ParseSpawnVars() ) {
|
|
trap->Error( ERR_DROP, "ParseEntities: no entities" );
|
|
}
|
|
|
|
SP_worldspawn();
|
|
|
|
// parse ents
|
|
while( CG_ParseSpawnVars() ) {
|
|
CG_ParseEntityFromSpawnVars();
|
|
}
|
|
|
|
cg.spawning = qfalse; // any future calls to CG_Spawn*() will be errors
|
|
}
|