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) {
150 class TreeXMLImporter : public TextOutputStream {
152 virtual TreeXMLImporter &pushElement(const XMLElement &element) = 0;
154 virtual void popElement(const char *name) = 0;
157 template<typename Type>
159 char m_storage[sizeof(Type)];
163 return *reinterpret_cast<Type *>( m_storage );
166 const Type &get() const
168 return *reinterpret_cast<const Type *>( m_storage );
172 class BreakImporter : public TreeXMLImporter {
174 BreakImporter(StringOutputStream &comment)
179 static const char *name()
184 TreeXMLImporter &pushElement(const XMLElement &element)
186 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
190 void popElement(const char *elementName)
192 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
195 std::size_t write(const char *data, std::size_t length)
201 class AttributeImporter : public TreeXMLImporter {
202 StringOutputStream &m_comment;
205 AttributeImporter(StringOutputStream &comment, EntityClass *entityClass, const XMLElement &element) : m_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")) {
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);
226 EntityClass_insertAttribute(*entityClass, key, EntityClassAttribute(type, name, value));
233 TreeXMLImporter &pushElement(const XMLElement &element)
235 ERROR_MESSAGE(PARSE_ERROR(element.name(), "attribute"));
239 void popElement(const char *elementName)
241 ERROR_MESSAGE(PARSE_ERROR(elementName, "attribute"));
244 std::size_t write(const char *data, std::size_t length)
246 return m_comment.write(data, length);
250 bool attributeSupported(const char *name)
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");
273 typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
275 bool listAttributeSupported(ListAttributeTypes &listTypes, const char *name)
277 return listTypes.find(name) != listTypes.end();
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;
289 ClassImporter(EntityClassCollector &collector, ListAttributeTypes &listTypes, const XMLElement &element)
290 : m_collector(collector), m_listTypes(listTypes)
292 m_eclass = Eclass_Alloc();
293 m_eclass->free = &Eclass_Free;
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;
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);
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();
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);
322 m_eclass->m_comments = m_comment.c_str();
323 m_collector.insert(m_eclass);
325 for (ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i) {
326 m_collector.insert((*i).first.c_str(), (*i).second);
330 static const char *name()
335 TreeXMLImporter &pushElement(const XMLElement &element)
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();
341 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
346 void popElement(const char *elementName)
348 if (attributeSupported(elementName) || listAttributeSupported(m_listTypes, elementName)) {
349 destructor(m_attribute.get());
351 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
355 std::size_t write(const char *data, std::size_t length)
357 return m_comment.write(data, length);
361 class ItemImporter : public TreeXMLImporter {
363 ItemImporter(ListAttributeType &list, const XMLElement &element)
365 const char *name = element.attribute("name");
366 const char *value = element.attribute("value");
367 list.push_back(name, value);
370 TreeXMLImporter &pushElement(const XMLElement &element)
372 ERROR_MESSAGE(PARSE_ERROR(element.name(), "item"));
376 void popElement(const char *elementName)
378 ERROR_MESSAGE(PARSE_ERROR(elementName, "item"));
381 std::size_t write(const char *data, std::size_t length)
387 bool isItem(const char *name)
389 return string_equal(name, "item");
392 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];
402 TreeXMLImporter &pushElement(const XMLElement &element)
404 if (isItem(element.name())) {
405 constructor(m_item.get(), makeReference(*m_listType), element);
408 ERROR_MESSAGE(PARSE_ERROR(element.name(), "list"));
413 void popElement(const char *elementName)
415 if (isItem(elementName)) {
416 destructor(m_item.get());
418 ERROR_MESSAGE(PARSE_ERROR(elementName, "list"));
422 std::size_t write(const char *data, std::size_t length)
428 bool classSupported(const char *name)
430 return string_equal(name, "group")
431 || string_equal(name, "point");
434 bool listSupported(const char *name)
436 return string_equal(name, "list");
439 class ClassesImporter : public TreeXMLImporter {
440 EntityClassCollector &m_collector;
441 Storage<ClassImporter> m_class;
442 Storage<ListAttributeImporter> m_list;
443 ListAttributeTypes m_listTypes;
446 ClassesImporter(EntityClassCollector &collector) : m_collector(collector)
450 static const char *name()
455 TreeXMLImporter &pushElement(const XMLElement &element)
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);
464 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
469 void popElement(const char *elementName)
471 if (classSupported(elementName)) {
472 destructor(m_class.get());
473 } else if (listSupported(elementName)) {
474 destructor(m_list.get());
476 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
480 std::size_t write(const char *data, std::size_t length)
486 class EclassXMLImporter : public TreeXMLImporter {
487 EntityClassCollector &m_collector;
488 Storage<ClassesImporter> m_classes;
491 EclassXMLImporter(EntityClassCollector &collector) : m_collector(collector)
495 static const char *name()
500 TreeXMLImporter &pushElement(const XMLElement &element)
502 if (string_equal(element.name(), ClassesImporter::name())) {
503 constructor(m_classes.get(), makeReference(m_collector));
504 return m_classes.get();
506 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
511 void popElement(const char *elementName)
513 if (string_equal(elementName, ClassesImporter::name())) {
514 destructor(m_classes.get());
516 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
520 std::size_t write(const char *data, std::size_t length)
526 class TreeXMLImporterStack : public XMLImporter {
527 std::vector<Reference<TreeXMLImporter> > m_importers;
529 TreeXMLImporterStack(TreeXMLImporter &importer)
531 m_importers.push_back(makeReference(importer));
534 void pushElement(const XMLElement &element)
536 m_importers.push_back(makeReference(m_importers.back().get().pushElement(element)));
539 void popElement(const char *name)
541 m_importers.pop_back();
542 m_importers.back().get().popElement(name);
545 std::size_t write(const char *buffer, std::size_t length)
547 return m_importers.back().get().write(buffer, length);
552 const char *GetExtension()
557 void ScanFile(EntityClassCollector &collector, const char *filename)
559 TextFileInputStream inputFile(filename);
560 if (!inputFile.failed()) {
561 XMLStreamParser parser(inputFile);
563 EclassXMLImporter importer(collector);
564 TreeXMLImporterStack stack(importer);
565 parser.exportXML(stack);
572 #include "modulesystem/singletonmodule.h"
574 class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef {
578 EntityClassScanner m_eclassxml;
580 typedef EntityClassScanner Type;
582 STRING_CONSTANT(Name, "xml");
586 m_eclassxml.scanFile = &ScanFile;
587 m_eclassxml.getExtension = &GetExtension;
590 EntityClassScanner *getTable()
596 typedef SingletonModule<EclassXMLAPI, EntityClassXMLDependencies> EclassXMLModule;
597 typedef Static<EclassXMLModule> StaticEclassXMLModule;
598 StaticRegisterModule staticRegisterEclassXML(StaticEclassXMLModule::instance());