og-openarena-gamecode/code/game/g_spawn.c
leilei- d371ff1482
Lei24 (#343)
- captureLimit should not end non-capturing games anymore
- additional surfaceflags for different surface types such as snow, wood, sand, gravel, foilage, ice, and glass
- improve hexcolors in the ui, allow for on-demand refresh of ui colors, add more hexcolors
- increase some limits of elements in the ui
- suppress excess ui warnings into developer
- sergei/kyonshi do not exist in OA3; default to sarge for missionpack currently (avoids crashes)
- don't attempt to load from characters/
- changing one model also changes teammodel
- add preliminary support for entity glow functionality and alternate shells for them (requires renderer engine changes to support). the primary idea behind alternate shell is performance and to avoid overdraw by force altering the model's vertex colors instead (overriding lighting and any other rgbGen op)
- add preliminary support for drawing player names above teammate heads (requires client engine changes to support)
- cg_hitSound camel-cased and defaults to 1 (again)
- reducing clown cruft by dropping the reference from the teamnames and just going for red/blue for now
- removed cg_leiChibi (again)
- bloodExplosion starts the shader (ancient bug/oversight)
- reverted superfluous cruft
2024-05-14 04:21:26 -04:00

696 lines
18 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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 2 of the License,
or (at your option) any later version.
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
#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_strequal( 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;
}
//
// 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_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},
{"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);
//standard domination:
void SP_domination_point ( 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_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_remove_powerups( gentity_t *ent );
void SP_target_give (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_laser (gentity_t *self);
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 );
void SP_team_CTF_redplayer( gentity_t *ent );
void SP_team_CTF_blueplayer( gentity_t *ent );
void SP_team_CTF_redspawn( gentity_t *ent );
void SP_team_CTF_bluespawn( gentity_t *ent );
void SP_team_blueobelisk( gentity_t *ent );
void SP_team_redobelisk( gentity_t *ent );
void SP_team_neutralobelisk( gentity_t *ent );
void SP_item_botroam( 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},
//Double Domination player spawn:
{"info_player_dd", SP_info_player_deathmatch},
{"info_player_dd_red", SP_info_player_deathmatch},
{"info_player_dd_blue", SP_info_player_deathmatch},
//Standard Domination point spawn:
{"domination_point", SP_domination_point},
{"info_player_dom_red", SP_info_player_deathmatch},
{"info_player_dom_blue", SP_info_player_deathmatch},
{"info_null", SP_info_null},
{"info_notnull", SP_info_notnull}, // use target_position instead
{"info_camp", SP_info_camp},
{"func_plat", SP_func_plat},
{"func_button", SP_func_button},
{"func_door", SP_func_door},
{"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_give", SP_target_give},
{"target_remove_powerups", SP_target_remove_powerups},
{"target_delay", SP_target_delay},
{"target_speaker", SP_target_speaker},
{"target_print", SP_target_print},
{"target_laser", SP_target_laser},
{"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},
{"shooter_rocket", SP_shooter_rocket},
{"shooter_grenade", SP_shooter_grenade},
{"shooter_plasma", SP_shooter_plasma},
{"team_CTF_redplayer", SP_team_CTF_redplayer},
{"team_CTF_blueplayer", SP_team_CTF_blueplayer},
{"team_CTF_redspawn", SP_team_CTF_redspawn},
{"team_CTF_bluespawn", SP_team_CTF_bluespawn},
{"team_redobelisk", SP_team_redobelisk},
{"team_blueobelisk", SP_team_blueobelisk},
{"team_neutralobelisk", SP_team_neutralobelisk},
{"item_botroam", SP_item_botroam},
{NULL, 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;
gitem_t *item;
char cvarname[128];
char itemname[128];
//Construct a replace cvar:
Com_sprintf(cvarname, sizeof(cvarname), "replace_%s", ent->classname);
//Look an alternative item up:
trap_Cvar_VariableStringBuffer(cvarname,itemname,sizeof(itemname));
if(itemname[0]==0) //If nothing found use original
Com_sprintf(itemname, sizeof(itemname), "%s", ent->classname);
else
G_Printf ("%s replaced by %s\n", ent->classname, itemname);
if ( itemname[0]==0) {
G_Printf ("G_CallSpawn: NULL classname\n");
return qfalse;
}
// check item spawn functions
for ( item=bg_itemlist+1 ; item->classname ; item++ ) {
if ( strequals(item->classname, itemname) ) {
G_SpawnItem( ent, item );
return qtrue;
}
}
// check normal spawn functions
for ( s=spawns ; s->name ; s++ ) {
if ( strequals(s->name, itemname) ) {
// found it
s->spawn(ent);
return qtrue;
}
}
G_Printf ("%s doesn't have a spawn function\n", itemname);
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;
//KK-OAX Changed to Tremulous's BG_Alloc
newb = BG_Alloc( l );
new_p = newb;
// turn \n into a real linefeed
for ( i=0 ; i< l ; i++ ) {
if ((i < l-1) && (string[i] == '\\')) {
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;
for ( f=fields ; f->name ; f++ ) {
if ( Q_strequal(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_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",
"elimination", "ctf", "lms", "dd", "dom", "pos"};
// 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 );
}
// check for "notsingle" flag
if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
G_SpawnInt( "notsingle", "0", &i );
if ( i ) {
G_FreeEntity( ent );
return;
}
}
// check for "notteam" flag (GT_FFA, GT_TOURNAMENT, GT_SINGLE_PLAYER)
if ( g_gametype.integer >= GT_TEAM && !g_ffa_gt ) {
/* if ( g_gametype.integer!=GT_FFA && g_gametype.integer!=GT_TOURNAMENT && g_gametype.integer!=GT_LMS && g_gametype.integer!=GT_POSSESSION ) { */
G_SpawnInt( "notteam", "0", &i );
if ( i ) {
G_FreeEntity( ent );
return;
}
} else {
G_SpawnInt( "notfree", "0", &i );
if ( i ) {
G_FreeEntity( ent );
return;
}
}
#ifdef MISSIONPACK
G_SpawnInt( "notta", "0", &i );
if ( i ) {
G_FreeEntity( ent );
return;
}
#else
G_SpawnInt( "notq3a", "0", &i );
if ( i ) {
G_FreeEntity( ent );
return;
}
#endif
if( G_SpawnString( "!gametype", NULL, &value ) ) {
if( g_gametype.integer >= GT_FFA && g_gametype.integer < ARRAY_LEN(gametypeNames) ) {
gametypeName = gametypeNames[g_gametype.integer];
s = strstr( value, gametypeName );
if( s ) {
G_FreeEntity( ent );
return;
}
}
}
if( G_SpawnString( "gametype", NULL, &value ) ) {
if( g_gametype.integer >= GT_FFA && g_gametype.integer < ARRAY_LEN(gametypeNames) ) {
gametypeName = gametypeNames[g_gametype.integer];
s = strstr( value, gametypeName );
if( !s ) {
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_VARS" );
}
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_strequal( 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 ) );
if ( *g_music.string && !Q_strequal( g_music.string, "none" ) ) {
trap_SetConfigstring( CS_MUSIC, g_music.string );
} else {
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( "enableFS", "0", &s );
trap_Cvar_Set( "g_enableFS", s );
G_SpawnString( "enableDust", "0", &s );
trap_Cvar_Set( "g_enableDust", s );
G_SpawnString( "enableBreath", "0", &s );
trap_Cvar_Set( "g_enableBreath", s );
G_SpawnString( "enableQ", "0", &s );
trap_Cvar_Set( "g_enableQ", s );
g_entities[ENTITYNUM_WORLD].s.number = ENTITYNUM_WORLD;
g_entities[ENTITYNUM_WORLD].r.ownerNum = ENTITYNUM_NONE;
g_entities[ENTITYNUM_WORLD].classname = "worldspawn";
g_entities[ENTITYNUM_NONE].s.number = ENTITYNUM_NONE;
g_entities[ENTITYNUM_NONE].r.ownerNum = ENTITYNUM_NONE;
g_entities[ENTITYNUM_NONE].classname = "nothing";
// 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
}