/*
Copyright (C) 1997-2001 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "g_local.h"
#include "g_gametypes.h"

typedef struct
{
	char	*name;
	void	(*spawn)(edict_t *ent);
} spawn_t;

void SP_info_player_start (edict_t *ent);
void SP_info_player_deathmatch (edict_t *ent);
void SP_info_player_intermission (edict_t *ent);

void SP_team_CTF_redspawn( edict_t *self );
void SP_team_CTF_bluespawn( edict_t *self );
void SP_team_CTF_redplayer( edict_t *self );
void SP_team_CTF_blueplayer( edict_t *self );

void SP_func_plat (edict_t *ent);
void SP_func_rotating (edict_t *ent);
void SP_func_button (edict_t *ent);
void SP_func_door (edict_t *ent);
void SP_func_door_secret (edict_t *ent);
void SP_func_door_rotating (edict_t *ent);
void SP_func_water (edict_t *ent);
void SP_func_train (edict_t *ent);
void SP_func_conveyor (edict_t *self);
void SP_func_wall (edict_t *self);
void SP_func_object (edict_t *self);
void SP_func_explosive (edict_t *self);
void SP_func_timer (edict_t *self);
void SP_func_killbox (edict_t *ent);
void SP_func_static (edict_t *ent);
void SP_func_bobbing ( edict_t *ent );
void SP_func_pendulum ( edict_t *ent );

void SP_trigger_always (edict_t *ent);
void SP_trigger_once (edict_t *ent);
void SP_trigger_multiple (edict_t *ent);
void SP_trigger_relay (edict_t *ent);
void SP_trigger_push (edict_t *ent);
void SP_trigger_hurt (edict_t *ent);
void SP_trigger_key (edict_t *ent);
void SP_trigger_counter (edict_t *ent);
void SP_trigger_elevator (edict_t *ent);
void SP_trigger_gravity (edict_t *ent);
void SP_trigger_monsterjump (edict_t *ent);

void SP_target_temp_entity (edict_t *ent);
void SP_target_speaker (edict_t *ent);
void SP_target_explosion (edict_t *ent);
void SP_target_splash (edict_t *ent);
void SP_target_spawner (edict_t *ent);
void SP_target_blaster (edict_t *ent);
void SP_target_crosslevel_trigger (edict_t *ent);
void SP_target_crosslevel_target (edict_t *ent);
void SP_target_laser (edict_t *self);
void SP_target_lightramp (edict_t *self);
void SP_target_earthquake (edict_t *ent);
void SP_target_character (edict_t *ent);
void SP_target_string (edict_t *ent);
void SP_target_location (edict_t *self);
void SP_target_position (edict_t *self);
void SP_target_print (edict_t *self);
void SP_target_starttimer (edict_t *self);
void SP_target_stoptimer (edict_t *self);
void SP_target_checkpoint (edict_t *self);
void SP_target_give(edict_t *self);
void SP_target_reset_flag_countdown( edict_t *self );
void SP_target_freeze_flag_countdown( edict_t *self );

void SP_worldspawn (edict_t *ent);
//void SP_viewthing (edict_t *ent);

void SP_light (edict_t *self);
void SP_light_mine (edict_t *ent);
void SP_info_null (edict_t *self);
void SP_info_notnull (edict_t *self);
void SP_info_camp (edict_t *self);
void SP_path_corner (edict_t *self);

void SP_misc_teleporter_dest (edict_t *self);
void SP_misc_model (edict_t *ent);
void SP_misc_portal_surface (edict_t *ent);
void SP_misc_portal_camera (edict_t *ent);

void SP_item_botroam (edict_t *self);	//MBotGame

spawn_t	spawns[] = {
	{"info_player_start", SP_info_player_start},
	{"info_player_deathmatch", SP_info_player_deathmatch},
	{"info_player_intermission", SP_info_player_intermission},
//ZOID
	{"team_CTF_redspawn", SP_team_CTF_redspawn},
	{"team_CTF_bluespawn", SP_team_CTF_bluespawn},
	{"team_CTF_redplayer", SP_team_CTF_redplayer},
	{"team_CTF_blueplayer", SP_team_CTF_blueplayer},
//ZOID

	{"func_plat", SP_func_plat},
	{"func_button", SP_func_button},
	{"func_door", SP_func_door},
	{"func_door_secret", SP_func_door_secret},
	{"func_door_rotating", SP_func_door_rotating},
	{"func_rotating", SP_func_rotating},
	{"func_train", SP_func_train},
	{"func_water", SP_func_water},
	{"func_conveyor", SP_func_conveyor},
	{"func_wall", SP_func_wall},
	{"func_object", SP_func_object},
	{"func_timer", SP_func_timer},
	{"func_explosive", SP_func_explosive},
	{"func_killbox", SP_func_killbox},
	{"func_static", SP_func_static},
	{"func_bobbing", SP_func_bobbing},
	{"func_pendulum", SP_func_pendulum},

	{"trigger_always", SP_trigger_always},
	{"trigger_once", SP_trigger_once},
	{"trigger_multiple", SP_trigger_multiple},
	{"trigger_relay", SP_trigger_relay},
	{"trigger_push", SP_trigger_push},
	{"trigger_hurt", SP_trigger_hurt},
	{"trigger_counter", SP_trigger_counter},
	{"trigger_elevator", SP_trigger_elevator},
	{"trigger_gravity", SP_trigger_gravity},
	{"trigger_monsterjump", SP_trigger_monsterjump},

	{"target_temp_entity", SP_target_temp_entity},
	{"target_speaker", SP_target_speaker},
	{"target_explosion", SP_target_explosion},
	{"target_splash", SP_target_splash},
	{"target_spawner", SP_target_spawner},
	{"target_blaster", SP_target_blaster},
	{"target_crosslevel_trigger", SP_target_crosslevel_trigger},
	{"target_crosslevel_target", SP_target_crosslevel_target},
	{"target_laser", SP_target_laser},
	{"target_lightramp", SP_target_lightramp},
	{"target_earthquake", SP_target_earthquake},
	{"target_character", SP_target_character},
	{"target_string", SP_target_string},
	{"target_location", SP_target_location},
	{"target_position", SP_target_position},
	{"target_print", SP_target_print},

	// minimal defrag
	{"target_starttimer", SP_target_starttimer},
	{"target_stoptimer", SP_target_stoptimer},
	{"target_checkpoint", SP_target_checkpoint},
	{"target_give", SP_target_give},
	{"target_reset_flag_countdown", SP_target_reset_flag_countdown},
	{"target_freeze_flag_countdown", SP_target_freeze_flag_countdown},

	{"worldspawn", SP_worldspawn},
	//{"viewthing", SP_viewthing},

	{"light", SP_light},
	{"light_mine1", SP_light_mine},
	{"info_null", SP_info_null},
	{"func_group", SP_info_null},
	{"info_notnull", SP_info_notnull},
	{"info_camp", SP_info_camp},
	{"path_corner", SP_path_corner},

	{"misc_teleporter_dest", SP_misc_teleporter_dest},
//ZOID
	{"trigger_teleport", SP_trigger_teleport},
	{"info_teleport_destination", SP_info_teleport_destination},
//ZOID
	{"misc_model", SP_misc_model},
	{"misc_portal_surface", SP_misc_portal_surface},
	{"misc_portal_camera", SP_misc_portal_camera},

	{"item_botroam", SP_item_botroam},	//MBotGame

	{NULL, NULL}
};

gitem_t *G_ItemForEntity( edict_t *ent )
{
	int i;
	gitem_t *item;

	// check item spawn functions
	for( i = 0; i < game.numItems; i++ )
	{
		item = game.items[i];
		if( !item )
			continue;

		if( item->classname && !Q_stricmp(item->classname, ent->classname) )
			return item;
	}

	// check again using q3 classnames
	for( i = 0; i < game.numItems; i++ )
	{
		item = game.items[i];
		if( !item )
			continue;

		if( item->q3classname && !Q_stricmp(item->q3classname, ent->classname) )
			return item;
	}

	return NULL;
}

//=============
//G_CanSpawnEntity
//=============
static qboolean G_CanSpawnEntity( edict_t *ent )
{
	gitem_t *item;

	if( ent == world )
		return qtrue;

	if( game.gametype == GAMETYPE_DM && ((ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH)||st.notfree) )
		return qfalse;
	if( game.gametype == GAMETYPE_DUEL && st.notduel )
		return qfalse;
	if( game.gametype == GAMETYPE_TDM && st.notteam )
		return qfalse;
	if( game.gametype == GAMETYPE_CTF && st.notctf )
		return qfalse;

	if( (item = G_ItemForEntity(ent)) != NULL )
	{
		// not pickable items aren't either spawnable
		if( !(item->flags & ITFLAG_PICKABLE) )
			return qfalse;

		if( !G_Gametype_CanSpawnItem(item) ) {
			return qfalse;
		}

		// some items are disabled by users, by setting up dmflags
		if( dmflags->integer & DF_NO_ARMOR && (item->flags & IT_ARMOR) )
			return qfalse;

		if( dmflags->integer & DF_NO_ITEMS && (item->flags & IT_POWERUP) )
			return qfalse;

		if( dmflags->integer & DF_NO_HEALTH && (item->flags & IT_HEALTH) )
			return qfalse;

		if( dmflags->integer & DF_INFINITE_AMMO && (item->flags & IT_AMMO) )
			return qfalse;

		if( game.gametype != GAMETYPE_CTF && (item->flags & IT_FLAG) )
			return qfalse;
	}

	return qtrue;
}

/*
===============
G_CallSpawn

Finds the spawn function for the entity and calls it
===============
*/
void G_CallSpawn( edict_t *ent )
{
	spawn_t	*s;
	gitem_t	*item;

	if( !ent->classname )
	{
		if( developer->integer )
			G_Printf( "G_CallSpawn: NULL classname\n" );
		return;
	}

	if( (item = G_ItemForEntity(ent)) != NULL )
	{
		ent->classname = item->classname; // for q3 support
		SpawnItem( ent, item );
		return;
	}

	// check normal spawn functions
	for( s = spawns; s->name; s++ )
	{
		if( !Q_stricmp(s->name, ent->classname) )
		{	// found it
			s->spawn(ent);
			return;
		}
	}

//	if( developer->integer )
		G_Printf( "%s doesn't have a spawn function\n", ent->classname );
}

