mirror of
https://github.com/OpenArena/gamecode
synced 2026-08-20 18:23:03 -04:00
- 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
696 lines
18 KiB
C
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
|
|
}
|
|
|