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)
137 template<typename TextOutputStreamType>
138 TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const IgnoreBreaks& ignoreBreaks)
140 for(const char* i = ignoreBreaks.m_first; i != ignoreBreaks.m_last; ++i)
153 class TreeXMLImporter : public TextOutputStream
156 virtual TreeXMLImporter& pushElement(const XMLElement& element) = 0;
157 virtual void popElement(const char* name) = 0;
160 template<typename Type>
163 char m_storage[sizeof(Type)];
167 return *reinterpret_cast<Type*>(m_storage);
169 const Type& get() const
171 return *reinterpret_cast<const Type*>(m_storage);
175 class BreakImporter : public TreeXMLImporter
178 BreakImporter(StringOutputStream& comment)
182 static const char* name()
186 TreeXMLImporter& pushElement(const XMLElement& element)
188 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
191 void popElement(const char* elementName)
193 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
195 std::size_t write(const char* data, std::size_t length)
201 class AttributeImporter : public TreeXMLImporter
203 StringOutputStream& m_comment;
206 AttributeImporter(StringOutputStream& comment, EntityClass* entityClass, const XMLElement& element) : m_comment(comment)
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");
213 ASSERT_MESSAGE(!string_empty(key), "key attribute not specified");
214 ASSERT_MESSAGE(!string_empty(name), "name attribute not specified");
216 if(string_equal(type, "flag"))
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);
227 EntityClass_insertAttribute(*entityClass, key, EntityClassAttribute(type, name, value));
232 TreeXMLImporter& pushElement(const XMLElement& element)
234 ERROR_MESSAGE(PARSE_ERROR(element.name(), "attribute"));
237 void popElement(const char* elementName)
239 ERROR_MESSAGE(PARSE_ERROR(elementName, "attribute"));
241 std::size_t write(const char* data, std::size_t length)
243 return m_comment.write(data, length);
247 bool attributeSupported(const char* name)
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");
270 typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
272 bool listAttributeSupported(ListAttributeTypes& listTypes, const char* name)
274 return listTypes.find(name) != listTypes.end();
278 class ClassImporter : public TreeXMLImporter
280 EntityClassCollector& m_collector;
281 EntityClass* m_eclass;
282 StringOutputStream m_comment;
283 Storage<AttributeImporter> m_attribute;
284 ListAttributeTypes& m_listTypes;
287 ClassImporter(EntityClassCollector& collector, ListAttributeTypes& listTypes, const XMLElement& element) : m_collector(collector), m_listTypes(listTypes)
289 m_eclass = Eclass_Alloc();
290 m_eclass->free = &Eclass_Free;
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;
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);
301 const char* model = element.attribute("model");
302 if(!string_empty(model))
304 StringOutputStream buffer(256);
305 buffer << PathCleaned(model);
306 m_eclass->m_modelpath = buffer.c_str();
309 const char* type = element.name();
310 if(string_equal(type, "point"))
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);
320 m_eclass->m_comments = m_comment.c_str();
321 m_collector.insert(m_eclass);
323 for(ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i)
325 m_collector.insert((*i).first.c_str(), (*i).second);
328 static const char* name()
332 TreeXMLImporter& pushElement(const XMLElement& element)
334 if(attributeSupported(element.name()) || listAttributeSupported(m_listTypes, element.name()))
336 constructor(m_attribute.get(), makeReference(m_comment), m_eclass, element);
337 return m_attribute.get();
341 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
345 void popElement(const char* elementName)
347 if(attributeSupported(elementName) || listAttributeSupported(m_listTypes, elementName))
349 destructor(m_attribute.get());
353 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
356 std::size_t write(const char* data, std::size_t length)
358 return m_comment.write(data, length);
362 class ItemImporter : public TreeXMLImporter
365 ItemImporter(ListAttributeType& list, const XMLElement& element)
367 const char* name = element.attribute("name");
368 const char* value = element.attribute("value");
369 list.push_back(name, value);
371 TreeXMLImporter& pushElement(const XMLElement& element)
373 ERROR_MESSAGE(PARSE_ERROR(element.name(), "item"));
376 void popElement(const char* elementName)
378 ERROR_MESSAGE(PARSE_ERROR(elementName, "item"));
380 std::size_t write(const char* data, std::size_t length)
386 bool isItem(const char* name)
388 return string_equal(name, "item");
391 class ListAttributeImporter : public TreeXMLImporter
393 ListAttributeType* m_listType;
394 Storage<ItemImporter> m_item;
396 ListAttributeImporter(ListAttributeTypes& listTypes, const XMLElement& element)
398 const char* name = element.attribute("name");
399 m_listType = &listTypes[name];
401 TreeXMLImporter& pushElement(const XMLElement& element)
403 if(isItem(element.name()))
405 constructor(m_item.get(), makeReference(*m_listType), element);
410 ERROR_MESSAGE(PARSE_ERROR(element.name(), "list"));
414 void popElement(const char* elementName)
416 if(isItem(elementName))
418 destructor(m_item.get());
422 ERROR_MESSAGE(PARSE_ERROR(elementName, "list"));
425 std::size_t write(const char* data, std::size_t length)
431 bool classSupported(const char* name)
433 return string_equal(name, "group")
434 || string_equal(name, "point");
437 bool listSupported(const char* name)
439 return string_equal(name, "list");
442 class ClassesImporter : public TreeXMLImporter
444 EntityClassCollector& m_collector;
445 Storage<ClassImporter> m_class;
446 Storage<ListAttributeImporter> m_list;
447 ListAttributeTypes m_listTypes;
450 ClassesImporter(EntityClassCollector& collector) : m_collector(collector)
453 static const char* name()
457 TreeXMLImporter& pushElement(const XMLElement& element)
459 if(classSupported(element.name()))
461 constructor(m_class.get(), makeReference(m_collector), makeReference(m_listTypes), element);
462 return m_class.get();
464 else if(listSupported(element.name()))
466 constructor(m_list.get(), makeReference(m_listTypes), element);
471 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
475 void popElement(const char* elementName)
477 if(classSupported(elementName))
479 destructor(m_class.get());
481 else if(listSupported(elementName))
483 destructor(m_list.get());
487 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
490 std::size_t write(const char* data, std::size_t length)
496 class EclassXMLImporter : public TreeXMLImporter
498 EntityClassCollector& m_collector;
499 Storage<ClassesImporter> m_classes;
502 EclassXMLImporter(EntityClassCollector& collector) : m_collector(collector)
505 static const char* name()
509 TreeXMLImporter& pushElement(const XMLElement& element)
511 if(string_equal(element.name(), ClassesImporter::name()))
513 constructor(m_classes.get(), makeReference(m_collector));
514 return m_classes.get();
518 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
522 void popElement(const char* elementName)
524 if(string_equal(elementName, ClassesImporter::name()))
526 destructor(m_classes.get());
530 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
533 std::size_t write(const char* data, std::size_t length)
539 class TreeXMLImporterStack : public XMLImporter
541 std::vector< Reference<TreeXMLImporter> > m_importers;
543 TreeXMLImporterStack(TreeXMLImporter& importer)
545 m_importers.push_back(makeReference(importer));
547 void pushElement(const XMLElement& element)
549 m_importers.push_back(makeReference(m_importers.back().get().pushElement(element)));
551 void popElement(const char* name)
553 m_importers.pop_back();
554 m_importers.back().get().popElement(name);
556 std::size_t write(const char* buffer, std::size_t length)
558 return m_importers.back().get().write(buffer, length);
564 const char* GetExtension()
568 void ScanFile(EntityClassCollector& collector, const char *filename)
570 TextFileInputStream inputFile(filename);
571 if(!inputFile.failed())
573 XMLStreamParser parser(inputFile);
575 EclassXMLImporter importer(collector);
576 TreeXMLImporterStack stack(importer);
577 parser.exportXML(stack);
584 #include "modulesystem/singletonmodule.h"
586 class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
592 EntityClassScanner m_eclassxml;
594 typedef EntityClassScanner Type;
595 STRING_CONSTANT(Name, "xml");
599 m_eclassxml.scanFile = &ScanFile;
600 m_eclassxml.getExtension = &GetExtension;
602 EntityClassScanner* getTable()
608 typedef SingletonModule<EclassXMLAPI, EntityClassXMLDependencies> EclassXMLModule;
609 typedef Static<EclassXMLModule> StaticEclassXMLModule;
610 StaticRegisterModule staticRegisterEclassXML(StaticEclassXMLModule::instance());