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