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