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