/* =========================================================================== 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 . =========================================================================== */ /* ** 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 }