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