/*
=============
ED_NewString
=============
*/
char *ED_NewString( char *string )
{
	char	*newb, *new_p;
	int		i,l;
	
	l = strlen(string) + 1;

	newb = G_Malloc(l);

	new_p = newb;

	for( i = 0; i < l; i++ )
	{
		if( string[i] == '\\' && i < l-1 )
		{
			i++;
			if( string[i] == 'n' ) {
				*new_p++ = '\n';
			} else {
				*new_p++ = '/';
				*new_p++ = string[i];
			}
		}
		else
			*new_p++ = string[i];
	}
	
	return newb;
}




/*
===============
ED_ParseField

Takes a key/value pair and sets the binary values
in an edict
===============
*/
void ED_ParseField( char *key, char *value, edict_t *ent )
{
	const field_t *f;
	qbyte	*b;
	float	v;
	vec3_t	vec;

	for( f=fields; f->name; f++ )
	{
		if( !Q_stricmp(f->name, key) )
		{	// found it
			if( f->flags & FFL_SPAWNTEMP )
				b = (qbyte *)&st;
			else
				b = (qbyte *)ent;

			switch( f->type )
			{
			case F_LSTRING:
				*(char **)(b+f->ofs) = ED_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;
			case F_IGNORE:
				break;
			default:
				break; // FIXME: Should this be error?
			}
			return;
		}
	}

	if( developer->integer )
		G_Printf( "%s is not a field\n", key );
}

