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