// Copyright (C) 1999-2000 Id Software, Inc. // /* * Portions Copyright (C) 2000-2001 Tim Angus * * This program is free software; you can redistribute it and/or modify it * under the terms of the OSML - Open Source Modification License v1.0 as * described in the file COPYING which is distributed with this source * code. * * 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. */ #include "g_local.h" qboolean G_SpawnString( const char *key, const char *defaultString, char **out ) { int i; if( !level.spawning ) { *out = (char *)defaultString; // G_Error( "G_SpawnString() called while not spawning" ); } for( i = 0; i < level.numSpawnVars; i++ ) { if( !Q_stricmp( key, level.spawnVars[ i ][ 0 ] ) ) { *out = level.spawnVars[ i ][ 1 ]; return qtrue; } } *out = (char *)defaultString; return qfalse; } qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ) { char *s; qboolean present; present = G_SpawnString( key, defaultString, &s ); *out = atof( s ); return present; } qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ) { char *s; qboolean present; present = G_SpawnString( key, defaultString, &s ); *out = atoi( s ); return present; } qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ) { char *s; qboolean present; present = G_SpawnString( key, defaultString, &s ); sscanf( s, "%f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ] ); return present; } qboolean G_SpawnVector4( const char *key, const char *defaultString, float *out ) { char *s; qboolean present; present = G_SpawnString( key, defaultString, &s ); sscanf( s, "%f %f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ], &out[ 3 ] ); return present; } // // fields are needed for spawning from the entity string // typedef enum { F_INT, F_FLOAT, F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL F_GSTRING, // string on disk, pointer in memory, TAG_GAME F_VECTOR, F_VECTOR4, //TA F_ANGLEHACK, F_ENTITY, // index on disk, pointer in memory F_ITEM, // index on disk, pointer in memory F_CLIENT, // index on disk, pointer in memory F_IGNORE } fieldtype_t; typedef struct { char *name; int ofs; fieldtype_t type; int flags; } field_t; field_t fields[ ] = { {"classname", FOFS(classname), F_LSTRING}, {"origin", FOFS(s.origin), F_VECTOR}, {"model", FOFS(model), F_LSTRING}, {"model2", FOFS(model2), F_LSTRING}, {"spawnflags", FOFS(spawnflags), F_INT}, {"speed", FOFS(speed), F_FLOAT}, {"target", FOFS(target), F_LSTRING}, {"targetname", FOFS(targetname), F_LSTRING}, {"message", FOFS(message), F_LSTRING}, {"team", FOFS(team), F_LSTRING}, {"wait", FOFS(wait), F_FLOAT}, {"random", FOFS(random), F_FLOAT}, {"count", FOFS(count), F_INT}, {"health", FOFS(health), F_INT}, {"light", 0, F_IGNORE}, {"dmg", FOFS(damage), F_INT}, {"angles", FOFS(s.angles), F_VECTOR}, {"angle", FOFS(s.angles), F_ANGLEHACK}, //TA {"bounce", FOFS(physicsBounce), F_FLOAT}, {"alpha", FOFS(pos1), F_VECTOR}, {"radius", FOFS(pos2), F_VECTOR}, {"acceleration", FOFS(acceleration), F_VECTOR}, {"animation", FOFS(animation), F_VECTOR4}, {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT}, //TA {"targetShaderName", FOFS(targetShaderName), F_LSTRING}, {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING}, {NULL} }; typedef struct { char *name; void (*spawn)(gentity_t *ent); } spawn_t; void SP_info_player_start( gentity_t *ent ); void SP_info_player_deathmatch( gentity_t *ent ); void SP_info_player_intermission( gentity_t *ent ); //TA: extra bits void SP_info_alien_intermission( gentity_t *ent ); void SP_info_human_intermission( gentity_t *ent ); void SP_info_firstplace( gentity_t *ent ); void SP_info_secondplace( gentity_t *ent ); void SP_info_thirdplace( gentity_t *ent ); void SP_info_podium( gentity_t *ent ); void SP_func_plat( gentity_t *ent ); void SP_func_static( gentity_t *ent ); void SP_func_rotating( gentity_t *ent ); void SP_func_bobbing( gentity_t *ent ); void SP_func_pendulum( gentity_t *ent ); void SP_func_button( gentity_t *ent ); void SP_func_door( gentity_t *ent ); void SP_func_door_rotating( gentity_t *ent ); //TA void SP_func_door_model( gentity_t *ent ); //TA void SP_func_train( gentity_t *ent ); void SP_func_timer( gentity_t *self); void SP_trigger_always( gentity_t *ent ); void SP_trigger_multiple( gentity_t *ent ); void SP_trigger_push( gentity_t *ent ); void SP_trigger_teleport( gentity_t *ent ); void SP_trigger_hurt( gentity_t *ent ); void SP_target_delay( gentity_t *ent ); void SP_target_speaker( gentity_t *ent ); void SP_target_print( gentity_t *ent ); void SP_target_character( gentity_t *ent ); void SP_target_score( gentity_t *ent ); void SP_target_teleporter( gentity_t *ent ); void SP_target_relay( gentity_t *ent ); void SP_target_kill( gentity_t *ent ); void SP_target_position( gentity_t *ent ); void SP_target_location( gentity_t *ent ); void SP_target_push( gentity_t *ent ); void SP_light( gentity_t *self ); void SP_info_null( gentity_t *self ); void SP_info_notnull( gentity_t *self ); void SP_info_camp( gentity_t *self ); void SP_path_corner( gentity_t *self ); void SP_misc_teleporter_dest( gentity_t *self ); void SP_misc_model( gentity_t *ent ); void SP_misc_portal_camera( gentity_t *ent ); void SP_misc_portal_surface( gentity_t *ent ); void SP_shooter_rocket( gentity_t *ent ); void SP_shooter_plasma( gentity_t *ent ); void SP_shooter_grenade( gentity_t *ent ); //TA: void SP_misc_spriter( gentity_t *ent ); void SP_misc_anim_model( gentity_t *ent ); void SP_misc_light_flare( gentity_t *ent ); spawn_t spawns[ ] = { // info entities don't do anything at all, but provide positional // information for things controlled by other processes {"info_player_start", SP_info_player_start}, {"info_player_deathmatch", SP_info_player_deathmatch}, {"info_player_intermission", SP_info_player_intermission}, //TA: extra bits {"info_alien_intermission", SP_info_alien_intermission}, {"info_human_intermission", SP_info_human_intermission}, {"info_null", SP_info_null}, {"info_notnull", SP_info_notnull}, // use target_position instead {"func_plat", SP_func_plat}, {"func_button", SP_func_button}, {"func_door", SP_func_door}, {"func_door_rotating", SP_func_door_rotating}, //TA {"func_door_model", SP_func_door_model}, //TA {"func_static", SP_func_static}, {"func_rotating", SP_func_rotating}, {"func_bobbing", SP_func_bobbing}, {"func_pendulum", SP_func_pendulum}, {"func_train", SP_func_train}, {"func_group", SP_info_null}, {"func_timer", SP_func_timer}, // rename trigger_timer? // Triggers are brush objects that cause an effect when contacted // by a living player, usually involving firing targets. // While almost everything could be done with // a single trigger class and different targets, triggered effects // could not be client side predicted (push and teleport). {"trigger_always", SP_trigger_always}, {"trigger_multiple", SP_trigger_multiple}, {"trigger_push", SP_trigger_push}, {"trigger_teleport", SP_trigger_teleport}, {"trigger_hurt", SP_trigger_hurt}, // targets perform no action by themselves, but must be triggered // by another entity {"target_delay", SP_target_delay}, {"target_speaker", SP_target_speaker}, {"target_print", SP_target_print}, {"target_score", SP_target_score}, {"target_teleporter", SP_target_teleporter}, {"target_relay", SP_target_relay}, {"target_kill", SP_target_kill}, {"target_position", SP_target_position}, {"target_location", SP_target_location}, {"target_push", SP_target_push}, {"light", SP_light}, {"path_corner", SP_path_corner}, {"misc_teleporter_dest", SP_misc_teleporter_dest}, {"misc_model", SP_misc_model}, {"misc_portal_surface", SP_misc_portal_surface}, {"misc_portal_camera", SP_misc_portal_camera}, {"misc_spriter", SP_misc_spriter}, {"misc_anim_model", SP_misc_anim_model}, {"misc_light_flare", SP_misc_light_flare}, {0, 0} }; /* =============== G_CallSpawn Finds the spawn function for the entity and calls it, returning qfalse if not found =============== */ qboolean G_CallSpawn( gentity_t *ent ) { spawn_t *s; buildable_t buildable; if( !ent->classname ) { G_Printf( "G_CallSpawn: NULL classname\n" ); return qfalse; } //check buildable spawn functions if( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE ) { /*if( BG_FindStagesForBuildable( buildable, 1 ) )*/ if( qtrue ) { G_SpawnBuildable( ent, buildable ); return qtrue; } else { G_Printf( S_COLOR_YELLOW "WARNING: %s not allowed in stage 1\n", BG_FindHumanNameForBuildable( buildable ) ); return qfalse; } } // check normal spawn functions for( s = spawns; s->name; s++ ) { if( !strcmp( s->name, ent->classname ) ) { // found it s->spawn( ent ); return qtrue; } } G_Printf( "%s doesn't have a spawn function\n", ent->classname ); return qfalse; } /* ============= G_NewString Builds a copy of the string, translating \n to real linefeeds so message texts can be multi-line ============= */ char *G_NewString( const char *string ) { char *newb, *new_p; int i,l; l = strlen( string ) + 1; newb = G_Alloc( l ); new_p = newb; // turn \n into a real linefeed for( i = 0 ; i < l ; i++ ) { if( string[ i ] == '\\' && i < l - 1 ) { i++; if( string[ i ] == 'n' ) *new_p++ = '\n'; else *new_p++ = '\\'; } else *new_p++ = string[ i ]; } return newb; } /* =============== G_ParseField Takes a key/value pair and sets the binary values in a gentity =============== */ void G_ParseField( const char *key, const char *value, gentity_t *ent ) { field_t *f; byte *b; float v; vec3_t vec; vec4_t vec4; for( f = fields; f->name; f++ ) { if( !Q_stricmp( f->name, key ) ) { // found it b = (byte *)ent; switch( f->type ) { case F_LSTRING: *(char **)( b + f->ofs ) = G_NewString( value ); break; case F_VECTOR: sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] ); ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ]; ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ]; ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ]; break; case F_VECTOR4: sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] ); ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ]; ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ]; ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ]; ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ]; break; case F_INT: *(int *)( b + f->ofs ) = atoi( value ); break; case F_FLOAT: *(float *)( b + f->ofs ) = atof( value ); break; case F_ANGLEHACK: v = atof( value ); ( (float *)( b + f->ofs ) )[ 0 ] = 0; ( (float *)( b + f->ofs ) )[ 1 ] = v; ( (float *)( b + f->ofs ) )[ 2 ] = 0; break; default: case F_IGNORE: break; } return; } } } /* =================== G_SpawnGEntityFromSpawnVars Spawn an entity and fill in all of the level fields from level.spawnVars[], then call the class specfic spawn function =================== */ void G_SpawnGEntityFromSpawnVars( void ) { int i; gentity_t *ent; char *s, *value, *gametypeName; static char *gametypeNames[ ] = { "ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester", "teamtournament" }; // get the next free entity ent = G_Spawn( ); for( i = 0 ; i < level.numSpawnVars ; i++ ) G_ParseField( level.spawnVars[ i ][ 0 ], level.spawnVars[ i ][ 1 ], ent ); G_SpawnInt( "notq3a", "0", &i ); if( i ) { G_FreeEntity( ent ); return; } // move editor origin to pos VectorCopy( ent->s.origin, ent->s.pos.trBase ); VectorCopy( ent->s.origin, ent->r.currentOrigin ); // if we didn't get a classname, don't bother spawning anything if( !G_CallSpawn( ent ) ) G_FreeEntity( ent ); } /* ==================== G_AddSpawnVarToken ==================== */ char *G_AddSpawnVarToken( const char *string ) { int l; char *dest; l = strlen( string ); if( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) G_Error( "G_AddSpawnVarToken: MAX_SPAWN_CHARS" ); dest = level.spawnVarChars + level.numSpawnVarChars; memcpy( dest, string, l + 1 ); level.numSpawnVarChars += l + 1; return dest; } /* ==================== G_ParseSpawnVars Parses a brace bounded set of key / value pairs out of the level's entity strings into level.spawnVars[] This does not actually spawn an entity. ==================== */ qboolean G_ParseSpawnVars( void ) { char keyname[ MAX_TOKEN_CHARS ]; char com_token[ MAX_TOKEN_CHARS ]; level.numSpawnVars = 0; level.numSpawnVarChars = 0; // parse the opening brace if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { // end of spawn string return qfalse; } if( com_token[ 0 ] != '{' ) G_Error( "G_ParseSpawnVars: found %s when expecting {", com_token ); // go through all the key / value pairs while( 1 ) { // parse key if( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) G_Error( "G_ParseSpawnVars: EOF without closing brace" ); if( keyname[0] == '}' ) break; // parse value if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) G_Error( "G_ParseSpawnVars: EOF without closing brace" ); if( com_token[0] == '}' ) G_Error( "G_ParseSpawnVars: closing brace without data" ); if( level.numSpawnVars == MAX_SPAWN_VARS ) G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" ); level.spawnVars[ level.numSpawnVars ][ 0 ] = G_AddSpawnVarToken( keyname ); level.spawnVars[ level.numSpawnVars ][ 1 ] = G_AddSpawnVarToken( com_token ); level.numSpawnVars++; } return qtrue; } /*QUAKED worldspawn (0 0 0) ? Every map should have exactly one worldspawn. "music" music wav file "gravity" 800 is default gravity "message" Text to print during connection process */ void SP_worldspawn( void ) { char *s; G_SpawnString( "classname", "", &s ); if( Q_stricmp( s, "worldspawn" ) ) G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" ); // make some data visible to connecting client trap_SetConfigstring( CS_GAME_VERSION, GAME_VERSION ); trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) ); G_SpawnString( "music", "", &s ); trap_SetConfigstring( CS_MUSIC, s ); G_SpawnString( "message", "", &s ); trap_SetConfigstring( CS_MESSAGE, s ); // map specific message trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day G_SpawnString( "gravity", "800", &s ); trap_Cvar_Set( "g_gravity", s ); G_SpawnString( "humanBuildPoints", "1000", &s ); trap_Cvar_Set( "g_humanBuildPoints", s ); G_SpawnString( "humanMaxStage", "2", &s ); trap_Cvar_Set( "g_humanMaxStage", s ); G_SpawnString( "humanStage2Threshold", "50", &s ); trap_Cvar_Set( "g_humanStage2Threshold", s ); G_SpawnString( "humanStage3Threshold", "100", &s ); trap_Cvar_Set( "g_humanStage3Threshold", s ); G_SpawnString( "alienBuildPoints", "1000", &s ); trap_Cvar_Set( "g_alienBuildPoints", s ); G_SpawnString( "alienMaxStage", "2", &s ); trap_Cvar_Set( "g_alienMaxStage", s ); G_SpawnString( "alienStage2Threshold", "50", &s ); trap_Cvar_Set( "g_alienStage2Threshold", s ); G_SpawnString( "alienStage3Threshold", "100", &s ); trap_Cvar_Set( "g_alienStage3Threshold", s ); G_SpawnString( "enableDust", "0", &s ); trap_Cvar_Set( "g_enableDust", s ); G_SpawnString( "enableBreath", "0", &s ); trap_Cvar_Set( "g_enableBreath", s ); g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD; g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn"; // see if we want a warmup time trap_SetConfigstring( CS_WARMUP, "" ); if( g_restarted.integer ) { trap_Cvar_Set( "g_restarted", "0" ); level.warmupTime = 0; } else if( g_doWarmup.integer ) { // Turn it on level.warmupTime = -1; trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); G_LogPrintf( "Warmup:\n" ); } } /* ============== G_SpawnEntitiesFromString Parses textual entity definitions out of an entstring and spawns gentities. ============== */ void G_SpawnEntitiesFromString( void ) { // allow calls to G_Spawn*() level.spawning = qtrue; level.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( !G_ParseSpawnVars( ) ) G_Error( "SpawnEntities: no entities" ); SP_worldspawn( ); // parse ents while( G_ParseSpawnVars( ) ) G_SpawnGEntityFromSpawnVars( ); level.spawning = qfalse; // any future calls to G_Spawn*() will be errors }