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"
32 #include "string/string.h"
33 #include "eclasslib.h"
36 #include "stream/stringstream.h"
37 #include "moduleobservers.h"
39 #include "stream/textfilestream.h"
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;
53 void EntityClassFGD_clear(){
54 for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i )
56 ( *i ).second->free( ( *i ).second );
58 g_EntityClassFGD_bases.clear();
59 g_listTypesFGD.clear();
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);
69 return ( *result.first ).second;
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 );
80 return ( *result.first ).second;
83 void EntityClassFGD_forEach( EntityClassVisitor& visitor ){
84 for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i )
86 visitor.visit( ( *i ).second );
90 inline bool EntityClassFGD_parseToken( Tokeniser& tokeniser, const char* token ){
91 return string_equal( tokeniser.getToken(), token );
94 #define PARSE_ERROR "error parsing entity class definition"
96 void EntityClassFGD_parseSplitString( Tokeniser& tokeniser, CopiedString& string ){
97 StringOutputStream buffer( 256 );
100 buffer << tokeniser.getToken();
101 if ( !string_equal( tokeniser.getToken(), "+" ) ) {
102 tokeniser.ungetToken();
103 string = buffer.c_str();
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 );
119 const char* property = tokeniser.getToken();
120 if ( string_equal( property, "=" ) ) {
123 else if ( string_equal( property, "base" ) ) {
124 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
127 const char* base = tokeniser.getToken();
128 if ( string_equal( base, ")" ) ) {
131 else if ( !string_equal( base, "," ) ) {
132 entityClass->m_parent.push_back( base );
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 );
151 entityClass->maxs = entityClass->mins;
152 vector3_negate( entityClass->mins );
153 ASSERT_MESSAGE( string_equal( token, ")" ), "" );
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 );
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 );
174 else if ( string_equal( property, "sprite" )
175 || string_equal( property, "decal" )
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 );
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();
197 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
199 else if ( string_equal( property, "line" )
200 || string_equal( property, "cylinder" ) ) {
201 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
203 tokeniser.getToken();
205 tokeniser.getToken();
207 tokeniser.getToken();
210 if ( string_equal( tokeniser.getToken(), ")" ) ) {
211 tokeniser.ungetToken();
215 tokeniser.getToken();
217 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
219 else if ( string_equal( property, "wirebox" ) ) {
220 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
222 tokeniser.getToken();
223 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR );
225 tokeniser.getToken();
226 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
228 else if ( string_equal( property, "halfgridsnap" ) ) {
232 ERROR_MESSAGE( PARSE_ERROR );
236 entityClass->m_name = tokeniser.getToken();
239 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
241 EntityClassFGD_parseSplitString( tokeniser, entityClass->m_comments );
244 tokeniser.nextLine();
246 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
248 tokeniser.nextLine();
252 CopiedString key = tokeniser.getToken();
253 if ( string_equal( key.c_str(), "]" ) ) {
254 tokeniser.nextLine();
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 );
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 );
273 tokeniser.ungetToken();
275 tokeniser.nextLine();
280 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
281 CopiedString type = tokeniser.getToken();
282 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
284 if ( string_equal_nocase( type.c_str(), "flags" ) ) {
285 EntityClassAttribute attribute;
287 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR );
288 tokeniser.nextLine();
289 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
290 tokeniser.nextLine();
293 const char* flag = tokeniser.getToken();
294 if ( string_equal( flag, "]" ) ) {
295 tokeniser.nextLine();
300 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
302 tokeniser.getToken();
304 const char* defaultSeparator = tokeniser.getToken();
305 if ( string_equal( defaultSeparator, ":" ) ) {
306 tokeniser.getToken();
308 const char* descriptionSeparator = tokeniser.getToken();
309 if ( string_equal( descriptionSeparator, ":" ) ) {
310 EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
314 tokeniser.ungetToken();
320 tokeniser.ungetToken();
324 tokeniser.nextLine();
326 EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
328 else if ( string_equal_nocase( type.c_str(), "choices" ) ) {
329 EntityClassAttribute attribute;
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;
341 tokeniser.ungetToken();
344 const char* descriptionSeparator = tokeniser.getToken();
345 if ( string_equal( descriptionSeparator, ":" ) ) {
346 EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
350 tokeniser.ungetToken();
356 tokeniser.ungetToken();
358 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR );
359 tokeniser.nextLine();
360 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
361 tokeniser.nextLine();
363 StringOutputStream listTypeName( 64 );
364 listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str();
365 attribute.m_type = listTypeName.c_str();
367 ListAttributeType& listType = g_listTypesFGD[listTypeName.c_str()];
371 const char* value = tokeniser.getToken();
372 if ( string_equal( value, "]" ) ) {
373 tokeniser.nextLine();
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() );
383 tokeniser.nextLine();
386 for ( ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i )
388 if ( string_equal( attribute.m_value.c_str(), ( *i ).first.c_str() ) ) {
389 attribute.m_value = ( *i ).second.c_str();
393 EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
395 else if ( string_equal_nocase( type.c_str(), "decal" ) ) {
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" )
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();
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";
430 EntityClassAttribute attribute;
431 attribute.m_type = attributeType;
432 attribute.m_name = tokeniser.getToken();
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;
442 tokeniser.ungetToken();
446 const char* descriptionSeparator = tokeniser.getToken();
447 if ( string_equal( descriptionSeparator, ":" ) ) {
448 EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
452 tokeniser.ungetToken();
458 tokeniser.ungetToken();
460 EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
464 ERROR_MESSAGE( "unknown key type: " << makeQuoted( type.c_str() ) );
466 tokeniser.nextLine();
470 EntityClassFGD_insertUniqueBase( entityClass );
474 EntityClassFGD_insertUnique( entityClass );
478 void EntityClassFGD_loadFile( const char* filename );
480 void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){
481 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream );
483 tokeniser.nextLine();
487 const char* blockType = tokeniser.getToken();
488 if ( blockType == 0 ) {
491 if ( string_equal( blockType, "@SolidClass" ) ) {
492 EntityClassFGD_parseClass( tokeniser, false, false );
494 else if ( string_equal( blockType, "@BaseClass" ) ) {
495 EntityClassFGD_parseClass( tokeniser, false, true );
497 else if ( string_equal( blockType, "@PointClass" )
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 );
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() );
512 else if ( string_equal( blockType, "@mapsize" ) ) {
513 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
515 tokeniser.getToken();
516 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR );
518 tokeniser.getToken();
519 ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
523 ERROR_MESSAGE( "unknown block type: " << makeQuoted( blockType ) );
531 void EntityClassFGD_loadFile( const char* filename ){
532 TextFileInputStream file( filename );
533 if ( !file.failed() ) {
534 globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << "\n";
536 EntityClassFGD_parse( file, filename );
540 EntityClass* EntityClassFGD_findOrInsert( const char *name, bool has_brushes ){
541 ASSERT_NOTNULL( name );
543 if ( string_empty( name ) ) {
544 return g_EntityClassFGD_bad;
547 EntityClasses::iterator i = g_EntityClassFGD_classes.find( name );
548 if ( i != g_EntityClassFGD_classes.end()
549 //&& string_equal((*i).first, name)
551 return ( *i ).second;
554 EntityClass* e = EntityClass_Create_Default( name, has_brushes );
555 return EntityClassFGD_insertUnique( e );
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;
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 )
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";
579 EntityClass* parentClass = ( *i ).second;
580 EntityClassFGD_resolveInheritance( parentClass );
581 if ( !derivedClass->colorSpecified ) {
582 derivedClass->colorSpecified = parentClass->colorSpecified;
583 derivedClass->color = parentClass->color;
585 if ( !derivedClass->sizeSpecified ) {
586 derivedClass->sizeSpecified = parentClass->sizeSpecified;
587 derivedClass->mins = parentClass->mins;
588 derivedClass->maxs = parentClass->maxs;
591 for ( EntityClassAttributes::iterator k = parentClass->m_attributes.begin(); k != parentClass->m_attributes.end(); ++k )
593 EntityClass_insertAttribute( *derivedClass, ( *k ).first.c_str(), ( *k ).second );
600 class EntityClassFGD : public ModuleObserver
602 std::size_t m_unrealised;
603 ModuleObservers m_observers;
605 EntityClassFGD() : m_unrealised( 3 ){
608 if ( --m_unrealised == 0 ) {
609 StringOutputStream filename( 256 );
610 filename << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << "/halflife.fgd";
611 EntityClassFGD_loadFile( filename.c_str() );
614 for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i )
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';
621 if ( !( *i ).second->colorSpecified ) {
622 globalErrorStream() << "color not specified for entity class: " << makeQuoted( ( *i ).second->m_name.c_str() ) << '\n';
628 for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i )
630 eclass_capture_state( ( *i ).second );
634 m_observers.realise();
638 if ( ++m_unrealised == 1 ) {
639 m_observers.unrealise();
640 EntityClassFGD_clear();
643 void attach( ModuleObserver& observer ){
644 m_observers.attach( observer );
646 void detach( ModuleObserver& observer ){
647 m_observers.detach( observer );
651 EntityClassFGD g_EntityClassFGD;
653 void EntityClassFGD_attach( ModuleObserver& observer ){
654 g_EntityClassFGD.attach( observer );
656 void EntityClassFGD_detach( ModuleObserver& observer ){
657 g_EntityClassFGD.detach( observer );
660 void EntityClassFGD_realise(){
661 g_EntityClassFGD.realise();
663 void EntityClassFGD_unrealise(){
664 g_EntityClassFGD.unrealise();
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 ), "" );
671 EntityClassFGD_realise();
674 void EntityClassFGD_destroy(){
675 EntityClassFGD_unrealise();
677 g_EntityClassFGD_bad->free( g_EntityClassFGD_bad );
680 class EntityClassFGDDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef
684 class EntityClassFGDAPI
686 EntityClassManager m_eclassmanager;
688 typedef EntityClassManager Type;
689 STRING_CONSTANT( Name, "halflife" );
692 EntityClassFGD_construct();
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;
702 GlobalRadiant().attachGameToolsPathObserver( g_EntityClassFGD );
703 GlobalRadiant().attachGameNameObserver( g_EntityClassFGD );
705 ~EntityClassFGDAPI(){
706 GlobalRadiant().detachGameNameObserver( g_EntityClassFGD );
707 GlobalRadiant().detachGameToolsPathObserver( g_EntityClassFGD );
709 EntityClassFGD_destroy();
711 EntityClassManager* getTable(){
712 return &m_eclassmanager;
716 #include "modulesystem/singletonmodule.h"
717 #include "modulesystem/moduleregistry.h"
719 typedef SingletonModule<EntityClassFGDAPI, EntityClassFGDDependencies> EntityClassFGDModule;
720 typedef Static<EntityClassFGDModule> StaticEntityClassFGDModule;
721 StaticRegisterModule staticRegisterEntityClassFGD( StaticEntityClassFGDModule::instance() );