/*
====================
ED_ParseEdict

Parses an edict out of the given string, returning the new position
ed should be a properly initialized empty edict.
====================
*/
char *ED_ParseEdict( char *data, edict_t *ent )
{
	qboolean	init;
	char		keyname[256];
	char		*com_token;

	init = qfalse;
	memset( &st, 0, sizeof(st) );

// go through all the dictionary pairs
	while( 1 )
	{	
	// parse key
		com_token = COM_Parse( &data );
		if( com_token[0] == '}' )
			break;
		if( !data )
			G_Error( "ED_ParseEntity: EOF without closing brace" );

		Q_strncpyz( keyname, com_token, sizeof(keyname) );
		
	// parse value	
		com_token = COM_Parse( &data );
		if( !data )
			G_Error( "ED_ParseEntity: EOF without closing brace" );

		if( com_token[0] == '}' )
			G_Error( "ED_ParseEntity: closing brace without data" );

		init = qtrue;	

	// keynames with a leading underscore are used for utility comments,
	// and are immediately discarded by quake
		if( keyname[0] == '_' )
			continue;

		ED_ParseField( keyname, com_token, ent );
	}

	if( !init )
		memset( ent, 0, sizeof(*ent) );

	return data;
}


/*
================
G_FindTeams

Chain together all entities with a matching team field.

All but the first will have the FL_TEAMSLAVE flag set.
All but the last will have the teamchain field set to the next one
================
*/
void G_FindTeams( void )
{
	edict_t	*e, *e2, *chain;
	int		i, j;
	int		c, c2;

	c = 0;
	c2 = 0;
	for( i=1, e=game.edicts+i; i < game.numentities; i++, e++ )
	{
		if( !e->r.inuse )
			continue;
		if( !e->team )
			continue;
		if( e->flags & FL_TEAMSLAVE )
			continue;
		chain = e;
		e->teammaster = e;
		c++;
		c2++;
		for( j=i+1, e2=e+1; j < game.numentities; j++,e2++ )
		{
			if( !e2->r.inuse )
				continue;
			if( !e2->team )
				continue;
			if( e2->flags & FL_TEAMSLAVE )
				continue;
			if( !strcmp(e->team, e2->team) )
			{
				c2++;
				chain->teamchain = e2;
				e2->teammaster = e;
				chain = e2;
				e2->flags |= FL_TEAMSLAVE;
			}
		}
	}

	if( developer->integer )
		G_Printf ("%i teams with %i entities\n", c, c2);
}

