* .def .ent .fgd can be used simultaneously (removed entityclass="halflife")

This commit is contained in:
Garux
2025-08-05 13:53:37 +05:00
parent dff8b0ea87
commit e4aaeaad88
4 changed files with 88 additions and 196 deletions
+1
View File
@@ -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")
+1
View File
@@ -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"
+4 -4
View File
@@ -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 );
}
};
+82 -192
View File
@@ -24,15 +24,14 @@
#include "debugging/debugging.h"
#include <map>
#include <set>
#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<const char*, EntityClass*, RawStringLessNoCase> EntityClasses;
EntityClasses g_EntityClassFGD_classes;
typedef std::map<const char*, EntityClass*, RawStringLess> BaseClasses;
typedef std::map<const char*, EntityClass*, RawStringLessNoCase> BaseClasses;
BaseClasses g_EntityClassFGD_bases;
EntityClass *g_EntityClassFGD_bad = 0;
typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
ListAttributeTypes g_listTypesFGD;
const auto pathLess = []( const CopiedString& one, const CopiedString& other ){
return path_less( one.c_str(), other.c_str() );
};
std::set<CopiedString, decltype( pathLess )> 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(const char *)>;
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<CopiedString, const char*, decltype( pathLess )> 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<EntityClassFGDAPI, EntityClassFGDDependencies> EntityClassFGDModule;
typedef Static<EntityClassFGDModule> 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<EclassFgdAPI, EntityClassFgdDependencies> EclassFgdModule;
typedef Static<EclassFgdModule> StaticEclassFgdModule;
StaticRegisterModule staticRegisterEclassFgd( StaticEclassFgdModule::instance() );