/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Wolf ET Source Code 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /* * name: cg_spawn.c * * desc: Client sided only map entities */ #include "cg_local.h" qboolean CG_SpawnString( const char *key, const char *defaultString, char **out ) { int i; if ( !cg.spawning ) { *out = (char *)defaultString; CG_Error( "CG_SpawnString() called while not spawning" ); } for ( i = 0 ; i < cg.numSpawnVars ; i++ ) { if ( !strcmp( 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_SpawnVector( const char *key, const char *defaultString, float *out ) { char *s; qboolean present; present = CG_SpawnString( key, defaultString, &s ); sscanf( s, "%f %f %f", &out[0], &out[1], &out[2] ); return present; } qboolean CG_SpawnVector2D( const char *key, const char *defaultString, float *out ) { char *s; qboolean present; present = CG_SpawnString( key, defaultString, &s ); sscanf( s, "%f %f", &out[0], &out[1] ); 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_path_corner_2( void ) { char* targetname; vec3_t origin; CG_SpawnString( "targetname", "", &targetname ); CG_SpawnVector( "origin", "0 0 0", origin ); if ( !*targetname ) { CG_Error( "path_corner_2 with no targetname at %s\n", vtos( origin ) ); return; } if ( numPathCorners >= MAX_PATH_CORNERS ) { CG_Error( "Maximum path_corners hit\n" ); return; } BG_AddPathCorner( targetname, origin ); } void SP_info_train_spline_main( void ) { char* targetname; char* target; char* control; vec3_t origin; int i; char* end; splinePath_t* spline; if ( !CG_SpawnVector( "origin", "0 0 0", origin ) ) { CG_Error( "info_train_spline_main with no origin\n" ); } if ( !CG_SpawnString( "targetname", "", &targetname ) ) { CG_Error( "info_train_spline_main with no targetname at %s\n", vtos( origin ) ); } CG_SpawnString( "target", "", &target ); spline = BG_AddSplinePath( targetname, target, origin ); if ( CG_SpawnString( "end", "", &end ) ) { spline->isEnd = qtrue; } else if ( CG_SpawnString( "start", "", &end ) ) { spline->isStart = qtrue; } for ( i = 1;; i++ ) { if ( !CG_SpawnString( i == 1 ? va( "control" ) : va( "control%i", i ), "", &control ) ) { break; } BG_AddSplineControl( spline, control ); } } void SP_misc_gamemodel( void ) { char* model; vec_t angle; vec3_t angles; vec_t scale; vec3_t vScale; vec3_t org; cg_gamemodel_t* gamemodel; int i; if ( CG_SpawnString( "targetname", "", &model ) || CG_SpawnString( "scriptname", "", &model ) || CG_SpawnString( "spawnflags", "", &model ) ) { // Gordon: this model may not be static, so let the server handle it return; } if ( cg.numMiscGameModels >= MAX_STATIC_GAMEMODELS ) { CG_Error( "^1MAX_STATIC_GAMEMODELS(%i) hit", MAX_STATIC_GAMEMODELS ); } CG_SpawnString( "model", "", &model ); CG_SpawnVector( "origin", "0 0 0", org ); 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 ); } } gamemodel = &cgs.miscGameModels[cg.numMiscGameModels++]; gamemodel->model = trap_R_RegisterModel( model ); AnglesToAxis( angles, gamemodel->axes ); for ( i = 0; i < 3; i++ ) { VectorScale( gamemodel->axes[i], vScale[i], gamemodel->axes[i] ); } VectorCopy( org, gamemodel->org ); if ( gamemodel->model ) { vec3_t mins, maxs; trap_R_ModelBounds( gamemodel->model, mins, maxs ); for ( i = 0; i < 3; i++ ) { mins[i] *= vScale[i]; maxs[i] *= vScale[i]; } gamemodel->radius = RadiusFromBounds( mins, maxs ); } else { gamemodel->radius = 0; } } void SP_trigger_objective_info( void ) { char* temp; CG_SpawnString( "infoAllied", "^1No Text Supplied", &temp ); Q_strncpyz( cg.oidTriggerInfoAllies[cg.numOIDtriggers2], temp, 256 ); CG_SpawnString( "infoAxis", "^1No Text Supplied", &temp ); Q_strncpyz( cg.oidTriggerInfoAxis[cg.numOIDtriggers2], temp, 256 ); cg.numOIDtriggers2++; } typedef struct { char *name; void ( *spawn )( void ); } spawn_t; spawn_t spawns[] = { {0, 0}, {"path_corner_2", SP_path_corner_2}, {"info_train_spline_main", SP_info_train_spline_main}, {"info_train_spline_control", SP_path_corner_2}, {"trigger_objective_info", SP_trigger_objective_info}, {"misc_gamemodel", SP_misc_gamemodel}, }; #define NUMSPAWNS ( sizeof( spawns ) / sizeof( spawn_t ) ) /* =================== CG_ParseEntityFromSpawnVars Spawn an entity and fill in all of the level fields from cg.spawnVars[], then call the class specfic spawn function =================== */ void CG_ParseEntityFromSpawnVars( void ) { int i; char *classname; // check for "notteam" / "notfree" flags CG_SpawnInt( "notteam", "0", &i ); if ( i ) { return; } if ( CG_SpawnString( "classname", "", &classname ) ) { for ( i = 0; i < NUMSPAWNS; i++ ) { if ( !Q_stricmp( spawns[i].name, classname ) ) { spawns[i].spawn(); break; } } } } /* ==================== CG_AddSpawnVarToken ==================== */ char *CG_AddSpawnVarToken( const char *string ) { int l; char *dest; l = strlen( string ); if ( cg.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) { CG_Error( "CG_AddSpawnVarToken: MAX_SPAWN_VARS" ); } 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_GetEntityToken( com_token, sizeof( com_token ) ) ) { // end of spawn string return qfalse; } if ( com_token[0] != '{' ) { CG_Error( "CG_ParseSpawnVars: found %s when expecting {",com_token ); } // go through all the key / value pairs while ( 1 ) { // parse key if ( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) { CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); } if ( keyname[0] == '}' ) { break; } // parse value if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); } if ( com_token[0] == '}' ) { CG_Error( "CG_ParseSpawnVars: closing brace without data" ); } if ( cg.numSpawnVars == MAX_SPAWN_VARS ) { CG_Error( "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; } void SP_worldspawn( void ) { char *s; int i; CG_SpawnString( "classname", "", &s ); if ( Q_stricmp( s, "worldspawn" ) ) { CG_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" ); } cgs.ccLayers = 0; if ( CG_SpawnVector2D( "mapcoordsmins", "-128 128", cg.mapcoordsMins ) && // top left CG_SpawnVector2D( "mapcoordsmaxs", "128 -128", cg.mapcoordsMaxs ) ) { // bottom right cg.mapcoordsValid = qtrue; } else { cg.mapcoordsValid = qfalse; } CG_ParseSpawns(); CG_SpawnString( "cclayers", "0", &s ); cgs.ccLayers = atoi( s ); for ( i = 0; i < cgs.ccLayers; i++ ) { CG_SpawnString( va( "cclayerceil%i",i ), "0", &s ); cgs.ccLayerCeils[i] = atoi( s ); } cg.mapcoordsScale[0] = 1 / ( cg.mapcoordsMaxs[0] - cg.mapcoordsMins[0] ); cg.mapcoordsScale[1] = 1 / ( cg.mapcoordsMaxs[1] - cg.mapcoordsMins[1] ); BG_InitLocations( cg.mapcoordsMins, cg.mapcoordsMaxs ); CG_SpawnString( "atmosphere", "", &s ); CG_EffectParse( s ); cg.fiveMinuteSound_g[0] = \ cg.fiveMinuteSound_a[0] = \ cg.twoMinuteSound_g[0] = \ cg.twoMinuteSound_a[0] = \ cg.thirtySecondSound_g[0] = \ cg.thirtySecondSound_a[0] = '\0'; CG_SpawnString( "twoMinuteSound_axis", "axis_hq_5minutes", &s ); Q_strncpyz( cg.fiveMinuteSound_g, s, sizeof( cg.fiveMinuteSound_g ) ); CG_SpawnString( "twoMinuteSound_allied", "allies_hq_5minutes", &s ); Q_strncpyz( cg.fiveMinuteSound_a, s, sizeof( cg.fiveMinuteSound_a ) ); CG_SpawnString( "twoMinuteSound_axis", "axis_hq_2minutes", &s ); Q_strncpyz( cg.twoMinuteSound_g, s, sizeof( cg.twoMinuteSound_g ) ); CG_SpawnString( "twoMinuteSound_allied", "allies_hq_2minutes", &s ); Q_strncpyz( cg.twoMinuteSound_a, s, sizeof( cg.twoMinuteSound_a ) ); CG_SpawnString( "thirtySecondSound_axis", "axis_hq_30seconds", &s ); Q_strncpyz( cg.thirtySecondSound_g, s, sizeof( cg.thirtySecondSound_g ) ); CG_SpawnString( "thirtySecondSound_allied", "allies_hq_30seconds", &s ); Q_strncpyz( cg.thirtySecondSound_a, s, sizeof( cg.thirtySecondSound_a ) ); // 5 minute axis if ( !*cg.fiveMinuteSound_g ) { cgs.media.fiveMinuteSound_g = 0; } else if ( strstr( cg.fiveMinuteSound_g, ".wav" ) ) { cgs.media.fiveMinuteSound_g = trap_S_RegisterSound( cg.fiveMinuteSound_g, qtrue ); } else { cgs.media.fiveMinuteSound_g = -1; } // 5 minute allied if ( !*cg.fiveMinuteSound_a ) { cgs.media.fiveMinuteSound_a = 0; } else if ( strstr( cg.fiveMinuteSound_a, ".wav" ) ) { cgs.media.fiveMinuteSound_a = trap_S_RegisterSound( cg.fiveMinuteSound_a, qtrue ); } else { cgs.media.fiveMinuteSound_a = -1; } // 2 minute axis if ( !*cg.twoMinuteSound_g ) { cgs.media.twoMinuteSound_g = 0; } else if ( strstr( cg.twoMinuteSound_g, ".wav" ) ) { cgs.media.twoMinuteSound_g = trap_S_RegisterSound( cg.twoMinuteSound_g, qtrue ); } else { cgs.media.twoMinuteSound_g = -1; } // 2 minute allied if ( !*cg.twoMinuteSound_a ) { cgs.media.twoMinuteSound_a = 0; } else if ( strstr( cg.twoMinuteSound_a, ".wav" ) ) { cgs.media.twoMinuteSound_a = trap_S_RegisterSound( cg.twoMinuteSound_a, qtrue ); } else { cgs.media.twoMinuteSound_a = -1; } // 30 seconds axis if ( !*cg.thirtySecondSound_g ) { cgs.media.thirtySecondSound_g = 0; } else if ( strstr( cg.thirtySecondSound_g, ".wav" ) ) { cgs.media.thirtySecondSound_g = trap_S_RegisterSound( cg.thirtySecondSound_g, qtrue ); } else { cgs.media.thirtySecondSound_g = -1; } // 30 seconds allied if ( !*cg.thirtySecondSound_a ) { cgs.media.thirtySecondSound_a = 0; } else if ( strstr( cg.thirtySecondSound_a, ".wav" ) ) { cgs.media.thirtySecondSound_a = trap_S_RegisterSound( cg.thirtySecondSound_a, qtrue ); } else { cgs.media.thirtySecondSound_a = -1; } } /* ============== CG_ParseEntitiesFromString Parses textual entity definitions out of an entstring and spawns gentities. ============== */ void CG_ParseEntitiesFromString( void ) { // allow calls to CG_Spawn*() cg.spawning = qtrue; cg.numSpawnVars = 0; cg.numMiscGameModels = 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() ) { CG_Error( "ParseEntities: no entities" ); } SP_worldspawn(); // parse ents while ( CG_ParseSpawnVars() ) { CG_ParseEntityFromSpawnVars(); } cg.spawning = qfalse; // any future calls to CG_Spawn*() will be errors }