/*
==============
SpawnMapEntities

Spawns all the entities in the map except the world
==============
*/
void G_SpawnMapEntities( qboolean worldspawn )
{
	edict_t		*ent = NULL;
	int			i = 0;
	char		*entities, *token;
	gitem_t		*item;

	// Reset Map Locations Names : wsw : jal 
	level.numLocations = 0;
	memset( level.locationNames, 0, sizeof(level.locationNames) );

	// pb: reset number of checkpoint
	level.nbcheckpoint=0;

	// location zero is unknown fallback
	G_RegisterMapLocationName( "someplace" );

	entities = game.map_entities;
	if( !entities )
		return;
	while( 1 )
	{
		// parse the opening brace	
		token = COM_Parse( &entities );
		if( !entities )
			break;
		if( token[0] != '{' )
			G_Error( "G_SpawnMapEntities: found %s when expecting {", token );
		
		if( !ent ) {
			if( worldspawn != qtrue )
				ent = G_Spawn();
			else
				ent = world;
		} else 
			ent = G_Spawn();
		
		entities = ED_ParseEdict( entities, ent );
		if( !Q_stricmp(ent->classname, "worldspawn") && !worldspawn ) {
			i++;
			G_FreeEdict(ent);
			continue;
		}

		if( (item = G_ItemForEntity(ent)) != NULL )
			PrecacheItem(item);
		
		if( !G_CanSpawnEntity(ent) ) {
			i++;
			G_FreeEdict(ent);
			continue;
		}
		
		G_CallSpawn(ent);

		// wsw : jal : locations names
		if( !Q_stricmp(ent->classname, "target_location") ) {
			if( ent->count > 0 && ent->count < 10 )
				G_RegisterMapLocationName( va("%c%c%s", Q_COLOR_ESCAPE, ent->count + '0', ent->message) );
			else
				G_RegisterMapLocationName( ent->message );
		}
	}

	G_FindTeams();
	
	// make sure server got the edicts data
	trap_LocateEntities( game.edicts, sizeof(game.edicts[0]), game.numentities, game.maxentities );

	// update *all* CS_LOCATIONS configstrings
	for( i = 0; i < MAX_LOCATIONS; i++ )
		trap_ConfigString( CS_LOCATIONS + i, level.locationNames[i] );

	// check number of checkpoints
	if(level.nbcheckpoint>MAX_RACE_CHECKPOINT)
	{
		G_Printf("Map contains to many target_checkpoint (found %d max %d) \n",
			level.nbcheckpoint,MAX_RACE_CHECKPOINT);
	}

	if(developer->integer)
	{
		G_Printf("Found %d target_checkpoint\n",level.nbcheckpoint);
	}
}

