/* Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant 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. GtkRadiant 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 GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ //#define FGD_VERBOSE // define this for extra info in the log. #include "plugin.h" /*! \file plugin.cpp \brief .fgd entity description format FGD loading code by Dominic Clifton - Hydra (Hydra@Hydras-World.com) Overview ======== This module loads .fgd files, fgd files are split into classes: base classes point classes (aka fixed size entities) solid classes (aka brush entity) This program first scans each file, building up a list structures in memory that contain the information for all the classes found in the file. Then the program looks at each of the non-base classes in the list and build the eclass_t structure from each one. Classes can request information in other classes. Solid/Base and Point/Base classes can have the same names as other classes but there can be only one of each solid/point/base class with the same name, e.g.: this is NOT allowed: @solidclass = "a" @solidclass = "a" this is NOT allowed: @pointclass = "a" @solidclass = "a" this IS allowed: @solidclass = "a" @baseclass = "a" Version History =============== v0.1 - 13/March/2002 - Initial version. v0.2 - 16/March/2002 - sets e->skinpath when it finds an iconsprite() token. v0.3 - 21/March/2002 - Core now supports > 8 spawnflags, changes reflected here too. - FIXED: mins/maxs were backwards when only w,h,d were in the FGD (as opposed to the actual mins/maxs being in the .def file) - Made sure all PointClass entities were fixed size entities and gave them a default bounding box size if a size() setting was not in the FGD file. - Removed the string check for classes requesting another class with the same name, adjusted Find_Class() so that it can search for baseclasses only, this fixes the problem with PointClass "light" requesting the BaseClass "light". v0.4 - 25/March/2002 - bleh, It turns out that non-baseclasses can request non-baseclasses so now I've changed Find_Class() so that it can ignore a specific class (i.e. the one that's asking for others, so that classes can't request themselves but they can request other classes of any kind with the same name). - made all spawnflag comments appear in one place, rather than being scattered all over the comments if a requested class also had some spawnflags v0.5 - 6/April/2002 - not using skinpath for sprites anymore, apprently we can code a model module to display sprites and model files. - model() tags are now supported. ToDo ==== * add support for setting the eclass_t's modelpath. (not useful for CS, but very useful for HL). * need to implement usage for e->skinpath in the core. * cleanup some areas now that GetTokenExtra() is available (some parts were written prior to it's creation). * Import the comments between each BaseClass's main [ ] set. (unfortunatly they're // cstyle comments, which GetToken skips over) But still ignore comments OUTSIDE the main [ ] set. */ _QERScripLibTable g_ScripLibTable; _EClassManagerTable g_EClassManagerTable; _QERFuncTable_1 g_FuncTable; _QERFileSystemTable g_FileSystemTable; // forward declare void Eclass_ScanFile( char *filename ); const char* EClass_GetExtension(){ return "fgd"; } CSynapseServer* g_pSynapseServer = NULL; CSynapseClientFGD g_SynapseClient; #if __GNUC__ >= 4 #pragma GCC visibility push(default) #endif extern "C" CSynapseClient * SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces( const char *version, CSynapseServer *pServer ) { #if __GNUC__ >= 4 #pragma GCC visibility pop #endif if ( strcmp( version, SYNAPSE_VERSION ) ) { Syn_Printf( "ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version ); return NULL; } g_pSynapseServer = pServer; g_pSynapseServer->IncRef(); Set_Syn_Printf( g_pSynapseServer->Get_Syn_Printf() ); g_SynapseClient.AddAPI( ECLASS_MAJOR, "fgd", sizeof( _EClassTable ) ); g_SynapseClient.AddAPI( SCRIPLIB_MAJOR, NULL, sizeof( g_ScripLibTable ), SYN_REQUIRE, &g_ScripLibTable ); g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable ); g_SynapseClient.AddAPI( ECLASSMANAGER_MAJOR, NULL, sizeof( g_EClassManagerTable ), SYN_REQUIRE, &g_EClassManagerTable ); // Needs a 'default' option for this minor because we certainly don't load anything from wad files :) g_SynapseClient.AddAPI( VFS_MAJOR, "wad", sizeof( g_FileSystemTable ), SYN_REQUIRE, &g_FileSystemTable ); return &g_SynapseClient; } bool CSynapseClientFGD::RequestAPI( APIDescriptor_t *pAPI ){ if ( !strcmp( pAPI->major_name, ECLASS_MAJOR ) ) { _EClassTable* pTable = static_cast<_EClassTable*>( pAPI->mpTable ); pTable->m_pfnGetExtension = &EClass_GetExtension; pTable->m_pfnScanFile = &Eclass_ScanFile; return true; } Syn_Printf( "ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo() ); return false; } #include "version.h" const char* CSynapseClientFGD::GetInfo(){ return ".fgd eclass module built " __DATE__ " " RADIANT_VERSION; } // ------------------------------------------------------------------------------------------------ #define CLASS_NOCLASS 0 #define CLASS_BASECLASS 1 #define CLASS_POINTCLASS 2 #define CLASS_SOLIDCLASS 3 char *classnames[] = {"NOT DEFINED","BaseClass","PointClass","SolidClass"}; #define OPTION_NOOPTION 0 #define OPTION_STRING 1 #define OPTION_CHOICES 2 #define OPTION_INTEGER 3 #define OPTION_FLAGS 4 char *optionnames[] = {"NOT DEFINED","String","Choices","Integer","Flags"}; typedef struct choice_s { int value; char *name; } choice_t; typedef struct option_s { int optiontype; char *optioninfo; char *epairname; char *optiondefault; GSList *choices; // list of choices_t } option_t; typedef struct class_s { int classtype; // see CLASS_* above. char *classname; GSList *l_baselist; // when building the eclass_t, other class_s's with these names are required. char *description; GSList *l_optionlist; // when building the eclass_t, other class_s's with these names are required. bool gotsize; // if set then boundingbox is valid. vec3_t boundingbox[2]; // mins, maxs bool gotcolor; // if set then color is valid. vec3_t color; // R,G,B, loaded as 0-255 char *model; // relative path + filename to a model (.spr/.mdl) file, or NULL } class_t; /* =========================================================== utility functions =========================================================== */ char *strlower( char *start ){ char *in; in = start; while ( *in ) { *in = tolower( *in ); in++; } return start; } char *addstr( char *dest,char *source ){ if ( dest ) { char *ptr; int len = strlen( dest ); ptr = (char *) malloc( len + strlen( source ) + 1 ); strcpy( ptr,dest ); strcpy( ptr + len,source ); free( dest ); dest = ptr; } else { dest = strdup( source ); } return( dest ); } int getindex( unsigned int a ){ unsigned int count = 0; unsigned int b = 0; for (; b != a; count++ ) { b = ( 1 << count ); if ( count > a ) { return -1; } } return ( count ); } void ClearGSList( GSList* lst ){ GSList *p = lst; while ( p ) { free( p->data ); p = g_slist_remove( p, p->data ); } } /*! free a choice_t structure and it's contents */ void Free_Choice( choice_t *p ){ if ( p->name ) { free( p->name ); } free( p ); } /* =========================================================== Main functions =========================================================== */ /*! free an option_t structure and it's contents */ void Free_Option( option_t *p ){ if ( p->epairname ) { free( p->epairname ); } if ( p->optiondefault ) { free( p->optiondefault ); } if ( p->optioninfo ) { free( p->optioninfo ); } GSList *l = p->choices; while ( l ) { Free_Choice( (choice_t *)l->data ); l = g_slist_remove( l, l->data ); } free( p ); } /*! free a class_t structure and it's contents */ void Free_Class( class_t *p ){ GSList *l = p->l_optionlist; while ( l ) { Free_Option( (option_t *)l->data ); l = g_slist_remove( l, l->data ); } if ( p->classname ) { free( p->classname ); } free( p ); } /*! find a class in the list */ class_t *Find_Class( GSList *l,char *classname, class_t *ignore ){ for ( GSList *clst = l; clst != NULL; clst = clst->next ) { class_t *c = (class_t *)clst->data; if ( c == ignore ) { continue; } // NOTE: to speed up we could make all the classnames lower-case when they're initialised. if ( !stricmp( c->classname,classname ) ) { return c; } } return NULL; } /*! Import as much as possible from a class_t into an eclass_t Note: this is somewhat recursive, as a class can require a class that requires a class and so on.. */ void EClass_ImportFromClass( eclass_t *e, GSList *l_classes, class_t *bc ){ char color[128]; // We allocate 16k here, but only the memory actually used is kept allocated. // this is just used for building the final comments string. // Note: if the FGD file contains comments that are >16k (per entity) then // radiant will crash upon loading such a file as the eclass_t will become // corrupted. // FIXME: we could add some length checking when building "newcomments", but // that'd slow it down a bit. char newcomments[16384] = ""; //Note: we override the values already in e. //and we do it in such a way that the items that appear last in the l_baselist //represent the final values. if ( bc->description ) { sprintf( newcomments,"%s\n",bc->description ); e->comments = addstr( e->comments,newcomments ); newcomments[0] = 0; // so we don't add them twice. } // import from other classes if required. if ( bc->l_baselist ) { // this class requires other base classes. for ( GSList *bclst = bc->l_baselist; bclst != NULL; bclst = bclst->next ) { char *requestedclass = (char *)bclst->data; // class_t *rbc = Find_Class(l_classes, requestedclass, true); class_t *rbc = Find_Class( l_classes, requestedclass, bc ); // make sure we don't request ourself! if ( rbc == bc ) { Sys_Printf( "WARNING: baseclass '%s' tried to request itself!\n", bclst->data ); } else { if ( !rbc ) { Sys_Printf( "WARNING: could not find the requested baseclass '%s' when building '%s'\n", requestedclass,bc->classname ); } else { // call ourself! EClass_ImportFromClass( e, l_classes, rbc ); } } } } // SIZE if ( bc->gotsize ) { e->fixedsize = true; memcpy( e->mins,bc->boundingbox[0],sizeof( vec3_t ) ); memcpy( e->maxs,bc->boundingbox[1],sizeof( vec3_t ) ); } /* // Hydra: apparently, this would be bad. if (bc->sprite) { // Hydra - NOTE: e->skinpath is not currently used by the editor but the code // to set it is used by both this eclass_t loader and the .DEF eclass_t loader. // TODO: implement using e->skinpath. if (e->skinpath) free (e->skinpath); e->skinpath = strdup(bc->sprite); } */ // MODEL if ( bc->model ) { if ( e->modelpath ) { free( e->modelpath ); } e->modelpath = strdup( bc->model ); } // COLOR if ( bc->gotcolor ) { memcpy( e->color,bc->color,sizeof( vec3_t ) ); sprintf( color, "(%f %f %f)", e->color[0], e->color[1], e->color[2] ); e->texdef.SetName( color ); } // SPAWNFLAGS and COMMENTS if ( bc->l_optionlist ) { for ( GSList *optlst = bc->l_optionlist; optlst != NULL; optlst = optlst->next ) { option_t *opt = (option_t*) optlst->data; if ( opt->optiontype != OPTION_FLAGS ) { // add some info to the comments. if ( opt->optioninfo ) { sprintf( newcomments + strlen( newcomments ),"%s '%s' %s%s\n", opt->epairname, opt->optioninfo ? opt->optioninfo : "", opt->optiondefault ? ", Default: " : "", opt->optiondefault ? opt->optiondefault : "" ); } else { sprintf( newcomments + strlen( newcomments ),"%s %s%s\n", opt->epairname, opt->optiondefault ? ", Default: " : "", opt->optiondefault ? opt->optiondefault : "" ); } } GSList *choicelst; switch ( opt->optiontype ) { case OPTION_FLAGS: // grab the flags. for ( choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next ) { choice_t *choice = (choice_t*) choicelst->data; int index = getindex( choice->value ); index--; if ( index < MAX_FLAGS ) { strcpy( e->flagnames[index],choice->name ); } else { Sys_Printf( "WARNING: baseclass '%s' has a spawnflag out of range, ignored!\n", bc->classname ); } } break; case OPTION_CHOICES: strcat( newcomments," Choices:\n" ); for ( choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next ) { choice_t *choice = (choice_t*) choicelst->data; sprintf( newcomments + strlen( newcomments )," %5d - %s\n",choice->value,choice->name ); } break; } } } // name if ( e->name ) { free( e->name ); } e->name = strdup( bc->classname ); // fixed size initialisation if ( bc->classtype == CLASS_POINTCLASS ) { e->fixedsize = true; // some point classes dont seem to have size()'s in the fgd, so set up a default here.. if ( ( e->mins[0] == 0 ) && ( e->mins[1] == 0 ) && ( e->mins[2] == 0 ) && ( e->maxs[0] == 0 ) && ( e->maxs[1] == 0 ) && ( e->maxs[2] == 0 ) ) { e->mins[0] = -8; e->mins[1] = -8; e->mins[2] = -8; e->maxs[0] = 8; e->maxs[1] = 8; e->maxs[2] = 8; } if ( e->texdef.GetName() == NULL ) { // no color specified for this entity in the fgd file // set one now // Note: if this eclass_t is not fully built, then this may be // overridden with the correct color. e->color[0] = 1; e->color[1] = 0.5; // how about a nice bright pink, mmm, nice! :) e->color[2] = 1; sprintf( color, "(%f %f %f)", e->color[0], e->color[1], e->color[2] ); e->texdef.SetName( color ); } } // COMMENTS if ( newcomments[0] ) { e->comments = addstr( e->comments,newcomments ); } } /*! create the eclass_t structures and add to the global list. */ void Create_EClasses( GSList *l_classes ){ int count = 0; // loop through the loaded structures finding all the non BaseClasses for ( GSList *clst = l_classes; clst != NULL; clst = clst->next ) { class_t *c = (class_t *)clst->data; if ( c->classtype == CLASS_BASECLASS ) { // not looking for these. continue; } eclass_t *e = (eclass_t *) malloc( sizeof( eclass_s ) ); memset( e,0,sizeof( eclass_s ) ); EClass_ImportFromClass( e, l_classes, c ); // radiant will crash if this is null, and it still could be at this point. if ( !e->comments ) { e->comments = strdup( "No description available, check documentation\n" ); } // dump the spawnflags to the end of the comments. int i; bool havespawnflags; havespawnflags = false; for ( i = 0 ; i < MAX_FLAGS ; i++ ) { if ( *e->flagnames[i] ) { havespawnflags = true; } } if ( havespawnflags ) { char spawnline[80]; e->comments = addstr( e->comments,"Spawnflags\n" ); for ( i = 0 ; i < MAX_FLAGS ; i++ ) { if ( *e->flagnames[i] ) { sprintf( spawnline," %d - %s\n", 1 << i, e->flagnames[i] ); e->comments = addstr( e->comments,spawnline ); } } } Eclass_InsertAlphabetized( e ); count++; // Hydra: ttimo, I don't think this code is required... // single ? *Get_Eclass_E() = e; Set_Eclass_Found( true ); if ( Get_Parsing_Single() ) { break; } } Sys_Printf( "FGD Loaded %d entities.\n", count ); } void Eclass_ScanFile( char *filename ){ int size; char *data; char temp[1024]; GSList *l_classes = NULL; char token_debug[1024]; //++Hydra FIXME: cleanup this. bool done = false; int len,classtype; char *token = Token(); QE_ConvertDOSToUnixName( temp, filename ); size = vfsLoadFullPathFile( filename, (void**)&data ); if ( size <= 0 ) { Sys_FPrintf( SYS_ERR, "Eclass_ScanFile: %s not found\n", filename ); return; } Sys_Printf( "ScanFile: %s\n", temp ); // start parsing the file StartTokenParsing( data ); // build a list of base classes first while ( !done ) { // find an @ sign. do { if ( !GetToken( true ) ) { done = true; break; } } while ( token[0] != '@' ); strcpy( temp,token + 1 ); // skip the @ classtype = CLASS_NOCLASS; if ( !stricmp( temp,"BaseClass" ) ) { classtype = CLASS_BASECLASS; } if ( !stricmp( temp,"PointClass" ) ) { classtype = CLASS_POINTCLASS; } if ( !stricmp( temp,"SolidClass" ) ) { classtype = CLASS_SOLIDCLASS; } if ( classtype ) { class_t *newclass = (class_t *) malloc( sizeof( class_s ) ); memset( newclass, 0, sizeof( class_s ) ); newclass->classtype = classtype; while ( 1 ) { GetTokenExtra( false,"(",false ); // option or = strcpy( token_debug,token ); if ( !strcmp( token,"=" ) ) { UnGetToken(); break; } else { strlower( token ); if ( !strcmp( token,"base" ) ) { GetTokenExtra( false,"(",true ); // ( if ( !strcmp( token,"(" ) ) { while ( GetTokenExtra( false,",)",false ) ) // option) or option, { newclass->l_baselist = g_slist_append( newclass->l_baselist, strdup( token ) ); GetTokenExtra( false,",)",true ); // , or ) if ( !strcmp( token,")" ) ) { break; } } } } else if ( !strcmp( token,"size" ) ) { // parse (w h d) or (x y z, x y z) GetTokenExtra( false,"(",true ); // ( if ( !strcmp( token,"(" ) ) { int sizedone = false; float w,h,d; GetToken( false ); w = atof( token ); GetToken( false ); h = atof( token ); GetToken( false ); // number) or number , strcpy( temp,token ); len = strlen( temp ); if ( temp[len - 1] == ')' ) { sizedone = true; } temp[len - 1] = 0; d = atof( temp ); if ( sizedone ) { // only one set of cordinates supplied, change the W,H,D to mins/maxs newclass->boundingbox[0][0] = 0 - ( w / 2 ); newclass->boundingbox[1][0] = w / 2; newclass->boundingbox[0][1] = 0 - ( h / 2 ); newclass->boundingbox[1][1] = h / 2; newclass->boundingbox[0][2] = 0 - ( d / 2 ); newclass->boundingbox[1][2] = d / 2; newclass->gotsize = true; } else { newclass->boundingbox[0][0] = w; newclass->boundingbox[0][1] = h; newclass->boundingbox[0][2] = d; GetToken( false ); newclass->boundingbox[1][0] = atof( token ); GetToken( false ); newclass->boundingbox[1][1] = atof( token ); /* GetToken(false); // "number)" or "number )" strcpy(temp,token); len = strlen(temp); if (temp[len-1] == ')') temp[len-1] = 0; else GetToken(false); // ) newclass->boundingbox[1][2] = atof(temp); */ GetTokenExtra( false,")",false ); // number newclass->boundingbox[1][2] = atof( token ); newclass->gotsize = true; GetTokenExtra( false,")",true ); // ) } } } else if ( !strcmp( token,"color" ) ) { GetTokenExtra( false,"(",true ); // ( if ( !strcmp( token,"(" ) ) { // get the color values (0-255) and normalize them if required. GetToken( false ); newclass->color[0] = atof( token ); if ( newclass->color[0] > 1 ) { newclass->color[0] /= 255; } GetToken( false ); newclass->color[1] = atof( token ); if ( newclass->color[1] > 1 ) { newclass->color[1] /= 255; } GetToken( false ); strcpy( temp,token ); len = strlen( temp ); if ( temp[len - 1] == ')' ) { temp[len - 1] = 0; } newclass->color[2] = atof( temp ); if ( newclass->color[2] > 1 ) { newclass->color[2] /= 255; } newclass->gotcolor = true; } } else if ( !strcmp( token,"iconsprite" ) ) { GetTokenExtra( false,"(",true ); // ( if ( !strcmp( token,"(" ) ) { GetTokenExtra( false,")",false ); // filename) // the model plugins will handle sprites too. // newclass->sprite = strdup(token); newclass->model = strdup( token ); GetTokenExtra( false,")",true ); // ) } } else if ( !strcmp( token,"model" ) ) { GetTokenExtra( false,"(",true ); // ( if ( !strcmp( token,"(" ) ) { GetTokenExtra( false,")",false ); // filename) newclass->model = strdup( token ); GetTokenExtra( false,")",true ); // ) } } else { // Unsupported GetToken( false ); // skip it. } } } GetToken( false ); // = strcpy( token_debug,token ); if ( !strcmp( token,"=" ) ) { GetToken( false ); newclass->classname = strdup( token ); } // Get the description if ( newclass->classtype != CLASS_BASECLASS ) { GetToken( false ); if ( !strcmp( token,":" ) ) { GetToken( false ); newclass->description = strdup( token ); } else{ UnGetToken(); // no description } } // now build the option list. GetToken( true ); // [ or [] if ( strcmp( token,"[]" ) ) { // got some options ? if ( !strcmp( token,"[" ) ) { // yup bool optioncomplete = false; option_t *newoption; while ( 1 ) { GetToken( true ); if ( !strcmp( token,"]" ) ) { break; // no more options } // parse the data and build the option_t strcpy( temp,token ); len = strlen( temp ); char *ptr = strchr( temp,'(' ); if ( !ptr ) { break; } newoption = (option_t *) malloc( sizeof( option_s ) ); memset( newoption, 0, sizeof( option_s ) ); *ptr++ = 0; newoption->epairname = strdup( temp ); len = strlen( ptr ); if ( ptr[len - 1] != ')' ) { break; } ptr[len - 1] = 0; strlower( ptr ); if ( !strcmp( ptr,"integer" ) ) { newoption->optiontype = OPTION_INTEGER; } else if ( !strcmp( ptr,"choices" ) ) { newoption->optiontype = OPTION_CHOICES; } else if ( !strcmp( ptr,"flags" ) ) { newoption->optiontype = OPTION_FLAGS; } else // string { newoption->optiontype = OPTION_STRING; } switch ( newoption->optiontype ) { case OPTION_STRING: case OPTION_INTEGER: if ( !TokenAvailable() ) { optioncomplete = true; break; } GetToken( false ); // : strcpy( token_debug,token ); if ( ( token[0] == ':' ) && ( strlen( token ) > 1 ) ) { newoption->optioninfo = strdup( token + 1 ); } else { GetToken( false ); newoption->optioninfo = strdup( token ); } if ( TokenAvailable() ) { // default value ? GetToken( false ); if ( !strcmp( token,":" ) ) { if ( GetToken( false ) ) { newoption->optiondefault = strdup( token ); optioncomplete = true; } } } else { optioncomplete = true; } break; case OPTION_CHOICES: GetTokenExtra( false,":",true ); // : or :"something like this" (bah!) strcpy( token_debug,token ); if ( ( token[0] == ':' ) && ( strlen( token ) > 1 ) ) { if ( token[1] == '\"' ) { strcpy( temp,token + 2 ); while ( 1 ) { if ( !GetToken( false ) ) { break; } strcat( temp," " ); strcat( temp,token ); len = strlen( temp ); if ( temp[len - 1] == '\"' ) { temp[len - 1] = 0; break; } } } newoption->optioninfo = strdup( temp ); } else { GetToken( false ); newoption->optioninfo = strdup( token ); } GetToken( false ); // : or = strcpy( token_debug,token ); if ( !strcmp( token,":" ) ) { GetToken( false ); newoption->optiondefault = strdup( token ); } else { UnGetToken(); } // And Follow on... case OPTION_FLAGS: GetToken( false ); // : or = strcpy( token_debug,token ); if ( strcmp( token,"=" ) ) { // missing ? break; } GetToken( true ); // [ strcpy( token_debug,token ); if ( strcmp( token,"[" ) ) { // missing ? break; } choice_t *newchoice; while ( 1 ) { GetTokenExtra( true,":",true ); // "]" or "number", or "number:" strcpy( token_debug,token ); if ( !strcmp( token,"]" ) ) { // no more ? optioncomplete = true; break; } strcpy( temp,token ); len = strlen( temp ); if ( temp[len - 1] == ':' ) { temp[len - 1] = 0; } else { GetToken( false ); // : if ( strcmp( token,":" ) ) { // missing ? break; } } if ( !TokenAvailable() ) { break; } GetToken( false ); // the name newchoice = (choice_t *) malloc( sizeof( choice_s ) ); memset( newchoice, 0, sizeof( choice_s ) ); newchoice->value = atoi( temp ); newchoice->name = strdup( token ); newoption->choices = g_slist_append( newoption->choices, newchoice ); // ignore any remaining tokens on the line while ( TokenAvailable() ) GetToken( false ); // and it we found a "]" on the end of the line, put it back in the queue. if ( !strcmp( token,"]" ) ) { UnGetToken(); } } break; } // add option to the newclass if ( optioncomplete ) { if ( newoption ) { // add it to the list. newclass->l_optionlist = g_slist_append( newclass->l_optionlist, newoption ); } } else { Sys_Printf( "%WARNING: Parse error occured in '%s - %s'\n",classnames[newclass->classtype],newclass->classname ); Free_Option( newoption ); } } } else { UnGetToken(); // shouldn't get here. } } // add it to our list. l_classes = g_slist_append( l_classes, newclass ); } } // finished with the file now. g_free( data ); Sys_Printf( "FGD scan complete, building entities...\n" ); // Once we get here we should have a few (!) lists in memory that we // can extract all the information required to build a the eclass_t structures. Create_EClasses( l_classes ); // Free everything GSList *p = l_classes; while ( p ) { class_t *tmpclass = (class_t *)p->data; #ifdef FGD_VERBOSE // DEBUG: dump the info... Sys_Printf( "%s: %s (", classnames[tmpclass->classtype],tmpclass->classname ); for ( GSList *tmp = tmpclass->l_baselist; tmp != NULL; tmp = tmp->next ) { if ( tmp != tmpclass->l_baselist ) { Sys_Printf( ", " ); } Sys_Printf( "%s", (char *)tmp->data ); } if ( tmpclass->gotsize ) { sprintf( temp,"(%.0f %.0f %.0f) - (%.0f %.0f %.0f)",tmpclass->boundingbox[0][0], tmpclass->boundingbox[0][1], tmpclass->boundingbox[0][2], tmpclass->boundingbox[1][0], tmpclass->boundingbox[1][1], tmpclass->boundingbox[1][2] ); } else{ strcpy( temp,"No Size" ); } Sys_Printf( ") '%s' Size: %s",tmpclass->description ? tmpclass->description : "No description",temp ); if ( tmpclass->gotcolor ) { sprintf( temp,"(%d %d %d)",tmpclass->color[0], tmpclass->color[1], tmpclass->color[2] ); } else{ strcpy( temp,"No Color" ); } Sys_Printf( " Color: %s Options:\n",temp ); if ( !tmpclass->l_optionlist ) { Sys_Printf( " No Options\n" ); } else { option_t *tmpoption; int count; GSList *olst; for ( olst = tmpclass->l_optionlist, count = 1; olst != NULL; olst = olst->next, count++ ) { tmpoption = (option_t *)olst->data; Sys_Printf( " %d, Type: %s, EPair: %s\n", count,optionnames[tmpoption->optiontype], tmpoption->epairname ); choice_t *tmpchoice; GSList *clst; int ccount; for ( clst = tmpoption->choices, ccount = 1; clst != NULL; clst = clst->next, ccount++ ) { tmpchoice = (choice_t *)clst->data; Sys_Printf( " %d, Value: %d, Name: %s\n", ccount, tmpchoice->value, tmpchoice->name ); } } } #endif // free the baselist. ClearGSList( tmpclass->l_baselist ); Free_Class( tmpclass ); p = g_slist_remove( p, p->data ); } }