]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/eclass_xml.cpp
iqmmodel: better format description
[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 public:
129     const char *m_first;
130     const char *m_last;
131
132     IgnoreBreaks(const char *first, const char *last) : m_first(first), m_last(last)
133     {
134     }
135 };
136
137 template<typename TextOutputStreamType>
138 TextOutputStreamType &ostream_write(TextOutputStreamType &ostream, const IgnoreBreaks &ignoreBreaks)
139 {
140     for (const char *i = ignoreBreaks.m_first; i != ignoreBreaks.m_last; ++i) {
141         if (*i != '\n') {
142             ostream << *i;
143         }
144     }
145     return ostream;
146 }
147
148 namespace {
149
150     class TreeXMLImporter : public TextOutputStream {
151     public:
152         virtual TreeXMLImporter &pushElement(const XMLElement &element) = 0;
153
154         virtual void popElement(const char *name) = 0;
155     };
156
157     template<typename Type>
158     class Storage {
159         char m_storage[sizeof(Type)];
160     public:
161         Type &get()
162         {
163             return *reinterpret_cast<Type *>( m_storage );
164         }
165
166         const Type &get() const
167         {
168             return *reinterpret_cast<const Type *>( m_storage );
169         }
170     };
171
172     class BreakImporter : public TreeXMLImporter {
173     public:
174         BreakImporter(StringOutputStream &comment)
175         {
176             comment << '\n';
177         }
178
179         static const char *name()
180         {
181             return "n";
182         }
183
184         TreeXMLImporter &pushElement(const XMLElement &element)
185         {
186             ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
187             return *this;
188         }
189
190         void popElement(const char *elementName)
191         {
192             ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
193         }
194
195         std::size_t write(const char *data, std::size_t length)
196         {
197             return length;
198         }
199     };
200
201     class AttributeImporter : public TreeXMLImporter {
202         StringOutputStream &m_comment;
203
204     public:
205         AttributeImporter(StringOutputStream &comment, EntityClass *entityClass, const XMLElement &element) : m_comment(
206                 comment)
207         {
208             const char *type = element.name();
209             const char *key = element.attribute("key");
210             const char *name = element.attribute("name");
211             const char *value = element.attribute("value");
212
213             ASSERT_MESSAGE(!string_empty(key), "key attribute not specified");
214             ASSERT_MESSAGE(!string_empty(name), "name attribute not specified");
215
216             if (string_equal(type, "flag")) {
217                 std::size_t bit = atoi(element.attribute("bit"));
218                 ASSERT_MESSAGE(bit < MAX_FLAGS, "invalid flag bit");
219                 ASSERT_MESSAGE(string_empty(entityClass->flagnames[bit]), "non-unique flag bit");
220                 strcpy(entityClass->flagnames[bit], key);
221             }
222
223             m_comment << key;
224             m_comment << " : ";
225
226             EntityClass_insertAttribute(*entityClass, key, EntityClassAttribute(type, name, value));
227         }
228
229         ~AttributeImporter()
230         {
231         }
232
233         TreeXMLImporter &pushElement(const XMLElement &element)
234         {
235             ERROR_MESSAGE(PARSE_ERROR(element.name(), "attribute"));
236             return *this;
237         }
238
239         void popElement(const char *elementName)
240         {
241             ERROR_MESSAGE(PARSE_ERROR(elementName, "attribute"));
242         }
243
244         std::size_t write(const char *data, std::size_t length)
245         {
246             return m_comment.write(data, length);
247         }
248     };
249
250     bool attributeSupported(const char *name)
251     {
252         return string_equal(name, "real")
253                || string_equal(name, "integer")
254                || string_equal(name, "boolean")
255                || string_equal(name, "string")
256                || string_equal(name, "array")
257                || string_equal(name, "flag")
258                || string_equal(name, "real3")
259                || string_equal(name, "integer3")
260                || string_equal(name, "direction")
261                || string_equal(name, "angle")
262                || string_equal(name, "angles")
263                || string_equal(name, "color")
264                || string_equal(name, "target")
265                || string_equal(name, "targetname")
266                || string_equal(name, "sound")
267                || string_equal(name, "texture")
268                || string_equal(name, "model")
269                || string_equal(name, "skin")
270                || string_equal(name, "integer2");
271     }
272
273     typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
274
275     bool listAttributeSupported(ListAttributeTypes &listTypes, const char *name)
276     {
277         return listTypes.find(name) != listTypes.end();
278     }
279
280
281     class ClassImporter : public TreeXMLImporter {
282         EntityClassCollector &m_collector;
283         EntityClass *m_eclass;
284         StringOutputStream m_comment;
285         Storage<AttributeImporter> m_attribute;
286         ListAttributeTypes &m_listTypes;
287
288     public:
289         ClassImporter(EntityClassCollector &collector, ListAttributeTypes &listTypes, const XMLElement &element)
290                 : m_collector(collector), m_listTypes(listTypes)
291         {
292             m_eclass = Eclass_Alloc();
293             m_eclass->free = &Eclass_Free;
294
295             const char *name = element.attribute("name");
296             ASSERT_MESSAGE(!string_empty(name), "name attribute not specified for class");
297             m_eclass->m_name = name;
298
299             const char *color = element.attribute("color");
300             ASSERT_MESSAGE(!string_empty(name), "color attribute not specified for class " << name);
301             string_parse_vector3(color, m_eclass->color);
302             eclass_capture_state(m_eclass);
303
304             const char *model = element.attribute("model");
305             if (!string_empty(model)) {
306                 StringOutputStream buffer(256);
307                 buffer << PathCleaned(model);
308                 m_eclass->m_modelpath = buffer.c_str();
309             }
310
311             const char *type = element.name();
312             if (string_equal(type, "point")) {
313                 const char *box = element.attribute("box");
314                 ASSERT_MESSAGE(!string_empty(box), "box attribute not found for class " << name);
315                 m_eclass->fixedsize = true;
316                 string_parse_vector(box, &m_eclass->mins.x(), &m_eclass->mins.x() + 6);
317             }
318         }
319
320         ~ClassImporter()
321         {
322             m_eclass->m_comments = m_comment.c_str();
323             m_collector.insert(m_eclass);
324
325             for (ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i) {
326                 m_collector.insert((*i).first.c_str(), (*i).second);
327             }
328         }
329
330         static const char *name()
331         {
332             return "class";
333         }
334
335         TreeXMLImporter &pushElement(const XMLElement &element)
336         {
337             if (attributeSupported(element.name()) || listAttributeSupported(m_listTypes, element.name())) {
338                 constructor(m_attribute.get(), makeReference(m_comment), m_eclass, element);
339                 return m_attribute.get();
340             } else {
341                 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
342                 return *this;
343             }
344         }
345
346         void popElement(const char *elementName)
347         {
348             if (attributeSupported(elementName) || listAttributeSupported(m_listTypes, elementName)) {
349                 destructor(m_attribute.get());
350             } else {
351                 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
352             }
353         }
354
355         std::size_t write(const char *data, std::size_t length)
356         {
357             return m_comment.write(data, length);
358         }
359     };
360
361     class ItemImporter : public TreeXMLImporter {
362     public:
363         ItemImporter(ListAttributeType &list, const XMLElement &element)
364         {
365             const char *name = element.attribute("name");
366             const char *value = element.attribute("value");
367             list.push_back(name, value);
368         }
369
370         TreeXMLImporter &pushElement(const XMLElement &element)
371         {
372             ERROR_MESSAGE(PARSE_ERROR(element.name(), "item"));
373             return *this;
374         }
375
376         void popElement(const char *elementName)
377         {
378             ERROR_MESSAGE(PARSE_ERROR(elementName, "item"));
379         }
380
381         std::size_t write(const char *data, std::size_t length)
382         {
383             return length;
384         }
385     };
386
387     bool isItem(const char *name)
388     {
389         return string_equal(name, "item");
390     }
391
392     class ListAttributeImporter : public TreeXMLImporter {
393         ListAttributeType *m_listType;
394         Storage<ItemImporter> m_item;
395     public:
396         ListAttributeImporter(ListAttributeTypes &listTypes, const XMLElement &element)
397         {
398             const char *name = element.attribute("name");
399             m_listType = &listTypes[name];
400         }
401
402         TreeXMLImporter &pushElement(const XMLElement &element)
403         {
404             if (isItem(element.name())) {
405                 constructor(m_item.get(), makeReference(*m_listType), element);
406                 return m_item.get();
407             } else {
408                 ERROR_MESSAGE(PARSE_ERROR(element.name(), "list"));
409                 return *this;
410             }
411         }
412
413         void popElement(const char *elementName)
414         {
415             if (isItem(elementName)) {
416                 destructor(m_item.get());
417             } else {
418                 ERROR_MESSAGE(PARSE_ERROR(elementName, "list"));
419             }
420         }
421
422         std::size_t write(const char *data, std::size_t length)
423         {
424             return length;
425         }
426     };
427
428     bool classSupported(const char *name)
429     {
430         return string_equal(name, "group")
431                || string_equal(name, "point");
432     }
433
434     bool listSupported(const char *name)
435     {
436         return string_equal(name, "list");
437     }
438
439     class ClassesImporter : public TreeXMLImporter {
440         EntityClassCollector &m_collector;
441         Storage<ClassImporter> m_class;
442         Storage<ListAttributeImporter> m_list;
443         ListAttributeTypes m_listTypes;
444
445     public:
446         ClassesImporter(EntityClassCollector &collector) : m_collector(collector)
447         {
448         }
449
450         static const char *name()
451         {
452             return "classes";
453         }
454
455         TreeXMLImporter &pushElement(const XMLElement &element)
456         {
457             if (classSupported(element.name())) {
458                 constructor(m_class.get(), makeReference(m_collector), makeReference(m_listTypes), element);
459                 return m_class.get();
460             } else if (listSupported(element.name())) {
461                 constructor(m_list.get(), makeReference(m_listTypes), element);
462                 return m_list.get();
463             } else {
464                 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
465                 return *this;
466             }
467         }
468
469         void popElement(const char *elementName)
470         {
471             if (classSupported(elementName)) {
472                 destructor(m_class.get());
473             } else if (listSupported(elementName)) {
474                 destructor(m_list.get());
475             } else {
476                 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
477             }
478         }
479
480         std::size_t write(const char *data, std::size_t length)
481         {
482             return length;
483         }
484     };
485
486     class EclassXMLImporter : public TreeXMLImporter {
487         EntityClassCollector &m_collector;
488         Storage<ClassesImporter> m_classes;
489
490     public:
491         EclassXMLImporter(EntityClassCollector &collector) : m_collector(collector)
492         {
493         }
494
495         static const char *name()
496         {
497             return "classes";
498         }
499
500         TreeXMLImporter &pushElement(const XMLElement &element)
501         {
502             if (string_equal(element.name(), ClassesImporter::name())) {
503                 constructor(m_classes.get(), makeReference(m_collector));
504                 return m_classes.get();
505             } else {
506                 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
507                 return *this;
508             }
509         }
510
511         void popElement(const char *elementName)
512         {
513             if (string_equal(elementName, ClassesImporter::name())) {
514                 destructor(m_classes.get());
515             } else {
516                 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
517             }
518         }
519
520         std::size_t write(const char *data, std::size_t length)
521         {
522             return length;
523         }
524     };
525
526     class TreeXMLImporterStack : public XMLImporter {
527         std::vector<Reference<TreeXMLImporter> > m_importers;
528     public:
529         TreeXMLImporterStack(TreeXMLImporter &importer)
530         {
531             m_importers.push_back(makeReference(importer));
532         }
533
534         void pushElement(const XMLElement &element)
535         {
536             m_importers.push_back(makeReference(m_importers.back().get().pushElement(element)));
537         }
538
539         void popElement(const char *name)
540         {
541             m_importers.pop_back();
542             m_importers.back().get().popElement(name);
543         }
544
545         std::size_t write(const char *buffer, std::size_t length)
546         {
547             return m_importers.back().get().write(buffer, length);
548         }
549     };
550
551
552     const char *GetExtension()
553     {
554         return "ent";
555     }
556
557     void ScanFile(EntityClassCollector &collector, const char *filename)
558     {
559         TextFileInputStream inputFile(filename);
560         if (!inputFile.failed()) {
561             XMLStreamParser parser(inputFile);
562
563             EclassXMLImporter importer(collector);
564             TreeXMLImporterStack stack(importer);
565             parser.exportXML(stack);
566         }
567     }
568
569
570 }
571
572 #include "modulesystem/singletonmodule.h"
573
574 class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef {
575 };
576
577 class EclassXMLAPI {
578     EntityClassScanner m_eclassxml;
579 public:
580     typedef EntityClassScanner Type;
581
582     STRING_CONSTANT(Name, "xml");
583
584     EclassXMLAPI()
585     {
586         m_eclassxml.scanFile = &ScanFile;
587         m_eclassxml.getExtension = &GetExtension;
588     }
589
590     EntityClassScanner *getTable()
591     {
592         return &m_eclassxml;
593     }
594 };
595
596 typedef SingletonModule<EclassXMLAPI, EntityClassXMLDependencies> EclassXMLModule;
597 typedef Static<EclassXMLModule> StaticEclassXMLModule;
598 StaticRegisterModule staticRegisterEclassXML(StaticEclassXMLModule::instance());