/* 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 */ ///\file ///\brief EntityClass plugin that supports the .ent xml entity-definition format. /// /// the .ent xml format expresses entity-definitions. /// /// /// /// /// /// /// /// /// /// /// /// /// the attributes of an entity type are defined like this: /// /// <[name of attribute type] /// key="[entity key name]" /// name="[name shown in gui]" /// value="[default entity key value]" /// >[comment text shown in gui] /// /// each attribute type has a specialised attribute-editor GUI /// /// currently-supported attribute types: /// /// string a string /// array an array of strings - value is a semi-colon-delimited string /// integer an integer value /// boolean an integer - shows as a checkbox - true = non-zero /// integer2 two integer values /// integer3 three integer values /// real3 three floating-point values /// angle specialisation of real - Yaw angle /// direction specialisation of real - Yaw angle, -1 = down, -2 = up /// angles specialisation of real3 - Pitch Yaw Roll /// color specialisation of real3 - RGB floating-point colour /// target a string that uniquely identifies an entity or group of entities /// targetname a string that uniquely identifies an entity or group of entities /// sound the VFS path to a sound file /// texture the VFS path to a texture file or a shader name /// model the VFS path to a model file /// skin the VFS path to a skin file /// /// /// flag attributes define a flag in the "spawnflags" key: /// /// [comment text shown in gui] /// /// the default value for a flag bit is always 0. /// /// /// List attributes have a set of valid values. /// Create new list attribute types like this: /// /// /// /// /// /// /// these can then be used as attribute types. /// /// /// An attribute definition should specify a default value that corresponds /// with the default value given by the game. If the default value is not /// specified in the attribute definition, it is assumed to be an empty string. /// /// If the currently-selected entity in Radiant does not specify a value for /// the key of an attribute, the default value from the attribute-definition /// will be displayed in the attribute-editor and used when visualising the /// entity in the preview windows. E.g. the Doom3 "light" entity has a /// "light_radius" key. Light entities without a "light_radius" key are /// displayed in Doom3 with a radius of 300. The default value for the /// "light_radius" attribute definition should be specified as "300 300 300". /// #include "eclass_xml.h" #include "ieclass.h" #include "irender.h" #include "ifilesystem.h" #include "iarchive.h" #include "xml/xmlparser.h" #include "generic/object.h" #include "generic/reference.h" #include "stream/stringstream.h" #include "stream/textfilestream.h" #include "os/path.h" #include "eclasslib.h" #include "modulesystem/moduleregistry.h" #include "stringio.h" #define PARSE_ERROR( elementName, name ) makeQuoted( elementName ) << " is not a valid child of " << makeQuoted( name ) class IgnoreBreaks { public: const char* m_first; const char* m_last; IgnoreBreaks( const char* first, const char* last ) : m_first( first ), m_last( last ){ } }; template TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const IgnoreBreaks& ignoreBreaks ){ for ( const char* i = ignoreBreaks.m_first; i != ignoreBreaks.m_last; ++i ) { if ( *i != '\n' ) { ostream << *i; } } return ostream; } namespace { class TreeXMLImporter : public TextOutputStream { public: virtual TreeXMLImporter& pushElement( const XMLElement& element ) = 0; virtual void popElement( const char* name ) = 0; }; template class Storage { char m_storage[sizeof( Type )]; public: Type& get(){ return *reinterpret_cast( m_storage ); } const Type& get() const { return *reinterpret_cast( m_storage ); } }; class BreakImporter : public TreeXMLImporter { public: BreakImporter( StringOutputStream& comment ){ comment << '\n'; } static const char* name(){ return "n"; } TreeXMLImporter& pushElement( const XMLElement& element ){ ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) ); return *this; } void popElement( const char* elementName ){ ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) ); } std::size_t write( const char* data, std::size_t length ){ return length; } }; class AttributeImporter : public TreeXMLImporter { StringOutputStream& m_comment; public: AttributeImporter( StringOutputStream& comment, EntityClass* entityClass, const XMLElement& element ) : m_comment( comment ){ const char* type = element.name(); const char* key = element.attribute( "key" ); const char* name = element.attribute( "name" ); const char* value = element.attribute( "value" ); ASSERT_MESSAGE( !string_empty( key ), "key attribute not specified" ); ASSERT_MESSAGE( !string_empty( name ), "name attribute not specified" ); if ( string_equal( type, "flag" ) ) { std::size_t bit = atoi( element.attribute( "bit" ) ); ASSERT_MESSAGE( bit < MAX_FLAGS, "invalid flag bit" ); ASSERT_MESSAGE( string_empty( entityClass->flagnames[bit] ), "non-unique flag bit" ); strcpy( entityClass->flagnames[bit], key ); } m_comment << key; m_comment << " : "; EntityClass_insertAttribute( *entityClass, key, EntityClassAttribute( type, name, value ) ); } ~AttributeImporter(){ } TreeXMLImporter& pushElement( const XMLElement& element ){ ERROR_MESSAGE( PARSE_ERROR( element.name(), "attribute" ) ); return *this; } void popElement( const char* elementName ){ ERROR_MESSAGE( PARSE_ERROR( elementName, "attribute" ) ); } std::size_t write( const char* data, std::size_t length ){ return m_comment.write( data, length ); } }; bool attributeSupported( const char* name ){ return string_equal( name, "real" ) || string_equal( name, "integer" ) || string_equal( name, "boolean" ) || string_equal( name, "string" ) || string_equal( name, "array" ) || string_equal( name, "flag" ) || string_equal( name, "real3" ) || string_equal( name, "integer3" ) || string_equal( name, "direction" ) || string_equal( name, "angle" ) || string_equal( name, "angles" ) || string_equal( name, "color" ) || string_equal( name, "target" ) || string_equal( name, "targetname" ) || string_equal( name, "sound" ) || string_equal( name, "texture" ) || string_equal( name, "model" ) || string_equal( name, "skin" ) || string_equal( name, "integer2" ); } typedef std::map ListAttributeTypes; bool listAttributeSupported( ListAttributeTypes& listTypes, const char* name ){ return listTypes.find( name ) != listTypes.end(); } class ClassImporter : public TreeXMLImporter { EntityClassCollector& m_collector; EntityClass* m_eclass; StringOutputStream m_comment; Storage m_attribute; ListAttributeTypes& m_listTypes; public: ClassImporter( EntityClassCollector& collector, ListAttributeTypes& listTypes, const XMLElement& element ) : m_collector( collector ), m_listTypes( listTypes ){ m_eclass = Eclass_Alloc(); m_eclass->free = &Eclass_Free; const char* name = element.attribute( "name" ); ASSERT_MESSAGE( !string_empty( name ), "name attribute not specified for class" ); m_eclass->m_name = name; const char* color = element.attribute( "color" ); ASSERT_MESSAGE( !string_empty( name ), "color attribute not specified for class " << name ); string_parse_vector3( color, m_eclass->color ); eclass_capture_state( m_eclass ); const char* model = element.attribute( "model" ); if ( !string_empty( model ) ) { StringOutputStream buffer( 256 ); buffer << PathCleaned( model ); m_eclass->m_modelpath = buffer.c_str(); } const char* type = element.name(); if ( string_equal( type, "point" ) ) { const char* box = element.attribute( "box" ); ASSERT_MESSAGE( !string_empty( box ), "box attribute not found for class " << name ); m_eclass->fixedsize = true; string_parse_vector( box, &m_eclass->mins.x(), &m_eclass->mins.x() + 6 ); } } ~ClassImporter(){ m_eclass->m_comments = m_comment.c_str(); m_collector.insert( m_eclass ); for ( ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i ) { m_collector.insert( ( *i ).first.c_str(), ( *i ).second ); } } static const char* name(){ return "class"; } TreeXMLImporter& pushElement( const XMLElement& element ){ if ( attributeSupported( element.name() ) || listAttributeSupported( m_listTypes, element.name() ) ) { constructor( m_attribute.get(), makeReference( m_comment ), m_eclass, element ); return m_attribute.get(); } else { ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) ); return *this; } } void popElement( const char* elementName ){ if ( attributeSupported( elementName ) || listAttributeSupported( m_listTypes, elementName ) ) { destructor( m_attribute.get() ); } else { ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) ); } } std::size_t write( const char* data, std::size_t length ){ return m_comment.write( data, length ); } }; class ItemImporter : public TreeXMLImporter { public: ItemImporter( ListAttributeType& list, const XMLElement& element ){ const char* name = element.attribute( "name" ); const char* value = element.attribute( "value" ); list.push_back( name, value ); } TreeXMLImporter& pushElement( const XMLElement& element ){ ERROR_MESSAGE( PARSE_ERROR( element.name(), "item" ) ); return *this; } void popElement( const char* elementName ){ ERROR_MESSAGE( PARSE_ERROR( elementName, "item" ) ); } std::size_t write( const char* data, std::size_t length ){ return length; } }; bool isItem( const char* name ){ return string_equal( name, "item" ); } class ListAttributeImporter : public TreeXMLImporter { ListAttributeType* m_listType; Storage m_item; public: ListAttributeImporter( ListAttributeTypes& listTypes, const XMLElement& element ){ const char* name = element.attribute( "name" ); m_listType = &listTypes[name]; } TreeXMLImporter& pushElement( const XMLElement& element ){ if ( isItem( element.name() ) ) { constructor( m_item.get(), makeReference( *m_listType ), element ); return m_item.get(); } else { ERROR_MESSAGE( PARSE_ERROR( element.name(), "list" ) ); return *this; } } void popElement( const char* elementName ){ if ( isItem( elementName ) ) { destructor( m_item.get() ); } else { ERROR_MESSAGE( PARSE_ERROR( elementName, "list" ) ); } } std::size_t write( const char* data, std::size_t length ){ return length; } }; bool classSupported( const char* name ){ return string_equal( name, "group" ) || string_equal( name, "point" ); } bool listSupported( const char* name ){ return string_equal( name, "list" ); } class ClassesImporter : public TreeXMLImporter { EntityClassCollector& m_collector; Storage m_class; Storage m_list; ListAttributeTypes m_listTypes; public: ClassesImporter( EntityClassCollector& collector ) : m_collector( collector ){ } static const char* name(){ return "classes"; } TreeXMLImporter& pushElement( const XMLElement& element ){ if ( classSupported( element.name() ) ) { constructor( m_class.get(), makeReference( m_collector ), makeReference( m_listTypes ), element ); return m_class.get(); } else if ( listSupported( element.name() ) ) { constructor( m_list.get(), makeReference( m_listTypes ), element ); return m_list.get(); } else { ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) ); return *this; } } void popElement( const char* elementName ){ if ( classSupported( elementName ) ) { destructor( m_class.get() ); } else if ( listSupported( elementName ) ) { destructor( m_list.get() ); } else { ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) ); } } std::size_t write( const char* data, std::size_t length ){ return length; } }; class EclassXMLImporter : public TreeXMLImporter { EntityClassCollector& m_collector; Storage m_classes; public: EclassXMLImporter( EntityClassCollector& collector ) : m_collector( collector ){ } static const char* name(){ return "classes"; } TreeXMLImporter& pushElement( const XMLElement& element ){ if ( string_equal( element.name(), ClassesImporter::name() ) ) { constructor( m_classes.get(), makeReference( m_collector ) ); return m_classes.get(); } else { ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) ); return *this; } } void popElement( const char* elementName ){ if ( string_equal( elementName, ClassesImporter::name() ) ) { destructor( m_classes.get() ); } else { ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) ); } } std::size_t write( const char* data, std::size_t length ){ return length; } }; class TreeXMLImporterStack : public XMLImporter { std::vector< Reference > m_importers; public: TreeXMLImporterStack( TreeXMLImporter& importer ){ m_importers.push_back( makeReference( importer ) ); } void pushElement( const XMLElement& element ){ m_importers.push_back( makeReference( m_importers.back().get().pushElement( element ) ) ); } void popElement( const char* name ){ m_importers.pop_back(); m_importers.back().get().popElement( name ); } std::size_t write( const char* buffer, std::size_t length ){ return m_importers.back().get().write( buffer, length ); } }; const char* GetExtension(){ return "ent"; } void ScanFile( EntityClassCollector& collector, const char *filename ){ TextFileInputStream inputFile( filename ); if ( !inputFile.failed() ) { XMLStreamParser parser( inputFile ); EclassXMLImporter importer( collector ); TreeXMLImporterStack stack( importer ); parser.exportXML( stack ); } } } #include "modulesystem/singletonmodule.h" class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef { }; class EclassXMLAPI { EntityClassScanner m_eclassxml; public: typedef EntityClassScanner Type; STRING_CONSTANT( Name, "xml" ); EclassXMLAPI(){ m_eclassxml.scanFile = &ScanFile; m_eclassxml.getExtension = &GetExtension; } EntityClassScanner* getTable(){ return &m_eclassxml; } }; typedef SingletonModule EclassXMLModule; typedef Static StaticEclassXMLModule; StaticRegisterModule staticRegisterEclassXML( StaticEclassXMLModule::instance() );