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