// 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 );
}


