/* =========================================================================== Copyright (C) 2008 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code 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. OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // g_spawn.cpp : spawner for scripts. #include "g_local.h" #include "g_spawn.h" #include "../script/scriptvariable.h" #include "object.h" #include "../qcommon/tiki.h" #include "../fgame/entity.h" #include "../fgame/actor.h" extern Event EV_Entity_Start; CLASS_DECLARATION(Class, SpawnArgs, NULL) { {NULL, NULL} }; Container> g_spawnlist; SpawnArgs::SpawnArgs() {} SpawnArgs::SpawnArgs(SpawnArgs& otherlist) { int num; int i; num = otherlist.NumArgs(); keyList.Resize(num); valueList.Resize(num); for (i = 1; i <= num; i++) { keyList.AddObject(otherlist.keyList.ObjectAt(i)); valueList.AddObject(otherlist.valueList.ObjectAt(i)); } } void SpawnArgs::Clear(void) { keyList.FreeObjectList(); valueList.FreeObjectList(); } /* ==================== Parse Parses spawnflags out of the given string, returning the new position. Clears out any previous args. ==================== */ char *SpawnArgs::Parse(char *data, bool bAllowUtils) { str keyname; const char *com_token; Clear(); // parse the opening brace com_token = COM_Parse(&data); if (!data) { return NULL; } if (com_token[0] != '{') { gi.Error(ERR_DROP, "SpawnArgs::Parse : found %s when expecting {", com_token); } // go through all the dictionary pairs while (1) { // parse key com_token = COM_Parse(&data); if (com_token[0] == '}') { break; } if (!data) { gi.Error(ERR_DROP, "SpawnArgs::Parse : EOF without closing brace"); } keyname = com_token; // parse value com_token = COM_Parse(&data); if (!data) { gi.Error(ERR_DROP, "SpawnArgs::Parse : EOF without closing brace"); } if (com_token[0] == '}') { gi.Error(ERR_DROP, "SpawnArgs::Parse : closing brace without data"); } // keynames with a leading underscore are used for utility comments, // and are immediately discarded by the game if (!bAllowUtils && keyname[0] == '_') { continue; } setArg(keyname.c_str(), com_token); } return data; } const char *SpawnArgs::getArg(const char *key, const char *defaultValue) { int i; int num; num = keyList.NumObjects(); for (i = 1; i <= num; i++) { if (keyList.ObjectAt(i) == key) { return valueList.ObjectAt(i); } } return defaultValue; } void SpawnArgs::setArg(const char *key, const char *value) { int i; int num; num = keyList.NumObjects(); for (i = 1; i <= num; i++) { if (keyList.ObjectAt(i) == key) { valueList.ObjectAt(i) = value; return; } } keyList.AddObject(str(key)); valueList.AddObject(str(value)); } void SpawnArgs::operator=(SpawnArgs& otherlist) { int num; int i; Clear(); num = otherlist.NumArgs(); keyList.Resize(num); valueList.Resize(num); for (i = 1; i <= num; i++) { keyList.AddObject(otherlist.keyList.ObjectAt(i)); valueList.AddObject(otherlist.valueList.ObjectAt(i)); } } int SpawnArgs::NumArgs(void) { return keyList.NumObjects(); } const char *SpawnArgs::getKey(int index) { return keyList.ObjectAt(index + 1); } const char *SpawnArgs::getValue(int index) { return valueList.ObjectAt(index + 1); } void SpawnArgs::Archive(Archiver& arc) { Class::Archive(arc); //keyList.Archive( arc ); //valueList.Archive( arc ); } /* =============== getClass Finds the spawn function for the entity and returns ClassDef * =============== */ ClassDef *SpawnArgs::getClassDef(qboolean *tikiWasStatic) { const char *classname; ClassDef *cls = NULL; dtiki_t *tiki = NULL; dtikianim_t *a = NULL; classname = getArg("classname"); if (tikiWasStatic) { *tikiWasStatic = false; } // // check normal spawn functions // see if the class name is stored within the model // if (classname) { // // explicitly inhibit lights // if (!Q_stricmp(classname, "light")) { // // HACK HACK HACK // hack to suppress a warning message // if (tikiWasStatic) { *tikiWasStatic = true; } return NULL; } cls = getClassForID(classname); if (!cls) { cls = getClass(classname); } } if (!cls) { const char *model; // // get Object in case we cannot find an alternative // cls = &Object::ClassInfo; model = getArg("model"); if (model) { int i; model = CanonicalTikiName(model); tiki = gi.modeltiki(model); // // get handle to def file // if (tiki) { const char *s; s = getArg("make_static"); if (s && atoi(s)) { // // if make_static then we don't want to spawn // if (tikiWasStatic) { *tikiWasStatic = true; } return NULL; } a = tiki->a; if (a->num_server_initcmds) { for (i = 0; i < a->num_server_initcmds; i++) { if (!Q_stricmp(a->server_initcmds[i].args[0], "classname")) { cls = getClass(a->server_initcmds[i].args[1]); break; } } if (i == a->num_server_initcmds) { gi.DPrintf( "Classname %s used, but 'classname' was not found in Initialization commands, using " "Object.\n", classname ); } } else { gi.DPrintf( "Classname %s used, but TIKI had no Initialization commands, using Object.\n", classname ); } } else { gi.DPrintf("Classname %s used, but model was not a TIKI, using Object.\n", classname); } } else { gi.DPrintf("Classname %s' used, but no model was set, using Object.\n", classname); } } return cls; } /* =============== Spawn Finds the spawn function for the entity and calls it. Returns pointer to Entity =============== */ Listener *SpawnArgs::Spawn(void) { SafePtr ent = (Listener *)SpawnInternal(); if (!ent) { return NULL; } ent->ProcessPendingEvents(); if (!ent) { // Fixed in OPM // Make sure to check the entity after processing events, // the ent could be deleted due to a posted remove event return NULL; } ent->ProcessEvent(EV_Entity_Start); return ent; } Listener *SpawnArgs::SpawnInternal(void) { str classname; ClassDef *cls; Listener *obj; Event *ev; int i; qboolean tikiWasStatic; // used to determine if entity was intentionally suppressed const char *key, *value; str keyname; int t1, t2; #ifdef WITH_SCRIPT_ENGINE unsigned int eventnum; EventDef *def; ScriptVariable *var; ScriptVariableList *varList; #endif if (!g_spawnentities->integer) { value = getArg("classname", NULL); // only spawn a playerstart or the world if (value) { if ((Q_stricmp(value, "info_player_start") && Q_stricmp(value, "worldspawn"))) { Com_Printf("%s\n", value); return NULL; } } } if (!g_spawnai->integer) { value = getArg("classname", NULL); //dont spawn ai_* if g_spawnai is 0 if (value) { if (!Q_stricmpn(value, "ai_", 3)) { Com_Printf("%s\n", value); return NULL; } } } t1 = gi.Milliseconds(); classname = getArg("classname", "Unspecified"); cls = getClassDef(&tikiWasStatic); if (!cls) { if (!tikiWasStatic) { gi.DPrintf("%s doesn't have a spawn function\n", classname.c_str()); if (g_scriptcheck->integer) { gi.Error(ERR_DROP, "Script check failed"); } } return NULL; } if (checkInheritance(PathNode::classinfostatic(), cls)) { // use the existing node that was loaded obj = PathManager.GetSpawnNode(cls); } else { obj = (Listener *)cls->newInstance(); } for (int i = g_spawnlist.NumObjects(); i > 0; i--) { if (g_spawnlist.ObjectAt(i) == NULL) { g_spawnlist.RemoveObjectAt(i); } } g_spawnlist.AddObject(obj); // post spawnarg events for (i = 0; i < NumArgs(); i++) { key = getKey(i); value = getValue(i); if (*key == '#') { // don't count the prefix keyname = (key + 1); #ifdef WITH_SCRIPT_ENGINE // initialize the object vars varList = obj->Vars(); var = varList->GetVariable(keyname); if (var) { Com_Printf( "^~^~^ variable '%s' already set with string value '%s' - failed to attempt to set with numeric " "value '%s'\n", keyname.c_str(), var->stringValue().c_str(), value ); continue; } eventnum = Event::FindSetterEventNum(keyname); if (!eventnum || !(def = cls->GetDef(eventnum))) { if (strchr(keyname.c_str(), '.')) { // it's a float varList->SetVariable(keyname, (float)atof(value)); } else { varList->SetVariable(keyname, atoi(value)); } } else if (def->type != EV_SETTER) { Com_Printf("^~^~^ Cannot set a read-only variable '%s'\n", keyname.c_str()); } else { ev = new Event(keyname, EV_SETTER); if (strchr(keyname.c_str(), '.')) { ev->AddFloat(atof(value)); } else { ev->AddInteger(atoi(value)); } obj->PostEvent(ev, EV_SPAWNARG); } #endif } else if (*key == '$') { keyname = (key + 1); #ifdef WITH_SCRIPT_ENGINE varList = obj->Vars(); var = varList->GetVariable(keyname); if (var) { Com_Printf( "^~^~^ variable '%s' already set with string value '%s' - failed to attempt to set with string " "value '%s'\n", keyname.c_str(), var->stringValue().c_str(), value ); continue; } eventnum = Event::FindSetterEventNum(keyname); if (!eventnum || !(def = cls->GetDef(eventnum))) { varList->SetVariable(keyname, value); } else if (def->type != EV_SETTER) { Com_Printf("^~^~^ Cannot set a read-only variable '%s'\n", keyname.c_str()); } else { ev = new Event(eventnum); ev->AddString(value); obj->PostEvent(ev, EV_SPAWNARG); } #endif } // if it is the "script" key, execute the script commands individually else if (!Q_stricmp(key, "script")) { char *ptr; char *token; ptr = const_cast(getValue(i)); while (1) { token = COM_ParseExt(&ptr, true); if (!token[0]) { break; } if (strchr(token, ':')) { gi.DPrintf("Label %s imbedded inside editor script for %s.\n", token, classname.c_str()); } else { ev = new Event(token); while (1) { token = COM_ParseExt(&ptr, false); if (!token[0]) { break; } ev->AddToken(token); } obj->PostEvent(ev, EV_SPAWNARG); } } } else { ev = new Event(key); ev->AddToken(value); if (Q_stricmp(key, "model") == 0) { if (obj->isSubclassOf(SimpleActor)) { if (Q_stricmpn(value, "human", 5) && Q_stricmpn(value, "models/human", 12) && Q_stricmpn(value, "models//human", 13) && Q_stricmpn(value, "animal", 6) && Q_stricmpn(value, "models/animal", 13)) { Com_Printf("^~^~^ model '%s' cannot be an actor - please fix the .tik file\n", value); delete obj; return NULL; } obj->PostEvent(ev, EV_PRIORITY_SPAWNACTOR); } else { obj->PostEvent(ev, EV_PRIORITY_SPAWNARG); } } else { if (obj->isSubclassOf(SimpleActor)) { obj->PostEvent(ev, EV_SPAWNACTOR); } else { obj->PostEvent(ev, EV_SPAWNARG); } } } } if (!obj) { gi.DPrintf("%s failed on newInstance\n", classname.c_str()); return NULL; } t2 = gi.Milliseconds(); if (t2 - t1 >= 100) { // // above 100 ms send a message to console // for (i = 0; i < NumArgs(); i++) { key = getKey(i); value = getValue(i); if (!Q_stricmp(key, "classname")) { Com_Printf("spawn '%s': %5.2f seconds\n", value, (t2 - t1) / 1000.f); break; } } } return obj; } ClassDef *FindClass(const char *name, qboolean *isModel) { ClassDef *cls; *isModel = false; // first lets see if it is a registered class name cls = getClass(name); if (!cls) { SpawnArgs args; // if that didn't work lets try to resolve it as a model args.setArg("model", name); cls = args.getClassDef(); if (cls) { *isModel = true; } } return cls; } /* ============== G_InitClientPersistant This is only called when the game first initializes in single player, but is called after each death and level change in deathmatch ============== */ void G_InitClientPersistant(gclient_t *client) { memset(&client->pers, 0, sizeof(client->pers)); } #if 0 # include "../qcommon/tiki_local.h" // fixme! 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; } gfield_t; gfield_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_rotatingdoor (gentity_t *ent); // IneQuation 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 ); 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 ); # endif void SP_item_botroam( 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_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_rotatingdoor", SP_func_rotatingdoor}, // IneQuation {"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}, // IneQuation {"info_player_axis", SP_team_CTF_redspawn}, {"info_player_allied", SP_team_CTF_bluespawn}, # ifdef MISSIONPACK {"team_redobelisk", SP_team_redobelisk}, {"team_blueobelisk", SP_team_blueobelisk}, {"team_neutralobelisk", SP_team_neutralobelisk}, # endif {"item_botroam", SP_item_botroam}, {NULL, 0} }; const char *G_FixTIKIPath(const char *in) { static char path[256]; void *buffer; if(in[0] == '*') return in; // that's a bsp inline model if(gi.FS_ReadFile( in, &buffer ) > 0) { return in; } Q_strncpyz(path,"models/", sizeof(path)); Q_strcat(path,sizeof(path),in); if( gi.FS_ReadFile( in, &buffer ) > 0 ) { return path; } return in; } /* =============== 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; tiki_t *tiki; if ( !ent->classname ) { G_Printf ("G_CallSpawn: NULL classname\n"); return qfalse; } // check normal spawn functions for ( s=spawns ; s->name ; s++ ) { if ( !strcmp(s->name, ent->classname) ) { // found it s->spawn(ent); return qtrue; } } // try to spawn a tiki model if(ent->model && ent->model[0] /*&& ent->model[0] != '*'*/) { if(ent->model[0] != '*') { char *ptr1, *ptr2; ptr1 = ent->model; ptr2 = ent->model; // su44: fix double '/' in TIKI paths from entities lump. // The same issue is also in R_LoadStaticModels. while(*ptr1) { if(ptr1[0] == '/' && ptr1[1] == '/') { ptr1++; } *ptr2 = *ptr1; ptr1++; ptr2++; } *ptr2 = 0; } # if 0 tiki = gi.TIKI_RegisterModel(ent->model); if(!tiki) { tiki = gi.TIKI_RegisterModel(va("models/%s",ent->model)); } if(tiki) { float dummy; ent->s.modelindex = G_ModelIndex(tiki->name); ent->s.eType = ET_MODELANIM; gi.TIKI_AppendFrameBoundsAndRadius(tiki,0,0,&dummy,ent->r.mins); ent->s.frameInfo[0].weight = 1.f; G_SetOrigin( ent, ent->s.origin ); VectorCopy( ent->s.angles, ent->s.angles ); gi.LinkEntity (ent); return qtrue; } # else ent->s.modelindex = G_ModelIndex( ( char * )G_FixTIKIPath( ent->model ) ); ent->s.eType = ET_MODELANIM; //if(ent->model[0]=='*') { // gi.SetBrushModel(ent, ent->model); //} else { VectorSet (ent->r.mins, -128, -128, -128); VectorSet (ent->r.maxs, 128, 128, 128); } ent->s.frameInfo[0].weight = 1.f; G_SetOrigin( ent, ent->s.origin ); gi.linkentity (ent); return qtrue; # endif } 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 = ( char * )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 ) { gfield_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; static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester", "teamtournament"}; // 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 G_SpawnInt( "notta", "0", &i ); if ( i ) { G_FreeEntity( ent ); return; } # else G_SpawnInt( "notq3a", "0", &i ); if ( i ) { G_FreeEntity( ent ); return; } # endif if( G_SpawnString( "gametype", NULL, &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->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; # if 0 // parse the opening brace if ( !gi.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 ( !gi.GetEntityToken( keyname, sizeof( keyname ) ) ) { G_Error( "G_ParseSpawnVars: EOF without closing brace" ); } if ( keyname[0] == '}' ) { break; } // parse value if ( !gi.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++; } # endif 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 */ void SP_worldspawn( void ) { char *s; int i; 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 gi.SetConfigstring( CS_GAME_VERSION, GAME_VERSION ); gi.SetConfigstring( CS_LEVEL_START_TIME, va("%i", level.startTime ) ); G_SpawnString( "music", "", &s ); gi.SetConfigstring( CS_MUSIC, s ); G_SpawnString( "message", "", &s ); gi.SetConfigstring( CS_MESSAGE, s ); // map specific message gi.SetConfigstring( CS_MOTD, g_motd.string ); // message of the day G_SpawnString( "gravity", "800", &s ); gi.cvar_set( "g_gravity", s ); G_SpawnString( "enableDust", "0", &s ); gi.cvar_set( "g_enableDust", s ); G_SpawnString( "enableBreath", "0", &s ); gi.cvar_set( "g_enableBreath", s ); for( i = 0; i < level.numSpawnVars; i++ ) { Event *ev = new Event( level.spawnVars[ i ][ 0 ] ); ev->AddString( level.spawnVars[ i ][ 1 ] ); world->PostEvent( ev, EV_SPAWNARG ); } g_entities[ENTITYNUM_WORLD].s.number = ENTITYNUM_WORLD; g_entities[ENTITYNUM_WORLD].classname = "worldspawn"; // see if we want a warmup time gi.SetConfigstring( CS_WARMUP, "" ); if ( g_restarted.integer ) { gi.cvar_set( "g_restarted", "0" ); level.warmupTime = 0; } else if ( g_doWarmup.integer ) { // Turn it on level.warmupTime = -1; gi.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 } void G_GetNullEntityState(entityState_t *nullState) { Com_Memset( nullState, 0, sizeof(entityState_t) ); nullState->alpha = 1.0f; nullState->scale = 1.0f; nullState->parent = ENTITYNUM_NONE; nullState->tag_num = -1; nullState->renderfx = 16; nullState->bone_tag[4] = -1; nullState->bone_tag[3] = -1; nullState->bone_tag[2] = -1; nullState->bone_tag[1] = -1; nullState->bone_tag[0] = -1; } #endif