/*
==============
SpawnEntities

Creates a server's entity / program execution context by
parsing textual entity definitions out of an ent file.
==============
*/
void SpawnEntities( char *mapname, char *entities, int entstrlen, char *spawnpoint )
{
	int			i;

	G_EmptyLevelPool();

	memset( &level, 0, sizeof(level) );
	memset( game.edicts, 0, game.maxentities * sizeof(game.edicts[0]) );
	game.map_entities = NULL;

	Q_strncpyz( level.mapname, mapname, sizeof(level.mapname) );
	Q_strncpyz( game.spawnpoint, spawnpoint, sizeof(game.spawnpoint) );

	// set client fields on player ents
	for( i=0 ; i<game.maxclients ; i++ )
		game.edicts[i+1].r.client = game.clients + i;

	if( !entities )
		G_Error( "SpawnEntities: Invalid worldspawn" );

	// wsw : Medar : hmpf, can't run G_Gametype_Update before we have the entities
	// so just change game.gametype here and do the rest later
	if( g_gametype->latched_string ) {
		game.gametype = GS_Gametype_FindByShortName(g_gametype->latched_string);
		if( game.gametype < GAMETYPE_DM || game.gametype >= GAMETYPE_TOTAL )
			game.gametype = GAMETYPE_DM;
	}

	// make a copy of the raw entities string
	game.map_entities = G_LevelMalloc( entstrlen );
	memcpy( game.map_entities, entities, entstrlen );
	G_SpawnMapEntities( qtrue );

	G_Gametype_Update();

	//newgametypes
	G_Match_NewMap();
	//AI_NewMap();//MbotGame
}


//===================================================================

