// Copyright (C) 1999-2000 Id Software, Inc.
//

#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_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_start (gentity_t *ent);
void SP_info_player_deathmatch (gentity_t *ent);
void SP_info_player_intermission (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 );
// Begin - Hand Grenade Shooter - Banshee
void SP_shooter_handgrenade( 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 );

//#ifdef MISSIONPACK
void SP_team_blueobelisk( gentity_t *ent );
void SP_team_redobelisk( gentity_t *ent );
void SP_team_neutralobelisk( gentity_t *ent );

void SP_team_neutralbase( gentity_t *ent );

//#else
void SP_team_bluebase( gentity_t *ent );
void SP_team_redbase( gentity_t *ent );
//#endif
void SP_item_botroam( gentity_t *ent ) {}
;//<---  INCIN -- what is this

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},
                       {"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},
                       // Begin - Hand grenade - Banshee
                       {"shooter_handgrenade", SP_shooter_handgrenade},
                       // End - Banshee

#ifdef MISSIONPACK
                       {"team_redobelisk", SP_team_redobelisk},
                       {"team_blueobelisk", SP_team_blueobelisk},
                       {"team_neutralobelisk", SP_team_neutralobelisk},
					   {"team_neutralbase", SP_team_neutralbase},
#endif
                       {"team_redbase", SP_team_redbase},
                       {"team_bluebase", SP_team_bluebase},
//#endif
                       {"item_botroam", SP_item_botroam},

                       {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 ) {
        G_Printf ("G_CallSpawn: NULL classname\n");
        return qfalse;
    }

    // Alliance: certain items will be inhibited in certain
    // configurations
    // using different version below

    // check item spawn functions
    for ( item=bg_itemlist+1 ; item->classname ; item++ ) {
        if ( !Q_stricmp(item->classname, ent->classname) ) {

            //G_Printf ("FinishSpawningItem: %s\n", item->classname);
			//edited by dio
            if((item->giType == IT_POWERUP  && !(g_battlePowerups.integer)) || g_dmflags.integer & DF_NO_IDPOWERUPS)
                return qfalse;

            if(item->giType == IT_POWERUP || item->giType == IT_PERSISTANT_POWERUP) {
                // should be 2-7
                //int pw = random() * 3.9 + 2.0;
                //int pw = random() * 4.9 + 2.0;
                //if (pw >= 7)
                //    pw = 7;

                //G_LogPrintf("randomly picked powerup 2-7: %d\n",
                  //          pw);
				int pw = item->giTag;

                item = BG_FindItemForPowerup(pw);
                ent->classname = item->classname;
            }

            G_SpawnItem( ent, item );
            return qtrue;
        }
    }

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

    G_Printf ("%s doesn't have a spawn function\n", ent->classname);
    return qfalse;
}
*/

qboolean G_CallSpawn( gentity_t *ent ) {
	spawn_t	*s;
	gitem_t	*item;

	if ( !ent->classname ) {
		G_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((item->giType == IT_POWERUP  && !(g_battlePowerups.integer))) //|| g_dmflags.integer & DF_NO_IDPOWERUPS)
                return qfalse;
			
			G_SpawnItem( ent, item );
			return qtrue;
		}
	}

	// check normal spawn functions
	for ( s=spawns ; s->name ; s++ ) {
		if ( !strcmp(s->name, ent->classname) ) {
			// found it
			s->spawn(ent);
			return qtrue;
		}
	}
	G_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 = G_Alloc( l );

    new_p = newb;

    // turn \n into a real linefeed
    for ( i=0 ; i< l ; i++ ) {
        if (string[i] == '\\' && i < l-1) {
            i++;
            if (string[i] == 'n') {
                *new_p++ = '\n';
            } else {
                *new_p++ = '\\';
            }
        } else {
            *new_p++ = string[i];
        }
    }

    return newb;
}




/*
===============
G_ParseField
 
Takes a key/value pair and sets the binary values
in a gentity
===============
*/
void G_ParseField( const char *key, const char *value, gentity_t *ent ) {
    field_t	*f;
    byte	*b;
    float	v;
    vec3_t	vec;

    for ( f=fields ; f->name ; f++ ) {
        if ( !Q_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_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;

#ifdef MISSIONPACK

    static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "rtf", "oneflag", "obelisk", "harvester"};
#else

    static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "rtf"};
#endif
    // 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_SpawnInt( "notteam", "0", &i );
        if ( i ) {
            G_FreeEntity( ent );
            return;
        }
    } else {
        G_SpawnInt( "notfree", "0", &i );
        if ( i ) {
            G_FreeEntity( ent );
            return;
        }
    }


#ifdef MISSIONPACK
	if(g_gametype.integer <= GT_RTF){
		G_SpawnInt( "notta", "0", &i );
		if ( i ) {
			G_FreeEntity( ent );
			return;
		}
	}
#endif 

	if(g_gametype.integer <= GT_RTF)
	{
		G_SpawnInt( "notq3a", "0", &i );
			if ( i ) {
				G_FreeEntity( ent );
				return;
			}
	}
