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
23 ///\brief EntityClass plugin that supports the .ent xml entity-definition format.
25 /// the .ent xml format expresses entity-definitions.
27 /// <!-- defines an entity which cannot have brushes grouped with it -->
28 /// <point name="[name of entity type]" colour="[RGB floating-point colour shown in editor]"
29 /// box="[minXYZ maxXYZ floating point bounding-box]" model="[model path]">
30 /// <!-- attribute definitions go here -->
33 /// <!-- defines an entity which can have brushes grouped with it -->
34 /// <group name="[name of entity type]" colour="[RGB floating-point colour shown in editor]">
35 /// <!-- attribute definitions go here -->
39 /// the attributes of an entity type are defined like this:
41 /// <[name of attribute type]
42 /// key="[entity key name]"
43 /// name="[name shown in gui]"
44 /// value="[default entity key value]"
45 /// >[comment text shown in gui]</[name of attribute type]>
47 /// each attribute type has a specialised attribute-editor GUI
49 /// currently-supported attribute types:
52 /// array an array of strings - value is a semi-colon-delimited string
53 /// integer an integer value
54 /// boolean an integer - shows as a checkbox - true = non-zero
55 /// integer2 two integer values
56 /// integer3 three integer values
57 /// real3 three floating-point values
58 /// angle specialisation of real - Yaw angle
59 /// direction specialisation of real - Yaw angle, -1 = down, -2 = up
60 /// angles specialisation of real3 - Pitch Yaw Roll
61 /// color specialisation of real3 - RGB floating-point colour
62 /// target a string that uniquely identifies an entity or group of entities
63 /// targetname a string that uniquely identifies an entity or group of entities
64 /// sound the VFS path to a sound file
65 /// texture the VFS path to a texture file or a shader name
66 /// model the VFS path to a model file
67 /// skin the VFS path to a skin file
70 /// flag attributes define a flag in the "spawnflags" key:
74 /// name="[name shown in gui]"
75 /// bit="[bit-index in spawnflags]"
76 /// >[comment text shown in gui]<flag>
78 /// the default value for a flag bit is always 0.
81 /// List attributes have a set of valid values.
82 /// Create new list attribute types like this:
84 /// <list name="[name of list attribute type]">
85 /// <item name="[first name shown in menu]" value="[entity key value]"/>
86 /// <item name="[second name shown in menu]" value="[entity key value]"/>
89 /// these can then be used as attribute types.
92 /// An attribute definition should specify a default value that corresponds
93 /// with the default value given by the game. If the default value is not
94 /// specified in the attribute definition, it is assumed to be an empty string.
96 /// If the currently-selected entity in Radiant does not specify a value for
97 /// the key of an attribute, the default value from the attribute-definition
98 /// will be displayed in the attribute-editor and used when visualising the
99 /// entity in the preview windows. E.g. the Doom3 "light" entity has a
100 /// "light_radius" key. Light entities without a "light_radius" key are
101 /// displayed in Doom3 with a radius of 300. The default value for the
102 /// "light_radius" attribute definition should be specified as "300 300 300".
108 #include "eclass_xml.h"
112 #include "ifilesystem.h"
113 #include "iarchive.h"
115 #include "xml/xmlparser.h"
116 #include "generic/object.h"
117 #include "generic/reference.h"
118 #include "stream/stringstream.h"
119 #include "stream/textfilestream.h"
121 #include "eclasslib.h"
122 #include "modulesystem/moduleregistry.h"
123 #include "stringio.h"
125 #define PARSE_ERROR( elementName, name ) makeQuoted( elementName ) << " is not a valid child of " << makeQuoted( name )
132 IgnoreBreaks( const char* first, const char* last ) : m_first( first ), m_last( last ){
136 template<typename TextOutputStreamType>
137 TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const IgnoreBreaks& ignoreBreaks ){
138 for ( const char* i = ignoreBreaks.m_first; i != ignoreBreaks.m_last; ++i )
150 class TreeXMLImporter : public TextOutputStream
153 virtual TreeXMLImporter& pushElement( const XMLElement& element ) = 0;
154 virtual void popElement( const char* name ) = 0;
157 template<typename Type>
160 char m_storage[sizeof( Type )];
163 return *reinterpret_cast<Type*>( m_storage );
165 const Type& get() const {
166 return *reinterpret_cast<const Type*>( m_storage );
170 class BreakImporter : public TreeXMLImporter
173 BreakImporter( StringOutputStream& comment ){
176 static const char* name(){
179 TreeXMLImporter& pushElement( const XMLElement& element ){
180 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
183 void popElement( const char* elementName ){
184 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
186 std::size_t write( const char* data, std::size_t length ){
191 class AttributeImporter : public TreeXMLImporter
193 StringOutputStream& m_comment;
196 AttributeImporter( StringOutputStream& comment, EntityClass* entityClass, const XMLElement& element ) : m_comment( comment ){
197 const char* type = element.name();
198 const char* key = element.attribute( "key" );
199 const char* name = element.attribute( "name" );
200 const char* value = element.attribute( "value" );
202 ASSERT_MESSAGE( !string_empty( key ), "key attribute not specified" );
203 ASSERT_MESSAGE( !string_empty( name ), "name attribute not specified" );
205 if ( string_equal( type, "flag" ) ) {
206 std::size_t bit = atoi( element.attribute( "bit" ) );
207 ASSERT_MESSAGE( bit < MAX_FLAGS, "invalid flag bit" );
208 ASSERT_MESSAGE( string_empty( entityClass->flagnames[bit] ), "non-unique flag bit" );
209 strcpy( entityClass->flagnames[bit], key );
215 EntityClass_insertAttribute( *entityClass, key, EntityClassAttribute( type, name, value ) );
217 ~AttributeImporter(){
219 TreeXMLImporter& pushElement( const XMLElement& element ){
220 ERROR_MESSAGE( PARSE_ERROR( element.name(), "attribute" ) );
223 void popElement( const char* elementName ){
224 ERROR_MESSAGE( PARSE_ERROR( elementName, "attribute" ) );
226 std::size_t write( const char* data, std::size_t length ){
227 return m_comment.write( data, length );
231 bool attributeSupported( const char* name ){
232 return string_equal( name, "real" )
233 || string_equal( name, "integer" )
234 || string_equal( name, "boolean" )
235 || string_equal( name, "string" )
236 || string_equal( name, "array" )
237 || string_equal( name, "flag" )
238 || string_equal( name, "real3" )
239 || string_equal( name, "integer3" )
240 || string_equal( name, "direction" )
241 || string_equal( name, "angle" )
242 || string_equal( name, "angles" )
243 || string_equal( name, "color" )
244 || string_equal( name, "target" )
245 || string_equal( name, "targetname" )
246 || string_equal( name, "sound" )
247 || string_equal( name, "texture" )
248 || string_equal( name, "model" )
249 || string_equal( name, "skin" )
250 || string_equal( name, "integer2" );
253 typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
255 bool listAttributeSupported( ListAttributeTypes& listTypes, const char* name ){
256 return listTypes.find( name ) != listTypes.end();
260 class ClassImporter : public TreeXMLImporter
262 EntityClassCollector& m_collector;
263 EntityClass* m_eclass;
264 StringOutputStream m_comment;
265 Storage<AttributeImporter> m_attribute;
266 ListAttributeTypes& m_listTypes;
269 ClassImporter( EntityClassCollector& collector, ListAttributeTypes& listTypes, const XMLElement& element ) : m_collector( collector ), m_listTypes( listTypes ){
270 m_eclass = Eclass_Alloc();
271 m_eclass->free = &Eclass_Free;
273 const char* name = element.attribute( "name" );
274 ASSERT_MESSAGE( !string_empty( name ), "name attribute not specified for class" );
275 m_eclass->m_name = name;
277 const char* color = element.attribute( "color" );
278 ASSERT_MESSAGE( !string_empty( name ), "color attribute not specified for class " << name );
279 string_parse_vector3( color, m_eclass->color );
280 eclass_capture_state( m_eclass );
282 const char* model = element.attribute( "model" );
283 if ( !string_empty( model ) ) {
284 StringOutputStream buffer( 256 );
285 buffer << PathCleaned( model );
286 m_eclass->m_modelpath = buffer.c_str();
289 const char* type = element.name();
290 if ( string_equal( type, "point" ) ) {
291 const char* box = element.attribute( "box" );
292 ASSERT_MESSAGE( !string_empty( box ), "box attribute not found for class " << name );
293 m_eclass->fixedsize = true;
294 string_parse_vector( box, &m_eclass->mins.x(), &m_eclass->mins.x() + 6 );
298 m_eclass->m_comments = m_comment.c_str();
299 m_collector.insert( m_eclass );
301 for ( ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i )
303 m_collector.insert( ( *i ).first.c_str(), ( *i ).second );
306 static const char* name(){
309 TreeXMLImporter& pushElement( const XMLElement& element ){
310 if ( attributeSupported( element.name() ) || listAttributeSupported( m_listTypes, element.name() ) ) {
311 constructor( m_attribute.get(), makeReference( m_comment ), m_eclass, element );
312 return m_attribute.get();
316 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
320 void popElement( const char* elementName ){
321 if ( attributeSupported( elementName ) || listAttributeSupported( m_listTypes, elementName ) ) {
322 destructor( m_attribute.get() );
326 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
329 std::size_t write( const char* data, std::size_t length ){
330 return m_comment.write( data, length );
334 class ItemImporter : public TreeXMLImporter
337 ItemImporter( ListAttributeType& list, const XMLElement& element ){
338 const char* name = element.attribute( "name" );
339 const char* value = element.attribute( "value" );
340 list.push_back( name, value );
342 TreeXMLImporter& pushElement( const XMLElement& element ){
343 ERROR_MESSAGE( PARSE_ERROR( element.name(), "item" ) );
346 void popElement( const char* elementName ){
347 ERROR_MESSAGE( PARSE_ERROR( elementName, "item" ) );
349 std::size_t write( const char* data, std::size_t length ){
354 bool isItem( const char* name ){
355 return string_equal( name, "item" );
358 class ListAttributeImporter : public TreeXMLImporter
360 ListAttributeType* m_listType;
361 Storage<ItemImporter> m_item;
363 ListAttributeImporter( ListAttributeTypes& listTypes, const XMLElement& element ){
364 const char* name = element.attribute( "name" );
365 m_listType = &listTypes[name];
367 TreeXMLImporter& pushElement( const XMLElement& element ){
368 if ( isItem( element.name() ) ) {
369 constructor( m_item.get(), makeReference( *m_listType ), element );
374 ERROR_MESSAGE( PARSE_ERROR( element.name(), "list" ) );
378 void popElement( const char* elementName ){
379 if ( isItem( elementName ) ) {
380 destructor( m_item.get() );
384 ERROR_MESSAGE( PARSE_ERROR( elementName, "list" ) );
387 std::size_t write( const char* data, std::size_t length ){
392 bool classSupported( const char* name ){
393 return string_equal( name, "group" )
394 || string_equal( name, "point" );
397 bool listSupported( const char* name ){
398 return string_equal( name, "list" );
401 class ClassesImporter : public TreeXMLImporter
403 EntityClassCollector& m_collector;
404 Storage<ClassImporter> m_class;
405 Storage<ListAttributeImporter> m_list;
406 ListAttributeTypes m_listTypes;
409 ClassesImporter( EntityClassCollector& collector ) : m_collector( collector ){
411 static const char* name(){
414 TreeXMLImporter& pushElement( const XMLElement& element ){
415 if ( classSupported( element.name() ) ) {
416 constructor( m_class.get(), makeReference( m_collector ), makeReference( m_listTypes ), element );
417 return m_class.get();
419 else if ( listSupported( element.name() ) ) {
420 constructor( m_list.get(), makeReference( m_listTypes ), element );
425 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
429 void popElement( const char* elementName ){
430 if ( classSupported( elementName ) ) {
431 destructor( m_class.get() );
433 else if ( listSupported( elementName ) ) {
434 destructor( m_list.get() );
438 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
441 std::size_t write( const char* data, std::size_t length ){
446 class EclassXMLImporter : public TreeXMLImporter
448 EntityClassCollector& m_collector;
449 Storage<ClassesImporter> m_classes;
452 EclassXMLImporter( EntityClassCollector& collector ) : m_collector( collector ){
454 static const char* name(){
457 TreeXMLImporter& pushElement( const XMLElement& element ){
458 if ( string_equal( element.name(), ClassesImporter::name() ) ) {
459 constructor( m_classes.get(), makeReference( m_collector ) );
460 return m_classes.get();
464 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
468 void popElement( const char* elementName ){
469 if ( string_equal( elementName, ClassesImporter::name() ) ) {
470 destructor( m_classes.get() );
474 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
477 std::size_t write( const char* data, std::size_t length ){
482 class TreeXMLImporterStack : public XMLImporter
484 std::vector< Reference<TreeXMLImporter> > m_importers;
486 TreeXMLImporterStack( TreeXMLImporter& importer ){
487 m_importers.push_back( makeReference( importer ) );
489 void pushElement( const XMLElement& element ){
490 m_importers.push_back( makeReference( m_importers.back().get().pushElement( element ) ) );
492 void popElement( const char* name ){
493 m_importers.pop_back();
494 m_importers.back().get().popElement( name );
496 std::size_t write( const char* buffer, std::size_t length ){
497 return m_importers.back().get().write( buffer, length );
503 const char* GetExtension(){
506 void ScanFile( EntityClassCollector& collector, const char *filename ){
507 TextFileInputStream inputFile( filename );
508 if ( !inputFile.failed() ) {
509 XMLStreamParser parser( inputFile );
511 EclassXMLImporter importer( collector );
512 TreeXMLImporterStack stack( importer );
513 parser.exportXML( stack );
520 #include "modulesystem/singletonmodule.h"
522 class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
528 EntityClassScanner m_eclassxml;
530 typedef EntityClassScanner Type;
531 STRING_CONSTANT( Name, "xml" );
534 m_eclassxml.scanFile = &ScanFile;
535 m_eclassxml.getExtension = &GetExtension;
537 EntityClassScanner* getTable(){
542 typedef SingletonModule<EclassXMLAPI, EntityClassXMLDependencies> EclassXMLModule;
543 typedef Static<EclassXMLModule> StaticEclassXMLModule;
544 StaticRegisterModule staticRegisterEclassXML( StaticEclassXMLModule::instance() );