2 Copyright (C) 2001-2006, William Joseph.
5 This file is part of GtkRadiant.
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.
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.
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
22 #include "eclass_fgd.h"
24 #include "debugging/debugging.h"
28 #include "ifilesystem.h"
29 #include "iscriplib.h"
30 #include "qerplugin.h"
31 #include "mainframe.h"
33 #include "string/string.h"
34 #include "eclasslib.h"
37 #include "stream/stringstream.h"
38 #include "moduleobservers.h"
40 #include "stream/textfilestream.h"
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;
54 void EntityClassFGD_clear(){
55 for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i )
57 ( *i ).second->free( ( *i ).second );
59 g_EntityClassFGD_bases.clear();
60 g_listTypesFGD.clear();
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);
70 return ( *result.first ).second;
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 );
81 return ( *result.first ).second;
84 void EntityClassFGD_forEach( EntityClassVisitor& visitor ){
85 for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i )
87 visitor.visit( ( *i ).second );
91 #define PARSE_ERROR "error parsing fgd entity class definition at line " << tokeniser.getLine() << ':' << tokeniser.getColumn()
93 static bool s_fgd_warned = false;
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 );
99 globalErrorStream() << PARSE_ERROR << "\nExpected " << makeQuoted( token ) << '\n';
106 #define ERROR_FGD( message )\
109 globalErrorStream() << message << '\n';\
111 ERROR_MESSAGE( message );\
112 s_fgd_warned = true;\
116 #define ERROR_FGD( message ) {}
118 void EntityClassFGD_parseSplitString( Tokeniser& tokeniser, CopiedString& string ){
119 StringOutputStream buffer( 256 );
122 buffer << tokeniser.getToken();
123 if ( !string_equal( tokeniser.getToken(), "+" ) ) {
124 tokeniser.ungetToken();
125 string = buffer.c_str();
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 );
141 const char* property = tokeniser.getToken();
142 if ( string_equal( property, "=" ) ) {
145 else if ( string_equal( property, "base" ) ) {
146 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
149 const char* base = tokeniser.getToken();
150 if ( string_equal( base, ")" ) ) {
153 else if ( !string_equal( base, "," ) ) {
154 entityClass->m_parent.push_back( base );
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 );
173 entityClass->maxs = entityClass->mins;
174 vector3_negate( entityClass->mins );
175 ASSERT_MESSAGE( string_equal( token, ")" ), "" );
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 );
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 );
196 else if ( string_equal( property, "sprite" )
197 || string_equal( property, "decal" )
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 );
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();
218 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
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();
227 entityClass->m_modelpath = token;
229 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
231 else if ( string_equal( property, "line" )
232 || string_equal( property, "cylinder" ) ) {
233 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
235 tokeniser.getToken();
237 tokeniser.getToken();
239 tokeniser.getToken();
242 if ( string_equal( tokeniser.getToken(), ")" ) ) {
243 tokeniser.ungetToken();
247 tokeniser.getToken();
249 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
251 else if ( string_equal( property, "wirebox" ) ) {
252 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
254 tokeniser.getToken();
255 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR );
257 tokeniser.getToken();
258 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
260 else if ( string_equal( property, "halfgridsnap" ) ) {
262 else if ( string_equal( property, "flags" ) ) {
263 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
266 const char* base = tokeniser.getToken();
267 if ( string_equal( base, ")" ) ) {
270 else if ( !string_equal( base, "," ) ) {
271 if( string_equal_nocase( base, "Angle" ) ){
273 // entityClass->has_angles = true;
280 ERROR_FGD( PARSE_ERROR );
284 entityClass->m_name = tokeniser.getToken();
287 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
289 EntityClassFGD_parseSplitString( tokeniser, entityClass->m_comments );
291 const char* urlSeparator = tokeniser.getToken();
292 if ( string_equal( urlSeparator, ":" ) ) {
294 EntityClassFGD_parseSplitString( tokeniser, tmp );
298 tokeniser.ungetToken();
302 tokeniser.nextLine();
304 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
306 tokeniser.nextLine();
310 CopiedString key = tokeniser.getToken();
311 if ( string_equal( key.c_str(), "]" ) ) {
312 tokeniser.nextLine();
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 );
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 );
331 tokeniser.ungetToken();
333 tokeniser.nextLine();
338 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
339 CopiedString type = tokeniser.getToken();
340 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
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 );
347 const char* flag = tokeniser.getToken();
348 if ( string_equal( flag, "]" ) ) {
349 tokeniser.nextLine();
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 );
358 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
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;
366 const char* defaultSeparator = tokeniser.getToken();
367 if ( string_equal( defaultSeparator, ":" ) ) {
368 tokeniser.getToken();
370 const char* descriptionSeparator = tokeniser.getToken();
371 if ( string_equal( descriptionSeparator, ":" ) ) {
372 EntityClassFGD_parseSplitString( tokeniser, attribute->m_description );
376 tokeniser.ungetToken();
382 tokeniser.ungetToken();
386 tokeniser.nextLine();
389 else if ( string_equal_nocase( type.c_str(), "choices" ) ) {
390 EntityClassAttribute attribute;
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;
402 tokeniser.ungetToken();
405 const char* descriptionSeparator = tokeniser.getToken();
406 if ( string_equal( descriptionSeparator, ":" ) ) {
407 EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
411 tokeniser.ungetToken();
417 tokeniser.ungetToken();
419 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR );
420 tokeniser.nextLine();
421 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
422 tokeniser.nextLine();
424 StringOutputStream listTypeName( 64 );
425 listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str();
426 attribute.m_type = listTypeName.c_str();
428 ListAttributeType& listType = g_listTypesFGD[listTypeName.c_str()];
432 const char* value = tokeniser.getToken();
433 if ( string_equal( value, "]" ) ) {
434 tokeniser.nextLine();
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() );
444 const char* descriptionSeparator = tokeniser.getToken();
445 if ( string_equal( descriptionSeparator, ":" ) ) {
446 EntityClassFGD_parseSplitString( tokeniser, tmp );
450 tokeniser.ungetToken();
453 tokeniser.nextLine();
456 for ( ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i )
458 if ( string_equal( attribute.m_value.c_str(), ( *i ).first.c_str() ) ) {
459 attribute.m_value = ( *i ).second.c_str();
463 EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
465 else if ( string_equal_nocase( type.c_str(), "decal" ) ) {
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" )
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();
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";
500 EntityClassAttribute attribute;
501 attribute.m_type = attributeType;
502 attribute.m_name = tokeniser.getToken();
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;
512 tokeniser.ungetToken();
516 const char* descriptionSeparator = tokeniser.getToken();
517 if ( string_equal( descriptionSeparator, ":" ) ) {
518 EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
522 tokeniser.ungetToken();
528 tokeniser.ungetToken();
530 EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
534 ERROR_FGD( "unknown key type: " << makeQuoted( type ) );
536 tokeniser.nextLine();
540 EntityClassFGD_insertUniqueBase( entityClass );
544 EntityClassFGD_insertUnique( entityClass );
548 void EntityClassFGD_loadFile( const char* filename );
550 void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){
551 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream );
553 tokeniser.nextLine();
557 const char* blockType = tokeniser.getToken();
558 if ( blockType == 0 ) {
561 if ( string_equal_nocase( blockType, "@SolidClass" ) ) {
562 EntityClassFGD_parseClass( tokeniser, false, false );
564 else if ( string_equal_nocase( blockType, "@BaseClass" ) ) {
565 EntityClassFGD_parseClass( tokeniser, false, true );
567 else if ( string_equal_nocase( blockType, "@PointClass" )
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 );
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() );
582 else if ( string_equal( blockType, "@mapsize" ) ) {
583 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
585 tokeniser.getToken();
586 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR );
588 tokeniser.getToken();
589 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
593 ERROR_FGD( "unknown block type: " << makeQuoted( blockType ) );
601 void EntityClassFGD_loadFile( const char* filename ){
602 TextFileInputStream file( filename );
603 if ( !file.failed() ) {
604 globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << "\n";
606 EntityClassFGD_parse( file, filename );
610 EntityClass* EntityClassFGD_findOrInsert( const char *name, bool has_brushes ){
611 ASSERT_NOTNULL( name );
613 if ( string_empty( name ) ) {
614 return g_EntityClassFGD_bad;
617 EntityClasses::iterator i = g_EntityClassFGD_classes.find( name );
618 if ( i != g_EntityClassFGD_classes.end()
619 //&& string_equal((*i).first, name)
621 return ( *i ).second;
624 EntityClass* e = EntityClass_Create_Default( name, has_brushes );
625 return EntityClassFGD_insertUnique( e );
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;
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 )
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";
649 EntityClass* parentClass = ( *i ).second;
650 EntityClassFGD_resolveInheritance( parentClass );
651 if ( !derivedClass->colorSpecified ) {
652 derivedClass->colorSpecified = parentClass->colorSpecified;
653 derivedClass->color = parentClass->color;
655 if ( !derivedClass->sizeSpecified ) {
656 derivedClass->sizeSpecified = parentClass->sizeSpecified;
657 derivedClass->mins = parentClass->mins;
658 derivedClass->maxs = parentClass->maxs;
661 for ( EntityClassAttributes::iterator k = parentClass->m_attributes.begin(); k != parentClass->m_attributes.end(); ++k )
663 EntityClass_insertAttribute( *derivedClass, ( *k ).first.c_str(), ( *k ).second );
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];
678 class EntityClassFGD : public ModuleObserver
680 std::size_t m_unrealised;
681 ModuleObservers m_observers;
683 EntityClassFGD() : m_unrealised( 3 ){
686 if ( --m_unrealised == 0 ) {
689 StringOutputStream baseDirectory( 256 );
690 baseDirectory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" ) << '/';
691 StringOutputStream gameDirectory( 256 );
692 gameDirectory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << '/';
694 const auto pathLess = []( const CopiedString& one, const CopiedString& other ){
695 return path_less( one.c_str(), other.c_str() );
697 std::map<CopiedString, const char*, decltype( pathLess )> name_path( pathLess );
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 );
706 constructDirectory( baseDirectory.c_str(), "fgd" );
707 if ( !string_equal( baseDirectory.c_str(), gameDirectory.c_str() ) ) {
708 constructDirectory( gameDirectory.c_str(), "fgd" );
711 for( const auto& [ name, path ] : name_path ){
712 StringOutputStream filename( 256 );
713 filename << path << name.c_str();
714 EntityClassFGD_loadFile( filename.c_str() );
719 for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i )
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';
726 if ( !( *i ).second->colorSpecified ) {
727 globalErrorStream() << "color not specified for entity class: " << makeQuoted( ( *i ).second->name() ) << '\n';
733 for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i )
735 eclass_capture_state( ( *i ).second );
739 m_observers.realise();
743 if ( ++m_unrealised == 1 ) {
744 m_observers.unrealise();
745 EntityClassFGD_clear();
748 void attach( ModuleObserver& observer ){
749 m_observers.attach( observer );
751 void detach( ModuleObserver& observer ){
752 m_observers.detach( observer );
756 EntityClassFGD g_EntityClassFGD;
758 void EntityClassFGD_attach( ModuleObserver& observer ){
759 g_EntityClassFGD.attach( observer );
761 void EntityClassFGD_detach( ModuleObserver& observer ){
762 g_EntityClassFGD.detach( observer );
765 void EntityClassFGD_realise(){
766 g_EntityClassFGD.realise();
768 void EntityClassFGD_unrealise(){
769 g_EntityClassFGD.unrealise();
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 ), "" );
776 EntityClassFGD_realise();
779 void EntityClassFGD_destroy(){
780 EntityClassFGD_unrealise();
782 g_EntityClassFGD_bad->free( g_EntityClassFGD_bad );
785 class EntityClassFGDDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef
789 class EntityClassFGDAPI
791 EntityClassManager m_eclassmanager;
793 typedef EntityClassManager Type;
794 STRING_CONSTANT( Name, "halflife" );
797 EntityClassFGD_construct();
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;
807 Radiant_attachGameToolsPathObserver( g_EntityClassFGD );
808 Radiant_attachGameNameObserver( g_EntityClassFGD );
810 ~EntityClassFGDAPI(){
811 Radiant_detachGameNameObserver( g_EntityClassFGD );
812 Radiant_detachGameToolsPathObserver( g_EntityClassFGD );
814 EntityClassFGD_destroy();
816 EntityClassManager* getTable(){
817 return &m_eclassmanager;
821 #include "modulesystem/singletonmodule.h"
822 #include "modulesystem/moduleregistry.h"
824 typedef SingletonModule<EntityClassFGDAPI, EntityClassFGDDependencies> EntityClassFGDModule;
825 typedef Static<EntityClassFGDModule> StaticEntityClassFGDModule;
826 StaticRegisterModule staticRegisterEntityClassFGD( StaticEntityClassFGDModule::instance() );