/* 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; extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) { 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< 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<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); } }