1150 lines
27 KiB
C
1150 lines
27 KiB
C
// Copyright (C) 2001-2002 Raven Software.
|
|
//
|
|
|
|
#include "g_local.h"
|
|
|
|
qboolean G_SpawnString( const char *key, const char *defaultString, char **out )
|
|
{
|
|
int i;
|
|
|
|
if ( !level.spawning )
|
|
{
|
|
*out = (char *)defaultString;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// 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_deathmatch (gentity_t *ent);
|
|
void SP_info_player_intermission (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 *ent);
|
|
void SP_func_glass (gentity_t *ent);
|
|
void SP_func_wall (gentity_t *ent);
|
|
|
|
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_trigger_ladder (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 *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_target_effect (gentity_t *ent);
|
|
|
|
void SP_info_notnull (gentity_t *ent);
|
|
void SP_info_camp (gentity_t *ent);
|
|
void SP_path_corner (gentity_t *ent);
|
|
|
|
void SP_misc_teleporter_dest (gentity_t *ent);
|
|
void SP_misc_model (gentity_t *ent);
|
|
void SP_misc_G2model (gentity_t *ent);
|
|
void SP_misc_portal_camera (gentity_t *ent);
|
|
void SP_misc_portal_surface (gentity_t *ent);
|
|
void SP_misc_bsp (gentity_t *ent);
|
|
void SP_terrain (gentity_t *ent);
|
|
|
|
void SP_model_static (gentity_t* ent);
|
|
|
|
void SP_gametype_item (gentity_t* ent);
|
|
void SP_gametype_trigger (gentity_t* ent);
|
|
void SP_gametype_player (gentity_t* ent);
|
|
void SP_mission_player (gentity_t* ent);
|
|
|
|
void SP_fx_play_effect (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_deathmatch", SP_info_player_deathmatch},
|
|
{"info_player_intermission", SP_info_player_intermission},
|
|
{"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_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_timer", SP_func_timer},
|
|
{"func_glass", SP_func_glass},
|
|
{"func_wall", SP_func_wall},
|
|
|
|
// 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},
|
|
{"trigger_ladder", SP_trigger_ladder },
|
|
|
|
// targets perform no action by themselves, but must be triggered
|
|
// by another entity
|
|
{"target_give", SP_target_give},
|
|
{"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},
|
|
{"target_effect", SP_target_effect},
|
|
|
|
{"path_corner", SP_path_corner},
|
|
|
|
{"misc_teleporter_dest", SP_misc_teleporter_dest},
|
|
{"misc_model", SP_misc_model},
|
|
{"client_model", SP_model_static},
|
|
{"misc_G2model", SP_misc_G2model},
|
|
{"misc_portal_surface", SP_misc_portal_surface},
|
|
{"misc_portal_camera", SP_misc_portal_camera},
|
|
{"misc_bsp", SP_misc_bsp},
|
|
{"terrain", SP_terrain},
|
|
|
|
{"model_static", SP_model_static },
|
|
|
|
{"gametype_item", SP_gametype_item },
|
|
{"gametype_trigger", SP_gametype_trigger },
|
|
{"gametype_player", SP_gametype_player },
|
|
{"mission_player", SP_mission_player },
|
|
|
|
// stuff from SP emulated
|
|
{"func_breakable_brush", SP_func_static},
|
|
{"fx_play_effect", SP_fx_play_effect},
|
|
|
|
// The following classnames are instantly removed when spawned. The RMG
|
|
// shares instances with single player which is what causes these things
|
|
// to attempt to spawn
|
|
{"light", 0},
|
|
{"func_group", 0},
|
|
{"info_camp", 0},
|
|
{"info_null", 0},
|
|
{"door_rotating", 0},
|
|
{"emplaced_wpn", 0},
|
|
{"info_NPC*", 0},
|
|
{"info_player_start", 0},
|
|
{"NPC_*", 0},
|
|
{"ce_*", 0},
|
|
{"pickup_ammo", 0},
|
|
{"script_runner", 0},
|
|
{"trigger_arioche_objective", 0},
|
|
|
|
{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;
|
|
gitem_t *item;
|
|
|
|
if ( !ent->classname )
|
|
{
|
|
Com_Printf ("G_CallSpawn: NULL classname\n");
|
|
return qfalse;
|
|
}
|
|
|
|
// check item spawn functions
|
|
for ( item=bg_itemlist+1 ; item->classname ; item++ )
|
|
{
|
|
if ( !strcmp(item->classname, ent->classname) )
|
|
{
|
|
// If this is a backpack then handle it specially
|
|
if ( item->giType == IT_BACKPACK )
|
|
{
|
|
if ( !level.gametypeData->backpack )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
G_SpawnItem ( ent, item );
|
|
return qtrue;
|
|
}
|
|
|
|
// Make sure pickups arent disabled
|
|
if ( !level.pickupsDisabled )
|
|
{
|
|
G_SpawnItem( ent, item );
|
|
return qtrue;
|
|
}
|
|
else
|
|
{ // Pickups dont spawn when disabled - this avoids the "doesn't have a spawn function" message
|
|
return qfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check normal spawn functions
|
|
for ( s=spawns ; s->name ; s++ )
|
|
{
|
|
char* wildcard = strchr ( s->name, '*' );
|
|
int result;
|
|
|
|
if ( wildcard )
|
|
{
|
|
result = Q_strncmp ( s->name, ent->classname, wildcard - s->name );
|
|
}
|
|
else
|
|
{
|
|
result = strcmp(s->name, ent->classname);
|
|
}
|
|
|
|
if ( !result )
|
|
{
|
|
if (s->spawn)
|
|
{ // found it
|
|
s->spawn(ent);
|
|
return qtrue;
|
|
}
|
|
else
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
Com_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 = trap_VM_LocalAlloc( 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_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_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_IsGametypeInList
|
|
|
|
Determines if the given gametype is in the given list.
|
|
===================
|
|
*/
|
|
qboolean G_IsGametypeInList ( const char* gametype, const char* list )
|
|
{
|
|
const char* buf = (char*) list;
|
|
char* token;
|
|
|
|
while ( 1 )
|
|
{
|
|
token = COM_Parse ( &buf );
|
|
if ( !token || !token[0] )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( Q_stricmp ( token, gametype ) == 0 )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
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( qboolean inSubBSP )
|
|
{
|
|
int i;
|
|
gentity_t *ent;
|
|
char *value;
|
|
|
|
if (inSubBSP)
|
|
{
|
|
// filter out the unwanted entities
|
|
G_SpawnString("filter", "", &value);
|
|
if (value[0] && Q_stricmp(level.mFilter, value))
|
|
{
|
|
// we are not matching up to the filter, so no spawney
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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 "notteam" flag (GT_DM)
|
|
if ( level.gametypeData->teams )
|
|
{
|
|
G_SpawnInt( "notteam", "0", &i );
|
|
if ( i )
|
|
{
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_SpawnInt( "notfree", "0", &i );
|
|
if ( i )
|
|
{
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Only spawn this entity in the specified gametype
|
|
if( G_SpawnString( "gametype", NULL, &value ) && value )
|
|
{
|
|
if ( !G_IsGametypeInList ( level.gametypeData->name, value ) )
|
|
{
|
|
if ( level.gametypeData->basegametype )
|
|
{
|
|
if ( !G_IsGametypeInList ( level.gametypeData->basegametype, value ) )
|
|
{
|
|
G_FreeEntity ( ent );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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 )
|
|
{
|
|
Com_Error( ERR_FATAL, "G_AddSpawnVarToken: MAX_SPAWN_CHARS" );
|
|
}
|
|
|
|
dest = level.spawnVarChars + level.numSpawnVarChars;
|
|
memcpy( dest, string, l+1 );
|
|
|
|
level.numSpawnVarChars += l + 1;
|
|
|
|
return dest;
|
|
}
|
|
|
|
void AddSpawnField(char *field, char *value)
|
|
{
|
|
int i;
|
|
|
|
for(i=0;i<level.numSpawnVars;i++)
|
|
{
|
|
if (Q_stricmp(level.spawnVars[i][0], field) == 0)
|
|
{
|
|
level.spawnVars[ i ][1] = G_AddSpawnVarToken( value );
|
|
return;
|
|
}
|
|
}
|
|
|
|
level.spawnVars[ level.numSpawnVars ][0] = G_AddSpawnVarToken( field );
|
|
level.spawnVars[ level.numSpawnVars ][1] = G_AddSpawnVarToken( value );
|
|
level.numSpawnVars++;
|
|
}
|
|
|
|
#define NOVALUE "novalue"
|
|
|
|
static void HandleEntityAdjustment(void)
|
|
{
|
|
char *value;
|
|
vec3_t origin, newOrigin, angles;
|
|
char temp[MAX_QPATH];
|
|
float rotation;
|
|
|
|
G_SpawnString("origin", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
sscanf( value, "%f %f %f", &origin[0], &origin[1], &origin[2] );
|
|
}
|
|
else
|
|
{
|
|
origin[0] = origin[1] = origin[2] = 0.0;
|
|
}
|
|
|
|
rotation = DEG2RAD(level.mRotationAdjust);
|
|
newOrigin[0] = origin[0]*cos(rotation) - origin[1]*sin(rotation);
|
|
newOrigin[1] = origin[0]*sin(rotation) + origin[1]*cos(rotation);
|
|
newOrigin[2] = origin[2];
|
|
VectorAdd(newOrigin, level.mOriginAdjust, newOrigin);
|
|
// damn VMs don't handle outputing a float that is compatible with sscanf in all cases
|
|
Com_sprintf(temp, MAX_QPATH, "%0.0f %0.0f %0.0f", newOrigin[0], newOrigin[1], newOrigin[2]);
|
|
AddSpawnField("origin", temp);
|
|
|
|
G_SpawnString("angles", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
sscanf( value, "%f %f %f", &angles[0], &angles[1], &angles[2] );
|
|
|
|
angles[1] = fmod(angles[1] + level.mRotationAdjust, 360.0);
|
|
// damn VMs don't handle outputing a float that is compatible with sscanf in all cases
|
|
Com_sprintf(temp, MAX_QPATH, "%0.0f %0.0f %0.0f", angles[0], angles[1], angles[2]);
|
|
AddSpawnField("angles", temp);
|
|
}
|
|
else
|
|
{
|
|
G_SpawnString("angle", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
sscanf( value, "%f", &angles[1] );
|
|
}
|
|
else
|
|
{
|
|
angles[1] = 0.0;
|
|
}
|
|
angles[1] = fmod(angles[1] + level.mRotationAdjust, 360.0);
|
|
Com_sprintf(temp, MAX_QPATH, "%0.0f", angles[1]);
|
|
AddSpawnField("angle", temp);
|
|
}
|
|
|
|
// RJR experimental code for handling "direction" field of breakable brushes
|
|
// though direction is rarely ever used.
|
|
G_SpawnString("direction", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
sscanf( value, "%f %f %f", &angles[0], &angles[1], &angles[2] );
|
|
}
|
|
else
|
|
{
|
|
angles[0] = angles[1] = angles[2] = 0.0;
|
|
}
|
|
angles[1] = fmod(angles[1] + level.mRotationAdjust, 360.0);
|
|
Com_sprintf(temp, MAX_QPATH, "%0.0f %0.0f %0.0f", angles[0], angles[1], angles[2]);
|
|
AddSpawnField("direction", temp);
|
|
|
|
|
|
AddSpawnField("BSPInstanceID", level.mTargetAdjust);
|
|
|
|
G_SpawnString("targetname", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("targetname", temp);
|
|
}
|
|
|
|
G_SpawnString("target", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("target", temp);
|
|
}
|
|
|
|
G_SpawnString("killtarget", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("killtarget", temp);
|
|
}
|
|
|
|
G_SpawnString("brushparent", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("brushparent", temp);
|
|
}
|
|
|
|
G_SpawnString("brushchild", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("brushchild", temp);
|
|
}
|
|
|
|
G_SpawnString("enemy", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("enemy", temp);
|
|
}
|
|
|
|
G_SpawnString("ICARUSname", NOVALUE, &value);
|
|
if (Q_stricmp(value, NOVALUE) != 0)
|
|
{
|
|
Com_sprintf(temp, MAX_QPATH, "%s%s", level.mTargetAdjust, value);
|
|
AddSpawnField("ICARUSname", temp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
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( qboolean inSubBSP )
|
|
{
|
|
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] != '{' )
|
|
{
|
|
Com_Error( ERR_FATAL, "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 ) ) )
|
|
{
|
|
Com_Error( ERR_FATAL, "G_ParseSpawnVars: EOF without closing brace" );
|
|
}
|
|
|
|
if ( keyname[0] == '}' )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// parse value
|
|
if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
|
|
{
|
|
Com_Error( ERR_FATAL, "G_ParseSpawnVars: EOF without closing brace" );
|
|
}
|
|
|
|
if ( com_token[0] == '}' )
|
|
{
|
|
Com_Error( ERR_FATAL, "G_ParseSpawnVars: closing brace without data" );
|
|
}
|
|
|
|
if ( level.numSpawnVars == MAX_SPAWN_VARS )
|
|
{
|
|
Com_Error( ERR_FATAL, "G_ParseSpawnVars: MAX_SPAWN_VARS" );
|
|
}
|
|
|
|
AddSpawnField(keyname, com_token);
|
|
}
|
|
|
|
if (inSubBSP)
|
|
{
|
|
HandleEntityAdjustment();
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
static char *defaultStyles[32][3] =
|
|
{
|
|
{ // 0 normal
|
|
"z",
|
|
"z",
|
|
"z"
|
|
},
|
|
{ // 1 FLICKER (first variety)
|
|
"mmnmmommommnonmmonqnmmo",
|
|
"mmnmmommommnonmmonqnmmo",
|
|
"mmnmmommommnonmmonqnmmo"
|
|
},
|
|
{ // 2 SLOW STRONG PULSE
|
|
"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcb",
|
|
"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcb",
|
|
"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcb"
|
|
},
|
|
{ // 3 CANDLE (first variety)
|
|
"mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
|
|
"mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
|
|
"mmmmmaaaaammmmmaaaaaabcdefgabcdefg"
|
|
},
|
|
{ // 4 FAST STROBE
|
|
"mamamamamama",
|
|
"mamamamamama",
|
|
"mamamamamama"
|
|
},
|
|
{ // 5 GENTLE PULSE 1
|
|
"jklmnopqrstuvwxyzyxwvutsrqponmlkj",
|
|
"jklmnopqrstuvwxyzyxwvutsrqponmlkj",
|
|
"jklmnopqrstuvwxyzyxwvutsrqponmlkj"
|
|
},
|
|
{ // 6 FLICKER (second variety)
|
|
"nmonqnmomnmomomno",
|
|
"nmonqnmomnmomomno",
|
|
"nmonqnmomnmomomno"
|
|
},
|
|
{ // 7 CANDLE (second variety)
|
|
"mmmaaaabcdefgmmmmaaaammmaamm",
|
|
"mmmaaaabcdefgmmmmaaaammmaamm",
|
|
"mmmaaaabcdefgmmmmaaaammmaamm"
|
|
},
|
|
{ // 8 CANDLE (third variety)
|
|
"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa",
|
|
"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa",
|
|
"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"
|
|
},
|
|
{ // 9 SLOW STROBE (fourth variety)
|
|
"aaaaaaaazzzzzzzz",
|
|
"aaaaaaaazzzzzzzz",
|
|
"aaaaaaaazzzzzzzz"
|
|
},
|
|
{ // 10 FLUORESCENT FLICKER
|
|
"mmamammmmammamamaaamammma",
|
|
"mmamammmmammamamaaamammma",
|
|
"mmamammmmammamamaaamammma"
|
|
},
|
|
{ // 11 SLOW PULSE NOT FADE TO BLACK
|
|
"abcdefghijklmnopqrrqponmlkjihgfedcba",
|
|
"abcdefghijklmnopqrrqponmlkjihgfedcba",
|
|
"abcdefghijklmnopqrrqponmlkjihgfedcba"
|
|
},
|
|
{ // 12 FAST PULSE FOR JEREMY
|
|
"mkigegik",
|
|
"mkigegik",
|
|
"mkigegik"
|
|
},
|
|
{ // 13 Test Blending
|
|
"abcdefghijklmqrstuvwxyz",
|
|
"zyxwvutsrqmlkjihgfedcba",
|
|
"aammbbzzccllcckkffyyggp"
|
|
},
|
|
{ // 14
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 15
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 16
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 17
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 18
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 19
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 20
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 21
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 22
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 23
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 24
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 25
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 26
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 27
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 28
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 29
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 30
|
|
"",
|
|
"",
|
|
""
|
|
},
|
|
{ // 31
|
|
"",
|
|
"",
|
|
""
|
|
}
|
|
};
|
|
|
|
qboolean SP_bsp_worldspawn ( void )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
/*QUAKED worldspawn (0 0 0) ?
|
|
|
|
Every map should have exactly one worldspawn.
|
|
"music" music wav file
|
|
"soundSet" soundset name to use (do not combine with 'noise', ignores all other flags)
|
|
"gravity" 800 is default gravity
|
|
"message" Text to print during connection process
|
|
"mission" Indicates which mission script file should be used to find the scripts for mission mode
|
|
*/
|
|
void SP_worldspawn( void )
|
|
{
|
|
char *text, temp[32];
|
|
int i;
|
|
int lengthRed, lengthBlue, lengthGreen;
|
|
|
|
G_SpawnString( "classname", "", &text );
|
|
if ( Q_stricmp( text, "worldspawn" ) )
|
|
{
|
|
Com_Error( ERR_FATAL, "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", "", &text );
|
|
trap_SetConfigstring( CS_MUSIC, text );
|
|
|
|
if (G_SpawnString( "soundSet", "", &text ) )
|
|
{
|
|
trap_SetConfigstring(CS_AMBIENT_SOUNDSETS, text );
|
|
}
|
|
|
|
if ( level.gametypeData->teams )
|
|
{
|
|
G_SpawnString( "redteam", "", &text );
|
|
if ( text && *text )
|
|
{
|
|
level.gametypeTeam[TEAM_RED] = trap_VM_LocalStringAlloc ( text );
|
|
}
|
|
|
|
G_SpawnString( "blueteam", "", &text );
|
|
if ( text && *text )
|
|
{
|
|
level.gametypeTeam[TEAM_BLUE] = trap_VM_LocalStringAlloc ( text );
|
|
}
|
|
|
|
if ( !level.gametypeTeam[TEAM_RED] ||
|
|
!level.gametypeTeam[TEAM_BLUE] )
|
|
{
|
|
level.gametypeTeam[TEAM_RED] = "marine";
|
|
level.gametypeTeam[TEAM_BLUE] = "thug";
|
|
}
|
|
|
|
trap_SetConfigstring( CS_GAMETYPE_REDTEAM, level.gametypeTeam[TEAM_RED] );
|
|
trap_SetConfigstring( CS_GAMETYPE_BLUETEAM, level.gametypeTeam[TEAM_BLUE] );
|
|
}
|
|
|
|
G_SpawnString( "message", "", &text );
|
|
trap_SetConfigstring( CS_MESSAGE, text ); // map specific message
|
|
|
|
trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day
|
|
|
|
G_SpawnString( "gravity", "800", &text );
|
|
trap_Cvar_Set( "g_gravity", text );
|
|
|
|
// Handle all the worldspawn stuff common to both main bsp and sub bsp
|
|
SP_bsp_worldspawn ( );
|
|
|
|
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" );
|
|
}
|
|
|
|
trap_SetConfigstring(CS_LIGHT_STYLES+(LS_STYLES_START*3)+0, defaultStyles[0][0]);
|
|
trap_SetConfigstring(CS_LIGHT_STYLES+(LS_STYLES_START*3)+1, defaultStyles[0][1]);
|
|
trap_SetConfigstring(CS_LIGHT_STYLES+(LS_STYLES_START*3)+2, defaultStyles[0][2]);
|
|
|
|
for(i=1;i<LS_NUM_STYLES;i++)
|
|
{
|
|
Com_sprintf(temp, sizeof(temp), "ls_%dr", i);
|
|
G_SpawnString(temp, defaultStyles[i][0], &text);
|
|
lengthRed = strlen(text);
|
|
trap_SetConfigstring(CS_LIGHT_STYLES+((i+LS_STYLES_START)*3)+0, text);
|
|
|
|
Com_sprintf(temp, sizeof(temp), "ls_%dg", i);
|
|
G_SpawnString(temp, defaultStyles[i][1], &text);
|
|
lengthGreen = strlen(text);
|
|
trap_SetConfigstring(CS_LIGHT_STYLES+((i+LS_STYLES_START)*3)+1, text);
|
|
|
|
Com_sprintf(temp, sizeof(temp), "ls_%db", i);
|
|
G_SpawnString(temp, defaultStyles[i][2], &text);
|
|
lengthBlue = strlen(text);
|
|
trap_SetConfigstring(CS_LIGHT_STYLES+((i+LS_STYLES_START)*3)+2, text);
|
|
|
|
if (lengthRed != lengthGreen || lengthGreen != lengthBlue)
|
|
{
|
|
Com_Error(ERR_DROP, "Style %d has inconsistent lengths: R %d, G %d, B %d",
|
|
i, lengthRed, lengthGreen, lengthBlue);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
G_SpawnEntitiesFromString
|
|
|
|
Parses textual entity definitions out of an entstring and spawns gentities.
|
|
==============
|
|
*/
|
|
void G_SpawnEntitiesFromString( qboolean inSubBSP )
|
|
{
|
|
// 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(inSubBSP) )
|
|
{
|
|
Com_Error( ERR_FATAL, "SpawnEntities: no entities" );
|
|
}
|
|
|
|
if (!inSubBSP)
|
|
{
|
|
SP_worldspawn();
|
|
}
|
|
else
|
|
{
|
|
// Skip this guy if its worldspawn fails
|
|
if ( !SP_bsp_worldspawn() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// parse ents
|
|
while( G_ParseSpawnVars(inSubBSP) )
|
|
{
|
|
G_SpawnGEntityFromSpawnVars(inSubBSP);
|
|
}
|
|
|
|
if (!inSubBSP)
|
|
{
|
|
level.spawning = qfalse; // any future calls to G_Spawn*() will be errors
|
|
}
|
|
}
|
|
|
|
/*QUAKED model_static (1 0 0) (-16 -16 -16) (16 16 16) NO_MP
|
|
"model" arbitrary .md3 file to display
|
|
*/
|
|
void SP_model_static ( gentity_t* ent )
|
|
{
|
|
if (ent->spawnflags & 1)
|
|
{ // NO_MULTIPLAYER
|
|
G_FreeEntity( ent );
|
|
}
|
|
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
|
|
VectorCopy(ent->s.angles, ent->r.currentAngles);
|
|
VectorCopy(ent->s.angles, ent->s.apos.trBase );
|
|
|
|
VectorCopy( ent->s.origin, ent->s.pos.trBase );
|
|
VectorCopy( ent->s.origin, ent->r.currentOrigin );
|
|
|
|
ent->s.modelindex = G_ModelIndex( ent->model );
|
|
ent->s.pos.trType = TR_STATIONARY;
|
|
ent->s.apos.trTime = level.time;
|
|
|
|
if (level.mBSPInstanceDepth)
|
|
{ // this means that this guy will never be updated, moved, changed, etc.
|
|
ent->s.eFlags = EF_PERMANENT;
|
|
}
|
|
|
|
trap_LinkEntity ( ent );
|
|
}
|
|
|
|
|