diff --git a/docs/changelog-custom.txt b/docs/changelog-custom.txt index 56c20cd5..0af01200 100644 --- a/docs/changelog-custom.txt +++ b/docs/changelog-custom.txt @@ -1124,6 +1124,7 @@ misc... support bsp without vis data add RBSP, FBSP support * bobToolz: new Aperture Door builder + * .def .ent .fgd can be used simultaneously (removed entityclass="halflife") diff --git a/include/ieclass.h b/include/ieclass.h index f481186b..59a2bac6 100644 --- a/include/ieclass.h +++ b/include/ieclass.h @@ -56,6 +56,7 @@ struct EntityClassScanner void ( *scanFile )( EntityClassCollector& collector, const char* filename ); const char* ( *getExtension )( ); + void ( *finalize )( EntityClassCollector& collector ) = []( EntityClassCollector& collector ){}; }; #include "modulesystem.h" diff --git a/radiant/eclass.cpp b/radiant/eclass.cpp index d8efbf0e..1a070dd9 100644 --- a/radiant/eclass.cpp +++ b/radiant/eclass.cpp @@ -157,9 +157,7 @@ public: } } - const auto relPath = StringStream( m_directory, name ); - - scanner.scanFile( g_collector, relPath ); + scanner.scanFile( g_collector, StringStream( m_directory, name ) ); } }; @@ -181,7 +179,7 @@ void EntityClassQuake3_constructDirectory( const char* directory, const char* ex /* facts: - modules are alpha sorted, so .def .ent loading order + modules are alpha sorted, so .def .fgd .ent (xml) loading order all base & mod files of the same type are alpha sorted together equal file name in mod is ignored equal eclass name inserted later is ignored @@ -209,6 +207,8 @@ void EntityClassQuake3_Construct(){ { EntityClassesLoadFile( table, path ) ( name.c_str() ); } + + table.finalize( g_collector ); } }; diff --git a/radiant/eclass_fgd.cpp b/radiant/eclass_fgd.cpp index a8c29d02..a7a0c2cf 100644 --- a/radiant/eclass_fgd.cpp +++ b/radiant/eclass_fgd.cpp @@ -24,15 +24,14 @@ #include "debugging/debugging.h" #include +#include -#include "ifilesystem.h" #include "iscriplib.h" #include "qerplugin.h" #include "string/string.h" #include "eclasslib.h" #include "os/path.h" -#include "os/dir.h" #include "stream/stringstream.h" #include "moduleobservers.h" #include "stringio.h" @@ -42,38 +41,41 @@ namespace { typedef std::map EntityClasses; EntityClasses g_EntityClassFGD_classes; -typedef std::map BaseClasses; +typedef std::map BaseClasses; BaseClasses g_EntityClassFGD_bases; -EntityClass *g_EntityClassFGD_bad = 0; typedef std::map ListAttributeTypes; ListAttributeTypes g_listTypesFGD; + +const auto pathLess = []( const CopiedString& one, const CopiedString& other ){ + return path_less( one.c_str(), other.c_str() ); +}; +std::set g_loadedFgds( pathLess ); } void EntityClassFGD_clear(){ - g_EntityClassFGD_classes.clear(); for ( auto [ name, eclass ] : g_EntityClassFGD_bases ) { + eclass_capture_state( eclass ); eclass->free( eclass ); } + g_EntityClassFGD_classes.clear(); g_EntityClassFGD_bases.clear(); g_listTypesFGD.clear(); + g_loadedFgds.clear(); } -EntityClass* EntityClassFGD_insertUniqueBase( EntityClass* entityClass, bool allowfree = true ){ +EntityClass* EntityClassFGD_insertUniqueBase( EntityClass* entityClass ){ auto [ it, inserted ] = g_EntityClassFGD_bases.insert( BaseClasses::value_type( entityClass->name(), entityClass ) ); if ( !inserted ) { globalErrorStream() << "duplicate base class: " << makeQuoted( entityClass->name() ) << '\n'; - if( allowfree ){ - eclass_capture_state( entityClass ); - entityClass->free( entityClass ); - } + eclass_capture_state( entityClass ); + entityClass->free( entityClass ); } return it->second; } EntityClass* EntityClassFGD_insertUnique( EntityClass* entityClass ){ - EntityClassFGD_insertUniqueBase( entityClass, false ); auto [ it, inserted ] = g_EntityClassFGD_classes.insert( EntityClasses::value_type( entityClass->name(), entityClass ) ); if ( !inserted ) { globalErrorStream() << "duplicate entity class: " << makeQuoted( entityClass->name() ) << '\n'; @@ -83,13 +85,6 @@ EntityClass* EntityClassFGD_insertUnique( EntityClass* entityClass ){ return it->second; } -void EntityClassFGD_forEach( EntityClassVisitor& visitor ){ - for ( auto [ name, eclass ] : g_EntityClassFGD_classes ) - { - visitor.visit( eclass ); - } -} - #define PARSE_ERROR "error parsing fgd entity class definition at line " << tokeniser.getLine() << ':' << tokeniser.getColumn() static bool s_fgd_warned = false; @@ -179,11 +174,11 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas entityClass->colorSpecified = true; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); Tokeniser_getFloat( tokeniser, entityClass->color.x() ); - entityClass->color.x() /= 256.0; + entityClass->color.x() /= 255.0; Tokeniser_getFloat( tokeniser, entityClass->color.y() ); - entityClass->color.y() /= 256.0; + entityClass->color.y() /= 255.0; Tokeniser_getFloat( tokeniser, entityClass->color.z() ); - entityClass->color.z() /= 256.0; + entityClass->color.z() /= 255.0; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else if ( string_equal( property, "iconsprite" ) ) { @@ -544,9 +539,9 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas } } -using LoadInclude = std::function; +void EntityClassFGD_loadUniqueFile( const char* filename ); -void EntityClassFGD_parse( TextInputStream& inputStream, const char* path, const LoadInclude& loadInclude ){ +void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){ Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream ); tokeniser.nextLine(); @@ -573,7 +568,7 @@ void EntityClassFGD_parse( TextInputStream& inputStream, const char* path, const } // hl2 below else if ( string_equal( blockType, "@include" ) ) { - loadInclude( tokeniser.getToken() ); + EntityClassFGD_loadUniqueFile( StringStream( PathFilenameless( path ), tokeniser.getToken() ) ); } else if ( string_equal( blockType, "@mapsize" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); @@ -594,40 +589,18 @@ void EntityClassFGD_parse( TextInputStream& inputStream, const char* path, const } -void EntityClassFGD_loadFile( const char* filename, const LoadInclude& loadInclude ){ +void EntityClassFGD_loadFile( const char* filename ){ TextFileInputStream file( filename ); if ( !file.failed() ) { globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << '\n'; - EntityClassFGD_parse( file, filename, loadInclude ); + EntityClassFGD_parse( file, filename ); } } -EntityClass* EntityClassFGD_findOrInsert( const char *name, bool has_brushes ){ - ASSERT_NOTNULL( name ); - - if ( string_empty( name ) ) { - return g_EntityClassFGD_bad; - } - - EntityClasses::iterator i = g_EntityClassFGD_classes.find( name ); - if ( i != g_EntityClassFGD_classes.end() - //&& string_equal( ( *i ).first, name ) - ) { - return ( *i ).second; - } - - EntityClass* e = EntityClass_Create_Default( name, has_brushes ); - return EntityClassFGD_insertUnique( e ); -} - -const ListAttributeType* EntityClassFGD_findListType( const char *name ){ - ListAttributeTypes::iterator i = g_listTypesFGD.find( name ); - if ( i != g_listTypesFGD.end() ) { - return &( *i ).second; - } - return 0; - +void EntityClassFGD_loadUniqueFile( const char* filename ){ + if( g_loadedFgds.insert( filename ).second ) + EntityClassFGD_loadFile( filename ); } @@ -638,9 +611,13 @@ void EntityClassFGD_resolveInheritance( EntityClass* derivedClass ){ { BaseClasses::iterator i = g_EntityClassFGD_bases.find( parentName.c_str() ); if ( i == g_EntityClassFGD_bases.end() ) { - globalErrorStream() << "failed to find entityDef " << makeQuoted( parentName.c_str() ) << " inherited by " << makeQuoted( derivedClass->name() ) << '\n'; + i = g_EntityClassFGD_classes.find( parentName.c_str() ); + if ( i == g_EntityClassFGD_classes.end() ) { + globalErrorStream() << "failed to find entityDef " << makeQuoted( parentName.c_str() ) << " inherited by " << makeQuoted( derivedClass->name() ) << '\n'; + continue; + } } - else + { EntityClass* parentClass = ( *i ).second; EntityClassFGD_resolveInheritance( parentClass ); @@ -662,7 +639,12 @@ void EntityClassFGD_resolveInheritance( EntityClass* derivedClass ){ for( size_t flag = 0; flag < MAX_FLAGS; ++flag ){ if( !string_empty( parentClass->flagnames[flag] ) && string_empty( derivedClass->flagnames[flag] ) ){ strncpy( derivedClass->flagnames[flag], parentClass->flagnames[flag], std::size( derivedClass->flagnames[flag] ) - 1 ); - derivedClass->flagAttributes[flag] = parentClass->flagAttributes[flag]; + // this ptr ref is cool, but requires parents to stay alive (e.g. bases) + // non base parent also may be deleted during insertion to global entity stack, if entity is already present there + // derivedClass->flagAttributes[flag] = parentClass->flagAttributes[flag]; + derivedClass->flagAttributes[flag] = &EntityClass_insertAttribute( *derivedClass, + parentClass->flagAttributes[flag]->m_name.c_str(), + *parentClass->flagAttributes[flag] ).second; } } } @@ -670,154 +652,62 @@ void EntityClassFGD_resolveInheritance( EntityClass* derivedClass ){ } } -class EntityClassFGD : public ModuleObserver -{ - std::size_t m_unrealised; - ModuleObservers m_observers; -public: - EntityClassFGD() : m_unrealised( 3 ){ - } - void realise(){ - if ( --m_unrealised == 0 ) { - { - const auto baseDirectory = StringStream( GlobalRadiant().getGameToolsPath(), GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" ), '/' ); - const auto gameDirectory = StringStream( GlobalRadiant().getGameToolsPath(), GlobalRadiant().getGameName(), '/' ); +void Eclass_ScanFile_fgd( EntityClassCollector& collector, const char *filename ){ + EntityClassFGD_loadUniqueFile( filename ); +} - const auto pathLess = []( const CopiedString& one, const CopiedString& other ){ - return path_less( one.c_str(), other.c_str() ); - }; - std::map name_path( pathLess ); - - const auto constructDirectory = [&name_path]( const char* directory, const char* extension ){ - globalOutputStream() << "EntityClass: searching " << makeQuoted( directory ) << " for *." << extension << '\n'; - Directory_forEach( directory, matchFileExtension( extension, [directory, &name_path]( const char *name ){ - name_path.emplace( name, directory ); - } ) ); - }; - - constructDirectory( baseDirectory, "fgd" ); - if ( !string_equal( baseDirectory, gameDirectory ) ) { - constructDirectory( gameDirectory, "fgd" ); - } - - const LoadInclude loadInclude = [&]( const char *name ){ - if( auto it = name_path.find( name ); it != name_path.end() && it->second != nullptr ) // null path == loaded - EntityClassFGD_loadFile( StringStream( std::exchange( it->second, nullptr ), name ), loadInclude ); - }; - - for( auto& [ name, path ] : name_path ){ - if( path != nullptr ) // null path == loaded - EntityClassFGD_loadFile( StringStream( std::exchange( path, nullptr ), name ), loadInclude ); - } +void EClass_finalize_fgd( EntityClassCollector& collector ){ + for ( auto [ name, eclass ] : g_EntityClassFGD_classes ) + { + EntityClassFGD_resolveInheritance( eclass ); + if ( eclass->fixedsize && eclass->m_modelpath.empty() ) { + if ( !eclass->sizeSpecified ) { + globalErrorStream() << "size not specified for entity class: " << makeQuoted( eclass->name() ) << '\n'; } - - { - for ( auto [ name, eclass ] : g_EntityClassFGD_classes ) - { - EntityClassFGD_resolveInheritance( eclass ); - if ( eclass->fixedsize && eclass->m_modelpath.empty() ) { - if ( !eclass->sizeSpecified ) { - globalErrorStream() << "size not specified for entity class: " << makeQuoted( eclass->name() ) << '\n'; - } - if ( !eclass->colorSpecified ) { - globalErrorStream() << "color not specified for entity class: " << makeQuoted( eclass->name() ) << '\n'; - } - } - } + if ( !eclass->colorSpecified ) { + globalErrorStream() << "color not specified for entity class: " << makeQuoted( eclass->name() ) << '\n'; } - { - for ( auto [ name, eclass ] : g_EntityClassFGD_bases ) - { - eclass_capture_state( eclass ); - } - } - - m_observers.realise(); } } - void unrealise(){ - if ( ++m_unrealised == 1 ) { - m_observers.unrealise(); - EntityClassFGD_clear(); - } - } - void attach( ModuleObserver& observer ){ - m_observers.attach( observer ); - } - void detach( ModuleObserver& observer ){ - m_observers.detach( observer ); - } -}; -EntityClassFGD g_EntityClassFGD; - -void EntityClassFGD_attach( ModuleObserver& observer ){ - g_EntityClassFGD.attach( observer ); -} -void EntityClassFGD_detach( ModuleObserver& observer ){ - g_EntityClassFGD.detach( observer ); + for ( auto [ name, eclass ] : g_EntityClassFGD_classes ) + { + eclass_capture_state( eclass ); + collector.insert( eclass ); + } + for( const auto& [ name, list ] : g_listTypesFGD ) + { + collector.insert( name.c_str(), list ); + } + EntityClassFGD_clear(); } -void EntityClassFGD_realise(){ - g_EntityClassFGD.realise(); -} -void EntityClassFGD_unrealise(){ - g_EntityClassFGD.unrealise(); -} - -void EntityClassFGD_construct(){ - // start by creating the default unknown eclass - g_EntityClassFGD_bad = EClass_Create( "UNKNOWN_CLASS", Vector3( 0.0f, 0.5f, 0.0f ), "" ); - - EntityClassFGD_realise(); -} - -void EntityClassFGD_destroy(){ - EntityClassFGD_unrealise(); - - g_EntityClassFGD_bad->free( g_EntityClassFGD_bad ); -} - -class EntityClassFGDDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef -{ -}; - -class EntityClassFGDAPI -{ - EntityClassManager m_eclassmanager; -public: - typedef EntityClassManager Type; - STRING_CONSTANT( Name, "halflife" ); - - EntityClassFGDAPI(){ - EntityClassFGD_construct(); - - m_eclassmanager.findOrInsert = &EntityClassFGD_findOrInsert; - m_eclassmanager.findListType = &EntityClassFGD_findListType; - m_eclassmanager.forEach = &EntityClassFGD_forEach; - m_eclassmanager.attach = &EntityClassFGD_attach; - m_eclassmanager.detach = &EntityClassFGD_detach; - m_eclassmanager.realise = &EntityClassFGD_realise; - m_eclassmanager.unrealise = &EntityClassFGD_unrealise; - - GlobalRadiant().attachGameToolsPathObserver( g_EntityClassFGD ); - GlobalRadiant().attachGameNameObserver( g_EntityClassFGD ); - } - ~EntityClassFGDAPI(){ - GlobalRadiant().detachGameNameObserver( g_EntityClassFGD ); - GlobalRadiant().detachGameToolsPathObserver( g_EntityClassFGD ); - - EntityClassFGD_destroy(); - } - EntityClassManager* getTable(){ - return &m_eclassmanager; - } -}; #include "modulesystem/singletonmodule.h" #include "modulesystem/moduleregistry.h" -typedef SingletonModule EntityClassFGDModule; -typedef Static StaticEntityClassFGDModule; -StaticRegisterModule staticRegisterEntityClassFGD( StaticEntityClassFGDModule::instance() ); +class EntityClassFgdDependencies : public GlobalShaderCacheModuleRef, public GlobalScripLibModuleRef +{ +}; + +class EclassFgdAPI +{ + EntityClassScanner m_eclassfgd; +public: + typedef EntityClassScanner Type; + STRING_CONSTANT( Name, "fgd" ); + + EclassFgdAPI(){ + m_eclassfgd.scanFile = &Eclass_ScanFile_fgd; + m_eclassfgd.getExtension = [](){ return "fgd"; }; + m_eclassfgd.finalize = &EClass_finalize_fgd; + } + EntityClassScanner* getTable(){ + return &m_eclassfgd; + } +}; + +typedef SingletonModule EclassFgdModule; +typedef Static StaticEclassFgdModule; +StaticRegisterModule staticRegisterEclassFgd( StaticEclassFgdModule::instance() );