og-openarena-gamecode/code/game/g_spawn.c
sago007 0bee41a517 Pull request #35. Replaces all the complicated gametype checks with helper functions
Squashed commit of the following:

commit c05ffecf38
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:53:54 2018 -0300

    This is also not needed anymore.

commit 14abe7715a
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:29:26 2018 -0300

    Last checks found. Hopefully that's enough.

commit cad2ab0c5a
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:14:33 2018 -0300

    We don't need g_ffa_gt anymore.

commit 2577547db4
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 22:04:09 2018 -0300

    More checks located.

commit d9c96477c6
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:29:41 2018 -0300

    Had to do this just to get it to compile.

commit 2089039745
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:18:25 2018 -0300

    Fixing several syntax errors.

commit 0edba34607
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:07:23 2018 -0300

    Oops... my mistakes. -.-

commit 05e9ef79d4
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Fri Nov 30 21:00:00 2018 -0300

    New checks for team flags, team obelisks, control points and rounds.

commit 06e325ab00
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 17:10:48 2018 -0300

    Some syntax errors fixed.

commit e394d4fc54
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 17:00:01 2018 -0300

    Finished the splitting.

commit 5e267d161f
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 16:34:01 2018 -0300

    Splitting the function.

commit 7c643f14aa
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 12:36:14 2018 -0300

    Syntax error.

commit aba598fde4
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Thu Nov 29 12:26:58 2018 -0300

    All the isTeam checks were moved to a centralized function.

commit b9a61dbe45
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Wed Nov 28 21:26:34 2018 -0300

    Revert "Toggleable missionpack checks. Disables team models and relevant checks interfering with testing. Some comments were also modified in order to work with SciTE."

    This reverts commit fe4c340e35.

commit fe4c340e35
Author: Neon_Knight <openarena.arg@gmail.com>
Date:   Wed Nov 28 21:21:31 2018 -0300

    Toggleable missionpack checks. Disables team models and relevant checks interfering with testing. Some comments were also modified in order to work with SciTE.
2018-12-01 10:43:03 +01:00

695 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 (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;
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
if (G_IsATeamGametype(g_gametype.integer) && G_UsesKeyObjectives(g_gametype.integer)) {
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
}