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