/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "eclass_fgd.h" #include "debugging/debugging.h" #include #include "ifilesystem.h" #include "iscriplib.h" #include "qerplugin.h" #include "mainframe.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" #include "stream/textfilestream.h" namespace { typedef std::map EntityClasses; EntityClasses g_EntityClassFGD_classes; typedef std::map BaseClasses; BaseClasses g_EntityClassFGD_bases; EntityClass *g_EntityClassFGD_bad = 0; typedef std::map ListAttributeTypes; ListAttributeTypes g_listTypesFGD; } void EntityClassFGD_clear(){ for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i ) { ( *i ).second->free( ( *i ).second ); } g_EntityClassFGD_bases.clear(); g_listTypesFGD.clear(); } EntityClass* EntityClassFGD_insertUniqueBase( EntityClass* entityClass ){ std::pair result = g_EntityClassFGD_bases.insert( BaseClasses::value_type( entityClass->name(), entityClass ) ); if ( !result.second ) { globalErrorStream() << "duplicate base class: " << makeQuoted( entityClass->name() ) << "\n"; //eclass_capture_state(entityClass); //entityClass->free(entityClass); } return ( *result.first ).second; } EntityClass* EntityClassFGD_insertUnique( EntityClass* entityClass ){ EntityClassFGD_insertUniqueBase( entityClass ); std::pair result = g_EntityClassFGD_classes.insert( EntityClasses::value_type( entityClass->name(), entityClass ) ); if ( !result.second ) { globalErrorStream() << "duplicate entity class: " << makeQuoted( entityClass->name() ) << "\n"; eclass_capture_state( entityClass ); entityClass->free( entityClass ); } return ( *result.first ).second; } void EntityClassFGD_forEach( EntityClassVisitor& visitor ){ for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i ) { visitor.visit( ( *i ).second ); } } inline bool EntityClassFGD_parseToken( Tokeniser& tokeniser, const char* token ){ return string_equal( tokeniser.getToken(), token ); } const char *PARSE_ERROR = "error parsing entity class definition"; void EntityClassFGD_parseSplitString( Tokeniser& tokeniser, CopiedString& string ){ StringOutputStream buffer( 256 ); for (;; ) { buffer << tokeniser.getToken(); if ( !string_equal( tokeniser.getToken(), "+" ) ) { tokeniser.ungetToken(); string = buffer.c_str(); return; } } } void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBase ){ EntityClass* entityClass = Eclass_Alloc(); entityClass->free = &Eclass_Free; entityClass->fixedsize = fixedsize; entityClass->inheritanceResolved = false; entityClass->mins = Vector3( -8, -8, -8 ); entityClass->maxs = Vector3( 8, 8, 8 ); for (;; ) { const char* property = tokeniser.getToken(); if ( string_equal( property, "=" ) ) { break; } else if ( string_equal( property, "base" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); for (;; ) { const char* base = tokeniser.getToken(); if ( string_equal( base, ")" ) ) { break; } else if ( !string_equal( base, "," ) ) { entityClass->m_parent.push_back( base ); } } } else if ( string_equal( property, "size" ) ) { entityClass->sizeSpecified = true; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); Tokeniser_getFloat( tokeniser, entityClass->mins.x() ); Tokeniser_getFloat( tokeniser, entityClass->mins.y() ); Tokeniser_getFloat( tokeniser, entityClass->mins.z() ); const char* token = tokeniser.getToken(); if ( string_equal( token, "," ) ) { Tokeniser_getFloat( tokeniser, entityClass->maxs.x() ); Tokeniser_getFloat( tokeniser, entityClass->maxs.y() ); Tokeniser_getFloat( tokeniser, entityClass->maxs.z() ); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else { entityClass->maxs = entityClass->mins; vector3_negate( entityClass->mins ); ASSERT_MESSAGE( string_equal( token, ")" ), "" ); } } else if ( string_equal( property, "color" ) ) { entityClass->colorSpecified = true; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); Tokeniser_getFloat( tokeniser, entityClass->color.x() ); entityClass->color.x() /= 256.0; Tokeniser_getFloat( tokeniser, entityClass->color.y() ); entityClass->color.y() /= 256.0; Tokeniser_getFloat( tokeniser, entityClass->color.z() ); entityClass->color.z() /= 256.0; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else if ( string_equal( property, "iconsprite" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); StringOutputStream buffer( 256 ); buffer << PathCleaned( tokeniser.getToken() ); entityClass->m_modelpath = buffer.c_str(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else if ( string_equal( property, "sprite" ) || string_equal( property, "decal" ) // hl2 below || string_equal( property, "overlay" ) || string_equal( property, "light" ) || string_equal( property, "keyframe" ) || string_equal( property, "animator" ) || string_equal( property, "quadbounds" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } // hl2 below else if ( string_equal( property, "sphere" ) || string_equal( property, "sweptplayerhull" ) || string_equal( property, "studio" ) || string_equal( property, "studioprop" ) || string_equal( property, "lightprop" ) || string_equal( property, "lightcone" ) || string_equal( property, "sidelist" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); if ( string_equal( tokeniser.getToken(), ")" ) ) { tokeniser.ungetToken(); } ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else if ( string_equal( property, "line" ) || string_equal( property, "cylinder" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); //const char* r = tokeniser.getToken(); //const char* g = tokeniser.getToken(); //const char* b = tokeniser.getToken(); for (;; ) { if ( string_equal( tokeniser.getToken(), ")" ) ) { tokeniser.ungetToken(); break; } //const char* name = tokeniser.getToken(); } ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else if ( string_equal( property, "wirebox" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); //const char* mins = tokeniser.getToken(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR ); //const char* maxs = tokeniser.getToken(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else if ( string_equal( property, "halfgridsnap" ) ) { } else { ERROR_MESSAGE( PARSE_ERROR ); } } entityClass->m_name = tokeniser.getToken(); if ( !isBase ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR ); EntityClassFGD_parseSplitString( tokeniser, entityClass->m_comments ); } tokeniser.nextLine(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR ); tokeniser.nextLine(); for (;; ) { CopiedString key = tokeniser.getToken(); if ( string_equal( key.c_str(), "]" ) ) { tokeniser.nextLine(); break; } if ( string_equal_nocase( key.c_str(), "input" ) || string_equal_nocase( key.c_str(), "output" ) ) { const char* name = tokeniser.getToken(); if ( !string_equal( name, "(" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); //const char* type = tokeniser.getToken(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); const char* descriptionSeparator = tokeniser.getToken(); if ( string_equal( descriptionSeparator, ":" ) ) { CopiedString description; EntityClassFGD_parseSplitString( tokeniser, description ); } else { tokeniser.ungetToken(); } tokeniser.nextLine(); continue; } } ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); CopiedString type = tokeniser.getToken(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); if ( string_equal_nocase( type.c_str(), "flags" ) ) { EntityClassAttribute attribute; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR ); tokeniser.nextLine(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR ); tokeniser.nextLine(); for (;; ) { const char* flag = tokeniser.getToken(); if ( string_equal( flag, "]" ) ) { tokeniser.nextLine(); break; } else { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR ); //const char* name = tokeniser.getToken(); { const char* defaultSeparator = tokeniser.getToken(); if ( string_equal( defaultSeparator, ":" ) ) { tokeniser.getToken(); { const char* descriptionSeparator = tokeniser.getToken(); if ( string_equal( descriptionSeparator, ":" ) ) { EntityClassFGD_parseSplitString( tokeniser, attribute.m_description ); } else { tokeniser.ungetToken(); } } } else { tokeniser.ungetToken(); } } } tokeniser.nextLine(); } EntityClass_insertAttribute( *entityClass, key.c_str(), attribute ); } else if ( string_equal_nocase( type.c_str(), "choices" ) ) { EntityClassAttribute attribute; ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR ); attribute.m_name = tokeniser.getToken(); const char* valueSeparator = tokeniser.getToken(); if ( string_equal( valueSeparator, ":" ) ) { const char* value = tokeniser.getToken(); if ( !string_equal( value, ":" ) ) { attribute.m_value = value; } else { tokeniser.ungetToken(); } { const char* descriptionSeparator = tokeniser.getToken(); if ( string_equal( descriptionSeparator, ":" ) ) { EntityClassFGD_parseSplitString( tokeniser, attribute.m_description ); } else { tokeniser.ungetToken(); } } } else { tokeniser.ungetToken(); } ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR ); tokeniser.nextLine(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR ); tokeniser.nextLine(); StringOutputStream listTypeName( 64 ); listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str(); attribute.m_type = listTypeName.c_str(); ListAttributeType& listType = g_listTypesFGD[listTypeName.c_str()]; for (;; ) { const char* value = tokeniser.getToken(); if ( string_equal( value, "]" ) ) { tokeniser.nextLine(); break; } else { CopiedString tmp( value ); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR ); const char* name = tokeniser.getToken(); listType.push_back( name, tmp.c_str() ); } tokeniser.nextLine(); } for ( ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i ) { if ( string_equal( attribute.m_value.c_str(), ( *i ).first.c_str() ) ) { attribute.m_value = ( *i ).second.c_str(); } } EntityClass_insertAttribute( *entityClass, key.c_str(), attribute ); } else if ( string_equal_nocase( type.c_str(), "decal" ) ) { } else if ( string_equal_nocase( type.c_str(), "string" ) || string_equal_nocase( type.c_str(), "integer" ) || string_equal_nocase( type.c_str(), "studio" ) || string_equal_nocase( type.c_str(), "sprite" ) || string_equal_nocase( type.c_str(), "color255" ) || string_equal_nocase( type.c_str(), "target_source" ) || string_equal_nocase( type.c_str(), "target_destination" ) || string_equal_nocase( type.c_str(), "sound" ) // hl2 below || string_equal_nocase( type.c_str(), "angle" ) || string_equal_nocase( type.c_str(), "origin" ) || string_equal_nocase( type.c_str(), "float" ) || string_equal_nocase( type.c_str(), "node_dest" ) || string_equal_nocase( type.c_str(), "filterclass" ) || string_equal_nocase( type.c_str(), "vector" ) || string_equal_nocase( type.c_str(), "sidelist" ) || string_equal_nocase( type.c_str(), "material" ) || string_equal_nocase( type.c_str(), "vecline" ) || string_equal_nocase( type.c_str(), "axis" ) || string_equal_nocase( type.c_str(), "npcclass" ) || string_equal_nocase( type.c_str(), "target_name_or_class" ) || string_equal_nocase( type.c_str(), "pointentityclass" ) || string_equal_nocase( type.c_str(), "scene" ) ) { if ( !string_equal( tokeniser.getToken(), "readonly" ) ) { tokeniser.ungetToken(); } ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR ); const char* attributeType = "string"; if ( string_equal_nocase( type.c_str(), "studio" ) ) { attributeType = "model"; } EntityClassAttribute attribute; attribute.m_type = attributeType; attribute.m_name = tokeniser.getToken(); const char* defaultSeparator = tokeniser.getToken(); if ( string_equal( defaultSeparator, ":" ) ) { const char* value = tokeniser.getToken(); if ( !string_equal( value, ":" ) ) { attribute.m_value = value; } else { tokeniser.ungetToken(); } { const char* descriptionSeparator = tokeniser.getToken(); if ( string_equal( descriptionSeparator, ":" ) ) { EntityClassFGD_parseSplitString( tokeniser, attribute.m_description ); } else { tokeniser.ungetToken(); } } } else { tokeniser.ungetToken(); } EntityClass_insertAttribute( *entityClass, key.c_str(), attribute ); } else { ERROR_MESSAGE( "unknown key type: " << makeQuoted( type.c_str() ) ); } tokeniser.nextLine(); } if ( isBase ) { EntityClassFGD_insertUniqueBase( entityClass ); } else { EntityClassFGD_insertUnique( entityClass ); } } void EntityClassFGD_loadFile( const char* filename ); void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){ Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream ); tokeniser.nextLine(); for (;; ) { const char* blockType = tokeniser.getToken(); if ( blockType == 0 ) { break; } if ( string_equal( blockType, "@SolidClass" ) ) { EntityClassFGD_parseClass( tokeniser, false, false ); } else if ( string_equal( blockType, "@BaseClass" ) ) { EntityClassFGD_parseClass( tokeniser, false, true ); } else if ( string_equal( blockType, "@PointClass" ) // hl2 below || string_equal( blockType, "@KeyFrameClass" ) || string_equal( blockType, "@MoveClass" ) || string_equal( blockType, "@FilterClass" ) || string_equal( blockType, "@NPCClass" ) ) { EntityClassFGD_parseClass( tokeniser, true, false ); } // hl2 below else if ( string_equal( blockType, "@include" ) ) { StringOutputStream includePath( 256 ); includePath << StringRange( path, path_get_filename_start( path ) ); includePath << tokeniser.getToken(); EntityClassFGD_loadFile( includePath.c_str() ); } else if ( string_equal( blockType, "@mapsize" ) ) { ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR ); //const char* min = tokeniser.getToken(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR ); //const char* max = tokeniser.getToken(); ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR ); } else { ERROR_MESSAGE( "unknown block type: " << makeQuoted( blockType ) ); } } tokeniser.release(); } void EntityClassFGD_loadFile( const char* filename ){ TextFileInputStream file( filename ); if ( !file.failed() ) { globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << "\n"; 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_resolveInheritance( EntityClass* derivedClass ){ if ( derivedClass->inheritanceResolved == false ) { derivedClass->inheritanceResolved = true; for ( StringList::iterator j = derivedClass->m_parent.begin(); j != derivedClass->m_parent.end(); ++j ) { BaseClasses::iterator i = g_EntityClassFGD_bases.find( ( *j ).c_str() ); if ( i == g_EntityClassFGD_bases.end() ) { globalErrorStream() << "failed to find entityDef " << makeQuoted( ( *j ).c_str() ) << " inherited by " << makeQuoted( derivedClass->m_name.c_str() ) << "\n"; } else { EntityClass* parentClass = ( *i ).second; EntityClassFGD_resolveInheritance( parentClass ); if ( !derivedClass->colorSpecified ) { derivedClass->colorSpecified = parentClass->colorSpecified; derivedClass->color = parentClass->color; } if ( !derivedClass->sizeSpecified ) { derivedClass->sizeSpecified = parentClass->sizeSpecified; derivedClass->mins = parentClass->mins; derivedClass->maxs = parentClass->maxs; } for ( EntityClassAttributes::iterator k = parentClass->m_attributes.begin(); k != parentClass->m_attributes.end(); ++k ) { EntityClass_insertAttribute( *derivedClass, ( *k ).first.c_str(), ( *k ).second ); } } } } } class EntityClassFGD : public ModuleObserver { std::size_t m_unrealised; ModuleObservers m_observers; public: EntityClassFGD() : m_unrealised( 3 ){ } void realise(){ if ( --m_unrealised == 0 ) { StringOutputStream filename( 256 ); filename << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << "/halflife.fgd"; EntityClassFGD_loadFile( filename.c_str() ); { for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i ) { EntityClassFGD_resolveInheritance( ( *i ).second ); if ( ( *i ).second->fixedsize && string_empty( ( *i ).second->m_modelpath.c_str() ) ) { if ( !( *i ).second->sizeSpecified ) { globalErrorStream() << "size not specified for entity class: " << makeQuoted( ( *i ).second->m_name.c_str() ) << '\n'; } if ( !( *i ).second->colorSpecified ) { globalErrorStream() << "color not specified for entity class: " << makeQuoted( ( *i ).second->m_name.c_str() ) << '\n'; } } } } { for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i ) { eclass_capture_state( ( *i ).second ); } } 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 ); } 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; Radiant_attachGameToolsPathObserver( g_EntityClassFGD ); Radiant_attachGameNameObserver( g_EntityClassFGD ); } ~EntityClassFGDAPI(){ Radiant_detachGameNameObserver( g_EntityClassFGD ); Radiant_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() );