/*QUAKED worldspawn (0 0 0) ?

Only used for the world.
"sky"	environment map name
"skyaxis"	vector axis for rotating sky
"skyrotate"	speed of rotation in degrees/second
"sounds"	music cd track number
"gravity"	800 is default gravity
"message"	text to print at user logon
*/
void SP_worldspawn( edict_t *ent )
{
	ent->movetype = MOVETYPE_PUSH;
	ent->r.solid = SOLID_BSP;
	ent->r.inuse = qtrue;			// since the world doesn't use G_Spawn()
	VectorClear( ent->s.origin );
	VectorClear( ent->s.angles );
	trap_SetBrushModel( ent, "*0" );	// sets mins / maxs and modelindex 1

	//---------------

	// reserve some spots for dead player bodies for deathmatch
	InitBodyQue();

	// set configstrings for items
	SetItemNames();

	if( st.nextmap ) 
		Q_strncpyz( level.nextmap, st.nextmap, sizeof(level.nextmap) );

	// make some data visible to the server
	if( ent->message && ent->message[0] ) {
		trap_ConfigString( CS_MESSAGE, ent->message );
		Q_strncpyz( level.level_name, ent->message, sizeof(level.level_name) );
	}
	else {
		trap_ConfigString( CS_MESSAGE, level.mapname );
		Q_strncpyz( level.level_name, level.mapname, sizeof(level.level_name) );
	}

	// send music
	if( st.music ) {
		trap_ConfigString( CS_AUDIOTRACK, st.music );
	}

	trap_ConfigString( CS_MAPNAME, level.mapname );
	trap_ConfigString( CS_MAXCLIENTS, va("%i", game.maxclients ) );
	trap_ConfigString( CS_HOSTNAME, trap_Cvar_VariableString("sv_hostname") );

	//---------------

	if( st.gravity )
		trap_Cvar_Set( "g_gravity", st.gravity );

	// items that are not in the map, but will still be used
	PrecacheItem( GS_FindItemByName("Gunblade") );
	PrecacheItem( GS_FindItemByName("Ammo Pack") );


	//jal[start] splitmodels 
	// viewable weapon models
	// THIS ORDER MUST MATCH THE DEFINES IN gs_public.h
	// you can add more, max 255

	trap_ModelIndex ( "#gunblade/v_gunblade.md3" );			// WEAP_GUNBLADE
	trap_ModelIndex ( "#shockwave/v_shockwave.md3" );		// WEAP_SHOCKWAVE
	trap_ModelIndex ( "#riotgun/v_riotgun.md3" );			// WEAP_RIOTGUN
	trap_ModelIndex ( "#glauncher/v_glauncher.md3" );		// WEAP_GRENADELAUNCHER
	trap_ModelIndex ( "#rlauncher/v_rlauncher.md3" );		// WEAP_ROCKETLAUNCHER
	trap_ModelIndex ( "#plasmagun/v_plasmagun.md3" );		// WEAP_PLASMAGUN
	trap_ModelIndex ( "#lasergun/v_lasergun.md3" );			// WEAP_LASERGUN
	trap_ModelIndex ( "#electrobolt/v_electrobolt.md3" );	// WEAP_ELECTROBOLT

	//-------------------

	// precache our basic player models, they are just a very few
	trap_ModelIndex( "$models/players/viciious" );
	trap_ModelIndex( "$models/players/monada" );
	trap_ModelIndex( "$models/players/silverclaw" );

	trap_SkinIndex( "models/players/viciious/default" );
	trap_SkinIndex( "models/players/monada/default" );
	trap_SkinIndex( "models/players/silverclaw/default" );

	//-------------------

	// jalfixme : most of these sounds can be played from the clients

	trap_SoundIndex (S_WORLD_WATER_IN);			// feet hitting water
	trap_SoundIndex (S_WORLD_WATER_OUT);			// feet leaving water
	trap_SoundIndex (S_WORLD_UNDERWATER);

	trap_SoundIndex (S_WORLD_SLIME_IN);
	trap_SoundIndex (S_WORLD_SLIME_OUT);
	trap_SoundIndex (S_WORLD_UNDERSLIME);

	trap_SoundIndex (S_WORLD_LAVA_IN);
	trap_SoundIndex (S_WORLD_LAVA_OUT);
	trap_SoundIndex (S_WORLD_UNDERLAVA);

	trap_SoundIndex (va(S_PLAYER_BURN_1_to_2, 1));
	trap_SoundIndex (va(S_PLAYER_BURN_1_to_2, 2));

	//wsw: pb disable unreferenced sounds
	//trap_SoundIndex (S_LAND);				// landing thud
	trap_SoundIndex (S_HIT_WATER);

	trap_SoundIndex (S_WEAPON_NOAMMO);

	// announcer

	// readyup
	trap_SoundIndex ( S_ANNOUNCER_READY_UP_POLITE );
	trap_SoundIndex ( S_ANNOUNCER_READY_UP_PISSEDOFF );

	// countdown
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_GET_READY_TO_FIGHT_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_GET_READY_TO_FIGHT_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_READY_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_READY_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 1, 1));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 3, 1));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 1, 2));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_COUNT_1_to_3_SET_1_to_2, 3, 2));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_FIGHT_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_COUNTDOWN_FIGHT_1_to_2, 2));

	// postmatch
	trap_SoundIndex (va(S_ANNOUNCER_POSTMATCH_GAMEOVER_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_POSTMATCH_GAMEOVER_1_to_2, 2));

	// timeout
	trap_SoundIndex (va(S_ANNOUNCER_TIMEOUT_MATCH_RESUMED_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_TIMEOUT_MATCH_RESUMED_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_TIMEOUT_TIMEOUT_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_TIMEOUT_TIMEOUT_1_to_2, 2));

	// callvote
	trap_SoundIndex (va(S_ANNOUNCER_CALLVOTE_CALLED_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CALLVOTE_CALLED_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CALLVOTE_FAILED_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CALLVOTE_FAILED_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CALLVOTE_PASSED_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CALLVOTE_PASSED_1_to_2, 2));
	trap_SoundIndex (S_ANNOUNCER_CALLVOTE_VOTE_NOW);

	// overtime
	trap_SoundIndex (S_ANNOUNCER_OVERTIME_GOING_TO_OVERTIME);
	trap_SoundIndex (S_ANNOUNCER_OVERTIME_OVERTIME);
	trap_SoundIndex (va(S_ANNOUNCER_OVERTIME_SUDDENDEATH_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_OVERTIME_SUDDENDEATH_1_to_2, 2));

	// score
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TAKEN_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TAKEN_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_TAKEN_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_TAKEN_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_LOST_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_LOST_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_LOST_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_LOST_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TIED_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TIED_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_TIED_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_TIED_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_TIED_LEAD_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_TIED_LEAD_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 1, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 1, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 3, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 3, 2));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 4, 1));
	trap_SoundIndex (va(S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, 4, 2));

	// ctf
	trap_SoundIndex (va(S_ANNOUNCER_CTF_RECOVERY_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_RECOVERY_1_to_2, 2));
	trap_SoundIndex (S_ANNOUNCER_CTF_RECOVERY_TEAM);
	trap_SoundIndex (S_ANNOUNCER_CTF_RECOVERY_ENEMY);
	trap_SoundIndex (S_ANNOUNCER_CTF_FLAG_TAKEN);
	trap_SoundIndex (va(S_ANNOUNCER_CTF_FLAG_TAKEN_TEAM_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_FLAG_TAKEN_TEAM_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_FLAG_TAKEN_ENEMY_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_FLAG_TAKEN_ENEMY_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_SCORE_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_SCORE_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_SCORE_TEAM_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_SCORE_TEAM_1_to_2, 2));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_SCORE_ENEMY_1_to_2, 1));
	trap_SoundIndex (va(S_ANNOUNCER_CTF_SCORE_ENEMY_1_to_2, 2));

	// FIXME: Temporarily use normal gib until the head is fixed
	trap_ModelIndex ("models/objects/gibs/gib1/gib1.md3");

	//
	// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.
	//

	// 0 normal
	trap_ConfigString( CS_LIGHTS+0, "m" );

	// 1 FLICKER (first variety)
	trap_ConfigString( CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo" );

	// 2 SLOW STRONG PULSE
	trap_ConfigString( CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba" );

	// 3 CANDLE (first variety)
	trap_ConfigString( CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg" );

	// 4 FAST STROBE
	trap_ConfigString( CS_LIGHTS+4, "mamamamamama" );

	// 5 GENTLE PULSE 1
	trap_ConfigString( CS_LIGHTS+5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj" );

	// 6 FLICKER (second variety)
	trap_ConfigString( CS_LIGHTS+6, "nmonqnmomnmomomno" );

	// 7 CANDLE (second variety)
	trap_ConfigString( CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm" );

	// 8 CANDLE (third variety)
	trap_ConfigString( CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa" );

	// 9 SLOW STROBE (fourth variety)
	trap_ConfigString( CS_LIGHTS+9, "aaaaaaaazzzzzzzz" );

	// 10 FLUORESCENT FLICKER
	trap_ConfigString( CS_LIGHTS+10, "mmamammmmammamamaaamammma" );

	// 11 SLOW PULSE NOT FADE TO BLACK
	trap_ConfigString( CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba" );

	// styles 32-62 are assigned by the light program for switchable lights

	// 63 testing
	trap_ConfigString( CS_LIGHTS+63, "a" );

	G_InitGameCommands(); // wsw : jal : gamecommandscompletion
}
