]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/eclassfgd/plugin.cpp
transfer from internal tree r5311 branches/1.4-gpl
[xonotic/netradiant.git] / plugins / eclassfgd / plugin.cpp
1 /*\r
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.\r
3 For a list of contributors, see the accompanying CONTRIBUTORS file.\r
4 \r
5 This file is part of GtkRadiant.\r
6 \r
7 GtkRadiant is free software; you can redistribute it and/or modify\r
8 it under the terms of the GNU General Public License as published by\r
9 the Free Software Foundation; either version 2 of the License, or\r
10 (at your option) any later version.\r
11 \r
12 GtkRadiant is distributed in the hope that it will be useful,\r
13 but WITHOUT ANY WARRANTY; without even the implied warranty of\r
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
15 GNU General Public License for more details.\r
16 \r
17 You should have received a copy of the GNU General Public License\r
18 along with GtkRadiant; if not, write to the Free Software\r
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\r
20 */\r
21 \r
22 //#define FGD_VERBOSE // define this for extra info in the log.\r
23 \r
24 #include "plugin.h"\r
25 \r
26 /*! \file plugin.cpp\r
27     \brief .fgd entity description format\r
28 \r
29     FGD loading code by Dominic Clifton - Hydra (Hydra@Hydras-World.com)\r
30 \r
31     Overview\r
32     ========\r
33 \r
34     This module loads .fgd files, fgd files are split into classes:\r
35 \r
36       base classes\r
37       point classes (aka fixed size entities)\r
38       solid classes (aka brush entity)\r
39 \r
40     This program first scans each file, building up a list structures\r
41     in memory that contain the information for all the classes found\r
42     in the file.\r
43 \r
44     Then the program looks at each of the non-base classes in the list\r
45     and build the eclass_t structure from each one.\r
46 \r
47     Classes can request information in other classes.\r
48 \r
49     Solid/Base and Point/Base classes can have the same names as other \r
50     classes but there can be only one of each solid/point/base class with\r
51     the same name,\r
52 \r
53     e.g.:  \r
54 \r
55     this is NOT allowed:\r
56 \r
57       @solidclass = "a"\r
58       @solidclass = "a"\r
59 \r
60     this is NOT allowed:\r
61 \r
62       @pointclass = "a"\r
63       @solidclass = "a"\r
64 \r
65     this IS allowed:\r
66 \r
67       @solidclass = "a"\r
68       @baseclass = "a"\r
69 \r
70     Version History\r
71     ===============\r
72 \r
73     v0.1 - 13/March/2002\r
74       - Initial version.\r
75 \r
76     v0.2 - 16/March/2002\r
77       - sets e->skinpath when it finds an iconsprite(<filename>) token.\r
78 \r
79     v0.3 - 21/March/2002\r
80       - Core now supports > 8 spawnflags, changes reflected here too.\r
81       - FIXED: mins/maxs were backwards when only w,h,d were in the FGD\r
82         (as opposed to the actual mins/maxs being in the .def file)\r
83       - Made sure all PointClass entities were fixed size entities\r
84         and gave them a default bounding box size if a size() setting\r
85         was not in the FGD file.\r
86       - Removed the string check for classes requesting another class\r
87         with the same name, adjusted Find_Class() so that it can search\r
88         for baseclasses only,  this fixes the problem with PointClass "light"\r
89         requesting the BaseClass "light".\r
90  \r
91     v0.4 - 25/March/2002\r
92       - bleh, It turns out that non-baseclasses can request non-baseclasses\r
93         so now I've changed Find_Class() so that it can ignore a specific class\r
94         (i.e. the one that's asking for others, so that classes can't request\r
95         themselves but they can request other classes of any kind with the\r
96         same name).\r
97       - made all spawnflag comments appear in one place, rather than being scattered\r
98         all over the comments if a requested class also had some spawnflags\r
99 \r
100     v0.5 - 6/April/2002\r
101       - not using skinpath for sprites anymore, apprently we can code a model\r
102         module to display sprites and model files.\r
103       - model() tags are now supported.\r
104 \r
105     ToDo\r
106     ====\r
107 \r
108     * add support for setting the eclass_t's modelpath.\r
109       (not useful for CS, but very useful for HL).\r
110 \r
111     * need to implement usage for e->skinpath in the core.\r
112 \r
113     * cleanup some areas now that GetTokenExtra() is available\r
114       (some parts were written prior to it's creation).\r
115 \r
116     * Import the comments between each BaseClass's main [ ] set.\r
117       (unfortunatly they're // cstyle comments, which GetToken skips over)\r
118       But still ignore comments OUTSIDE the main [ ] set.\r
119 \r
120 */\r
121 \r
122 _QERScripLibTable g_ScripLibTable;\r
123 _EClassManagerTable g_EClassManagerTable;\r
124 _QERFuncTable_1 g_FuncTable;\r
125 _QERFileSystemTable g_FileSystemTable;\r
126 \r
127 // forward declare\r
128 void Eclass_ScanFile (char *filename);\r
129 \r
130 const char* EClass_GetExtension()\r
131 {\r
132   return "fgd";\r
133 }\r
134 \r
135 CSynapseServer* g_pSynapseServer = NULL;\r
136 CSynapseClientFGD g_SynapseClient;\r
137 \r
138 extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer)\r
139 {\r
140   if (strcmp(version, SYNAPSE_VERSION))\r
141   {\r
142     Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version);\r
143     return NULL;\r
144   }\r
145   g_pSynapseServer = pServer;\r
146   g_pSynapseServer->IncRef();\r
147   Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf());\r
148   \r
149   g_SynapseClient.AddAPI(ECLASS_MAJOR, "fgd", sizeof(_EClassTable));\r
150   g_SynapseClient.AddAPI(SCRIPLIB_MAJOR, NULL, sizeof(g_ScripLibTable), SYN_REQUIRE, &g_ScripLibTable);\r
151   g_SynapseClient.AddAPI(RADIANT_MAJOR, NULL, sizeof(g_FuncTable), SYN_REQUIRE, &g_FuncTable);\r
152   g_SynapseClient.AddAPI(ECLASSMANAGER_MAJOR, NULL, sizeof(g_EClassManagerTable), SYN_REQUIRE, &g_EClassManagerTable);\r
153   \r
154   // Needs a 'default' option for this minor because we certainly don't load anything from wad files :)\r
155   g_SynapseClient.AddAPI(VFS_MAJOR, "wad", sizeof(g_FileSystemTable), SYN_REQUIRE, &g_FileSystemTable);\r
156 \r
157   return &g_SynapseClient;\r
158 }\r
159 \r
160 bool CSynapseClientFGD::RequestAPI(APIDescriptor_t *pAPI)\r
161 {\r
162   if (!strcmp(pAPI->major_name, ECLASS_MAJOR))\r
163   {\r
164     _EClassTable* pTable= static_cast<_EClassTable*>(pAPI->mpTable);\r
165 \r
166     pTable->m_pfnGetExtension = &EClass_GetExtension;\r
167     pTable->m_pfnScanFile = &Eclass_ScanFile;\r
168     \r
169     return true;    \r
170   }\r
171   \r
172   Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo());\r
173   return false;\r
174 }\r
175 \r
176 #include "version.h"\r
177 \r
178 const char* CSynapseClientFGD::GetInfo()\r
179 {\r
180   return ".fgd eclass module built " __DATE__ " " RADIANT_VERSION;\r
181 }\r
182 \r
183 // ------------------------------------------------------------------------------------------------\r
184 \r
185 #define CLASS_NOCLASS     0\r
186 #define CLASS_BASECLASS   1\r
187 #define CLASS_POINTCLASS  2\r
188 #define CLASS_SOLIDCLASS  3\r
189 \r
190 char *classnames[] = {"NOT DEFINED","BaseClass","PointClass","SolidClass"};\r
191 \r
192 #define OPTION_NOOPTION   0\r
193 #define OPTION_STRING     1\r
194 #define OPTION_CHOICES    2\r
195 #define OPTION_INTEGER    3\r
196 #define OPTION_FLAGS      4\r
197 \r
198 char *optionnames[] = {"NOT DEFINED","String","Choices","Integer","Flags"};\r
199 \r
200 typedef struct choice_s {\r
201   int value;\r
202   char *name;\r
203 } choice_t;\r
204 \r
205 typedef struct option_s {\r
206   int optiontype;\r
207   char *optioninfo;\r
208   char *epairname;\r
209   char *optiondefault;\r
210   GSList *choices; // list of choices_t\r
211 } option_t;\r
212 \r
213 typedef struct class_s {\r
214   int classtype; // see CLASS_* above.\r
215   char *classname;\r
216   GSList *l_baselist; // when building the eclass_t, other class_s's with these names are required.\r
217   char *description;\r
218 \r
219   GSList *l_optionlist; // when building the eclass_t, other class_s's with these names are required.\r
220 \r
221   bool gotsize; // if set then boundingbox is valid.\r
222   vec3_t boundingbox[2]; // mins, maxs\r
223         \r
224   bool gotcolor; // if set then color is valid.  \r
225   vec3_t color; // R,G,B, loaded as 0-255 \r
226         \r
227   char *model; // relative path + filename to a model (.spr/.mdl) file, or NULL\r
228 } class_t;\r
229 \r
230 /*\r
231 ===========================================================\r
232 utility functions\r
233 \r
234 ===========================================================\r
235 */\r
236 char *strlower (char *start)\r
237 {\r
238         char    *in;\r
239         in = start;\r
240         while (*in)\r
241         {\r
242                 *in = tolower(*in); \r
243                 in++;\r
244         }\r
245         return start;\r
246 }\r
247 \r
248 char *addstr(char *dest,char *source)\r
249 {\r
250   if (dest)\r
251                 {\r
252     char *ptr;\r
253     int len = strlen(dest);\r
254     ptr = (char *) malloc (len + strlen(source) + 1);\r
255     strcpy(ptr,dest);\r
256     strcpy(ptr+len,source);\r
257     free(dest);\r
258     dest = ptr;\r
259   }\r
260   else\r
261                         {\r
262     dest = strdup(source);\r
263                         }\r
264   return(dest);\r
265 }\r
266 \r
267 int getindex(unsigned int a)\r
268 {\r
269   unsigned int count = 0;\r
270   unsigned int b = 0;\r
271   for (;b != a;count++)\r
272   {\r
273     b = (1<<count);\r
274     if (count > a)\r
275       return -1;    \r
276   }\r
277   return (count);\r
278 }\r
279 \r
280 void ClearGSList (GSList* lst)\r
281 {\r
282   GSList *p = lst;\r
283   while (p)\r
284   {\r
285     free (p->data);\r
286     p = g_slist_remove (p, p->data);\r
287                 }\r
288 }\r
289         \r
290 /*!\r
291 free a choice_t structure and it's contents\r
292 */\r
293 void Free_Choice(choice_t *p)\r
294 {\r
295   if (p->name) free(p->name);\r
296   free (p);\r
297         \r
298 }\r
299 \r
300 /*\r
301 ===========================================================\r
302 Main functions\r
303 \r
304 ===========================================================\r
305 */\r
306 \r
307 /*!\r
308 free an option_t structure and it's contents\r
309 */\r
310 void Free_Option(option_t *p)\r
311 {\r
312   if (p->epairname) free(p->epairname);\r
313   if (p->optiondefault) free(p->optiondefault);\r
314   if (p->optioninfo) free(p->optioninfo);\r
315   GSList *l = p->choices;\r
316   while (l)\r
317         {\r
318     Free_Choice ((choice_t *)l->data);\r
319     l = g_slist_remove (l, l->data);\r
320         }\r
321   free (p);\r
322 }\r
323 \r
324 /*!\r
325 free a class_t structure and it's contents\r
326 */\r
327 void Free_Class(class_t *p)\r
328 {\r
329   GSList *l = p->l_optionlist;\r
330   while (l)\r
331   {\r
332     Free_Option ((option_t *)l->data);\r
333     l = g_slist_remove (l, l->data);\r
334         }\r
335         \r
336   if (p->classname) free(p->classname);\r
337   free (p);\r
338 }\r
339 \r
340 /*!\r
341 find a class in the list\r
342 */\r
343 class_t *Find_Class(GSList *l,char *classname, class_t *ignore)\r
344 {\r
345   for (GSList *clst = l; clst != NULL; clst = clst->next)\r
346                 {\r
347     class_t *c = (class_t *)clst->data;\r
348 \r
349     if (c == ignore)\r
350       continue;\r
351 \r
352     // NOTE: to speed up we could make all the classnames lower-case when they're initialised.\r
353     if (!stricmp(c->classname,classname))\r
354                         {\r
355       return c;\r
356                         }\r
357       \r
358                 }\r
359   return NULL;\r
360 }\r
361 \r
362 /*!\r
363 Import as much as possible from a class_t into an eclass_t\r
364 Note: this is somewhat recursive, as a class can require a class that requires a class and so on..\r
365 */\r
366 void EClass_ImportFromClass(eclass_t *e, GSList *l_classes, class_t *bc)\r
367 {\r
368   char  color[128];\r
369 \r
370   // We allocate 16k here, but only the memory actually used is kept allocated.\r
371   // this is just used for building the final comments string.\r
372   // Note: if the FGD file contains comments that are >16k (per entity) then\r
373   // radiant will crash upon loading such a file as the eclass_t will become\r
374   // corrupted.\r
375   // FIXME: we could add some length checking when building "newcomments", but\r
376   // that'd slow it down a bit.\r
377   char newcomments[16384] = "";\r
378 \r
379   //Note: we override the values already in e.\r
380   //and we do it in such a way that the items that appear last in the l_baselist\r
381   //represent the final values.\r
382   \r
383   if (bc->description)\r
384         {\r
385     sprintf(newcomments,"%s\n",bc->description);\r
386     e->comments = addstr(e->comments,newcomments);\r
387     newcomments[0] = 0; // so we don't add them twice.\r
388         } \r
389 \r
390 \r
391   // import from other classes if required.\r
392 \r
393   if (bc->l_baselist)\r
394   {\r
395     // this class requires other base classes.\r
396         \r
397     for (GSList *bclst = bc->l_baselist; bclst != NULL; bclst = bclst->next)\r
398     {      \r
399       char *requestedclass = (char *)bclst->data;\r
400 \r
401 //      class_t *rbc = Find_Class(l_classes, requestedclass, true);\r
402       class_t *rbc = Find_Class(l_classes, requestedclass, bc);\r
403 \r
404       // make sure we don't request ourself!\r
405       if (rbc == bc)\r
406       {\r
407         Sys_Printf ("WARNING: baseclass '%s' tried to request itself!\n", bclst->data);\r
408       }\r
409                   else\r
410       {\r
411         if (!rbc)\r
412         {\r
413           Sys_Printf ("WARNING: could not find the requested baseclass '%s' when building '%s'\n", requestedclass,bc->classname);\r
414         }\r
415         else\r
416         {\r
417           // call ourself!\r
418           EClass_ImportFromClass(e, l_classes, rbc);\r
419         }\r
420         }\r
421     }\r
422   }\r
423   // SIZE\r
424   if (bc->gotsize)\r
425   {\r
426     e->fixedsize = true;\r
427     memcpy(e->mins,bc->boundingbox[0],sizeof( vec3_t ));\r
428     memcpy(e->maxs,bc->boundingbox[1],sizeof( vec3_t ));\r
429   }\r
430 /*\r
431   // Hydra: apparently, this would be bad.\r
432 \r
433   if (bc->sprite)\r
434   {\r
435     // Hydra - NOTE: e->skinpath is not currently used by the editor but the code\r
436     // to set it is used by both this eclass_t loader and the .DEF eclass_t loader.\r
437     // TODO: implement using e->skinpath.\r
438     if (e->skinpath)\r
439       free (e->skinpath); \r
440         \r
441     e->skinpath = strdup(bc->sprite);\r
442   }\r
443 */\r
444 \r
445   // MODEL\r
446   if (bc->model)\r
447   {\r
448     if (e->modelpath)\r
449       free (e->modelpath);\r
450 \r
451     e->modelpath = strdup(bc->model);\r
452   }\r
453 \r
454   // COLOR\r
455   if (bc->gotcolor)\r
456   {\r
457     memcpy(e->color,bc->color,sizeof( vec3_t ));\r
458     sprintf (color, "(%f %f %f)", e->color[0], e->color[1], e->color[2]);\r
459         e->texdef.SetName(color);\r
460   }\r
461 \r
462   // SPAWNFLAGS and COMMENTS\r
463   if (bc->l_optionlist)\r
464   {\r
465     for (GSList *optlst = bc->l_optionlist; optlst != NULL; optlst = optlst->next)\r
466     {\r
467       option_t *opt = (option_t*) optlst->data;\r
468         \r
469       if (opt->optiontype != OPTION_FLAGS)\r
470       {\r
471         // add some info to the comments.\r
472         if (opt->optioninfo)\r
473         {\r
474           sprintf(newcomments+strlen(newcomments),"%s '%s' %s%s\n",\r
475             opt->epairname, \r
476             opt->optioninfo ? opt->optioninfo : "", \r
477             opt->optiondefault ? ", Default: " : "",\r
478             opt->optiondefault ? opt->optiondefault : "");      \r
479         }\r
480         else\r
481         {\r
482           sprintf(newcomments+strlen(newcomments),"%s %s%s\n",\r
483             opt->epairname, \r
484             opt->optiondefault ? ", Default: " : "",\r
485             opt->optiondefault ? opt->optiondefault : "");      \r
486         }\r
487       }\r
488 \r
489       GSList *choicelst;\r
490       switch(opt->optiontype)\r
491       {        \r
492         case OPTION_FLAGS :\r
493           // grab the flags.\r
494           for (choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next)\r
495           {\r
496             choice_t *choice = (choice_t*) choicelst->data;\r
497         \r
498             int index = getindex(choice->value);\r
499             index--;\r
500             if (index < MAX_FLAGS)\r
501             {\r
502               strcpy(e->flagnames[index],choice->name);\r
503             }\r
504             else\r
505                   {\r
506               Sys_Printf ("WARNING: baseclass '%s' has a spawnflag out of range, ignored!\n", bc->classname);\r
507             }\r
508           }\r
509                             break;\r
510         case OPTION_CHOICES :\r
511           strcat(newcomments,"  Choices:\n");\r
512           for (choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next)\r
513           {\r
514             choice_t *choice = (choice_t*) choicelst->data;\r
515             sprintf(newcomments+strlen(newcomments),"  %5d - %s\n",choice->value,choice->name);\r
516           }       \r
517           break;\r
518       }\r
519     }\r
520         }\r
521 \r
522   // name\r
523   if (e->name) free(e->name);\r
524   e->name = strdup(bc->classname);\r
525         \r
526   // fixed size initialisation\r
527   if (bc->classtype == CLASS_POINTCLASS)\r
528   {    \r
529     e->fixedsize = true;\r
530     // some point classes dont seem to have size()'s in the fgd, so set up a default here..\r
531     if ((e->mins[0] == 0) && (e->mins[1] == 0) && (e->mins[2] == 0) && \r
532         (e->maxs[0] == 0) && (e->maxs[1] == 0) && (e->maxs[2] == 0))\r
533   {\r
534       e->mins[0] = -8;\r
535       e->mins[1] = -8;\r
536       e->mins[2] = -8;\r
537       e->maxs[0] = 8;\r
538       e->maxs[1] = 8;\r
539       e->maxs[2] = 8;\r
540   }\r
541 \r
542     if (e->texdef.GetName() == NULL)\r
543     {\r
544       // no color specified for this entity in the fgd file\r
545       // set one now\r
546       // Note: if this eclass_t is not fully built, then this may be\r
547       // overridden with the correct color.\r
548       \r
549       e->color[0] = 1;\r
550       e->color[1] = 0.5; // how about a nice bright pink, mmm, nice! :)\r
551       e->color[2] = 1;\r
552 \r
553       sprintf (color, "(%f %f %f)", e->color[0], e->color[1], e->color[2]);\r
554         e->texdef.SetName(color);\r
555     }\r
556   }\r
557 \r
558   // COMMENTS\r
559   if (newcomments[0])\r
560   {\r
561     e->comments = addstr(e->comments,newcomments);\r
562   }\r
563 }\r
564 \r
565 /*!\r
566 create the eclass_t structures and add to the global list.\r
567 */\r
568 void Create_EClasses(GSList *l_classes)\r
569 {\r
570   int count = 0;\r
571   // loop through the loaded structures finding all the non BaseClasses\r
572   for (GSList *clst = l_classes; clst != NULL; clst = clst->next)\r
573   {\r
574     class_t *c = (class_t *)clst->data;\r
575 \r
576     if (c->classtype == CLASS_BASECLASS) // not looking for these.\r
577       continue;\r
578 \r
579     eclass_t *e = (eclass_t *) malloc( sizeof( eclass_s ));\r
580     memset(e,0,sizeof( eclass_s ));\r
581 \r
582     EClass_ImportFromClass(e, l_classes, c );\r
583 \r
584     // radiant will crash if this is null, and it still could be at this point.\r
585     if (!e->comments)\r
586     {\r
587       e->comments=strdup("No description available, check documentation\n");\r
588     }\r
589 \r
590     // dump the spawnflags to the end of the comments.\r
591     int i;\r
592     bool havespawnflags;\r
593 \r
594     havespawnflags = false;\r
595     for (i = 0 ; i < MAX_FLAGS ; i++)\r
596     {\r
597       if (*e->flagnames[i]) havespawnflags = true;\r
598     }\r
599 \r
600     if (havespawnflags)\r
601     {\r
602       char spawnline[80];\r
603       e->comments = addstr(e->comments,"Spawnflags\n");\r
604       for (i = 0 ; i < MAX_FLAGS ; i++)\r
605       {\r
606         if (*e->flagnames[i]) \r
607         {\r
608           sprintf(spawnline,"  %d - %s\n", 1<<i, e->flagnames[i]); \r
609           e->comments = addstr(e->comments,spawnline);\r
610         }\r
611       }\r
612     }\r
613 \r
614     Eclass_InsertAlphabetized (e);\r
615     count ++;\r
616     // Hydra: ttimo, I don't think this code is required...\r
617                 // single ?\r
618                 *Get_Eclass_E() = e;\r
619     Set_Eclass_Found(true);\r
620                 if ( Get_Parsing_Single() )\r
621                         break;\r
622   }\r
623 \r
624   Sys_Printf ("FGD Loaded %d entities.\n", count);\r
625 }\r
626 \r
627 void Eclass_ScanFile (char *filename)\r
628 {\r
629         int             size;\r
630         char    *data;\r
631         char    temp[1024];\r
632   GSList *l_classes = NULL;\r
633         char  token_debug[1024]; //++Hydra FIXME: cleanup this.\r
634   bool done = false;\r
635   int len,classtype;\r
636 \r
637   char *token = Token();\r
638         \r
639         QE_ConvertDOSToUnixName( temp, filename );\r
640         \r
641         size = vfsLoadFullPathFile (filename, (void**)&data);\r
642   if (size <= 0)\r
643   {\r
644     Sys_FPrintf (SYS_ERR, "Eclass_ScanFile: %s not found\n", filename);\r
645     return;\r
646   }\r
647         Sys_Printf ("ScanFile: %s\n", temp);    \r
648 \r
649   // start parsing the file\r
650   StartTokenParsing(data);\r
651 \r
652   // build a list of base classes first\r
653 \r
654   while (!done)\r
655                 {\r
656     // find an @ sign.\r
657     do\r
658     {\r
659       if (!GetToken(true))\r
660       {\r
661         done = true;\r
662         break;\r
663       }\r
664     } while (token[0] != '@');\r
665 \r
666     strcpy(temp,token+1); // skip the @\r
667 \r
668     classtype = CLASS_NOCLASS;\r
669     if (!stricmp(temp,"BaseClass")) classtype = CLASS_BASECLASS;\r
670     if (!stricmp(temp,"PointClass")) classtype = CLASS_POINTCLASS;\r
671     if (!stricmp(temp,"SolidClass")) classtype = CLASS_SOLIDCLASS;\r
672 \r
673     if (classtype)\r
674     {\r
675       class_t *newclass = (class_t *) malloc( sizeof(class_s) );\r
676       memset( newclass, 0, sizeof(class_s) );\r
677       newclass->classtype = classtype;\r
678 \r
679       while (1)\r
680       {\r
681         GetTokenExtra(false,"(",false); // option or =\r
682         strcpy(token_debug,token);\r
683 \r
684         if (!strcmp(token,"="))\r
685         {\r
686           UnGetToken();\r
687           break;\r
688         }\r
689                         else\r
690         {\r
691           strlower(token);\r
692           if (!strcmp(token,"base"))\r
693           {\r
694             GetTokenExtra(false,"(",true); // (\r
695 \r
696             if (!strcmp(token,"("))\r
697             {\r
698               while (GetTokenExtra(false,",)",false)) // option) or option,\r
699               {\r
700                 newclass->l_baselist = g_slist_append (newclass->l_baselist, strdup(token));\r
701 \r
702                 GetTokenExtra(false,",)",true); // , or )\r
703                 if (!strcmp(token,")"))\r
704                   break;\r
705 \r
706               }\r
707             }\r
708           }\r
709           else if (!strcmp(token,"size"))\r
710           {\r
711             // parse (w h d) or (x y z, x y z)\r
712 \r
713             GetTokenExtra(false,"(",true); // (\r
714             if (!strcmp(token,"("))\r
715             {\r
716               int sizedone = false;\r
717               float w,h,d;\r
718               GetToken(false);\r
719               w = atof(token);\r
720               GetToken(false);\r
721               h = atof(token);\r
722               GetToken(false); // number) or number ,\r
723               strcpy(temp,token);\r
724               len = strlen(temp);\r
725               if (temp[len-1] == ')') sizedone = true;                \r
726               temp[len-1] = 0;\r
727               d = atof(temp);\r
728               if (sizedone)\r
729               {\r
730                 // only one set of cordinates supplied, change the W,H,D to mins/maxs\r
731                 newclass->boundingbox[0][0] = 0 - (w / 2);\r
732                 newclass->boundingbox[1][0] = w / 2;\r
733                 newclass->boundingbox[0][1] = 0 - (h / 2);\r
734                 newclass->boundingbox[1][1] = h / 2;\r
735                 newclass->boundingbox[0][2] = 0 - (d / 2);\r
736                 newclass->boundingbox[1][2] = d / 2;\r
737                 newclass->gotsize = true;\r
738               }\r
739               else\r
740               {\r
741                 newclass->boundingbox[0][0] = w;\r
742                 newclass->boundingbox[0][1] = h;\r
743                 newclass->boundingbox[0][2] = d;\r
744                 GetToken(false);\r
745                 newclass->boundingbox[1][0] = atof(token);\r
746                 GetToken(false);\r
747                 newclass->boundingbox[1][1] = atof(token);\r
748 /*\r
749                 GetToken(false); // "number)" or "number )"\r
750                 strcpy(temp,token);\r
751                 len = strlen(temp);\r
752                 if (temp[len-1] == ')') \r
753                   temp[len-1] = 0;\r
754                 else\r
755                   GetToken(false); // )\r
756                 newclass->boundingbox[1][2] = atof(temp);\r
757 */\r
758                 GetTokenExtra(false,")",false); // number\r
759                 newclass->boundingbox[1][2] = atof(token);\r
760                 newclass->gotsize = true;\r
761                 GetTokenExtra(false,")",true); // )\r
762               }\r
763             }\r
764           }\r
765           else if (!strcmp(token,"color"))\r
766           {\r
767             GetTokenExtra(false,"(",true); // (\r
768             if (!strcmp(token,"("))\r
769             {\r
770               // get the color values (0-255) and normalize them if required.\r
771               GetToken(false);\r
772               newclass->color[0] = atof(token);\r
773               if (newclass->color[0] > 1)\r
774                 newclass->color[0]/=255;\r
775               GetToken(false);\r
776               newclass->color[1] = atof(token);\r
777               if (newclass->color[1] > 1)\r
778                 newclass->color[1]/=255;\r
779               GetToken(false);\r
780               strcpy(temp,token);\r
781               len = strlen(temp);\r
782               if (temp[len-1] == ')') temp[len-1] = 0;\r
783               newclass->color[2] = atof(temp);\r
784               if (newclass->color[2] > 1)\r
785                 newclass->color[2]/=255;\r
786               newclass->gotcolor = true;\r
787             }\r
788           }\r
789           else if (!strcmp(token,"iconsprite"))\r
790           {\r
791             GetTokenExtra(false,"(",true); // (\r
792             if (!strcmp(token,"("))\r
793             {\r
794               GetTokenExtra(false,")",false); // filename)\r
795               // the model plugins will handle sprites too.\r
796               // newclass->sprite = strdup(token);\r
797               newclass->model = strdup(token);\r
798               GetTokenExtra(false,")",true); // )\r
799             }\r
800           }\r
801           else if (!strcmp(token,"model"))\r
802           {\r
803             GetTokenExtra(false,"(",true); // (\r
804             if (!strcmp(token,"("))\r
805             {\r
806               GetTokenExtra(false,")",false); // filename)\r
807               newclass->model = strdup(token);\r
808               GetTokenExtra(false,")",true); // )\r
809             }\r
810           }\r
811           else \r
812           {\r
813             // Unsupported\r
814             GetToken(false); // skip it.\r
815           }\r
816 \r
817         }\r
818       }\r
819 \r
820       GetToken(false); // =\r
821       strcpy(token_debug,token);\r
822       if (!strcmp(token,"="))\r
823       {\r
824         GetToken(false);\r
825         newclass->classname = strdup(token);\r
826       }\r
827 \r
828       // Get the description\r
829       if (newclass->classtype != CLASS_BASECLASS)\r
830       {\r
831         GetToken(false);\r
832         if (!strcmp(token,":"))\r
833         {\r
834           GetToken(false);\r
835           newclass->description = strdup(token);\r
836         } else UnGetToken(); // no description\r
837       }\r
838 \r
839       // now build the option list.\r
840       GetToken(true); // [ or []\r
841 \r
842       if (strcmp(token,"[]"))  // got some options ?\r
843       {\r
844         if (!strcmp(token,"["))\r
845         {\r
846           // yup\r
847           bool optioncomplete = false;\r
848           option_t *newoption;\r
849 \r
850           while (1)\r
851           {\r
852             GetToken(true);\r
853             if (!strcmp(token,"]"))\r
854               break; // no more options\r
855 \r
856             // parse the data and build the option_t\r
857 \r
858             strcpy(temp,token);\r
859             len = strlen(temp);\r
860             char *ptr = strchr(temp,'(');\r
861 \r
862             if (!ptr)\r
863                                       break;\r
864 \r
865             newoption = (option_t *) malloc ( sizeof( option_s ));\r
866             memset( newoption, 0, sizeof( option_s ));\r
867 \r
868             *ptr++ = 0;\r
869             newoption->epairname = strdup(temp);\r
870 \r
871             len = strlen(ptr);\r
872             if (ptr[len-1] != ')')\r
873               break;\r
874 \r
875             ptr[len-1] = 0;\r
876             strlower(ptr);\r
877             if (!strcmp(ptr,"integer"))\r
878             {\r
879               newoption->optiontype = OPTION_INTEGER;\r
880             }\r
881             else if (!strcmp(ptr,"choices"))\r
882             {\r
883               newoption->optiontype = OPTION_CHOICES;\r
884             }\r
885             else if (!strcmp(ptr,"flags"))\r
886             {\r
887               newoption->optiontype = OPTION_FLAGS;\r
888             }\r
889             else // string\r
890             {\r
891               newoption->optiontype = OPTION_STRING;\r
892             }\r
893 \r
894             switch (newoption->optiontype)\r
895             {\r
896               case OPTION_STRING :\r
897               case OPTION_INTEGER :\r
898                 if (!TokenAvailable())\r
899                 {\r
900                   optioncomplete = true;\r
901                                 break;\r
902                 }\r
903                 GetToken(false); // :\r
904                 strcpy(token_debug,token);\r
905                 if ((token[0] == ':') && (strlen(token) > 1))\r
906                 {\r
907                   newoption->optioninfo = strdup(token+1);\r
908                 }\r
909                 else\r
910                 {\r
911                   GetToken(false);\r
912                   newoption->optioninfo = strdup(token);\r
913                 }\r
914                 if (TokenAvailable()) // default value ?\r
915                 {\r
916                   GetToken(false);\r
917                   if (!strcmp(token,":"))\r
918                   {\r
919                     if (GetToken(false))\r
920                     {\r
921                       newoption->optiondefault = strdup(token);\r
922                       optioncomplete = true;\r
923                     }\r
924                   }\r
925                 }\r
926                 else\r
927                 {\r
928                   optioncomplete = true;\r
929                 }\r
930                 break;\r
931 \r
932               case OPTION_CHOICES :\r
933                 GetTokenExtra(false,":",true); // : or :"something like this" (bah!)\r
934                 strcpy(token_debug,token);\r
935                 if ((token[0] == ':') && (strlen(token) > 1))\r
936                 {\r
937                   if (token[1] == '\"')\r
938                   {\r
939                     strcpy(temp,token+2);\r
940                     while (1)\r
941                     {\r
942                       if (!GetToken(false))\r
943                         break;\r
944                       strcat(temp," ");\r
945                       strcat(temp,token);\r
946                       len = strlen(temp);\r
947                       if (temp[len-1] == '\"')\r
948                       {\r
949                         temp[len-1] = 0;\r
950                         break;\r
951                       }\r
952                     }\r
953                   }\r
954                   newoption->optioninfo = strdup(temp);\r
955                 }\r
956                 else\r
957                 {\r
958                   GetToken(false);\r
959                   newoption->optioninfo = strdup(token);\r
960                 }\r
961                 GetToken(false); // : or =\r
962                 strcpy(token_debug,token);\r
963                 if (!strcmp(token,":"))\r
964                 {\r
965                   GetToken(false);\r
966                   newoption->optiondefault = strdup(token);\r
967                 }\r
968                 else\r
969                 {\r
970                   UnGetToken();\r
971                 }\r
972                 // And Follow on...\r
973               case OPTION_FLAGS :\r
974                 GetToken(false); // : or =\r
975                 strcpy(token_debug,token);\r
976                 if (strcmp(token,"=")) // missing ?\r
977                   break;\r
978 \r
979                 GetToken(true); // [\r
980                 strcpy(token_debug,token);\r
981                 if (strcmp(token,"[")) // missing ?\r
982                   break;\r
983 \r
984                 choice_t *newchoice;\r
985                 while (1)\r
986                 {\r
987                   GetTokenExtra(true,":",true); // "]" or "number", or "number:"\r
988                   strcpy(token_debug,token);\r
989                   if (!strcmp(token,"]")) // no more ?\r
990                   {\r
991                     optioncomplete = true;\r
992                     break;\r
993                   }\r
994                   strcpy(temp,token);\r
995                   len = strlen(temp);\r
996                   if (temp[len-1] == ':')\r
997                   {\r
998                     temp[len-1] = 0;\r
999                   }\r
1000                   else\r
1001                   {\r
1002                     GetToken(false); // :\r
1003                     if (strcmp(token,":")) // missing ?\r
1004                       break;\r
1005                   }\r
1006                   if (!TokenAvailable())\r
1007                     break;\r
1008                   GetToken(false); // the name\r
1009 \r
1010                   newchoice = (choice_t *) malloc ( sizeof( choice_s ));\r
1011                   memset( newchoice, 0, sizeof( choice_s ));\r
1012 \r
1013                   newchoice->value = atoi(temp);\r
1014                   newchoice->name = strdup(token);\r
1015 \r
1016                   newoption->choices = g_slist_append(newoption->choices, newchoice);\r
1017 \r
1018                   // ignore any remaining tokens on the line\r
1019                   while (TokenAvailable()) GetToken(false);\r
1020 \r
1021                   // and it we found a "]" on the end of the line, put it back in the queue.\r
1022                   if (!strcmp(token,"]")) UnGetToken();\r
1023                 }\r
1024                                 break;\r
1025 \r
1026             }\r
1027 \r
1028             // add option to the newclass\r
1029 \r
1030             if (optioncomplete)\r
1031             {\r
1032               if (newoption)\r
1033               {\r
1034                 // add it to the list.\r
1035                 newclass->l_optionlist = g_slist_append(newclass->l_optionlist, newoption);\r
1036               }\r
1037             }\r
1038             else\r
1039             {\r
1040               Sys_Printf ("%WARNING: Parse error occured in '%s - %s'\n",classnames[newclass->classtype],newclass->classname);\r
1041               Free_Option(newoption);\r
1042             }\r
1043 \r
1044           }\r
1045         }\r
1046         else\r
1047         {\r
1048           UnGetToken(); // shouldn't get here.\r
1049         }\r
1050       }\r
1051 \r
1052       // add it to our list.\r
1053       l_classes = g_slist_append (l_classes, newclass);\r
1054 \r
1055     }\r
1056   }\r
1057 \r
1058   // finished with the file now.\r
1059   g_free(data);\r
1060 \r
1061   Sys_Printf ("FGD scan complete, building entities...\n");\r
1062 \r
1063   // Once we get here we should have a few (!) lists in memory that we\r
1064   // can extract all the information required to build a the eclass_t structures.\r
1065 \r
1066   Create_EClasses(l_classes);\r
1067 \r
1068   // Free everything\r
1069 \r
1070   GSList *p = l_classes;\r
1071   while (p)\r
1072   {\r
1073     class_t *tmpclass = (class_t *)p->data;\r
1074 \r
1075 #ifdef FGD_VERBOSE\r
1076     // DEBUG: dump the info...\r
1077     Sys_Printf ("%s: %s (", classnames[tmpclass->classtype],tmpclass->classname);\r
1078     for (GSList *tmp = tmpclass->l_baselist; tmp != NULL; tmp = tmp->next)\r
1079     {\r
1080       if (tmp != tmpclass->l_baselist)\r
1081       {\r
1082         Sys_Printf (", ");\r
1083       }\r
1084       Sys_Printf ("%s", (char *)tmp->data);\r
1085     }\r
1086     if (tmpclass->gotsize)\r
1087     {\r
1088       sprintf(temp,"(%.0f %.0f %.0f) - (%.0f %.0f %.0f)",tmpclass->boundingbox[0][0],\r
1089         tmpclass->boundingbox[0][1],\r
1090         tmpclass->boundingbox[0][2],\r
1091         tmpclass->boundingbox[1][0],\r
1092         tmpclass->boundingbox[1][1],\r
1093         tmpclass->boundingbox[1][2]);\r
1094     } else strcpy(temp,"No Size");\r
1095     Sys_Printf (") '%s' Size: %s",tmpclass->description ? tmpclass->description : "No description",temp);\r
1096     if (tmpclass->gotcolor)\r
1097     {\r
1098       sprintf(temp,"(%d %d %d)",tmpclass->color[0],\r
1099         tmpclass->color[1],\r
1100         tmpclass->color[2]);\r
1101     } else strcpy(temp,"No Color");\r
1102     Sys_Printf (" Color: %s Options:\n",temp);\r
1103     if (!tmpclass->l_optionlist)\r
1104     {\r
1105       Sys_Printf ("  No Options\n");\r
1106     }\r
1107     else\r
1108     {\r
1109       option_t *tmpoption;\r
1110       int count;\r
1111       GSList *olst;\r
1112       for (olst = tmpclass->l_optionlist, count = 1; olst != NULL; olst = olst->next, count ++)\r
1113       {\r
1114         tmpoption = (option_t *)olst->data;\r
1115         Sys_Printf ("  %d, Type: %s, EPair: %s\n", count,optionnames[tmpoption->optiontype], tmpoption->epairname );\r
1116 \r
1117         choice_t *tmpchoice;\r
1118         GSList *clst;\r
1119         int ccount;\r
1120         for (clst = tmpoption->choices, ccount = 1; clst != NULL; clst = clst->next, ccount ++)\r
1121         {\r
1122           tmpchoice = (choice_t *)clst->data;\r
1123           Sys_Printf ("    %d, Value: %d, Name: %s\n", ccount, tmpchoice->value, tmpchoice->name);\r
1124         }\r
1125       }\r
1126     }\r
1127 \r
1128 #endif\r
1129 \r
1130     // free the baselist.\r
1131     ClearGSList(tmpclass->l_baselist);\r
1132     Free_Class (tmpclass);\r
1133     p = g_slist_remove (p, p->data);\r
1134                 }\r
1135                 \r
1136 }\r