// Copyright (C) 1999-2000 Id Software, Inc. // #include "g_local.h" #include "g_spawn.h" #include "g_items.h" #include "g_lua.h" #include "g_syscalls.h" 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 }, { "paintarget", FOFS(paintarget), F_LSTRING }, { "targetname", FOFS(targetname), F_LSTRING }, { "message", FOFS(message), F_LSTRING }, { "team", FOFS(team), F_LSTRING }, { "splashDamage", FOFS(splashDamage), F_INT }, { "splashRadius", FOFS(splashRadius), F_INT }, { "wait", FOFS(wait), F_FLOAT }, { "random", FOFS(random), F_FLOAT }, { "count", FOFS(count), F_INT }, { "material", FOFS(s.powerups), 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 }, { "pos2", FOFS(pos2), F_VECTOR }, { "apos1", FOFS(apos1), F_VECTOR }, { "apos2", FOFS(apos2), F_VECTOR }, { "swapname", FOFS(swapname), F_LSTRING }, //RPG-X Modification | Phenix | 13/06/2004 { "truename", FOFS(truename), F_LSTRING }, { "falsename", FOFS(falsename), F_LSTRING }, { "truetarget", FOFS(truetarget), F_LSTRING }, { "falsetarget", FOFS(falsetarget), F_LSTRING }, { "booleanstate", FOFS(booleanstate), F_INT }, { "distance", FOFS(distance), F_FLOAT }, // VALKYRIE: for rotating doors { "targetname2", FOFS(targetname2), F_LSTRING }, { "bluename", FOFS(bluename), F_LSTRING }, { "greensnd", FOFS(greensound), F_LSTRING }, { "yellowsnd", FOFS(yellowsound), F_LSTRING }, { "redsnd", FOFS(redsound), F_LSTRING }, { "bluesnd", FOFS(bluesound), F_LSTRING }, { "targetShaderName", FOFS(targetShaderName), F_LSTRING }, { "targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING }, #ifdef G_LUA { "luaThink", FOFS(luaThink), F_LSTRING }, { "luaTouch", FOFS(luaTouch), F_LSTRING }, { "luaUse", FOFS(luaUse), F_LSTRING }, { "luaHurt", FOFS(luaHurt), F_LSTRING }, { "luaDie", FOFS(luaDie), F_LSTRING }, { "luaFree", FOFS(luaFree), F_LSTRING }, { "luaTrigger", FOFS(luaTrigger), F_LSTRING }, { "luaReached", FOFS(luaReached), F_LSTRING }, { "luaReachedAngular", FOFS(luaReachedAngular), F_LSTRING }, { "luaSpawn", FOFS(luaSpawn), F_LSTRING }, { "luaParm1", FOFS(luaParm1), F_LSTRING }, { "luaParm2", FOFS(luaParm2), F_LSTRING }, { "luaParm3", FOFS(luaParm3), F_LSTRING }, { "luaParm4", FOFS(luaParm4), F_LSTRING }, { "luaEntity", FOFS(luaEntity), F_INT }, #endif { "startRGBA", FOFS(startRGBA), F_VECTOR4 }, { "finalRGBA", FOFS(finalRGBA), F_VECTOR4 }, { NULL } }; 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 (!strcmp(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; } qboolean G_SpawnVector4(const char *key, const char *defaultString, float *out) { char *s; qboolean present; present = G_SpawnString(key, defaultString, &s); sscanf(s, "%f %f %f %f", &out[0], &out[1], &out[2], &out[3]); return present; } typedef struct { char *name; void(*spawn)(gentity_t *ent); } spawn_t; 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_deathmatch }, { "NPC_BioHulk", SP_info_player_deathmatch }, { "NPC_starfleet", SP_info_player_deathmatch }, { "NPC_starfleet_random", SP_info_player_deathmatch }, { "NPC_Tuvok", SP_info_player_deathmatch }, { "NPC_Kim", SP_info_player_deathmatch }, { "NPC_Doctor", SP_info_player_deathmatch }, { "NPC_Paris", SP_info_player_deathmatch }, { "NPC_Torres", SP_info_player_deathmatch }, { "NPC_Janeway", SP_info_player_deathmatch }, { "NPC_Seven", SP_info_player_deathmatch }, { "NPC_Chakotay", SP_info_player_deathmatch }, { "NPC_Neelix", SP_info_player_deathmatch }, { "NPC_Vorik", SP_info_player_deathmatch }, { "NPC_Foster", SP_info_player_deathmatch }, { "NPC_Munro", SP_info_player_deathmatch }, { "NPC_MunroScav", SP_info_player_deathmatch }, { "NPC_Telsia", SP_info_player_deathmatch }, { "NPC_Biessman", SP_info_player_deathmatch }, { "NPC_Chang", SP_info_player_deathmatch }, { "NPC_Chell", SP_info_player_deathmatch }, { "NPC_Jurot", SP_info_player_deathmatch }, { "NPC_borg", SP_info_player_deathmatch }, { "NPC_klingon", SP_info_player_deathmatch }, { "NPC_Malon", SP_info_player_deathmatch }, { "NPC_Hirogen", SP_info_player_deathmatch }, { "NPC_Hirogen_Alpha", SP_info_player_deathmatch }, { "NPC_Imperial", SP_info_player_deathmatch }, { "NPC_Imperial_Blue", SP_info_player_deathmatch }, { "NPC_Imperial_Gold", SP_info_player_deathmatch }, { "NPC_Imperial_Raider", SP_info_player_deathmatch }, { "NPC_Stasis", SP_info_player_deathmatch }, { "NPC_Species8472", SP_info_player_deathmatch }, { "NPC_Reaver", SP_info_player_deathmatch }, { "NPC_ReaverGuard", SP_info_player_deathmatch }, { "NPC_Avatar", SP_info_player_deathmatch }, { "NPC_Vohrsoth", SP_info_player_deathmatch }, { "NPC_Desperado", SP_info_player_deathmatch }, { "NPC_Paladin", SP_info_player_deathmatch }, { "NPC_ChaoticaGuard", SP_info_player_deathmatch }, { "NPC_Chaotica", SP_info_player_deathmatch }, { "NPC_CaptainProton", SP_info_player_deathmatch }, { "NPC_SatansRobot", SP_info_player_deathmatch }, { "NPC_Buster", SP_info_player_deathmatch }, { "NPC_Goodheart", SP_info_player_deathmatch }, { "info_player_deathmatch", SP_info_player_deathmatch }, { "info_player_intermission", SP_info_player_intermission }, { "info_null", SP_info_null }, { "info_notnull", SP_info_notnull }, { "info_camp", SP_info_camp }, { "func_plat", SP_func_plat }, { "func_button", SP_func_button }, { "func_door", SP_func_door }, { "func_forcefield", SP_func_forcefield }, { "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? { "func_usable", SP_func_usable }, { "func_breakable", SP_func_breakable }, { "func_door_rotating", SP_func_door_rotating }, { "func_brushmodel", SP_func_brushmodel }, // Hijack me haha { "func_lightchange", SP_func_lightchange }, { "func_targetmover", SP_func_targetmover }, { "func_stasis_door", SP_func_stasis_door }, // 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_transporter", SP_trigger_transporter }, { "trigger_radiation", SP_trigger_radiation }, // 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_teleporter", SP_target_teleporter }, { "target_relay", SP_target_relay }, { "target_kill", SP_target_kill }, { "target_position", SP_info_notnull }, { "target_location", SP_target_location }, { "target_push", SP_target_push }, { "target_counter", SP_target_counter }, { "target_objective", SP_target_objective }, { "target_boolean", SP_target_boolean }, // RPG-X | Phenix | 13/06/2004 { "target_gravity", SP_target_gravity }, //RPG-X Phenix/J2J 03/08/04 { "target_shake", SP_target_shake }, //RPG-X Phenix/J2J 16/11/04 { "target_evosuit", SP_target_evosuit }, //RPG-X Phenix/J2J 16/11/04 - RedTechie: Fixed a typo you have evo suit pointing to shake function { "target_turbolift", SP_target_turbolift }, { "target_doorlock", SP_target_doorLock }, //RPG-X | GSIO01 | 08/05/2009 { "target_repair", SP_target_repair }, //RPG-X | GSIO01 | 09/05/2009 { "target_alert", SP_target_alert }, //RPG-X | GSIO01 { "target_warp", SP_target_warp }, //RPG-X | GSIO01 | 19/05/2009 { "target_deactivate", SP_target_deactivate }, { "target_serverchange", SP_target_serverchange }, { "target_levelchange", SP_target_levelchange }, { "target_shaderremap", SP_target_shaderremap }, { "target_selfdestruct", SP_target_selfdestruct }, { "target_safezone", SP_target_zone }, { "target_zone", SP_target_zone }, { "target_shiphealth", SP_target_shiphealth }, { "target_sequence", SP_target_sequence }, { "light", SP_light }, { "path_corner", SP_path_corner }, { "misc_teleporter_dest", SP_info_notnull }, { "misc_model", SP_misc_model }, { "misc_model_breakable", SP_misc_model_breakable }, { "misc_portal_surface", SP_misc_portal_surface }, { "misc_portal_camera", SP_misc_portal_camera }, { "misc_turret", SP_misc_turret }, { "misc_laser", SP_laser_arm }, { "misc_ammo_station", SP_misc_ammo_station }, { "shooter_rocket", SP_shooter_rocket }, { "shooter_grenade", SP_shooter_grenade }, { "shooter_plasma", SP_shooter_plasma }, { "shooter_torpedo", SP_shooter_torpedo }, { "team_CTF_redplayer", SP_info_player_deathmatch }, { "team_CTF_blueplayer", SP_info_player_deathmatch }, { "team_CTF_redspawn", SP_info_player_deathmatch }, { "team_CTF_bluespawn", SP_info_player_deathmatch }, // extra Trek stuff { "fx_spark", SP_fx_spark }, { "fx_steam", SP_fx_steam }, { "fx_bolt", SP_fx_bolt }, { "fx_transporter", SP_fx_transporter }, { "fx_drip", SP_fx_drip }, { "fx_fountain", SP_fx_fountain }, { "fx_surface_explosion", SP_fx_surface_explosion }, { "fx_blow_chunks", SP_fx_blow_chunks }, { "fx_smoke", SP_fx_smoke }, { "fx_electrical_explosion", SP_fx_electrical_explosion }, { "fx_phaser", SP_fx_phaser }, { "fx_torpedo", SP_fx_torpedo }, { "fx_particle_fire", SP_fx_particleFire }, { "fx_fire", SP_fx_fire }, // Additional ports from SP by Harry Young { "fx_cooking_steam", SP_fx_cooking_steam }, { "fx_elecfire", SP_fx_electricfire }, //{"fx_forge_bolt", SP_fx_forge_bolt}, //{"fx_plasma", SP_fx_plasma}, //{"fx_energy_stream", SP_fx_stream}, //{"fx_transporter_stream", SP_fx_transporter_stream}, //{"fx_explosion_trail", SP_fx_explosion_trail}, //{"fx_borg_energy_beam", SP_fx_borg_energy_beam}, { "fx_shimmery_thing", SP_fx_shimmery_thing }, { "fx_borg_bolt", SP_fx_borg_bolt }, { "func_mover", SP_func_mover }, { "path_point", SP_path_point }, // ui entities { "ui_transporter", SP_ui_transporter }, { "ui_msd", SP_ui_msd }, { "ui_holodeck", SP_ui_holodeck }, { "ref_tag", SP_info_notnull }, // cinematic entities { "cinematic_camera", SP_cinematic_camera }, { 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 == NULL) { 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)) { // found it if (item->giType == IT_TEAM && g_gametype.integer != GT_CTF) { return qfalse; } G_SpawnItem(ent, item); #ifdef G_LUA if (ent->luaSpawn) { LuaHook_G_EntitySpawn(ent->luaSpawn, ent->s.number); } #endif 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; } } if (Q_stricmp("item_botroam", ent->classname) != 0) {//suppress error message about botroams as those are actually valid DEVELOPER(G_Printf(S_COLOR_RED "%s doesn't have a spawn function\n", ent->classname);); } #ifdef G_LUA if (ent->luaSpawn) { LuaHook_G_EntitySpawn(ent->luaSpawn, ent->s.number); } #endif 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; if (string == NULL) { return NULL; } l = strlen(string) + 1; newb = (char *)G_Alloc(l); if (newb == NULL) { return NULL; } 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 =============== */ qboolean G_ParseField(const char *key, const char *value, gentity_t *ent) { field_t *f; byte *b; float v; vec3_t vec; vec4_t vec4; 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_VECTOR4: sscanf(value, "%f %f %f %f", &vec4[0], &vec[1], &vec[2], &vec[3]); ((float *)(b + f->ofs))[0] = vec4[0]; ((float *)(b + f->ofs))[0] = vec4[1]; ((float *)(b + f->ofs))[0] = vec4[2]; ((float *)(b + f->ofs))[0] = vec4[3]; 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: return qfalse; break; } 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(void) { int i; gentity_t *ent; char *s, *value, *gametypeName; static char *gametypeNames[] = { "ffa", "tournament", "single", "team", "ctf" }; // 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" / "notfree" flags if (g_gametype.integer == GT_SINGLE_PLAYER) { G_SpawnInt("notsingle", "0", &i); if (i) { G_FreeEntity(ent); return; } } 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; } } if (G_SpawnString("gametype", "", &value)) { if (g_gametype.integer >= GT_FFA && g_gametype.integer < GT_MAX_GAME_TYPE) { gametypeName = gametypeNames[g_gametype.integer]; s = strstr(value, gametypeName); if (!s) { G_FreeEntity(ent); return; } } } // move editor origin to pos VectorCopy(ent->s.origin, ent->s.pos.trBase); VectorCopy(ent->s.origin, ent->r.currentOrigin); // if we didn't get a classname, don't bother spawning anything if (!G_CallSpawn(ent)) { G_FreeEntity(ent); } } /* ==================== G_AddSpawnVarToken ==================== */ char *G_AddSpawnVarToken(const char *string) { int l; char *dest; l = strlen(string); if (level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS) { G_Error("G_AddSpawnVarToken: MAX_SPAWN_VARS"); } dest = level.spawnVarChars + level.numSpawnVarChars; memcpy(dest, string, l + 1); level.numSpawnVarChars += l + 1; return dest; } /* ==================== G_ParseSpawnVars Parses a brace bounded set of key / value pairs out of the level's entity strings into level.spawnVars[] This does not actually spawn an entity. ==================== */ qboolean G_ParseSpawnVars(void) { char keyname[MAX_TOKEN_CHARS]; char com_token[MAX_TOKEN_CHARS]; level.numSpawnVars = 0; level.numSpawnVarChars = 0; // parse the opening brace if (!trap_GetEntityToken(com_token, sizeof(com_token))) { // end of spawn string return qfalse; } if (com_token[0] != '{') { G_Error("G_ParseSpawnVars: found %s when expecting {", com_token); } // go through all the key / value pairs while (1) { // parse key if (!trap_GetEntityToken(keyname, sizeof(keyname))) { Com_Printf(S_COLOR_RED "G_ParseSpawnVars: Keyname - %s\n", keyname); G_Error("G_ParseSpawnVars: EOF without closing brace"); } if (keyname[0] == '}') { break; } // parse value if (!trap_GetEntityToken(com_token, sizeof(com_token))) { Com_Printf(S_COLOR_RED "G_ParseSpawnVars: Token - %s\n", 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) ? -----DESCRIPTION----- Every map should have exactly one worldspawn. It holds some general information on the map. -----SPAWNFLAGS----- none -----KEYS----- "music" - path to WAV or MP3 files (e.g. "music\intro.mp3 music\loopfile.mp3") "gravity" - 800 is default gravity "message" - Text to print during connection process Keys irrelevant for RPG-X "fraglimit" - overrides server's limit "capturelimit" - overrides server's capturelimit (use with team AddScores) "timelimit" - overrides server's timelimit "timelimitWinningTeam" - "red" or "blue" - this team will win when the timelimit runs out q3map2: "_blocksize" block size for unconditional BSP subdivisions "_celshader" use the specified cel shader for the world "_lightmapscale" set the lightmapscale for the world "_ignoreleaks" when set, no leak test is performed "_foghull" must be set to a sky shader when _fog is used "_fog" if set, the whole map is fogged using the given shader name "gridsize" resolution of the light grid "_ambient" amount of ambient light "_minvertexlight" amount of minimum vertex light "_mingridlight" amount of minimum grid light "_minlight" amount of minimum light "_keepLights" if set, light entities are not stripped from the BSP file when compiling "_style42rgbgen" |rgbGen|-like shader definition string for light style 42 (works the same way for all style numbers) "_style42alphagen" |alphaGen|-like shader definition string for light style 42 (works the same way for all style numbers) */ 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, GAME_VERSION); 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 trap_SetConfigstring(CS_CON_FAIL, rpg_passMessage.string); G_SpawnString("gravity", "800", &s); trap_Cvar_Set("g_gravity", s); //FIXME: in some cases, want to carry over from previous running of this map G_SpawnString("fraglimit", "0", &s); if (s && atoi(s) != 0) { trap_Cvar_Set("fraglimit", s); } G_SpawnString("capturelimit", "0", &s); if (s && atoi(s) != 0) { trap_Cvar_Set("capturelimit", s); } G_SpawnString("timelimit", "0", &s); if (s && atoi(s) != 0) { trap_Cvar_Set("timelimit", s); } G_SpawnString("timelimitWinningTeam", "", &s); if (s) { trap_Cvar_Set("timelimitWinningTeam", s); } 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) { level.warmupTime = 0; } } /* ============== 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 }