]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/eclass_fgd.cpp
make it work with older Gtk again
[divverent/netradiant.git] / radiant / eclass_fgd.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
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 #include "eclass_fgd.h"
23
24 #include "debugging/debugging.h"
25
26 #include <map>
27
28 #include "ifilesystem.h"
29 #include "iscriplib.h"
30 #include "qerplugin.h"
31
32 #include "string/string.h"
33 #include "eclasslib.h"
34 #include "os/path.h"
35 #include "os/dir.h"
36 #include "stream/stringstream.h"
37 #include "moduleobservers.h"
38 #include "stringio.h"
39 #include "stream/textfilestream.h"
40
41 namespace
42 {
43   typedef std::map<const char*, EntityClass*, RawStringLessNoCase> EntityClasses;
44   EntityClasses g_EntityClassFGD_classes;
45   typedef std::map<const char*, EntityClass*, RawStringLess> BaseClasses;
46   BaseClasses g_EntityClassFGD_bases;
47   EntityClass   *g_EntityClassFGD_bad = 0;
48   typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
49   ListAttributeTypes g_listTypesFGD;
50 }
51
52
53 void EntityClassFGD_clear()
54 {
55   for(BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i)
56   {
57     (*i).second->free((*i).second);
58   }
59   g_EntityClassFGD_bases.clear();
60   g_listTypesFGD.clear();
61 }
62
63 EntityClass* EntityClassFGD_insertUniqueBase(EntityClass* entityClass)
64 {
65   std::pair<BaseClasses::iterator, bool> result = g_EntityClassFGD_bases.insert(BaseClasses::value_type(entityClass->name(), entityClass));
66   if(!result.second)
67   {
68     globalErrorStream() << "duplicate base class: " << makeQuoted(entityClass->name()) << "\n";
69     //eclass_capture_state(entityClass);
70     //entityClass->free(entityClass);
71   }
72   return (*result.first).second;
73 }
74
75 EntityClass* EntityClassFGD_insertUnique(EntityClass* entityClass)
76 {
77   EntityClassFGD_insertUniqueBase(entityClass);
78   std::pair<EntityClasses::iterator, bool> result = g_EntityClassFGD_classes.insert(EntityClasses::value_type(entityClass->name(), entityClass));
79   if(!result.second)
80   {
81     globalErrorStream() << "duplicate entity class: " << makeQuoted(entityClass->name()) << "\n";
82     eclass_capture_state(entityClass);
83     entityClass->free(entityClass);
84   }
85   return (*result.first).second;
86 }
87
88 void EntityClassFGD_forEach(EntityClassVisitor& visitor)
89 {
90   for(EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i)
91   {
92     visitor.visit((*i).second);
93   }
94 }
95
96 inline bool EntityClassFGD_parseToken(Tokeniser& tokeniser, const char* token)
97 {
98   return string_equal(tokeniser.getToken(), token);
99 }
100
101 #define PARSE_ERROR "error parsing entity class definition"
102
103 void EntityClassFGD_parseSplitString(Tokeniser& tokeniser, CopiedString& string)
104 {
105   StringOutputStream buffer(256);
106   for(;;)
107   {
108     buffer << tokeniser.getToken();
109     if(!string_equal(tokeniser.getToken(), "+"))
110     {
111       tokeniser.ungetToken();
112       string = buffer.c_str();
113       return;
114     }
115   }
116 }
117
118 void EntityClassFGD_parseClass(Tokeniser& tokeniser, bool fixedsize, bool isBase)
119 {
120   EntityClass* entityClass = Eclass_Alloc();
121   entityClass->free = &Eclass_Free;
122   entityClass->fixedsize = fixedsize;
123   entityClass->inheritanceResolved = false;
124   entityClass->mins = Vector3(-8, -8, -8);
125   entityClass->maxs = Vector3(8, 8, 8);
126
127   for(;;)
128   {
129     const char* property = tokeniser.getToken();
130     if(string_equal(property, "="))
131     {
132       break;
133     }
134     else if(string_equal(property, "base"))
135     {
136       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
137       for(;;)
138       {
139         const char* base = tokeniser.getToken();
140         if(string_equal(base, ")"))
141         {
142           break;
143         }
144         else if(!string_equal(base, ","))
145         {
146           entityClass->m_parent.push_back(base);
147         }
148       }
149     }
150     else if(string_equal(property, "size"))
151     {
152       entityClass->sizeSpecified = true;
153       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
154       Tokeniser_getFloat(tokeniser, entityClass->mins.x());
155       Tokeniser_getFloat(tokeniser, entityClass->mins.y());
156       Tokeniser_getFloat(tokeniser, entityClass->mins.z());
157       const char* token = tokeniser.getToken();
158       if(string_equal(token, ","))
159       {
160         Tokeniser_getFloat(tokeniser, entityClass->maxs.x());
161         Tokeniser_getFloat(tokeniser, entityClass->maxs.y());
162         Tokeniser_getFloat(tokeniser, entityClass->maxs.z());
163         ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
164       }
165       else
166       {
167         entityClass->maxs = entityClass->mins;
168         vector3_negate(entityClass->mins);
169         ASSERT_MESSAGE(string_equal(token, ")"), "");
170       }
171     }
172     else if(string_equal(property, "color"))
173     {
174       entityClass->colorSpecified = true;
175       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
176       Tokeniser_getFloat(tokeniser, entityClass->color.x());
177       entityClass->color.x() /= 256.0;
178       Tokeniser_getFloat(tokeniser, entityClass->color.y());
179       entityClass->color.y() /= 256.0;
180       Tokeniser_getFloat(tokeniser, entityClass->color.z());
181       entityClass->color.z() /= 256.0;
182       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
183     }
184     else if(string_equal(property, "iconsprite"))
185     {
186       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
187       StringOutputStream buffer(256);
188       buffer << PathCleaned(tokeniser.getToken());
189       entityClass->m_modelpath = buffer.c_str();
190       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
191     }
192     else if(string_equal(property, "sprite")
193       || string_equal(property, "decal")
194       // hl2 below
195       || string_equal(property, "overlay")
196       || string_equal(property, "light")
197       || string_equal(property, "keyframe")
198       || string_equal(property, "animator")
199       || string_equal(property, "quadbounds"))
200     {
201       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
202       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
203     }
204     // hl2 below
205     else if(string_equal(property, "sphere")
206       || string_equal(property, "sweptplayerhull")
207       || string_equal(property, "studio")
208       || string_equal(property, "studioprop")
209       || string_equal(property, "lightprop")
210       || string_equal(property, "lightcone")
211       || string_equal(property, "sidelist"))
212     {
213       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
214       if(string_equal(tokeniser.getToken(), ")"))
215       {
216         tokeniser.ungetToken();
217       }
218       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
219     }
220     else if(string_equal(property, "line")
221       || string_equal(property, "cylinder"))
222     {
223       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
224       //const char* r =
225       tokeniser.getToken();
226       //const char* g =
227       tokeniser.getToken();
228       //const char* b =
229       tokeniser.getToken();
230       for(;;)
231       {
232         if(string_equal(tokeniser.getToken(), ")"))
233         {
234           tokeniser.ungetToken();
235           break;
236         }
237         //const char* name =
238         tokeniser.getToken();
239       }
240       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
241     }
242     else if(string_equal(property, "wirebox"))
243     {
244       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
245       //const char* mins =
246       tokeniser.getToken();
247       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ","), PARSE_ERROR);
248       //const char* maxs =
249       tokeniser.getToken();
250       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
251     }
252     else if(string_equal(property, "halfgridsnap"))
253     {
254     }
255     else
256     {
257       ERROR_MESSAGE(PARSE_ERROR);
258     }
259   }
260
261   entityClass->m_name = tokeniser.getToken();
262
263   if(!isBase)
264   {
265     ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
266
267     EntityClassFGD_parseSplitString(tokeniser, entityClass->m_comments);
268   }
269
270   tokeniser.nextLine();
271
272   ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR);
273
274   tokeniser.nextLine();
275
276   for(;;)
277   {
278     CopiedString key = tokeniser.getToken();
279     if(string_equal(key.c_str(), "]"))
280     {
281       tokeniser.nextLine();
282       break;
283     }
284
285     if(string_equal_nocase(key.c_str(), "input")
286       || string_equal_nocase(key.c_str(), "output"))
287     {
288       const char* name = tokeniser.getToken();
289       if(!string_equal(name, "("))
290       {
291         ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
292         //const char* type =
293         tokeniser.getToken();
294         ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
295         const char* descriptionSeparator = tokeniser.getToken();
296         if(string_equal(descriptionSeparator, ":"))
297         {
298           CopiedString description;
299           EntityClassFGD_parseSplitString(tokeniser, description);
300         }
301         else
302         {
303           tokeniser.ungetToken();
304         }
305         tokeniser.nextLine();
306         continue;
307       }
308     }
309
310     ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
311     CopiedString type = tokeniser.getToken();
312     ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
313
314     if(string_equal_nocase(type.c_str(), "flags"))
315     {
316       EntityClassAttribute attribute;
317
318       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "="), PARSE_ERROR);
319       tokeniser.nextLine();
320       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR);
321       tokeniser.nextLine();
322       for(;;)
323       {
324         const char* flag = tokeniser.getToken();
325         if(string_equal(flag, "]"))
326         {
327           tokeniser.nextLine();
328           break;
329         }
330         else
331         {
332           ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
333           //const char* name =
334           tokeniser.getToken();
335           {
336             const char* defaultSeparator = tokeniser.getToken();
337             if(string_equal(defaultSeparator, ":"))
338             {
339               tokeniser.getToken();
340               {
341                 const char* descriptionSeparator = tokeniser.getToken();
342                 if(string_equal(descriptionSeparator, ":"))
343                 {
344                   EntityClassFGD_parseSplitString(tokeniser, attribute.m_description);
345                 }
346                 else
347                 {
348                   tokeniser.ungetToken();
349                 }
350               }
351             }
352             else
353             {
354               tokeniser.ungetToken();
355             }
356           }
357         }
358         tokeniser.nextLine();
359       }
360       EntityClass_insertAttribute(*entityClass, key.c_str(), attribute);
361     }
362     else if(string_equal_nocase(type.c_str(), "choices"))
363     {
364       EntityClassAttribute attribute;
365
366       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
367       attribute.m_name = tokeniser.getToken();
368       const char* valueSeparator = tokeniser.getToken();
369       if(string_equal(valueSeparator, ":"))
370       {
371         const char* value = tokeniser.getToken();
372         if(!string_equal(value, ":"))
373         {
374           attribute.m_value = value;
375         }
376         else
377         {
378           tokeniser.ungetToken();
379         }
380         {
381           const char* descriptionSeparator = tokeniser.getToken();
382           if(string_equal(descriptionSeparator, ":"))
383           {
384             EntityClassFGD_parseSplitString(tokeniser, attribute.m_description);
385           }
386           else
387           {
388             tokeniser.ungetToken();
389           }
390         }
391       }
392       else
393       {
394         tokeniser.ungetToken();
395       }
396       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "="), PARSE_ERROR);
397       tokeniser.nextLine();
398       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR);
399       tokeniser.nextLine();
400
401       StringOutputStream listTypeName(64);
402       listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str();
403       attribute.m_type = listTypeName.c_str();
404
405       ListAttributeType& listType = g_listTypesFGD[listTypeName.c_str()];
406
407       for(;;)
408       {
409         const char* value = tokeniser.getToken();
410         if(string_equal(value, "]"))
411         {
412           tokeniser.nextLine();
413           break;
414         }
415         else
416         {
417           CopiedString tmp(value);
418           ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
419           const char* name = tokeniser.getToken();
420           listType.push_back(name, tmp.c_str());
421         }
422         tokeniser.nextLine();
423       }
424
425       for(ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i)
426       {
427         if(string_equal(attribute.m_value.c_str(), (*i).first.c_str()))
428         {
429           attribute.m_value = (*i).second.c_str();
430         }
431       }
432
433       EntityClass_insertAttribute(*entityClass, key.c_str(), attribute);
434     }
435     else if(string_equal_nocase(type.c_str(), "decal"))
436     {
437     }
438     else if(string_equal_nocase(type.c_str(), "string")
439       || string_equal_nocase(type.c_str(), "integer")
440       || string_equal_nocase(type.c_str(), "studio")
441       || string_equal_nocase(type.c_str(), "sprite")
442       || string_equal_nocase(type.c_str(), "color255")
443       || string_equal_nocase(type.c_str(), "target_source")
444       || string_equal_nocase(type.c_str(), "target_destination")
445       || string_equal_nocase(type.c_str(), "sound")
446       // hl2 below
447       || string_equal_nocase(type.c_str(), "angle")
448       || string_equal_nocase(type.c_str(), "origin")
449       || string_equal_nocase(type.c_str(), "float")
450       || string_equal_nocase(type.c_str(), "node_dest")
451       || string_equal_nocase(type.c_str(), "filterclass")
452       || string_equal_nocase(type.c_str(), "vector")
453       || string_equal_nocase(type.c_str(), "sidelist")
454       || string_equal_nocase(type.c_str(), "material")
455       || string_equal_nocase(type.c_str(), "vecline")
456       || string_equal_nocase(type.c_str(), "axis")
457       || string_equal_nocase(type.c_str(), "npcclass")
458       || string_equal_nocase(type.c_str(), "target_name_or_class")
459       || string_equal_nocase(type.c_str(), "pointentityclass")
460       || string_equal_nocase(type.c_str(), "scene"))
461     {
462       if(!string_equal(tokeniser.getToken(), "readonly"))
463       {
464         tokeniser.ungetToken();
465       }
466
467       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
468       const char* attributeType = "string";
469       if(string_equal_nocase(type.c_str(), "studio"))
470       {
471         attributeType = "model";
472       }
473
474       EntityClassAttribute attribute;
475       attribute.m_type = attributeType;
476       attribute.m_name = tokeniser.getToken();
477
478       const char* defaultSeparator = tokeniser.getToken();
479       if(string_equal(defaultSeparator, ":"))
480       {
481         const char* value = tokeniser.getToken();
482         if(!string_equal(value, ":"))
483         {
484           attribute.m_value = value;
485         }
486         else
487         {
488           tokeniser.ungetToken();
489         }
490
491         {
492           const char* descriptionSeparator = tokeniser.getToken();
493           if(string_equal(descriptionSeparator, ":"))
494           {
495             EntityClassFGD_parseSplitString(tokeniser, attribute.m_description);
496           }
497           else
498           {
499             tokeniser.ungetToken();
500           }
501         }
502       }
503       else
504       {
505         tokeniser.ungetToken();
506       }
507       EntityClass_insertAttribute(*entityClass, key.c_str(), attribute);
508     }
509     else
510     {
511       ERROR_MESSAGE("unknown key type: " << makeQuoted(type.c_str()));
512     }
513     tokeniser.nextLine();
514   }
515
516   if(isBase)
517   {
518     EntityClassFGD_insertUniqueBase(entityClass);
519   }
520   else
521   {
522     EntityClassFGD_insertUnique(entityClass);
523   }
524 }
525
526 void EntityClassFGD_loadFile(const char* filename);
527
528 void EntityClassFGD_parse(TextInputStream& inputStream, const char* path)
529 {
530   Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream);
531
532   tokeniser.nextLine();
533
534   for(;;)
535   {
536     const char* blockType = tokeniser.getToken();
537     if(blockType == 0)
538     {
539       break;
540     }
541     if(string_equal(blockType, "@SolidClass"))
542     {
543       EntityClassFGD_parseClass(tokeniser, false, false);
544     }
545     else if(string_equal(blockType, "@BaseClass"))
546     {
547       EntityClassFGD_parseClass(tokeniser, false, true);
548     }
549     else if(string_equal(blockType, "@PointClass")
550       // hl2 below
551       || string_equal(blockType, "@KeyFrameClass")
552       || string_equal(blockType, "@MoveClass")
553       || string_equal(blockType, "@FilterClass")
554       || string_equal(blockType, "@NPCClass"))
555     {
556       EntityClassFGD_parseClass(tokeniser, true, false);
557     }
558     // hl2 below
559     else if(string_equal(blockType, "@include"))
560     {
561       StringOutputStream includePath(256);
562       includePath << StringRange(path, path_get_filename_start(path));
563       includePath << tokeniser.getToken();
564       EntityClassFGD_loadFile(includePath.c_str());
565     }
566     else if(string_equal(blockType, "@mapsize"))
567     {
568       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
569       //const char* min =
570       tokeniser.getToken();
571       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ","), PARSE_ERROR);
572       //const char* max =
573       tokeniser.getToken();
574       ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
575     }
576     else
577     {
578       ERROR_MESSAGE("unknown block type: " << makeQuoted(blockType));
579     }
580   }
581
582   tokeniser.release();
583 }
584
585
586 void EntityClassFGD_loadFile(const char* filename)
587 {
588         TextFileInputStream file(filename);
589   if(!file.failed())
590   {
591     globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n";
592
593     EntityClassFGD_parse(file, filename);
594   }
595 }
596
597 EntityClass* EntityClassFGD_findOrInsert(const char *name, bool has_brushes)
598 {
599         ASSERT_NOTNULL(name);
600
601   if(string_empty(name))
602   {
603     return g_EntityClassFGD_bad;
604   }
605
606   EntityClasses::iterator i = g_EntityClassFGD_classes.find(name);
607   if(i != g_EntityClassFGD_classes.end()
608     //&& string_equal((*i).first, name)
609     )
610   {
611     return (*i).second;
612   }
613
614         EntityClass* e = EntityClass_Create_Default(name, has_brushes);
615         return EntityClassFGD_insertUnique(e);
616 }
617
618 const ListAttributeType* EntityClassFGD_findListType(const char *name)
619 {
620   ListAttributeTypes::iterator i = g_listTypesFGD.find(name);
621   if(i != g_listTypesFGD.end())
622   {
623     return &(*i).second;
624   }
625   return 0;
626
627 }
628
629
630 void EntityClassFGD_resolveInheritance(EntityClass* derivedClass)
631 {
632   if(derivedClass->inheritanceResolved == false)
633   {
634     derivedClass->inheritanceResolved = true;
635     for(StringList::iterator j = derivedClass->m_parent.begin(); j != derivedClass->m_parent.end(); ++j)
636     {
637       BaseClasses::iterator i = g_EntityClassFGD_bases.find((*j).c_str());
638       if(i == g_EntityClassFGD_bases.end())
639       {
640         globalErrorStream() << "failed to find entityDef " << makeQuoted((*j).c_str()) << " inherited by "  << makeQuoted(derivedClass->m_name.c_str()) << "\n";
641       }
642       else
643       {
644         EntityClass* parentClass = (*i).second;
645         EntityClassFGD_resolveInheritance(parentClass);
646         if(!derivedClass->colorSpecified)
647         {
648           derivedClass->colorSpecified = parentClass->colorSpecified;
649           derivedClass->color = parentClass->color;
650         }
651         if(!derivedClass->sizeSpecified)
652         {
653           derivedClass->sizeSpecified = parentClass->sizeSpecified;
654           derivedClass->mins = parentClass->mins;
655           derivedClass->maxs = parentClass->maxs;
656         }
657
658         for(EntityClassAttributes::iterator k = parentClass->m_attributes.begin(); k != parentClass->m_attributes.end(); ++k)
659         {
660           EntityClass_insertAttribute(*derivedClass, (*k).first.c_str(), (*k).second);
661         }
662       }
663     }
664   }
665 }
666
667 class EntityClassFGD : public ModuleObserver
668 {
669   std::size_t m_unrealised;
670   ModuleObservers m_observers;
671 public:
672   EntityClassFGD() : m_unrealised(3)
673   {
674   }
675   void realise()
676   {
677     if(--m_unrealised == 0)
678     {
679       StringOutputStream filename(256);
680       filename << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << "/halflife.fgd";
681       EntityClassFGD_loadFile(filename.c_str());
682
683       {
684         for(EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i)
685         {
686           EntityClassFGD_resolveInheritance((*i).second);
687           if((*i).second->fixedsize && string_empty((*i).second->m_modelpath.c_str()))
688           {
689             if(!(*i).second->sizeSpecified)
690             {
691               globalErrorStream() << "size not specified for entity class: " << makeQuoted((*i).second->m_name.c_str()) << '\n';
692             }
693             if(!(*i).second->colorSpecified)
694             {
695               globalErrorStream() << "color not specified for entity class: " << makeQuoted((*i).second->m_name.c_str()) << '\n';
696             }
697           }
698         }
699       }
700       {
701         for(BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i)
702         {
703           eclass_capture_state((*i).second);
704         }
705       }
706
707       m_observers.realise();
708     }
709   }
710   void unrealise()
711   {
712     if(++m_unrealised == 1)
713     {
714       m_observers.unrealise();
715       EntityClassFGD_clear();
716     }
717   }
718   void attach(ModuleObserver& observer)
719   {
720     m_observers.attach(observer);
721   }
722   void detach(ModuleObserver& observer)
723   {
724     m_observers.detach(observer);
725   }
726 };
727
728 EntityClassFGD g_EntityClassFGD;
729
730 void EntityClassFGD_attach(ModuleObserver& observer)
731 {
732   g_EntityClassFGD.attach(observer);
733 }
734 void EntityClassFGD_detach(ModuleObserver& observer)
735 {
736   g_EntityClassFGD.detach(observer);
737 }
738
739 void EntityClassFGD_realise()
740 {
741   g_EntityClassFGD.realise();
742 }
743 void EntityClassFGD_unrealise()
744 {
745   g_EntityClassFGD.unrealise();
746 }
747
748 void EntityClassFGD_construct()
749 {
750   // start by creating the default unknown eclass
751   g_EntityClassFGD_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
752
753   EntityClassFGD_realise();
754 }
755
756 void EntityClassFGD_destroy()
757 {
758   EntityClassFGD_unrealise();
759
760   g_EntityClassFGD_bad->free(g_EntityClassFGD_bad);
761 }
762
763 class EntityClassFGDDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef
764 {
765 };
766
767 class EntityClassFGDAPI
768 {
769   EntityClassManager m_eclassmanager;
770 public:
771   typedef EntityClassManager Type;
772   STRING_CONSTANT(Name, "halflife");
773
774   EntityClassFGDAPI()
775   {
776     EntityClassFGD_construct();
777
778     m_eclassmanager.findOrInsert = &EntityClassFGD_findOrInsert;
779     m_eclassmanager.findListType = &EntityClassFGD_findListType;
780     m_eclassmanager.forEach = &EntityClassFGD_forEach;
781     m_eclassmanager.attach = &EntityClassFGD_attach;
782     m_eclassmanager.detach = &EntityClassFGD_detach;
783     m_eclassmanager.realise = &EntityClassFGD_realise;
784     m_eclassmanager.unrealise = &EntityClassFGD_unrealise;
785
786     GlobalRadiant().attachGameToolsPathObserver(g_EntityClassFGD);
787     GlobalRadiant().attachGameNameObserver(g_EntityClassFGD);
788   }
789   ~EntityClassFGDAPI()
790   {
791     GlobalRadiant().detachGameNameObserver(g_EntityClassFGD);
792     GlobalRadiant().detachGameToolsPathObserver(g_EntityClassFGD);
793
794     EntityClassFGD_destroy();
795   }
796   EntityClassManager* getTable()
797   {
798     return &m_eclassmanager;
799   }
800 };
801
802 #include "modulesystem/singletonmodule.h"
803 #include "modulesystem/moduleregistry.h"
804
805 typedef SingletonModule<EntityClassFGDAPI, EntityClassFGDDependencies> EntityClassFGDModule;
806 typedef Static<EntityClassFGDModule> StaticEntityClassFGDModule;
807 StaticRegisterModule staticRegisterEntityClassFGD(StaticEntityClassFGDModule::instance());