]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/eclass_xml.cpp
Auto cap button
[xonotic/netradiant.git] / radiant / eclass_xml.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
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.
11
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.
16
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
20  */
21
22 ///\file
23 ///\brief EntityClass plugin that supports the .ent xml entity-definition format.
24 ///
25 /// the .ent xml format expresses entity-definitions.
26 ///
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 -->
31 /// </point>
32 ///
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 -->
36 /// </group>
37 ///
38 ///
39 /// the attributes of an entity type are defined like this:
40 ///
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]>
46 ///
47 /// each attribute type has a specialised attribute-editor GUI
48 ///
49 /// currently-supported attribute types:
50 ///
51 /// string          a string
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
68 ///
69 ///
70 /// flag attributes define a flag in the "spawnflags" key:
71 ///
72 ///   <flag
73 ///     key="[flag name]"
74 ///     name="[name shown in gui]"
75 ///     bit="[bit-index in spawnflags]"
76 ///   >[comment text shown in gui]<flag>
77 ///
78 /// the default value for a flag bit is always 0.
79 ///
80 ///
81 /// List attributes have a set of valid values.
82 /// Create new list attribute types like this:
83 ///
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]"/>
87 /// </list>
88 ///
89 /// these can then be used as attribute types.
90 ///
91 ///
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.
95 ///
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".
103 ///
104
105
106
107
108 #include "eclass_xml.h"
109
110 #include "ieclass.h"
111 #include "irender.h"
112 #include "ifilesystem.h"
113 #include "iarchive.h"
114
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"
120 #include "os/path.h"
121 #include "eclasslib.h"
122 #include "modulesystem/moduleregistry.h"
123 #include "stringio.h"
124
125 #define PARSE_ERROR( elementName, name ) makeQuoted( elementName ) << " is not a valid child of " << makeQuoted( name )
126
127 class IgnoreBreaks
128 {
129 public:
130 const char* m_first;
131 const char* m_last;
132 IgnoreBreaks( const char* first, const char* last ) : m_first( first ), m_last( last ){
133 }
134 };
135
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 )
139         {
140                 if ( *i != '\n' ) {
141                         ostream << *i;
142                 }
143         }
144         return ostream;
145 }
146
147 namespace
148 {
149
150 class TreeXMLImporter : public TextOutputStream
151 {
152 public:
153 virtual TreeXMLImporter& pushElement( const XMLElement& element ) = 0;
154 virtual void popElement( const char* name ) = 0;
155 };
156
157 template<typename Type>
158 class Storage
159 {
160 char m_storage[sizeof( Type )];
161 public:
162 Type& get(){
163         return *reinterpret_cast<Type*>( m_storage );
164 }
165 const Type& get() const {
166         return *reinterpret_cast<const Type*>( m_storage );
167 }
168 };
169
170 class BreakImporter : public TreeXMLImporter
171 {
172 public:
173 BreakImporter( StringOutputStream& comment ){
174         comment << '\n';
175 }
176 static const char* name(){
177         return "n";
178 }
179 TreeXMLImporter& pushElement( const XMLElement& element ){
180         ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
181         return *this;
182 }
183 void popElement( const char* elementName ){
184         ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
185 }
186 std::size_t write( const char* data, std::size_t length ){
187         return length;
188 }
189 };
190
191 class AttributeImporter : public TreeXMLImporter
192 {
193 StringOutputStream& m_comment;
194
195 public:
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" );
201
202         ASSERT_MESSAGE( !string_empty( key ), "key attribute not specified" );
203         ASSERT_MESSAGE( !string_empty( name ), "name attribute not specified" );
204
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 );
210         }
211
212         m_comment << key;
213         m_comment << " : ";
214
215         EntityClass_insertAttribute( *entityClass, key, EntityClassAttribute( type, name, value ) );
216 }
217 ~AttributeImporter(){
218 }
219 TreeXMLImporter& pushElement( const XMLElement& element ){
220         ERROR_MESSAGE( PARSE_ERROR( element.name(), "attribute" ) );
221         return *this;
222 }
223 void popElement( const char* elementName ){
224         ERROR_MESSAGE( PARSE_ERROR( elementName, "attribute" ) );
225 }
226 std::size_t write( const char* data, std::size_t length ){
227         return m_comment.write( data, length );
228 }
229 };
230
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" );
251 }
252
253 typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
254
255 bool listAttributeSupported( ListAttributeTypes& listTypes, const char* name ){
256         return listTypes.find( name ) != listTypes.end();
257 }
258
259
260 class ClassImporter : public TreeXMLImporter
261 {
262 EntityClassCollector& m_collector;
263 EntityClass* m_eclass;
264 StringOutputStream m_comment;
265 Storage<AttributeImporter> m_attribute;
266 ListAttributeTypes& m_listTypes;
267
268 public:
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;
272
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;
276
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 );
281
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();
287         }
288
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 );
295         }
296 }
297 ~ClassImporter(){
298         m_eclass->m_comments = m_comment.c_str();
299         m_collector.insert( m_eclass );
300
301         for ( ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i )
302         {
303                 m_collector.insert( ( *i ).first.c_str(), ( *i ).second );
304         }
305 }
306 static const char* name(){
307         return "class";
308 }
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();
313         }
314         else
315         {
316                 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
317                 return *this;
318         }
319 }
320 void popElement( const char* elementName ){
321         if ( attributeSupported( elementName ) || listAttributeSupported( m_listTypes, elementName ) ) {
322                 destructor( m_attribute.get() );
323         }
324         else
325         {
326                 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
327         }
328 }
329 std::size_t write( const char* data, std::size_t length ){
330         return m_comment.write( data, length );
331 }
332 };
333
334 class ItemImporter : public TreeXMLImporter
335 {
336 public:
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 );
341 }
342 TreeXMLImporter& pushElement( const XMLElement& element ){
343         ERROR_MESSAGE( PARSE_ERROR( element.name(), "item" ) );
344         return *this;
345 }
346 void popElement( const char* elementName ){
347         ERROR_MESSAGE( PARSE_ERROR( elementName, "item" ) );
348 }
349 std::size_t write( const char* data, std::size_t length ){
350         return length;
351 }
352 };
353
354 bool isItem( const char* name ){
355         return string_equal( name, "item" );
356 }
357
358 class ListAttributeImporter : public TreeXMLImporter
359 {
360 ListAttributeType* m_listType;
361 Storage<ItemImporter> m_item;
362 public:
363 ListAttributeImporter( ListAttributeTypes& listTypes, const XMLElement& element ){
364         const char* name = element.attribute( "name" );
365         m_listType = &listTypes[name];
366 }
367 TreeXMLImporter& pushElement( const XMLElement& element ){
368         if ( isItem( element.name() ) ) {
369                 constructor( m_item.get(), makeReference( *m_listType ), element );
370                 return m_item.get();
371         }
372         else
373         {
374                 ERROR_MESSAGE( PARSE_ERROR( element.name(), "list" ) );
375                 return *this;
376         }
377 }
378 void popElement( const char* elementName ){
379         if ( isItem( elementName ) ) {
380                 destructor( m_item.get() );
381         }
382         else
383         {
384                 ERROR_MESSAGE( PARSE_ERROR( elementName, "list" ) );
385         }
386 }
387 std::size_t write( const char* data, std::size_t length ){
388         return length;
389 }
390 };
391
392 bool classSupported( const char* name ){
393         return string_equal( name, "group" )
394                    || string_equal( name, "point" );
395 }
396
397 bool listSupported( const char* name ){
398         return string_equal( name, "list" );
399 }
400
401 class ClassesImporter : public TreeXMLImporter
402 {
403 EntityClassCollector& m_collector;
404 Storage<ClassImporter> m_class;
405 Storage<ListAttributeImporter> m_list;
406 ListAttributeTypes m_listTypes;
407
408 public:
409 ClassesImporter( EntityClassCollector& collector ) : m_collector( collector ){
410 }
411 static const char* name(){
412         return "classes";
413 }
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();
418         }
419         else if ( listSupported( element.name() ) ) {
420                 constructor( m_list.get(), makeReference( m_listTypes ), element );
421                 return m_list.get();
422         }
423         else
424         {
425                 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
426                 return *this;
427         }
428 }
429 void popElement( const char* elementName ){
430         if ( classSupported( elementName ) ) {
431                 destructor( m_class.get() );
432         }
433         else if ( listSupported( elementName ) ) {
434                 destructor( m_list.get() );
435         }
436         else
437         {
438                 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
439         }
440 }
441 std::size_t write( const char* data, std::size_t length ){
442         return length;
443 }
444 };
445
446 class EclassXMLImporter : public TreeXMLImporter
447 {
448 EntityClassCollector& m_collector;
449 Storage<ClassesImporter> m_classes;
450
451 public:
452 EclassXMLImporter( EntityClassCollector& collector ) : m_collector( collector ){
453 }
454 static const char* name(){
455         return "classes";
456 }
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();
461         }
462         else
463         {
464                 ERROR_MESSAGE( PARSE_ERROR( element.name(), name() ) );
465                 return *this;
466         }
467 }
468 void popElement( const char* elementName ){
469         if ( string_equal( elementName, ClassesImporter::name() ) ) {
470                 destructor( m_classes.get() );
471         }
472         else
473         {
474                 ERROR_MESSAGE( PARSE_ERROR( elementName, name() ) );
475         }
476 }
477 std::size_t write( const char* data, std::size_t length ){
478         return length;
479 }
480 };
481
482 class TreeXMLImporterStack : public XMLImporter
483 {
484 std::vector< Reference<TreeXMLImporter> > m_importers;
485 public:
486 TreeXMLImporterStack( TreeXMLImporter& importer ){
487         m_importers.push_back( makeReference( importer ) );
488 }
489 void pushElement( const XMLElement& element ){
490         m_importers.push_back( makeReference( m_importers.back().get().pushElement( element ) ) );
491 }
492 void popElement( const char* name ){
493         m_importers.pop_back();
494         m_importers.back().get().popElement( name );
495 }
496 std::size_t write( const char* buffer, std::size_t length ){
497         return m_importers.back().get().write( buffer, length );
498 }
499 };
500
501
502
503 const char* GetExtension(){
504         return "ent";
505 }
506 void ScanFile( EntityClassCollector& collector, const char *filename ){
507         TextFileInputStream inputFile( filename );
508         if ( !inputFile.failed() ) {
509                 XMLStreamParser parser( inputFile );
510
511                 EclassXMLImporter importer( collector );
512                 TreeXMLImporterStack stack( importer );
513                 parser.exportXML( stack );
514         }
515 }
516
517
518 }
519
520 #include "modulesystem/singletonmodule.h"
521
522 class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
523 {
524 };
525
526 class EclassXMLAPI
527 {
528 EntityClassScanner m_eclassxml;
529 public:
530 typedef EntityClassScanner Type;
531 STRING_CONSTANT( Name, "xml" );
532
533 EclassXMLAPI(){
534         m_eclassxml.scanFile = &ScanFile;
535         m_eclassxml.getExtension = &GetExtension;
536 }
537 EntityClassScanner* getTable(){
538         return &m_eclassxml;
539 }
540 };
541
542 typedef SingletonModule<EclassXMLAPI, EntityClassXMLDependencies> EclassXMLModule;
543 typedef Static<EclassXMLModule> StaticEclassXMLModule;
544 StaticRegisterModule staticRegisterEclassXML( StaticEclassXMLModule::instance() );