//#endif

	if(g_gametype.integer >= GT_TEAM){
		if(Q_stricmp(ent->classname, "team_CTF_bluespawn") == 0 ||
		Q_stricmp(ent->classname, "team_CTF_blueplayer") == 0 ||
		Q_stricmp(ent->classname, "team_CTF_redspawn") == 0 ||
		Q_stricmp(ent->classname, "team_CTF_redplayer") == 0 )
		{
			BattleSpawnInit(ent);
		}
	}


    //INCIN -- INHIBIT spawning of grapples all together
    //dont allow spawning of grapples in maps..since we give them..
    //plus our code doesnt allow for pickup grapple items to be used..

    if(Q_stricmp(ent->classname , "weapon_offhandgrapplinghook") == 0) {
        G_FreeEntity( ent );
        return;
    }
    //dont allow spawning of grapples in maps..since we give them
    if(Q_stricmp(ent->classname , "weapon_grapplinghook") == 0) {
        G_FreeEntity( ent );
        return;
    }
    //INCIN
    //remove moving Platforms from maps
    if ( g_dmflags.integer & DF_NO_PLATS && Q_stricmp(ent->classname, "func_plat") == 0) {
        G_FreeEntity( ent );
        return;
    }
    //INCIN
    //removes doors from maps
    if ( g_dmflags.integer & DF_NO_DOORS && Q_stricmp(ent->classname, "func_door") == 0) {
        G_FreeEntity( ent );
        return;
    }

    if ( g_dmflags.integer & DF_NO_HOLDABLES) {
        if(Q_stricmp(ent->classname, "holdable_teleporter") == 0) {
            G_FreeEntity( ent );
            return;
        } else if(Q_stricmp(ent->classname, "holdable_medkit") == 0) {
            G_FreeEntity( ent );
            return;
        }
    }
    //INCIN
    // Remove ID Powerups from maps
    // dmflags 256
    // this will override settings from the ui
    if ( g_dmflags.integer & DF_NO_IDPOWERUPS) {
        if(Q_stricmp(ent->classname, "item_enviro") == 0) {
            G_FreeEntity( ent );
            return;
        } else if(Q_stricmp(ent->classname, "item_flight") == 0) {
            G_FreeEntity( ent );
            return;
        } else if(Q_stricmp(ent->classname, "item_haste") == 0) {
            G_FreeEntity( ent );
            return;
        } else if(Q_stricmp(ent->classname, "item_invis") == 0) {
            G_FreeEntity( ent );
            return;
        } else if(Q_stricmp(ent->classname, "item_quad") == 0) {
            G_FreeEntity( ent );
            return;
        } else if(Q_stricmp(ent->classname, "item_regen") == 0) {
            G_FreeEntity( ent );
            return;
        }

    }

    if( G_SpawnString( "gametype", NULL, &value ) ) {
        if( g_gametype.integer >= GT_FFA && g_gametype.integer < GT_MAX_GAME_TYPE ) {

            //INCIN this fixes alot of maps that get flags not showing up in RTF gamemode
            //how you say -- maps dont recognize the rtf variables that have been added
            //heres a clip from q3wc8.map that i extracted to see why the hell we werent getting flag

            /*
            //DO NOT UNCOMMENT THIS BLOCK -- FOR DOCUMENTATION PURPOSES ONLY
            {
            	"gametype" "ctf,oneflag"
            	"classname" "team_CTF_blueflag"
            	"angle" "45"
            	"origin" "-160 -2976 48"
            }
            //DO NOT UNCOMMENT THIS BLOCK -- FOR DOCUMENTATION PURPOSES ONLY
            */

            if(g_gametype.integer == GT_RTF)
                gametypeName = gametypeNames[g_gametype.integer - 1];//fakes GT_CTF
            else
                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_CHARS" );
    }

    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
// begin Hugo 
"grapple"   enable grapple with worldspawn
// end Hugo
 
*/
void SP_worldspawn( void ) {
    char	*s;

    G_SpawnString( "classname", "", &s );
    if ( Q_stricmp( s, "worldspawn" ) ) {
        G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" );
    }

    // make some data visible to connecting client
    trap_SetConfigstring( CS_GAME_VERSION, GAMEVERSION );
	

    trap_SetConfigstring( CS_LEVEL_START_TIME, va("%i", level.startTime ) );

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

    // BB
    G_SpawnString( "grapple", "1", &s );

    G_SpawnString( "g_blueTeam", "blue", &s );
    trap_Cvar_Set( "g_blueTeam", s );

    G_SpawnString( "g_redTeam", "red", &s );
    trap_Cvar_Set( "g_redTeam", 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( "atmosphere", "", &s );					//Atmospheric affects -- INCIN
    //trap_SetConfigstring( CS_ATMOSEFFECT, s );  	  	  	// Atmospheric effect

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

}


/*
==============
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
}

